QT+OpenGL高级数据和高级GLSL
本篇完整工程见gitee:QtOpenGL 对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主
高级数据
● OpenGL中的缓冲区 对象管理特定的GPU内存
● 在将缓冲区绑定到特定的缓冲区目标时候赋予它意义
● OpenGL在内部会保存每个目标(缓冲区)的引用,并且根据目标以不同的方式处理缓冲区。
glBufferData 填充缓冲对象所管理的内存
glBufferSubData 填充缓冲的特定区域
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data);
将数据导入缓冲区的另外一种方法:
● 通过调用glMapBuffer函数,OpenGL会返回当前绑定的缓冲区的内存指针
● 直接将数据复制到缓冲中 glMapBuffer使用的时候,缓冲区的内存必须已经存在
float data[] = { 0.5f, 1.0f, -0.35f ... }; glBindBuffer(GL_ARRAY_BUFFER, buffer); // 获取指针 void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); // 复制数据到内存 memcpy(ptr, data, sizeof(data)); // 记得告诉OpenGL我们不再需要这个指针了 glUnmapBuffer(GL_ARRAY_BUFFER);
分批顶点属性
float positions[] = { ... }; float normals[] = { ... }; float tex[] = { ... }; // 填充缓冲 glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions); glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals); glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
使用glVertexAttribPointer,指定顶点数组缓冲区内筒的属性布局。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions))); glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
复制缓冲
当你的缓冲已经填充好数据之后,你可能会想与其它的缓冲共享其中的数据,或者想要将缓冲的内容复制到另一个缓冲当中。glCopyBufferSubData能够让我们相对容易地从一个缓冲中复制数据到另一个缓冲中。这个函数的原型如下:
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
readtarget和writetarget参数需要填入复制源和复制目标的缓冲目标
如果想读写数据的两个不同缓冲都为顶点数组缓冲该怎么办?使用下面的例子:
float vertexData[] = { ... }; glBindBuffer(GL_COPY_READ_BUFFER, vbo1); glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
float vertexData[] = { ... }; glBindBuffer(GL_COPY_READ_BUFFER, vbo1); glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
高级GLSL
顶点着色器变量
● gl_Position : 输出变量,顶点着色器的裁减空间位置向量
● gl_PointSize :输出变量,是一个float变量,设置点的宽高(像素)
glEnable(GL_PROGRAM_POINT_SIZE);
● gl_VertexID :输入变量,储存了正在绘制顶点的当前ID
shader.vert
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; out vec3 Normal; out vec3 FragPos; out vec2 TexCoords; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { TexCoords=aTexCoords; Normal = mat3(transpose(inverse(model))) * aNormal; FragPos=vec3(model * vec4(aPos,1.0)); gl_Position = projection * view * model * vec4(aPos, 1.0); gl_PointSize = gl_Position.z; }
片段着色器变量
● gl_FragCoord :的x和y分量是片段的窗口空间的坐标,其原点为窗口的左下角,z分量等于对应片段的深度值
● gl_FrontFacing :变量是一个bool如果当前片段是正面向的一部分那么就是true, 否则就是false
● gl_FragDepth : 着色器内设置片段的深度值
shader.frag
void main() { if(gl_FragCoord.x < 400 ) FragColor = vec4(1.0, 0.0, 0.0, 1.0); else FragColor = vec4(0.0, 1.0, 0.0, 1.0); }
gl_FrontFacing :变量是一个bool如果当前片段是正面向的一部分那么就是true, 否则就是false
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D frontTexture; uniform sampler2D backTexture; void main() { if(gl_FrontFacing) FragColor = texture(frontTexture, TexCoords); else FragColor = texture(backTexture, TexCoords); }
gl_FragDepth : 着色器内设置片段的深度值
#version 420 core // 注意GLSL的版本! out vec4 FragColor; layout (depth_greater) out float gl_FragDepth; void main() { FragColor = vec4(1.0); gl_FragDepth = gl_FragCoord.z + 0.1; }
layout (depth_) out float gl_FragDepth;
condition可以为下面的值:
| 条件 | 描述 |
any |
默认值。提前深度测试是禁用的,你会损失很多性能 |
greater |
你只能让深度值比gl_FragCoord.z更大 |
less |
你只能让深度值比gl_FragCoord.z更小 |
unchanged |
如果你要写入gl_FragDepth,你将只能写入gl_FragCoord.z的值 |
接口块
接口块的声明和struct的声明有点相像,不同的是,现在根据它是一个输入还是输出块(Block),使用in或者out关键字来定义的。
shader.vert
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 aTexCoords; uniform mat4 model; uniform mat4 view; uniform mat4 projection; out VS_OUT { vec2 TexCoords; } vs_out; void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); vs_out.TexCoords = aTexCoords; }
shader.frag
#version 330 core out vec4 FragColor; in VS_OUT { vec2 TexCoords; } fs_in; uniform sampler2D texture; void main() { FragColor = texture(texture, fs_in.TexCoords); }
只要两个接口块的名字一样,对应的输入和输出将会匹配起来。
块名应该是和着色器中一样的(VS_OUT)但是实例名称(vs_out, fs_in)是可以随意的。
Uniform缓冲对象
当使用多于一个的着色器时候,尽管大部分的uniform变量都是相同的,我们还是需要不断的设置它们。解决办法:全局uniform变量。
shader.vert
#version 330 core layout (location = 0) in vec3 aPos; layout (std140) uniform Matrices { mat4 projection; mat4 view; }; uniform mat4 model; void main() { // uniform 块中的变量可以直接访问,不需要加块名称作为前缀。 gl_Position = projection * view * model * vec4(aPos, 1.0); }
其中 layout (std140) 设置了Uniform块布局
使用std140布局计算出每个成员的对齐偏移量:
layout (std140) uniform ExampleBlock { // 基准对齐量 // 对齐偏移量 float value; // 4 // 0 vec3 vector; // 16 // 16 (必须是16的倍数,所以 4->16) mat4 matrix; // 16 // 32 (列 0) // 16 // 48 (列 1) // 16 // 64 (列 2) // 16 // 80 (列 3) float values[3]; // 16 // 96 (values[0]) // 16 // 112 (values[1]) // 16 // 128 (values[2]) bool boolean; // 4 // 144 int integer; // 4 // 148 };
| 类型 | 布局规则 |
| 标量,比如int和bool | 每个标量的基准对齐量为N。 |
| 向量 | 2N或者4N。这意味着vec3的基准对齐量为4N。 |
| 标量或向量的数组 | 每个元素的基准对齐量与vec4的相同。 |
| 矩阵 | 储存为列向量的数组,每个向量的基准对齐量与vec4的相同。 |
| 结构体 | 等于所有元素根据规则计算后的大小,但会填充到vec4大小的倍数。 |
我们已经讨论了如何在着色器中定义Uniform块,并设定它们的内存布局了,但我们还没有讨论该如何使用它们。
首先,我们需要调用glGenBuffers,创建一个Uniform缓冲对象。一旦我们有了一个缓冲对象,我们需要将它绑定到GL_UNIFORM_BUFFER目标,并调用glBufferData,分配足够的内存。
unsigned int uboExampleBlock; glGenBuffers(1, &uboExampleBlock); glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock); glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存 glBindBuffer(GL_UNIFORM_BUFFER, 0);
UBO代码实现
具体实现请参照tag-UBO

