【OpenGL随笔】03 _着色器数据块接口

1. uniform 块

着色器与应用程序之间,或者着色器各阶段之间共享的变量可以组织为变量块的形式,并且有的时候必须采用这种形式。

uniform b {     // 限定符可以为 uniform、in、out 或者 buffer
    vec4 v1;    // 块中的变量列表
    bool v2;    // ...
};              // 访问匿名块成员时使用v1、v2

uniform b {     // 限定符可以为 uniform、in、out 或者 buffer
vec4 v1;        // 块中的变量列表
bool v2;        // ...
} name;         // 访问有名块成员时使用 name.v1、name.v2

通常会在多个着色器程序中用到同一个 uniform 变量。
由于 uniform 变量的位置是着色器链接的时候产生的(也就是调用 glLinkProgram() 的时候),因此它在应用程序中获得的索引可能会有变化,即使我们给 uniform 变量设置的值可能是完全相同的。

uniform变量是同时存在于用户应用程序和着色器当中的,因此需要同时修改着色器的内容并调用OpenGL函数来设置uniform缓存对象。

1.1 指定着色器中的 uniform 块

uniform Matrices {
    mat4 ModelView;
    mat4 Projection;
    mat4 Color;
};

Note

  • 着色器中的数据类型有两种:不透明的和透明的;其中不透明类型包括采样器、图像和原子计数器;
  • uniform 块中只可以包含透明类型的变量;
  • uniform 块必须在全局作用域内声明。

1.2 uniform 块的布局控制

1.2.1 uniform 的布局限制符

布局限制符描 述
binding = N设置缓存的绑定位置,需要用到 OpenGL API
shared设置 uniform 块是多个程序间共享的(这是默认的布局方式,与 shared 存储限制符不存在
混淆)
packed设置 uniform 块占用最小的内存空间,但是这样会禁止程序间共享这个块
stdl40使用标准布局方式来设置 uniform 块或者着色器存储的 buffer 块, 参见附录H
std430使用标准布局方式来设置 uniform 块,参见附录I
offset = N强制设置成员变量位于缓存的N字节偏移处
align = N强制设置成员变址的偏移位置是N的倍数
row_major使用行主序的方式来存储 uniform 块中的矩阵
column_major使用列主序的方式来存储 uniform 块中的矩阵(这也是默认的顺序)

1.2.2 布局声明

Example 01 :需要共享一个uniform块,并且使用行主序的方式来存储数据

layout (shared, row_major) uniform {
    ...... 
};
  • 多个限制符可以通过圆括号中的逗号来分隔。

Example 02 :需要对所有后继的uniform块设置同一种布局

layout (packed, column_major) uniform;
  • 当前行之后的所有 uniform 块都会使用这种布局方式,除非再次改变全局的布局,或者对某个块的声明单独设置专属的布局方式。

1.2.3 缓存布局

如果你在着色器和应用程序之间共享了一块缓存,那么这两者都需要确认成员变量所处的内存偏移地址。

此时可以通过 offset 限制符来控制成员的精确位置,或者用 align 限制符来设置一个模糊的对齐方式。
你可以只对某些成员应用这些限制符,从而确保应用程序和着色器之间的布局是同步的。

#version 440
layout (std140) uniform b {
    float size;                        //默认从 0 字节位置开始
    layout (of fset=32) vec4 color;     // 从 32 字节开始
    layout (align=1024) vec4 a[12];     //从下一个 1024 倍数的字节位置开始
    vec4 b[12];                              //从 a[12] 之后的偏移地址开始
} buf;

Note

  • 对齐过程是自然的,只不过 stdl40 需要对类似 vec4 这样的类型增加一个额外的 16 字节对齐的限制;
  • vec4 的基本对齐方式是 4 * 其基本类型的对齐方式(即:float),因此 vec4 的基本对齐方式为 16

1.3 从应用程序中访问 uniform 块

1.3.1 uniform 块的声明

  • 在顶点着色器和片元着色器中添加固定的 uniform 块作为缓冲区

顶点着色器和片元着色器共享同一个名称为 “Uniforms” 的 uniform 块

顶点着色器 vShader

#version 330 core

uniform Uniforms {
    vec3 traiislation;
    float scale;
    vec4 rotation;
    bool enabled;
};

