OpenGL
OpenGl
OpenGl管线
GLSL着色器
代码片段
// 声明GLSL的版本为3.3
#version 330 core
// 传入参数
in vec4 vPosition;
in vec4 vColor;
// 传出数据
out vec4 color;
// 程序控制输入数据
uniform mat4 ModelViewProjectionMaxtrix;
void
main()
{
color = vColor; // 颜色不变
gl_Position = ModelViewProjectionMaxtrix * vPosition; // 位置根据传入矩阵进行修改
// 无需返回值
}
/*
多行注释写法
*/
基础数据类型
基本数据类型
类型 | 描述 |
---|---|
float | 32位浮点值 |
double | 64位浮点值 |
int | 有符号二进制补码的32位整数 |
uint | 无符号32位整数 |
bool | 布尔值 |
- 所有变量必须在声明的同时进行初始化
int i, num = 1500;
float force, g= -9.5;
bool falling = true;
double pi = 3.1415926535;
-
作用域
- 几乎跟C语言一样
- 在任何函数之外定义的是全局变量
- 在一组大括号之内定义的,只在打括号内有效
- for(int i=0; i<cout; i++) i只在这个for循环中有效
-
隐式转换
- GLSL的隐式转换比C语言少,int f = flase 会报错
- GLSL更注重类型安全
目标类型 | 可以隐式转换到目标类型 |
---|---|
uint | int |
float | int, uint |
double | int, uint, float |
聚合类型
矩阵
基本类型 | 2D向量 | 3D向量 | 4D向量 | 矩阵类型 |
---|---|---|---|---|
float | vec2 | vec3 | vec4 | mat2 mat3 mat4 mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4 |
double | dvec2 | dvec3 | dvec4 | dmat2 dmat3 dmat4 dmat2x2 dmat2x3 dmat2x4 dmat3x2 dmat3x3 dmat3x4 dmat4x2 dmat4x3 dmat4x4 |
int | ivec2 | ivec3 | ivec4 | —————— |
uint | uvec2 | uvec3 | uvec4 | —————— |
bool | bvec2 | bvec3 | bvec4 | —————— |
- 构建聚合类型
vec3 vel = vec3(0.0, 2.0, 3.0);
ivec3 step = ivec3(vel);
vec4 color;
vec3 RGB = vec3(color); // 只包含color的前四个分量
vec3 whtie = vec3(1.0f); // white = (1.0, 1.0, 1.0)
vec4 trans = vec4(whtie, 0.5); // trans = (1.0, 1.0, 1.0, 0.5)
mat3 m = mat3(4.0); // 3x3的矩阵中的主对角线值为4.0,其他位置值为0
mat3 m = mat3(1.0, 2.0, 3.0,
4.0, 5.0, 6.0,
7.0, 8.0, 9.0);
vec3 row1 = vec3(1.0, 2.0, 3.0);
vec3 row2 = vec3(4.0, 5.0, 6.0);
vec3 row3 = vec3(7.0, 8.0, 9.0);
mat3 m = mat3(row1, row2, row3);
- 访问聚合类型
分量访问符 | 符号描述 |
---|---|
(x,y,z,w) | 与位置相关的分量 |
(r,g,b,a) | 与颜色相关的分量 |
(s,t,p,d) | 与纹理坐标相关的分量 |
vec3 lum = color.rrr;
color = color.abgr; // 反转color的每个分量
// 一条语句中,只能使用一种类型的访问符
vec4 color = otherColor.rgz; // 错误 z不是(rgba)访问符中的
// 访问元素不要超过变量类型范围
vec2 pos;
float oPos = pos.z; // 错误 z超过2D向量(x,y)
结构体
struct Particle{
float lifeTime;
vec3 position;
vec3 vecl;
};
Particle p = Particle(1.0, vec3(1.0, 2.0, 3.0), vec3(1.0, 2.0, 3.0));
数组
- GLSL 4.3 之后可以数组套数组,之前不可
- 使用 [ ] 来进行索引
- 不允许使用负数索引或者超出范围的索引
- 数组具有构造函数
float coff[3]; // 3个float元素的数组
float[3] coff; // 同上
int intdic[]; // 未定义维数,可以后面重新声明
float coff[3] = float[3](2.22, 3.14, 4.2); // 通过数组的构造函数创建数组
int len = coff.length(); // 获得数组长度
mat3x4 m;
m.length(); // 列数为3
m[0].length(); // 行数为4
float digitl[m.length()]; // 设置数组大小与矩阵列数相同
float digitl[gl_in.length()]; // 设置数组大小与几何着色器的输入定点数相同
存储限制符
类型修饰符 | 描述 |
---|---|
const | 将一个变量定义为只读形式。如果它初始化时使用的是一个编译时的常量,那么它本身会称为编译时的常量 |
in | 设置这个变量为着色器阶段的输入变量 |
out | 设置这个变量为着色器阶段的输出变量 |
uniform | 设置这个变量为用户应用程序传递给着色器的数据,它对于给定的图元而言是一个常量 |
buffer | 设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存(storage buffer)使用 |
shared | 设置变量是本地工作组(local work group)*享的。它之恩那个用于计算着色器中 |
- const 存储限制符
- in 存储限制符:可以是顶点属性(对于顶点着色器),或者是前一个着色器阶段的输出变量
- out 存储限制符:定义着色器阶段的输出变量,例如着色器中输出变换后的其次坐标,或者片元着色器中输出的最终片元颜色
- buffer 存储限制符:需要在应用程序*享一大块缓存给着色器时使用buffer,着色器对缓存可以读和写
- shared 存储限制符:只能用于计算着色器中,可以建立本地工作组共享的内存
- uniform 存储限制符:是应用程序设置好的,在图元处理中不会发生变化,所有可用的着色阶段共享,必须定义为全局变量
uniform使用
应用程序在着色器运行之前可以,可以设置着色器中uniform变量的值,且该变量在着色器计算中不可更改。
uniform vec4 BaseColor; // 着色器中定义
着色器中可以直接通过BaseColor变量名来使用,但是在应用程序中不能直接通过变量名来设置它的值。
GLSL编译器会在链接着色器程序时创建一个uniform列表,在OpenGL中通过GLint glGetUniformLocation(GLuint program, const char* name)
函数来获得uniform列表
GLint glGetUniformLocation(GLuint program, const char* name)
// name为着色器中uniform变量名,name是以NULL结尾的字符串,不存在空格
// 如果name与启用的着色器中的素有uniform变量民都不想符或者与内部保留的着色器变量名相同返回-1
void glUniform****(GLint location, ...) // 以glUniform开头的函数 来设置unifrom的值
- 样例代码
GLint timeLoc; // 着色器中uniform变量time的索引
GLfloat timeValue; // 程序运行时间
ShaderInfo shaders[] = {
{GL_VERTEX_SHADER, "triangles.vert"}, // 顶点着色器
{GL_FRAGMENT_SHADER, "triangles.frag"}, // 片段着色器
{GL_NONE, NULL}
};
GLuint program = LoadShaders(shaders);
glUseProgram(program);
timeLoc = glGetUniformLocation(program, "time");
glUniformlf(timeLoc, timeValue);
语句
算数运算符
优先级 | 操作符 | 可用类型 | 描述 |
---|---|---|---|
1 | ( ) | —— | 成组操作 |
2 | [ ] f() .(句点) ++ – |
数组、矩阵、向量 函数 结构体 算数类型 |
数组的下标 函数调用与构造函数 访问结构体的域变量或者方法 后置递增/递减 |
3 | ++ – + - ~ ~ |
算数类型 算数类型 整型 布尔型 |
前置递增/缔结 一元正/负 一元按位 非 一元逻辑非 |
4 | * / % | 算术类型 | 乘法运算 |
5 | + - | 算数类型 | 相加运算 |
6 | << >> | 整型 | 按位操作 |
7 | <> <= >= | 算数操作 | 关系比较操作 |
8 | == != | 任意 | 相等操作 |
9 | & | 整型 | 按位与 |
10 | ^ | 整型 | 按位异或 |
11 | | | 整型 | 按位或 |
12 | && | 布尔型 | 逻辑与 |
13 | ^^ | 布尔型 | 逻辑异或 |
14 | || | 布尔型 | 逻辑或 |
15 | a?b:c | 布尔型?任意:任意 | 三元操作符 |
16 | = += -= *= /= %= <<= >>= &= |= ^= |
任意 算数类型 算数类型 整型 整型 |
赋值 算数赋值 |
17 | ,(逗号) | 任意 | 操作符序列 |
操作符重载
- GLSL中大部分操作符都是经过重载的
- 如果需要进行向量和矩阵之间的乘法(操作数和顺序非常重要,必须符合规则)
- 基本限制条件是要求矩阵和向量的维度必须是匹配的
- 两个向量相乘得到的是一个逐分量相乘的新向量(即按位相乘,而不会生成矩阵)
- 两个矩阵相乘得到的是通常矩阵相乘的结果(按照矩阵乘法相乘)
流控制
- if-else
- switch
if(truth){
} else{
}
switch(int_value){
case b:
break;
case d:
break;
default:
break;
}
循环
- for
- while
- do…while
for(int i=0; i<10; i++>){
}
while(n<10){
}
do{
}while(n < 10);
流控制语句
- break
- continue
- return
- discard:丢弃当前的片元,终止着色器的执行。discard语句只能用于片元着色器中。
函数
returnType functionName([accessModifier] type1 variable1, [accessModifier] type1 variable12, ...)
{
// 函数体
return returnValue;
}
参数限制符
访问修饰符 | 描述 |
---|---|
in | 将数据拷贝到函数中(如果没有指定修饰符,默认该形式) |
const in | 将只读数据拷贝到函数中 |
out | 从函数中获取数值(因此输入函数的值是未定义的) |
inout | 将数据拷贝到函数中,并且返回函数中修改的数据 |
- 在函数中写入一个in类型的变量,相当于对变量的局部拷贝进行了修改,只在函数自身范围内产生作用
计算的不确定性
- GLSL无法保证在不同的着色器中,两个完全相同的计算会得到完全一样的结果。这一问题与CPU对应用程序进行计算时的问题相同,即不同的优化方式可能会导致结果非常细微的差异。这细微的差异对于多通道的算法会产生问题,因为各个着色器阶段可能需要计算得到完全一致的结果。
- GLSL保证着色器之间的计算不变性:invariant 或 precise关键字
- 这两个关键字都需要在图形设备上完成计算过程,来确保同一表达式的结果可以保证重复(不变)性
- 但是对于宿主计算机和图形硬件各自的计算,这两种方法都无法保证结果的完全一致性
uniform float ten; // 假设应用程序设置该值为10.0
const float f = sin(10.0); // 宿主机的编译器负责计算
float g = sin(ten); // 图形硬件负责计算
void main(){
if(f == g); // 无法保证该值相等
}
invariant
invariant gl_Position; // 内置的输出变量
invariant centroid out vec3 Color; // 自定义输出变量
invariant 可以设置任何着色器的输出变量。它可以确保如果两个着色器的输出变量使用了同样的表达式,并且表达式中的变量也是相同值,那么产生的结果也是相同的。
输出变量的作用是将一个着色器的数据从一个阶段传递到下一个。可以在着色器中用到某个变量或者内置变量之前的任何位置,对该变量设置关键字invariant
// 如果需要将着色器中的所有可变量都设置为invariant
#param STDGL invariant(all)
precise
precise可以设置任何计算中的变量或者函数的返回值。它的作用不是增加数据精度而是增加计算的可复用性。
这段还没弄懂 后面写代码注意一下
数据块接口
着色器和应用程序之间,着色器各个阶段之间共享的变量可以组织为变量快的形式,并且有时候必须采用这种形式。uniform变量可以使用uniform块,输入和输出变量可以使用in和out块,着色器的存储缓存可以使用buffer块
// uniform块的写法
uniform b{ // 限定访问符可以是uniform in out 或者 buffer
vec4 v1; // 块中的变量列表
bool v2;
}; // 访问块成员时使用v1,v2
uniform b{
vec4 v1;
bool v2;
}name; // 访问块成员时使用name.v1 name.v2
上述代码中,块开始的部分(代码中为 b)对应于外部访问时的接口名称,结尾部分的名称(代码中为name)用于在着色器代码中访问具体成员变量
如果着色器程序变得复杂,那么用到unifrom变量的数量会上升,通常多个着色器程序会用到同一个uniform变量。
由于uniform变量的索引位置是在glLinkProgram()的时候产生的,所以应用程序获得的索引有可能发生变化。
而uniform缓存对象就是优化对uniform变量的访问,以及在不同着色器程序之间共享uniform数据的方法
着色器编译
- 为什么创建多个着色器对象
- 可能在不同的程序中复用同一个GLSL程序
- 调用
glCreateShader
来创建着色器对象 - 将着色器的源代码关联到这个对象上,使用
glShaderSource
- 如果要编译着色器对象的源代码,使用
glCompileShader
- 如果编译错误,可以通过调取编译日志来判断错误的原因
-
glGetShaderInfoLog
函数会返回一个具体实现相关问题的信息
- 当创建并编译了所有必要的着色器对象之后,创建可执行程序,
glCreateProgram
(创建空的着色器程序) - 将空的着色器程序关联到必要的着色器对象上
glAttachShader
- 如果要从程序中移除一个着色器对象
glDetachShader
- 当所有必要的着色器惯量到着色器程序之后,可以链接对象来生成可执行程序
glLinkProgram
- 由于着色器对象中可能存在问题,因此链接过程可能失败
- 调用
glGetProgramiv
来查询连接操作的结果,如果返回GL_TRUE则链接成功,GL_FALSE链接失败 - 如果链接失败,调用
glGetProgramInfoLog
来获取程序链接的日志信息
- 如果链接成功,可以使用
glUseProgram
来启用顶点或者片元程序 - 当着色器任务完成之后,可以通过
glDeleteShader
来删除 - 当不再需要着色器程序之后调用
glDeleteProgram
来删除着色器程序 - 最后,调用
glIsShader
来判断某个着色器对象是否存在,或者调用glIsProgram
来判断着色器程序是否存在
OpenGL绘制方式
图元
- OpenGL支持很多不同的图元类型
- 点,线,或者三角形(可以再组合为条带、循环体、扇面等,大多数设备都支持的基础图元类型)
- 作为细分器输入的Patch类型
- 作为几何着色器输入的临街图元
点
-
通过单一的顶点来表示,一个点就是一个四维的其次坐标值,因此点不存在面积
-
OpenGl通过显示屏幕上的一个四边形区域来模拟点
-
渲染点图元的时候,OpenGL会通过一系列光栅化规则来判断点所覆盖的像素位置
-
设置点的大小
- 默认的点大小为1.0
glPointSize(GLfloat size)
- 可以在顶点、细分、几何着色器中设置gl_PointSize来设置
线、条带与循环
-
OpenGL中的线指的是线段,独立的线用一对顶点来表达,每个顶点为线段端点
- 多线段链接首位闭合的叫做 循环线
- 开放的多线段叫做 条带线
-
设置线段的宽度
void glLineWidth(GLfloat width)
- width表示线的宽度,必须是一个大于0.0的值
三角形、条带和扇面
- 三角形:独立的三角形
- 条带:相邻三角形共享一条边
- 扇面:第一个点作为共享点存在
两个三角形的共享边上的像素值因为同时被两者所覆盖,因此不可能不收到光照计算的影响
两个三角形的共享边上的像素值,不可能受到多于一个三角形的光照计算影响
上面两句话总结就是:OpenGL对于模型三角形共享边的光栅化过程不会产生任何裂缝,也不会产生重复的绘制
OpenGL图元的模式标识
图元模型 | OpenGL枚举量 |
---|---|
点 | GL_POINTS |
线 | GL_LINES |
条带线 | GL_LINESTRIP |
循环线 | GL_LINE_LOOP |
独立三角形 | GL_TRIANGLES |
三角形条带 | GL_TRIANGLE_STRIP |
三角形扇面 | GL_TRIANGLE_FAN |
将多变形渲染为点集、轮廓线、实体
- 一个多变形有 正面和背面,不同的面朝向观察者时可能会有不同的渲染结果
- 默认情况下正面和背面的绘制方法是一样的,如果要修改这个属性需要设置
glPolygonMode
void glPolygonMode(GLenum face, Glenum mode);
// face 必须是 GL_FRONT_ANDBACK
// mode 可以是 GL_POINT、GL_LINE、GL_FILL
// 绘制模式是 点集、轮廓线、填充模式
多变形的反转和裁剪
-
一般来说,多变形的顶点在屏幕上应该是逆时针方向排列的(法线向上,右手比划一下大拇指向上时其余手指弯曲方向是逆时针)
- 对于球体、环形体、茶壶等都是有向的
- 对于莫比乌斯环、克林瓶不是逆时针
-
如果我们要描述一个有向的模型表面,但是它的外侧需要使用逆时针方向进行描述,使用
glFrontFace
来反转
void glFrontFace(GLenum mode);
// 控制多变形正面的判断方式
// 默认模式是GL_CCW:多变形投影到窗口坐标系之后,顶点按照逆时针排列的面作为正面
// 模式是GL_CW: 采取顺时针方向的面将被认为是正面
- 对于一个由不透明的且方向一致的多变形组成、完全封闭的模型表面来看说,它的所有背面多变形都是不可见的——会背正面多边形遮挡
- 如果位于模型外侧,开启裁剪可来直接抛弃OpenGL中的背面多变形
- 如果位于模型内存,需要指示OpenGL自动抛弃正面或者背面的多变形
void glCullFace(Glenum mode);
// 在转换到屏幕空间渲染之前,设置需要抛弃(裁剪)哪一类多变形
// mode 可以是:GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK
// 分别表示:正面、背面或者所有多变形
void glEnable()
// GL_CULL_FACE参数来开启裁剪
void glDisable()
// 同样的 GL_CULL_FACE来关闭