Advanced GLSL 高级 GLSL
LearnOpenGL 顺序:Advanced OpenGL → Advanced GLSL · 原教程
这一节在渲染管线中的位置
这一节没有增加新的绘制阶段,而是在整理 Shader 能从哪里拿数据、怎样把数据传给下一个 Shader、怎样让多个 Shader 共享同一批 uniform。
GLSL 内建变量:有些数据不用自己声明接口
以前 Vertex Shader 最常见的是自己声明 in,再写 gl_Position。GLSL 还提供了一批 gl_ 开头的内建变量,它们由管线直接提供或接收。
顶点阶段常见的有:
gl_Position // 顶点最终的裁剪空间位置
gl_PointSize // GL_POINTS 的点大小
gl_VertexID // 当前顶点编号例如:
void main()
{
gl_Position = ...;
gl_PointSize = 5.0 + float(gl_VertexID) * 4.0;
}如果用 GL_POINTS 绘制,不同顶点就可以直接产生不同大小的点。
Fragment Shader 里常见的有:
gl_FragCoord // 当前 fragment 的窗口坐标,z 是深度
gl_FrontFacing // 当前 fragment 来自正面还是背面
gl_FragDepth // 手动写 fragment 的深度gl_FragDepth 很强,但一旦 Fragment Shader 可能任意改深度,GPU 往往不能放心地提前做深度测试。桌面 OpenGL 4.2+ 可以通过 conservative depth layout 告诉驱动深度只会往某个方向改,从而保留更多 Early-Z 优化。
Interface Block:把跨 Shader 的 varying 组成一个接口
当 Vertex Shader 只传一个 TexCoords 时,分别声明 out / in 很简单;变量多以后,可以把它们组成一个块。
Vertex Shader:
out VS_OUT
{
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
} vs_out;Fragment Shader:
in VS_OUT
{
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
} fs_in;这里重要的是 块的接口要匹配。它解决的是 Shader 阶段之间的数据组织问题,不是额外的 GPU Buffer。
可以把它理解成:
Vertex Shader
↓
VS_OUT { FragPos, Normal, TexCoords }
↓
Rasterization / interpolation
↓
Fragment ShaderUBO:把多个 uniform 装进一个 Buffer
这一节最重要的工具是 Uniform Buffer Object。
以前四个 Shader 都需要 projection 和 view,通常要分别:
Shader Red ← projection + view
Shader Green ← projection + view
Shader Blue ← projection + view
Shader Yellow ← projection + view同样的数据重复设置四遍。
UBO 把它变成:
UBO: Matrices
[ projection | view ]
↑ binding point 0
┌────┼────┬────┐
↓ ↓ ↓ ↓
Red Green Blue YellowShader 里声明统一的 Uniform Block:
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};CPU 创建 GL_UNIFORM_BUFFER,把这块 Buffer 绑定到某个 binding point,再把每个 Shader Program 的 Matrices block 指向同一个 binding point。
GLuint index = glGetUniformBlockIndex(shader.ID, "Matrices");
glUniformBlockBinding(shader.ID, index, 0);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER,
2 * sizeof(glm::mat4),
NULL,
GL_STATIC_DRAW);
glBindBufferRange(GL_UNIFORM_BUFFER,
0,
uboMatrices,
0,
2 * sizeof(glm::mat4));之后只更新这一个 Buffer:
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER,
sizeof(glm::mat4),
sizeof(glm::mat4),
glm::value_ptr(view));四个 Shader 都能读到新的 view。
这正好接上上一节 Advanced Data:UBO 仍然是 Buffer Object,只是 Target 换成了 GL_UNIFORM_BUFFER,里面的字节按 Uniform Block 的布局规则解释。
为什么有 std140
GPU 读取 uniform block 时需要明确每个成员在 Buffer 里的偏移。std140 给出一套标准化对齐规则,让 CPU 可以按规则准备内存,不必依赖每个驱动自己的布局。
初学阶段先记住两点就够:
std140 = 一套固定的对齐/排布规则
mat4 = 4 个 vec4 列,通常占 64 bytes所以教程里:
projection mat4 → offset 0
view mat4 → offset 64 bytes后面遇到 vec3 + float、数组、结构体时,再具体算 std140 对齐。
model 为什么没有也塞进这个 UBO
教程的四个立方体共享:
projection
view但每个立方体自己的位置不同,所以 model 不共享。
因此:
UBO / Matrices
→ projection + view
→ 多个 Shader / 多个物体共用
普通 model uniform
→ 每个物体单独设置这就是 UBO 最适合的使用场景:一批 Shader 都需要、而且更新频率相同的数据。
Interactive Lab
Lab 复现教程的四个不同 Shader Program:红、绿、黄、蓝四个物体共享一个 Matrices UBO。改变 Camera / Projection 时,只更新这一块 UBO;每个物体仍使用自己的 model uniform。页面同时展示 gl_VertexID / gl_PointSize 与常用 Fragment built-in 的含义。
放回渲染管线
这一节主要改的是 数据接口:
Buffer / Uniform Buffer
↓
Vertex Shader → Interface → Fragment Shader
↑ ↑
built-in variables built-in variables它不是新的线性 Stage。重点是把原来零散的 Shader 输入输出,整理成更清楚、更可共享的数据通道。