前言:
在第三章开始,每个源码示例几乎都使用了一些预建的存储着色器,这些存执着色器执行一些例行程序和典型的渲染操作。本章,将开始学习如何编写自己的着色器,即服务器端的着色器应用:着色器编程和着色语言。
一、OpenGL着色语言
我们至少需要两个着色器:一个顶点着色器、一个片段着色器。还有一种可选的着色器成为几何着色器,在第11章学习。我们可以以3种方式中选择一种向顶点着色器传递数据:一是参数,是对每个顶点而言的;二是统一值,是针对整个顶点数据批次的常亮(所以是一致的);最后是加载和使用纹理数据。
将顶点属性发送到片段着色器毫无意义,因为片段着色器只是用来在图元进行光栅化后对片段(最基本的是像素)进行填充。但是,每个顶点数据都可以通过顶点程序传递到片段着色器。但是在这种情况下,这些数据可能是常亮(每个片段的都是同样的值),或者这些值也可以用不同的方式在图元表面进行插值。
1、变量和数据类型
可用的数据类型只有4种:整数(包括有符号和无符号整数)、浮点型(单精度float)和布尔型(bool)。没有指针、字符串或字符。函数可以返回任何数据类型中的一种,但是不允许是void指针。其应用与他们在C/C++中一样。
1)向量类型(列向量)
OpenGL着色语言的另一个独特的性质是对一个向量的独立元素进行寻址的方式。与C/C++中的union非常类似。
使用点号来确定多达4个向量元素的地址,但也可以使用下列3组标识符中的任意一组:xyzw、rgba或stpq。
vVertexPos.x = 3.0f; vVertexPos.xy = vec2(3.0f,5.0f);vVertexPos.xyz = vNewPos.xyz;
vColor.r = 1.0f;vColor.rgba = vec4(1.0f,1.0f,0.5f,1.0f);
vTexCoord.st = vec2(1.0f,0.0f);.........
2)矩阵类型
实际上在OpenGL着色语言中,一个矩阵就是一个由向量组成的数组——列向量。例如:
mModelView[3]=vec4(0.0f,0.0f,0.0f,1.0f);
vec4 vTranslation = mModelView[3];或者vec4 vTranslation = mModelView[3].xyz;
矩阵类型也有自己的构造函数,例如下面是创建单位矩阵:
2、存储限定符
限定符用于将变量标记为输入变量(in或uniform)、输出变(out)或常量(const)。输入变量接受来自OpenGL客户端(通过C/C++提交的属性)或者以前的着色器阶段(例如从顶点着色器传递到片段着色器)。输出变量是指在任何着色器阶段进行写入的变量,我们希望后续的着色器阶段能看到这些变量。(下图可能有些不清楚,但是中文的...)
注意:
1、inout只能在一个函数中声明一个参数时使用。因为OpenGL着色语言并不支持指针(或者引用),所以这是将一个值传递到一个函数并且允许函数修改并返回同一个变量的唯一方法。
2、除非正在对一个多重采样缓冲区进行渲染,否则centroid限定符不会起作用。第九章学习。
3、默认情况下,参数将在两个着色器阶段之间以一种透视正确的方法进行插值。可以通过noperspective关键词来指定一个非透视插值,或者甚至可以通过flat关键词而不进行插值;还可以使用smooth关键词来声明以透视正确的方法进行插补,实际上已经是默认设置。
3、真正的着色器
GLShaderManager类有一个存储着色器,叫单位着色器。这种着色器不会对几何图形进行人格转换,而是使用单一的颜色绘制图元。
顶点程序:
#version 330
in vec4 vVertex; //顶点位置属性
in vec4 vColor; //顶点颜色属性
out vec4 vVaryingColor;//传递到片段着色器的颜色值
void main (void)
{
vVaryingColor= vColor;//对片段进行颜色插值
gl_Position = vVertex; //简单传递顶点位置
}
vVaryingColor变量将成为将要传送到片段着色器的顶点的颜色值。
gl_Postion是一个预定义的内建4分量向量,它包含顶点着色器要求的一个输出。输入gl_Postion的值是几何图形阶段用来装配图元的。
片段程序
#version 330
out vec4 vFragColor;//将要进行光栅化的片段颜色
in vec4 vVaryingColor; //从顶点阶段得到的颜色
void main (void)
{
vFragColor = vVaryingColor;//对片段进行颜色插值
}
vFragColor是最终输出的颜色。
每个着色器的第一行非命令行都是指定版本。#version 330,指定这个着色器要求的OpenGL着色语言的最低版本是3.3。
4、编译、绑定和连接
着色器源代码被传递给驱动程序,然后进行编译,最后进行连接。此外,着色器中的属性名需要绑定到由GLSL提供的16个预分配属性槽中的某一个上。
OpenGL API不支持任何类型的文件I/O操作。着色器的源代码采用什么样的方式,由程序员根据哪种方式对应用有利来进行选择。
1)最简单的方式是存储在ASCII纯文本文件中,顶点着色器采用扩展名.vp,而片段着色器则采用扩展名.fp。
2)将文本作为硬编码的字符数组存储在C/C++源代码中。编辑麻烦,而且使程序更加自我封闭。
3)其他方法,通过算法生成这着色器源代码;从数据库中恢复;从某种加密数据文件中恢复。这些在移植一个应用程序有很大用处。
GLTools函数gltLoadShaderPairWithAttributs(const char *szVertexProg,const char* szFragmentProg,....)是加载和初始化着色器的函数。接受可变参数。
加载和初始化着色器的过程概述:
1)指定属性,就是附件参数(....)的值。
2)设置源代码:创建两个着色器对象。
3)编译着色器,编译器由硬件提供提供。
4)进行连接和绑定。
5)连接着色器
例如:
gltLoadShaderPairWithAttributs(“vertexProg.vp”,“fragmentPrg.vp”,2,GLT_ATTRIBUTE_VERTEX, "vVertex", GLT_ATTRIBUTE_COLOR, "vColor");
前两个char类型指针指向顶点和片段程序的文件,2代表顶点程序包含属性的数量;之后是一一对应的属性索引和属性名;函数详细学习可以查看GLTools源码。在后面学习源码示例ShaderTriangle.cpp时再理解。
5、使用着色器
要使用GLSL着色器,必须使用glUseProgram函数选定它,
glUseProgram(myShaderProgram);
这样就将着色器设置为活动的,现在顶点着色器和片段着色器会处理所有提交的几何图形。在提交顶点属性之前,要对Uniform值和纹理进行设置。
二、着色器统一值
属性是每个顶点位置、表面法线和纹理坐标等都需要的,而统一值则用于为整个图元批次向保持不变的(统一(uniform)的)着色器传递数据。现在我们要开始编写自己的着色器,就需要能够设置自己的统一值,而不只是矩阵值了。例如:
uinform float fTime; uniform int iIndex; uinform vec4 vColorValue; uinform mat4 mvpMatrix;
统一值不能被标记为in或out,它们也不能在着色器阶段之间进行插值,而且它们总是只读的。
1、寻找统一值
在一个着色器进行编译和连接之后,我们必须在着色器中寻找统一值位置。
可以从加载的着色器中获取一个名为vColorValue的统一值的位置:
着色器变量名是区分大小写的,如果glGetUniformLocation的返回值是-1.就说明统一值的名称在着色器中不能被定为。
2、设置标量和向量统一值
通常寻找统一值和设置统一值一起使用,例如:
3、设置统一数组
location是寻找的统一值变量,count值代表每个含有x个分量的数组有多少个元素,v是指向数组的指针,x是位于函数名末尾的数字
例如:
1)GLFloat vColor[4]={1.0f,1.0f,1.0f,1.0f}是一个包含4个值的单个数组,调用:
glUniform4fv(iColorLocation,1,vColors);
2)GLFloat vColor[2][4]={{1.0f,1.0f,1.0f,1.0f},{1.0f,1.0f,1.0f,1.0f}};代表含有4分量的数组有2个元素
glUniform4fv(iColorLocation,2,vColors);
3)GLFloat fValue = 45.2f;
glUniform1fv(iColorLocation,1,&fValue);
4、设置统一矩阵
变量count代表指针参数m中存储的矩阵数量。如果矩阵已经按照列优先排序进行存储,布尔值标记transpose将被设置为GL_TURE。将这个值设置为GL_FALSE会导致这个矩阵在复制到着色器中时发生变换。
三、小结
在学习了着色器的基本概念、渲染方式和一些基本函数之后,后面通过源码示例进一步理解着色器和着色语言。我们要编写自己的顶点和片段程序,加载程序片段渲染绘制的图形,所以这节的学习作为后面源码学习的基础。后面要学习顶点和片段着色器即服务端的程序,也要巩固学习客户端的应用程序代码(C/C++),两者结合起来。