OpenGL ES 3.0编程指南:第四章. Shaders and Programs --- (二)Uniforms and Attributes

[TOC]

摘要

Uniform是由应用程序传给shader的只读常量。

Uniform的集合有两类。第一类是命名uniform block,uniform的值由一个缓冲区对象储存。每个命名uniform block被分配了一个uniform block索引。示例:

uniform TransformBlock
{
    mat4 matViewProj;
    mat3 matNormal;
    mat3 matTexGen;
};

第二类是默认的uniform block,用于在命名uniform block之外声明的unifrom。默认的uniform block没有名字和索引。示例:

uniform mat4 matViewProj;
uniform mat3 matNormal;
uniform mat3 matTexGen;

如果一个uniform在顶点shader和片段shader中都有声明,那么它们的类型必须相同,它们的值也会相同。在链接阶段,链接器会为默认uniform block中的每个活动的uniform指定位置,应用程序会通过这些位置来为uniform加载数值。链接器也会为命名uniform block中的活动uniform分配偏移和跨距(offsets and strides)。

1.Getting and Setting Uniforms

一个uniform如果在程序中被使用,则认为这个uniform是活动的(active),否则如果只是被声明而没有被使用,则在链接阶段很可能就会被优化去除。

通过用参数 GL_ACTIVE_UNIFORMS 和 GL_ACTIVE_UNIFORM_MAX_LENGTH 调用 glGetProgramiv 函数,可以分别获得当前 program对象中激活的uniform的数量 和 uniform名字字符数的最大值,一旦这两个值获得以后,就可以查询uniform的详细信息:

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

  • program : 要查询的program对象
  • index : 要查询的uniform索引(也就是第几个uniform)
  • bufsize : uniform名字的字符数量(上面查询的字符最大值,按照字面意思理解,也就是要分配的缓冲区大小)
  • length : 如果这个值不是NULL,会被写入uniform名字的字符数(不包括终止字符)
  • size : 如果要查询的uniform是一个数组,那么这个值会被写入程序中使用的最大数组元素(加1),如果不是一个数组,则该值为1
  • type : 被写入该uniform的类型,可取值为
    GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4,
    GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4,
    GL_UNSIGNED_INT, GL_UNSIGNED_INT_VEC2, GL_UNSIGNED_INT_VEC3, GL_UNSIGNED_INT_VEC4,
    GL_BOOL, GL_BOOL_VEC2, GL_BOOL_VEC3, GL_BOOL_VEC4,
    GL_FLOAT_MAT2, GL_FLOAT_MAT3, GL_FLOAT_MAT4,
    GL_FLOAT_MAT2x3, GL_FLOAT_MAT2x4,
    GL_FLOAT_MAT3x2, GL_FLOAT_MAT3x4,
    GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3,
    GL_SAMPLER_2D, GL_SAMPLER_3D,

GL_SAMPLER_CUBE, GL_SAMPLER_CUBE_SHADOW,
GL_SAMPLER_2D_SHADOW, GL_SAMPLER_2D_ARRAY, GL_SAMPLER_2D_ARRAY_SHADOW,
GL_INT_SAMPLER_2D, GL_INT_SAMPLER_3D,
GL_INT_SAMPLER_CUBE, GL_INT_SAMPLER_2D_ARRAY,
GL_UNSIGNED_INT_SAMPLER_2D, GL_UNSIGNED_INT_SAMPLER_3D,
GL_UNSIGNED_INT_SAMPLER_CUBE, GL_UNSIGNED_INT_SAMPLER_2D_ARRAY

  • name : 被写入uniform的名字,最多有bufsize个字符,以终止字符结尾