in vec2 vPos;
in vec3 vColor;
out vec4 fColor;

void main()
{
    vec3 pos = vec3 (vPos, 0.0);
    
    // rotation 中存储的四元数信息(旋转角,旋转轴)
    float angle = radians(rotation[0]);
    vec3 axis = normalize(rotation.yzw);

    // 换算出旋转矩阵 rot
    mat3 I = mat3(1.0);
    mat3 S = mat3(        0, - axis.z,   axis.y,
                     axis.z,        0, - axis.x,
                   - axis.y,   axis.x,        0 );
    mat3 uuT = outerProduct(axis, axis);
    mat3 rot = uuT + cos(angle) * (I - uuT) + sin(angle) * S;

    pos *= scale;
    pos *= rot;
    pos += translation;

    fColor = vec4 (scale, scale, scale, 1);
    gl_Position = vec4 (pos, 1);
}

片元着色器 fShader

#version 330 core

uniform Uniforms {
    vec3 translation;
    float scale;
    vec4 rotation;
    bool enabled;
};

in vec4 fColor;
out vec4 color;

void main()
{
    color = fColor;
}

1.3.2 获取存储大小

用于将 GLSL 类型转换为存储大小的辅助函数

size_t
TypeSize(GLenum type)
{
    size__t size;

    #define CASE(Enum, Count, Type) \
    case Enum: size = Count * sizeof(Type); break

    switch (type) {
        CASE (GL__FLOAT,                1,  GLfloat);
        CASE (GL_FLOAT__VEC2,           2,  GLfloat);
        CASE (GL_FLOAT__VEC3,           3,  GLfloat);
        CASE (GL_FLOAT_VEC4,            4,  GLfloat);
        CASE (GL__INT,                  1,  GLint);
        CASE (GL_INT_VEC2,              2,  GLint);
        CASE (GL_INT__VEC3,             3,  GLint);
        CASE (GL INT VEC4,              4,  GLint);
        CASE (GL_UNSIGNED_INT,          1,  GLuint);
        CASE (GL_UNSIGNED_INT_ VEC2,    2,  GLuint);
        CASE (GL_UNSIGNED_INT_ VEC3,    3,  GLuint);
        CASE (GL_UNSIGNED_INT_ VEC4,    4,  GLuint);
        CASE (GL_BOOL,                  1,  GLboolean);
        CASE (GL_BOOL_VEC2,             2,  GLboolean);
        CASE (GL_BOOL_VEC3,             3,  GLboolean);
        CASE (GL_BOOL_VEC4,             4,  GLboolean);
        CASE (GL_FLOAT_MAT2,            4,  GLfloat);
        CASE (GL_FLOAT_MAT2x3,          6,  GLfloat);
        CASE (GL_FLOAT_MAT2x4,          8,  GLfloat);
        CASE (GL_FLOAT_MAT3,            9,  GLfloat);
        CASE (GL_FLOAT_MAT3X2,          6,  GLfloat);
        CASE (GL_FLOAT_MAT3 x4,         12, GLfloat);
        CASE (GL_FLOAT_MAT4,            16, GLfloat);
        CASE (GL_FLOAT_MAT4 x2,         8,  GLfloat);
        CASE (GL_FLOAT_MAT4x3,          12, GLfloat);
        #undef CASE
        default:
        fprintf (stderr, "Unknown type: 0x%x\n",type);
        exit(EXIT_FAILURE);
        break;
    }

    return size;
}

1.3.3 uniform 块的初始化

