着色器(shader)是运行在GPU上小程序。
也是一种非常独立的程序,它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
着色器的开头总是要声明版本,接着是输入和输出变量,uniform和main函数。
每个输入变量也叫顶点属性(Vertex Attribute)。能声明的顶点属性是有上限的,OpenGL确保至少有16个包含4分量的顶点属性可用。
可以通过以下查询:
GLint nrAttributes; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,&nrAttributes); cout<< "Maximum nr of vertex attributes supported: "<< nrAttributes <<endl;
数据类型:
GLSL有数据类型可以来指定变量的种类。
GLSL中包含C等其他语言大部分的默认基础数据类型:
int、float、double、uint、bool;
GLSL也有两种容器类型:
向量(Vector) 和 矩阵(Matrix);
向量
向量是一个可以包含1、2、3或者4个分量的容器:
vecn 包含N个float分量的默认向量
bvecn 包含n个bool分量的向量
ivecn 包含n个int分量的向量
uvecn 包含n个unsigned int分量的向量
dvecn 包含n个double分量的向量
一个向量的分量可以通过vec.x这种方式获取。vec.x、vec.y、vec.z、vec.w来获取第1、2、3、4个分量。
对颜色使用rgba 对纹理使用stqp
GLSL定义了in或out关键字专门来实现数据交流和传递。
每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,
它就会传递下去。但顶点和片段着色器中会有点不同。
顶点着色器应该接收的是一种特殊形式的输入,否则就会效率低下。
顶点着色器的输入特殊在,它从顶点数据中直接接收输入。
为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在cpu上配置顶点属性。
也可以忽略layout (location=0)标识符,通过在OpenGL代码中使用glGetAttribLocation查询属性位置值(Location)。//没太懂!
另外一个例外是片段着色器,它需要一个vec4颜色输出变量,如果在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或)白色。
一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。
当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。
Uniform
Uniform是一种从cpu中的应用想GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。
首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且可以被着色器程序的任意阶段访问。
第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
如果声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本并不会包含它。
下面通过uniform设置三角形的颜色:
#version 330 core out vec4 color; uniform vec4 ourColor; void main() { color = ourColor; }
首先,需要找到这个着色器中uniform属性的索引/位置值。
当我们得到uniform的索引/位置值后,我们就可以更新它的值了。
我们用glGetUniformLocation 查询uniform ourColor的位置值。我们查询函数提供着色器程序和uniform的名字。
如果glGetUniformLocation 返回-1 就代表没有找到这个位置值。
最后我们可以通过glUniform4f函数设置uniform值。
注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform 之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。
OpenGL不支持类型重载,在函数参数不同的时候就要为其定义新的函数;
glUniform是一个典型例子。这个函数有一个特定的后缀,标识设定uniform的类型。
可能的后缀有:
f 函数需要一个float作为它的值
i 函数需要一个int作为它的值
ui 函数需要一个unsigned int作为它的值
3f 函数需要3个float作为它的值
fv 函数需要一个float向量/数组作为它的值
如果打算让颜色慢慢变化,就需要在游戏循环每一次迭代中更新这个uniform。
更多属性
我们同样打算把颜色数据加进顶点数据中。我们将把颜色数据添加为3个float值至vertices数组。
将把三角形的三个角分别指定为红、绿、蓝:
GLfloat vertices[] = { //位置 //颜色 0.5f,-0.5f,0.0f, 1.0f,0.0f,0.0f, //右下 -0.5f,-0.5f,0.0f, 0.0f,1.0f,0.0f, //左下 0.0f,0.5f,0.0f, 0.0f,0.0f,1.0f //顶部 };
由于更多数据要发送到顶点着色器,有必要调整一下顶点着色器,使它能够接收颜色值作为一个顶点属性输入。
需要注意的是我们用layout标识符来把color 属性的位置值设置为1:
#version 330 core layout (location =0) in vec3 position; //位置变量的属性位置值为0 layout (location =1) in vec3 color; //颜色变量的属性位置值为1 out vec3 ourColor; void main() { gl_Position = vec4(position,1.0); ourColor = color; }
修改片段着色器:
#version 330 core in vec3 ourColor; out vec4 color; void main() { color = vec4(ourColor,1.0f); }
VBO内存中的数据。
------------------------
知道了现在使用的布局,我们就可以使用glVertexAttribPointer 函数更新顶点格式:
//位置属性 glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(GLfloat),(GLvoid*)0); glEnableVertexAttribArray(0); //颜色属性 glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(GLfloat),(GLvoid*)(3*sizeof(GLfloat))); glEnableVertexAttribArray(1);
由于我们现在有了两个顶点属性,我们不得不重新计算步长值。
为获得数据队列中下一个属性值(比如位置向量的下个x分量)我们必须向右移动6个float ,其中3个是位置值,另外3个颜色值。
同样必须指定一个偏移量。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。
颜色属性紧随位置数据之后,所以偏移量就是3*sizeof(GLfloat)。
片段插值(Fragment Interpolation)
当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原来指定顶点更多的片段。
光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。