数据类型
基本数据类型
GLSL 基本数据类型有 int、float、double、uint 和 bool,GLSL 是强类型语言,没有隐式类型转换,支持数组。
cpp
int a = int(2.0); // 正确:使用构造函数显式转换
// int c = 2.0; // 错误:类型不匹配 [1](@ref)
向量
GLSL中的向量是一个可以包含有2、3或者4个分量的容器。
cpp
vec3 position;
vec4 color;
bvec2 boolvec; // 包含 2 个分量的 bool 型向量
ivec3 intvec; // 包含 3 个int分量的向量
uvec4 usignvec; // 包含 4 个unsigned int分量的向量
dvec4 doublevec;// 包含 4 个double分量的向量
矩阵
在 GLSL 中,矩阵的最大维度是 4×4 矩阵的形式如下:
cpp
mat2 m22; // 2 * 2 的矩阵
// mat2x2 m22; 和上面一行等价
mat2x4; m24; // 2 * 4 的矩阵
layout
cpp
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
在前面的文章中,我们定义了这样一个顶点着色器,layout (location = 0) 用于定义属性位置值。
cpp
layout (location = 0) in vec3 aPosition; // 位置属性
layout (location = 1) in vec3 aNormal; // 法线属性
layout (location = 2) in vec2 aTexCoord; // 纹理坐标
layout (location = 3) in vec3 aTangent; // 切线
layout (location = 4) in vec3 aBitangent; // 副切线
layout (location = 5) in vec4 aColor; // 顶点颜色
layout 也可用于定义纹理等的绑定点,指定输入输出图元类型等,这里就不一一解释了。
cpp
// 定义绑定点
layout(binding = 0) uniform sampler2D diffuseMap;
layout(binding = 1) uniform sampler2D normalMap;
layout(binding = 2) uniform sampler2D specularMap;
// 指定输入图元类型
layout(points) in; // 点
layout(lines) in; // 线
layout(triangles) in; // 三角形
layout(line_strip) in; // 线带
layout(triangle_strip) in; // 三角形带
layout(triangles_adjacency) in; // 邻接三角形
layout(lines_adjacency) in; // 邻接线
// 指定输出图元类型和最大点数
layout(points, max_vertices = 64) out; // 最多输出64个点
layout(line_strip, max_vertices = 2) out; // 最多输出2个顶点形成线段
layout(triangle_strip, max_vertices = 3) out; // 最多输出3个顶点形成三角形
它并不是必须的,比如 location 我们也可以在代码中通过 glGetAttribLocation 获取。
cpp
GLint posAttrib = glGetAttribLocation(shaderProgram, "aPos");
GLint colorAttrib = glGetAttribLocation(shaderProgram, "aColor");
// 启用和配置顶点属性
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
通过 layout 定义 location = 0 则可以省去 glGetAttribLocation 的步骤,直接使用 location 的值。
输入输出
in 和 out
in 和 out 关键字来设定输入和输出,再来看看我们的顶点着色器代码,这里顶点着色器接收一个 aPos 输入,然后原封不动的赋值给 gl_position(gl_position 是 OpenGL 内置变量,用于在着色器之间传递数据以及与固定功能管线交互,表示顶点在裁剪空间中的位置)
cpp
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
现在我们为其增加一个输出变量 color,把它传递给片段着色器
cpp
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
ourColor = vec3(1.0, 0.0, 0.0);
}
然后把这个输出传递给片段着色器
cpp
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor.x, ourColor.y, ourColor.z, 1.0f);
}
运行程序,我们就得到了一个红色的三角形

uniform
uniform是全局的,所有着色器都可以访问它, 在着色器中是只读的,不能修改。它用于从CPU 上传递数据到 GPU 上的着色器
我们定义一个 uniform 的 ourColor 变量,然后把片段着色器的输出设为 ourColor。注意: OpenGL 3.3/GLSL 330 不支持uniform的layout location
cpp
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;
void main()
{
FragColor = ourColor;
}
设置uniform值
cpp
while (!glfwWindowShouldClose(window))
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
// 设置uniform值
float redValue = 1.0;
float greenValue = 0.0;
float blueValue = 1.0;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, redValue, greenValue, blueValue, 1.0f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
内置变量
OpenGL 内置变量是着色器中预定义的变量,用于在管线不同阶段之间传递数据或访问渲染状态。
顶点着色器内置变量
输入变量
gl_VertexID:当前顶点的索引
gl_InstanceID:实例化渲染的实例索引
gl_DrawID:绘制调用索引
gl_BaseVertex、gl_BaseInstance:基础顶点/实例索引
输出变量
gl_Position:顶点在裁剪空间中的位置(必须设置)
gl_PointSize:点的大小
gl_ClipDistance[]:自定义裁剪距离数组
gl_CullDistance[]:自定义剔除距离数组
几何着色器内置变量
输入变量
gl_PrimitiveIDIn:输入图元ID
gl_InvocationID:几何着色器调用索引
输出变量
gl_PrimitiveID:输出图元ID
gl_Layer:渲染目标数组的图层
gl_ViewportIndex:视口索引
片段着色器内置变量
输入变量
gl_FragCoord:片段在窗口中的坐标(x, y, z, 1/w)
gl_FrontFacing:是否为正面片段
gl_SampleID、gl_SamplePosition:多重采样信息
gl_SampleMaskIn[]:输入样本掩码
gl_ClipDistance[]:裁剪距离
gl_Layer、gl_ViewportIndex:来自几何着色器
gl_PrimitiveID:当前图元ID
输出变量
gl_FragDepth:自定义深度值
gl_SampleMask[]:输出样本掩码
gl_FragColor(兼容性模式)
gl_FragData[](兼容性模式)
细心的读者可能会发现,顶点着色器通过 gl_position 这个内置变量传递给 OpenGL,而在片段着色器中却自己定义了一个输出变量。这是因为顶点位置是系统定义的、用途固定的,片段着色器输出是用户定义的、用途可变的。
gl_Position 的值会被 OpenGL 固定渲染管线 直接接管:执行「透视除法」(除以 w 分量) 得到归一化设备坐标 (NDC) → 执行视口变换 (Viewport Transform) 得到屏幕坐标 → 最终送入「光栅化阶段」生成片元。这个过程是 OpenGL 底层硬件固化的逻辑,如果采取像着色器那样的方式可能增加运行时开销:每次绘制调用都需要查询"哪个输出是位置",优化困难:编译器难以优化固定功能阶段等问题。
而片段着色器,除了输出颜色之外,还可以输出 法线向量(用于延迟渲染的光照计算)
深度值、粗糙度、金属度(PBR 物理渲染)纹理坐标、物体 ID(后期特效 / 拾取)等,使用自定义变量会更加灵活。
由于笔者水平有限,错误不足之处,烦请各位读者斧正。