void glGetActiveUniformsiv (GLuint program, GLsizei count, const GLuint * indices, GLenum pname, GLint * params)

  • program : 要查询的program对象
  • count : indices数组元素数量
  • indices : uniform索引列表
  • pname : 要查询的属性,可取值为
    GL_UNIFORM_TYPE,
    GL_UNIFORM_SIZE,
    GL_UNIFORM_NAME_LENGTH,
    GL_UNIFORM_BLOCK_INDEX,
    GL_UNIFORM_OFFSET,
    GL_UNIFORM_ARRAY_STRIDE,
    GL_UNIFORM_MATRIX_STRIDE,
    GL_UNIFORM_IS_ROW_MAJOR
  • params : 查询结果

查询到uniform的名字之后,就可以根据名字查询uniform的位置,注意,命名uniform block里的uniform不会被分配一个位置(猜测应该是整个uniform block会被分配一个位置,而里面的每个uniform不会再被单独分配一个):

GLint glGetUniformLocation (GLuint program, const char * name)

  • program : 相应的program对象
  • name : 要查询位置的uniform名字
  • 如果这个uniform是非激活状态,返回值为-1

查询到uniform的位置之后,在根据位置和上面查询到的size和type,就可以为这个uniform加载数据:

void glUniform1f (GLint location, GLfloat x)
void glUniform1fv (GLint location, GLsizei count, const GLfloat * value)

void glUniform1i (GLint location, GLint x)
void glUniform1iv (GLint location, GLsizei count, const GLint * value)

void glUniform1ui (GLint location, GLuint x)
void glUniform1uiv (GLint location, GLsizei count, const GLuint * value)

void glUniform2f (GLint location, GLfloat x, GLfloat y)
void glUniform2fv (GLint location, GLsizei count, const GLfloat * value)

void glUniform2i (GLint location, GLint x, GLint y)
void glUniform2iv (GLint location, GLsizei count, const GLint * value)

void glUniform2ui (GLint location, GLuint x, GLuint y)
void glUniform2uiv (GLint location, GLsizei count, const GLuint * value)

void glUniform3f (GLint location, GLfloat x, GLfloat y, GLfloat z)
void glUniform3fv (GLint location, GLsizei count, const GLfloat * value)

void glUniform3i (GLint location, GLint x, GLint y, GLint z)
void glUniform3iv (GLint location, GLsizei count, const GLint * value)

void glUniform3ui (GLint location, GLuint x, GLuint y, GLuint z)
void glUniform3uiv (GLint location, GLsizei count, const GLuint * value)

void glUniform4f (GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
void glUniform4fv (GLint location, GLsizei count, const GLfloat * value)

void glUniform4i (GLint location, GLint x, GLint y, GLint z, GLint w)
void glUniform4iv (GLint location, GLsizei count, const GLint * value)

void glUniform4ui (GLint location, GLuint x, GLuint y, GLuint z, GLuint w)
void glUniform4uiv (GLint location, GLsizei count, const GLuint * value)

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)

void glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)
void glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)
void glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)
void glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)
void glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)
void glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)

  • location : uniform的位置
  • count : 需要加载数据的数组元素的数量或者需要修改的矩阵的数量
  • transpose : 指明矩阵是列优先(column major)矩阵(GL_FALSE)还是行优先(row major)矩阵(GL_TRUE)
  • x, y, z, w : uniform的值
  • value : 指向由count个元素的数组的指针

上面的这些 glUniformXXX 方法并不用指定一个program作为参数,因为这些方法总是作用在当前使用的program上,由当前的program对象保存这些uniform的值。也就是说,当你在一个program1对象上给一个uniform1赋予了一个值,那么即使你使用另一个program2对象,uniform1的值仍然在program1里。


Example 4-3

GLint maxUniformLen;
GLint numUniforms;
char * uniformName;
GLint indenx;

glGetProgramiv (progObj, GL_ACTIVE_UNIFORMS, &numUniforms);
glGetProgramiv (progObj, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen);

uniformName = malloc ( sizeof(char) * maxUniformLen);

