shader和program

Shader Object是包含单个shader的对象。它接收源代码并将之编译成object形式(类似c中的.obj)。
完成编译后,shader object可以被指定给一个program object。
Program object可附加多个shader object,在OpenGL ES中,每个program object有且仅有一个vertex shader object和一个fragment shader object。其后,program object被链接成一个“可执行文件”,最终,program object就可以用来渲染了。

创建和编译shader

创建shader:

GLuint glCreateShader(GLenum type);

type可以是GL_VERTEX_SHADER或GL_FRAGMENT_SHADER。
删除shader:

void glDeleteShader(GLuint shader);

当shader被指定给program object时,glDeleteShader并不会立即删除shader,而是将shader标记为删除,直到shader不在指定给任何program object时才会真正释放内存。
输入源代码给shader:

void glShaderSource(GLuint shader, GLsizei count, const char** string, const GLint* length);

需要注意的是,实际上,并不是所有的OpenGL ES 2.0都提供编译能力,有些是要求离线编译的,这时,就要使用shader binary。
编译shader:

void glCompileShader(GLuint shader);

获取shader状态:

void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);

其中,pname可以是GL_COMPILE_STATUS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_SHADER_SOURCE_LENGTH、GL_SHADER_TYPE。
查询编译日志:

void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);

创建和编译program

创建program:

GLuint glCreateProgram(void);

删除program:

GLuint glDeleteProgram(GLuint program);

附加shader:

void glAttachShader(GLuint program, GLuint shader);

解除附加:

void glDetachShader(GLuint program, GLuint shader);

链接程序:

void glLinkProgram(GLuint program);

获取程序状态:

void glGetProgramiv(GLuint program, GLenum pname, GLint *params);

其中,pname可以是GL_ACTIVE_ATTRIBUTES、GL_ACTIVE_ATRIBUTE_MAX_LENGTH、GL_ACTIVE_UNIFORMS、GL_ACTIVE_UNIFORM_MAX_LENGTH、GL_ATTACHED_SHADERS、GL_DELETE_STATUS、GL_INFO_LOG_LENGTH、GL_LINK_STATUS、GL_VALIDATE_STATUS。
查询链接日志:

void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar **infoLog);

验证程序合法性,例如,是否所有sampler都绑定了纹理:

void glValidateProgram(GLuint program);

设置程序为当前活跃程序:

void glUseProgram(GLuint program);

uniform和attribute

Uniform是保存应用通过OpenGL ES 2.0 API传递给shader的只读常数值的变量。
Uniform会被整个program共享。
如果一个uniform在vertex和fragment shader中都被使用,它的类型必须相同,它的值对所有shader来说也是一致的。
在link阶段,链接器会为所有活跃的uniform分配位置,uniform的位置被用作应用给uniform加载数值的标识符。

读取和设置uniform

仅当被使用的时候,uniform才被认为有效,即,未被使用的uniform会被链接器优化掉。
查询有效的uniform:

void glGetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, char *name);

查询uniform的location:

GLuint glGetUniformLocation(GLuint program, char *name);

为uniform加载数值:

// float
void glUniform1f(GLint location, GLfloat x);
void glUniform2f(GLint location, GLfloat x, GLfloat y);
void glUniform3f(GLint location, GLfloat x, GLfloat y, GLfloat z);
void glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
// float vec
void glUniform1fv(GLint location, const GLfloat *v);
void glUniform2fv(GLint location, const GLfloat *v);
void glUniform3fv(GLint location, const GLfloat *v);
void glUniform4fv(GLint location, const GLfloat *v);
// int
void glUniform1i(GLint location, GLint x);
void glUniform2i(GLint location, GLint x, GLint y);
void glUniform3i(GLint location, GLint x, GLint y, GLint z);
void glUniform4i(GLint location, GLint x, GLint y, GLint z, GLint w);
// int vec
void glUniform1iv(GLint location, const GLint *v);
void glUniform2iv(GLint location, const GLint *v);
void glUniform3iv(GLint location, const GLint *v);
void glUniform4iv(GLint location, const GLint *v);
// float matrix, notice: no int matrix
void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

注意:

  1. 当uniform为数组时,仅可使用vec形式的函数,且会一次性加载全部的分量的数值;
  2. 设置uniform的函数的参数不包括program,它只作用于当前使用的program,且一旦设置,就会保存在该program中,即使它不再是当前使用的program。

获取和设置attribute

获取attribute信息的方法和uniform非常相似。
而设置attribute需要对图元以及vertex shader有更深的理解,待第六章讲解。

注意:Uniform的location是linker决定的,因此设置uniform时需要查询其位置后设置;attribute的location可以通过glBindAttribteLocation来指定。

Shader编译器和Shader二进制文件

由于再现编译、优化需要花费CPU时间和内存,spec允许实现仅支持二进制shader。
OpenGL ES 2.0 vendor提供离线工具将shader源代码编译成实现可识别的二进制格式。
spec没有对二进制格式进行规定,vendor可执行设计。
可以使用glGetBooleanv查询GL_SHADER_COMPILER来确定实现是否支持在线编译。
可以使用glGetIntegerv查询GL_NUM_SHADER_BINARY_FORMATS查询实现支持的二进制格式数量;
可以使用glGetIntegerv查询GL_SHADER_BINARY_FORMATS查询实现具体支持的二进制格式。

当编译完成后,可以给实现发送可以释放编译器资源的提示:

void glRleaseShaderCompiler(void);

加载二进制文件到shader:

void glShaderBinary(GLint n, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLint length);

一个二进制文件中可包含一个或多个shader,且可以同时存在vertex和fragment shader。
verdor可以要求vertex shader和fragment分别给定,然后在线link,也可以要求shader的link也离线完成。
加载完二进制文件的shader状态,除了不能读回源码外,和编译后几乎一样。