void
init ()
{
    GLuint program;
    glClearColor(1, 0, 0, 1);

    Shaderinfo shaders[] = {
        { GL_VERTEX_SHADER, vShader },
        { GL_FRAGMENT_SHADER, fShader },
        { GL_NONE, NULL }
    };

    program = LoadShaders(shaders);
    glUseProgram(program);

    // 初始化uniform块"Uniforms”中的变量
    GLuint ubolndex;
    GLint xiboSize;
    GLuint ubo;
    GLvoid *buffer;

    // 查找 "Uniforms" 的 uniform 缓存索引,并判断整个块的大小
    ubolndex = glGetUniformBlockindex (program, "Uniforms");
    glGetActiveUniformBlockiv(program, ubolndex, GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);

    buffer = malloc(uboSize);

    if (buffer == NULL) {
        fprintf(stderr, "Unable to allocate buffer\n");
        exit(EXIT_FAILURE);
    }
    else {
        enum { Translation, Scale, Rotation, Enabled, NumUniforms };

        // 准备存储在缓存对象中的值
        GLfloat     scale = 0.5;
        GLfloat     translation[] = { 0.1, 0.1, 0.0 };
        GLfloat     rotation[] = { 90, 0.0, 0.0, 1.0 };
        GLboolean   enabled = GL_TRUE;

        // 我们可以建立一个变量名称数组.对应块中已知的 uniform 变量
        const char* names[NumUniforms] = {
            "translation",
            "scale",
            "rotation",
            "enabled"
        };

        // 查询对应的属性,以判断向数据然存中写入数值的位置
        GLuint  indices[NumUniforms];
        GLint   size[NumUniforms];
        GLint   offset[NumUniforms];
        GLint   type[NumUniforms];

        // 返回所有 NumUniforms 个 uniform 变量的索引位置
        // 变量名称通过字符串数组 names 指定
        // 返回值保存在 indices 中,例如:names[0] == translationIndex
        glGetUniformindices(program, NumUniforms, names, indices);
        
        // 获得指定索引位置的偏移量,大小和类型
        glGetActiveUniformsiv(program, NumUniforms, indices, GL_UNIFORM_OFFSET, offset);
        glGetActiveUniformsiv(program, NumUniforms, indices, GL_UNIFORM_SIZE, size);
        glGetActiveUniformsiv(program, NumUniforms, indices, GL_UNIFORM_TYPE, type);

        // 将 uniform 变量值拷贝到缓存中
        memcpy(buffer + offset [Scale], &scale, 
               size[Scale] * TypeSize(type[Scale]));
        memcpy(buffer + offset[Translation], &translation,
               size[Translation] * TypeSize(type[Translation]));
        memcpy(buffer + offset[Rotation], &rotation,
               size[Rotation] * TypeSize(type[Rotation]));
        memcpy(buffer + offset[Enabled], &enabled,
               size[Enabled] * TypeSize(type[Enabled]));


        // 建立uniform绫存对象,初始化存储内容,并且与着色器程序建立关联
        glGenBuffers(1, &ubo);
        glBindBuffer(GL_UNIFORM_BUFFER, ubo);
        glBufferData(GL_UNIFORM_BUFFER, uboSize, buffer, GL_STATIC_RAW);

        glBindBufferBase(GL_UNIFORM_BUFFER, ubolndex, ubo);
    }
        ......
}

2. buffer 块

buffer 块与 uniform 块的区别

  • 着色器可以写入buffer块,修改其中的内容并呈现给其他的着色器调用或者应用程序本身;
  • 可以在渲染之前再决定它的大小,而不是编译和链接的时候。
buffer Bufferobject {   // 创建一个可读写的 buffer 块
int mode;               // 序言(preamble)成员
vec4 points[];          // 最后一个成员可以是未定义大小的数组
};

如果在着色器中没有给出上面的数组的大小,那么可以在应用程序中编译和连接之后,渲染之前设置它的大小。
着色器中可以通过 length() 方法获取渲染时的数组大小。

Note

  • buffer 块只可以使用 std430 布局

3. in / out 块

3.1 着色器的输入块与输出块

着色器变量从一个阶段输出,然后再输入到下一个阶段中,这一过程可以使用块接口来表示。

顶点着色器的输出

out Lighting {
    vec3 normal;
    vec2 bumpCoord;
);

片元着色器的输入

in Lighting {
    vec3 normal;
    vec2 bumpCoord;
);

3.2 设置成员的位置

layout(location=N) 被用于每个独立的输入和输出变量。
它也可以作用于输入和输出块的成员,显式地设置它们的位置:

#version 440

in Lighting {
    layout(location=l) vec3 normal;
    layout(location=2) vec2 bumpCoord;
};

无论这些 location 位置信息是否在块中,都是可以等价于一个vec4的。

3.3 分量

如果用户希望把多个小的对象设置到同一个位置上,那么也可以使用分量(component)关键字。

#version 440
in Lighting {
    layout(location=l, component=0) vec2 offset;
    layout(location=l, component=2) vec2 bumpCoord;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值