for (index = 0; index < numUniforms; index++)
{
    GLint size;
    GLenum type;
    GLint locationl

    glGetActiveUniform (progObj, index, maxUniformLen, NULL, &size, &type, uniformName);

    location = glGetUniformLocation (progObj, uniformName);

    switch (type)
    {
        case GL_FLOAT:
            break;
        case GL_FLOAT_VEC2:
            break;
        default:
            break;
     }
}

2.Uniform Buffer Objects

通过使用uniform缓冲对象来储存uniform数据,我们可以在程序之间或者shader之间共享uniform。

可以通过使用glBufferData, glBufferSubData, glMapBufferRange 和 glUnmapBuffer来修改uniform缓冲对象的内容,不能使用上文介绍的glUniformXXX方法。

在uniform缓存对象中,uniform在内存中按照如下方式储存:

  • 类型为bool,int,uint,float的变量在内存中按照单个uint,int,uint,float以指定的偏移储存
  • bool,int,uint,float类型的向量以指定的偏移开始储存在连续内存位置中,其中第一个分量储存在最低偏移处
  • C列R行的列优先矩阵被当做一个有C个列向量的数组,每个向量包含R个分量;类似的,R行C列的行优先矩阵,被当做一个有R个行向量的数组,每个向量包含C个分量。列向量或者行向量是连续储存的,但是之间可能有缺口(偏移)。矩阵中两个向量之间的偏移被称为列跨距或行跨距(GL_UNIFORM_MATRIX_STRIDE),可以通过glGetActiveUniformsiv来查询。
  • 标量数组、向量数组和矩阵数组在内存中按照元素的顺序储存,索引为0的成员储存在最低偏移处。数组中每对元素之间的偏移是一个常数,被称为数组跨距(GL_UNIFORM_ARRAY_STRIDE),可以通过glGetActiveUniformsiv查询。

除非使用默认的std140 uniform block布局,否则需要查询获得字节偏移和跨距,以便在uniform缓冲对象中设置uniform数据。std140布局使用有OpenGL ES 3.0明确规范定义的布局规范来进行特定打包,因此,使用std140布局,可以在不同的OpenGL ES 3.0实现之间共享uniform block。其他打包格式可能允许一些OpenGL ES 3.0实现以比std140更紧凑的方式打包数据。

使用std140布局的命名uniform block示例:

layout (std140) uniform LightBlock
{
    vec3 lightDirection;
    vec3 lightPosition;
};

std140布局规定如下:

  1. 标量 :基础对齐量是标量类型的大小,例如sizeof(GLint)
  2. 二元向量 :基础对齐量是标量类型大小的两倍
  3. 三元或四元向量 : 基础对齐量是标量类型大小的四倍
  4. 标量数组或向量数组 : 基础对齐量和数组跨距都是其单个元素的基础对齐量。整个数组被填充为vec4大小的整数倍,不足则在末尾补齐
  5. C列R行的列优先矩阵 : 储存为一个由C个具有R个分量的向量组成的数组,数组规则符合规则4
  6. 由M个C列R行的列优先矩阵组成的数组 : 储存为一个由M * C个具有R个分量的向量组成的数组,数组规则符合规则4
  7. C列R行的行优先矩阵 : 储存为一个由R个具有C个分量的向量组成的数组,数组规则符合规则4
  8. 由M个C列R行的行优先矩阵组成的数组 : 储存为一个由M * R个具有C个分量的向量组成的数组,数组规则符合规则4
  9. 单个结构体 : 偏移和大小根据前面的规则计算。结构体的大小为vec4大小的整数倍,不足不足则在末尾补齐
  10. 结构体组成的数组 : 基础对齐量需要考虑单个结构体的对齐和补齐

和上文提到的一个uniform的位置用来表示一个uniform类似,一个uniform block index用来表示一个uniform block。

获得uniform block的名字:

void glGetActiveUniformBlockName (GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLchar * blockName)

  • program : program对象
  • index : 要查询的索引
  • bufSize : 名字的(最大)字符数
  • length : 如果这个值不是NULL,会被写入uniform名字的字符数(不包括终止字符)
  • name : 被写入uniform的名字,最多有bufsize个字符,以终止字符结尾

