一、前言
在博客 【我的OpenGL学习进阶之旅】你好,三角形:一个OpenGL ES 3.0示例。 分别使用C++ Native & Java 两种方式来实现 中,我们介绍了绘制一个三角形的简单程序。
在那个例子中,我们创建了两个着色器对象(一个顶点着色器,一个片段着色器)和一个程序对象,以渲染三角形。
着色器对象和程序对象是使用OpenGL ES 3.0着色器的基本概念。
接下来,我们要讲解的主题,如下所示:
- 着色器和程序对象概述
- 创建和编译着色器
- 创建和链接程序
- 获取和设置统一变量
- 获取和设置属性
- 着色器编译器和程序二进制代码
二、着色器和程序
需要创建两个基本对象才能用着色器进行渲染:着色器对象和程序对象。
理解着色器对象和程序对象的最佳方式就是将它们比作C语言的编译器和链接程序。 C编译器为一段源代码生成目标代码(例如,.obj或者.o文件)。在创建目标文件之后,C链接程序将对象文件链接为最后的程序。
OpenGL ES 在着色器的表现上使用类似的方式。
- 着色器对象是包含单个着色器的对象。
- 源代码提供给着色器对象,然后着色器对象被编译为一个目标形式(类似与.obj文件)。
- 编译之后,着色器对象可以连接到一个程序对象。程序对象可以连接多个着色器对象。
- 在OpenGL ES中,每个程序对象必须连接一个顶点着色器和一个片段着色器(不多也不少),这和桌面OpenGL不同。
- 程序对象被链接为用于渲染的最后“可执行程序”。
获得连接后的着色器对象的一般过程包括6个步骤:
- 创建一个顶点着色器对象和一个片段着色器对象
- 将源代码连接到每个着色器对象。
- 编译着色器对象。
- 创建一个程序对象。
- 将编译后的着色器对象连接到程序对象。
- 链接程序对象。
如果没有错误,就可以在任何时候通知GL使用这个程序绘图。
这篇博客我们先介绍着色器相关的知识。
2.1 创建和编译一个着色器
2.1.1 创建着色器
使用着色器对象的第一步是创建着色器,这可以用glCreateShader 完成。
GLuint glCreateShader (GLenum type);
参数 type : 创建的着色器类型可以是GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER
调用glCreateShader 将根据传入的 type 参数创建一个新的顶点着色器或者片段着色器。 返回值是指向新着色器对象的句柄。
2.1.2 删除着色器
当完成着色器对象时,可以使用glDeleteShader 删除
void glDeleteShader (GLuint shader);
参数shader 表示: 要删除的着色器对象的句柄。
注意,如果一个着色器链接到一个程序对象,那么调用glDeleteShader不会立刻删除着色器,而且将着色器标记为删除,在着色器不再连接到任何程序对象时,它的内存将被释放。
2.1.3 提供着色器源代码
一旦创建着色器对象,下一件事通常是使用glShaderSource 提供着色器源代码
void glShaderSource (GLuint shader, GLsizei count,
const GLchar *const*string,
const GLint *length);
参数讲解:
- shader
指向着色器对象的句柄 - count
着色器源字符串的数量。着色器可以由多个源字符串组成,但是每个着色器只能有一个main 函数。 - string
指向保存数量为count 的着色器源字符串的数组指针。 - length
指向保存每个着色器字符串大小且元素数量为count 的整数数组指针。如果length 为NULL ,着色器字符串将被认定为空。如果length 不为NULL ,则它的每个元素保存对应string 数组的着色器的字符数量。如果任何元素的length 值均小于0,则该字符串被认定以null 结束。
2.1.4 编译色器
指定着色器源代码之后,下一步是用glCompileShader 编译着色器。
void glCompileShader (GLuint shader);
参数 shader 表示: 需要编译的着色器对象句柄。
2.1.4 查询有关着色器对象的信息
调用glCompileShader 将编译已经保存在着色器对象的着色器源代码。 和常规的语言编译器一样,编译之后你想知道的第一件事是不是没有错误。你可以使用glGetShaderiv 查询这一信息和其他有关着色器对象的信息。
void glGetShaderiv (GLuint shader, GLenum pname, GLint *params);
参数讲解:
shader 执向需要获取信息的着色器对象的句柄pname 获取信息的参数,可以是:
GL_COMPILE_STATUS GL_DELETE_STATUS GL_INFO_LOG_LENGTH GL_SHADER_SOURCE_LENGTH GL_SHADER_TYPE params 指向查询结果的整数存储位置的指针。
2.1.4.1 查询GL_COMPILE_STATUS
要检查着色器是否编译成功,可以用pname 参数GL_COMPILE_STATUS 在着色器对象上调用glGetShaderiv 。 如果着色器编译成功,结果将是GL_TRUE 。如果编译失败,结果将是GL_FALSE ,编译错误将写入信息日志。
信息日志是由编译器写入并包含错误信息或者警告的日志。 即使编译操作成功,也会在信息日志中写入信息。要检索信息日志,可以使用GL_INFO_LOG_LENGTH 查询它的长度。
日志本身可以用glGetShaderInfoLog 检索。
2.1.4.2 查询GL_SHADER_TYPE
查询GL_SHADER_TYPE 将返回着色器类型:GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER
2.1.4.3 查询GL_DELETE_STATUS
查询GL_DELETE_STATUS 返回着色器是否用glDeleteShader 标记为删除。
2.1.5 获取信息日志
编译着色器并检查信息日志长度之后,你可能希望检索信息日志(特别是在编译失败时查看原因)。为此,首先需要查询GL_INFO_LOG_LENGTH 并分配一个足以存储信息日志的字符串。 然后,用glGetShaderInfoLog 检索信息日志。
void glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
参数说明:
- shader
需要获取信息日志的着色器对象句柄 - bufSize
保存信息日志的缓冲区大小 - length
写入的信息日志的长度(减去null终止符)。如果不需要知道长度,这个参数可以为NULL - infoLog
指向保存信息日志的字符缓冲区的指针。
信息日志没有任何强制的格式或者必须的信息。但是,大部分OpenGL ES 3.0实现将返回错误信息,包括编译器发现的错误源代码行号。有些实现还在日志中提供警告或者附件信息。
比如我的博客 【我的OpenGL学习进阶之旅】解决着色器编译错误:#version directive must occur on the first line of the shader中描述的,产生的信息:
2021-11-15 15:09:07.406 26065-26107/opengles3.book.hello_Triangle E/ESShader: ERROR: 0:2: '
' : #version directive must occur on the first line of the shader
ERROR: 0:9: 'in' : storage qualifier supported in GLSL ES 3.00 only
三、加载着色器示例
到现在为止,我们已经说明了创建着色器、编译、找出编译状态和查询信息日志所需的所有函数。
下面写一个示例,来加载一个着色器。
static GLuint loadShader(GLenum shaderType, const char** source) {
GLuint shader;
GLint compiled;
shader = glCreateShader(shaderType);
if (shader == 0) {
return 0;
}
glShaderSource(shader, 1, source, nullptr);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char* infoLog = (char*)malloc(sizeof(char) * infoLen);
glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
LOGE("Error compiling shader:\n%s\n", infoLog);
free(infoLog);
}
glDeleteShader(shader);
return 0;
}
return shader;
}
|