在上篇文章渲染流程 已经说到了着色器,在使用着色器进行渲染的时候,需要两个对象:着色器对象 和 程序对象。着色器对象和程序对象就好比编译器和链接对象,着色器对象是包含单个着色器的对象,着色器对象被编译为一个目标形式 (类似.o .obj文件),编译之后着色器对戏那个可以链接到程序对象。
程序对象可以链接多个着色器对象。在OpenGL ES中,每个程序对象必须连接一个
顶点着色器
和一个片段着色器
。程序对象被链接为用于渲染的最后“可执行程序”。
获得链接后的着色器对象的步骤:
- 创建一个顶色器对象和一个片段着色器对象
- 将着色器源代码连接到每个着色器对象
- 编译着色器对象
- 创建一个程序对象
- 将编译后的着色器连接到程序对象
- 链接程序对象
下面结合着OpenGL ES的API挨个说
创建和编译一个着色器
-
创建着色器
使用着色器的第一步是创建着色器
:
// type: 创建的着色器类型可以是:GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER
// 返回值:指向着色器的句柄
GLuint glCreateShader (GLenum type);
-
删除着色器
当使用完着色器对象的时候,可以 删除着色器
:
// shader: 要删除的着色器对象的句柄
void glDeleteShader (GLuint shader);
注意:如果一个着色器连接到一个程序对象,调用glDeleteShader不会立刻删除着色器,而是将着色器标记为删除,在着色器不再连接任何程序对象时,内存将被释放。
-
给着色器对象提供源代码
创建了着色器对象之后,就是给着色器对象提供源代码
,着色器源代码以字符串的形式提供:
// shader: 指向着色器对象的句柄
// count: 着色器源代码字符串的数量。着色器可以由多个源字符串组成,但是每个着色器只能有一个main函数
// string: 指向着色器源代码的字符串指针
// length: 指向保存着多个(如果有多个)源代码字符串大小的整型数组指针
void glShaderSource (GLuint shader,
GLsizei count,
const GLchar *const string,
const GLint *length );
-
编译着色器
指定完着色器源代码之后,下一步就是用glCompileShader编译着色器
:
void glCompileShader (GLuint shader);
可以用glGetShaderiv
的GL_COMPILE_STATUS
参数查询编译的信息
-
查询着色器状态信息
// shader: 着色器句柄
/* pname: 要查询的信息的参数:
GL_COMPILE_STATUS: 编译状态
GL_DELETE_STATUS: 着色器是否用glDeleteShader标记为删除
GL_INFO_LOG_LENGTH: 编译信息日志长度
GL_SHADER_SOURCE_LENGTH: 着色器源代码长度
GL_SHADER_TYPE: 着色器类型
*/
// params: 指向 “查询结果” 的整数存储位置的指针
void glGetShaderiv (GLuint shader, GLenum pname, GLint *params);
检查着色器是否编译成功,用GL_COMPILE_STATUS参数,成功编译,params结果是GL_TRUE,编译失败GL_FALSE,编译错误信息会写入信息日志。即使编译成功,也会在信息日志中写入信息。
可以用GL_INFO_LOG_LENGTH查询日志的长度。日志可以用glGetShaderInfoLog
检索。
-
获取信息日志
// maxLength: 保存信息日志的缓冲区大小
// length: 要写入的信息日志的长度,减去null终止符
// infoLog: 指向保存信息日志的字符缓冲区的指针
void glGetShaderInfoLog (GLuint shader,
GLsizei maxLength,
GLsizei *length,
GLchar *infoLog);
-
创建程序对象
上面的都是如果创建着色器对象,下面是创建一个程序对象。程序对象是一个容器对象,可以将着色器对象与之相连接,并链接到一个最终的可执行程序
。操作程序对象的API与着色器的API比较相似。
// glCreateProgram没有参数,返回一个指向新程序对象的句柄
GLuint glCreateProgram();
-
删除程序对象
// program: 要删除的程序对象的句柄
void glDeleteProgram(GLuint program);
-
连接 程序对象 和 着色器对象
// program: 程序对象的句柄
// shader: 着色器对象的句柄
void glAttachShader(GLuint program, GLuint shader);
注意:这个函数将着色器连接到指定的程序。着色器可以在任何时候连接,着色器连接到程序之前不一定需要编译,甚至可以没有源代码。唯一的要求是:每一个程序对象必须有且只有一个顶点着色器和一个片段着色器与之相连。
可以用glDetachShader
断开连接。
-
断开 连接
void glDetachShader (GLuint program, GLuint shader);
-
链接生成最终的可执行程序
前提:程序连接了着色器,并且着色器编译成功。
void glLinkProgram (GLuint program);
链接操作负责生成最终的可执行程序,生成在硬件上运行的最终硬件指令。链接操作将检查各种对象的数量,确保成功的链接。
链接程序将确保:
- 确保 Vertex Shader 写入 Fragment Shader 的输出变量,和Fragment Shader使用的输入变量,有相同的声明。
确保在Vertex Shader 和 Fragment Shader中声明的统一变量和统一变量缓冲区的类型相同
确保最终的程序符合具体实现的限制,例如,属性、统一变量或者输入输出着色器变量的数量
-
查询链接状态
上面说过 查询shader的编译状态glGetShaderiv,下面是查询程序的链接链接状态glGetProgramiv
// pname: 要获取的参数信息
void glGetProgramiv (GLuint program, GLenum pname, GLint *params);
要检查链接是否成功,可以查询 GL_LINK_STATUS,和着色器一样,程序对象存储在一个信息日志,日志的长度可以用GL_INFO_LOG_LENGTH来查询
-
获取程序的日志信息
// 参数跟着色器部分一样
void glGetProgramInfoLog (GLuint program,
GLsizei maxLength,
GLsizei *length,
GLchar *infoLog);
程序成功链接后,就已经差不多为使用它渲染做好了准备。但是在这之前,我们需要检查程序是否有效,即:链接成功并不能保证执行成功,例如,应用程序没有把有效的纹理绑定到采样器,这种错误在链接的时候我们无法知道,但是在绘图的时候会出现,可以调用glValidateProgram来校验
-
校验应用程序有效性
void glValidateProgram (GLuint program);
校验的结果可以用glGetProgramiv来查询,pname参数使用GL_VALIDATE_STATUS来检查,同时信息日志也会更新。
注意:glValidateProgram是一个比较耗时的操作,一般只是用作调试。
在渲染之前,还有一件事要做:把应用程序设置成活动程序
-
应用程序设置成 活动程序
void glUseProgram (GLuint program);
设置成活动程序之后,就可以开始渲染了。
未完待续 。。。。。