获得uniform block的其他属性:

void glGetActiveUniformBlockiv (GLuint program, GLuint index, GLenum pname, GLint * params)

  • program : program对象
  • index : 要查询的索引
  • pname : 要查询的属性,可取值为
    GL_UNIFORM_BLOCK_BINDING 返回uniform block的最后一个绑定点(如果uniform block不存在,则为0)
    GL_UNIFORM_BLOCK_DATA_SIZE 返回包含uniform block中所有uniform的缓冲对象的最小尺寸
    GL_UNIFORM_BLOCK_NAME_LENGTH 返回uniform block名字的总长度(包括终止字符)
    GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 返回uniform block中活动的uniform的数量
    GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 返回uniform block中活动的uniform的索引列表
    GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 返回uniform block是否由顶点shader引用
    GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 返回uniform block是否由片段shader引用
  • params : 查询结果

根据名字获得uniform block index:

GLuint glGetUniformBlockIndex (GLuint program, const GLchar * blockName)

  • program : program对象
  • blockName : uniform block的名字

然后可以将uniform block index和program中的一个binding point进行绑定:

void glUniformBlockBinding (GLuint program, GLuint blockIndex, GLuint blockBinding)

  • program : program对象
  • blockIndex : uniform block index
  • blockBinding : uniform缓冲对象绑定点

最后,将一个uniform buffer object和这个binding point绑定:

void glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)
void glBindBufferBase (GLenum target, GLuint index, GLuint buffer)

  • target : 必须是 GL_UNIFORM_BUFFER 或者是 GL_TRANSFORM_FEEDBACK_BUFFER
  • index : 绑定索引
  • buffer : 缓冲对象
  • offset : 缓冲对象的起始偏移字节数
  • size : 能从缓冲对象读取或者写入缓冲对象的数据量

当编程使用到uniform时,有以下限制要注意:

  • 顶点shader或片段shader能使用的活动的uniform block是有数量限制的,最大值可以通过glGetIntegerv调用GL_MAX_VERTEX_UNIFORM_BLOCKS或者GL_MAX_FRAGMENT_UNIFORM_BLOCKS来查询。任何实现的最大值都不会小于12。
  • 一个program对象中所有shader能使用的活动的uniform block也是有数量限制的,最大值可以通过glGetIntegerv调用GL_MAX_COMBINED_UNIFORM_BLOCKS查询。任何实现的最大值都不会小于24。
  • 每个uniform缓冲对象的最大可用储存量的大小可以通过glGetInteger64v来查询。任何实现的最大值都不会小于16KB。

以下示例展示了如何用前面描述的命名uniform block LightBlock来建立一个uniform缓冲对象:

GLuint blockId, bufferId;
GLint blockSize;
GLuint bindingPoint = 1;
GLfloat lightData[] =
{
    // lightDirection (padded to vec4 based on std140 rule)
    1.0f, 0.0f, 0.0f, 0.0f
    
    // lightPosition
    0.0f, 0.0f, 0.0f, 1.0f
};

// Retrieve the uniform block index
blockId = glGetUniformBlockIndex (program, "LightBlock")

glUniformBlockBinding (program, blockId, bindingPoint)

glGetActiveUniformBlockiv (program, blockId, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize)

glGenBuffers (1, &bufferId);
glBindBuffer (GL_UNIFORM_BUFFER, bufferId);
glBufferData (GL_UNIFORM_BUFFER, blockSize, lightData, GL_DYNAMIC_DRAW);

glBindBufferBase (GL_UNIFORM_BUFFER, bindingPoint, bufferId);

3.Getting and Setting Attributes

通过program对象除了可以查询uniform信息外,还可以用来查询顶点属性信息,具体在第六章讲述。

你可能感兴趣的:(OpenGL ES 3.0编程指南:第四章. Shaders and Programs --- (二)Uniforms and Attributes)