OpenGL着色器语言(GLSL)

数据类型

基本数据类型

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(后期特效 / 拾取)等,使用自定义变量会更加灵活。

由于笔者水平有限,错误不足之处,烦请各位读者斧正。

相关推荐
net3m332 小时前
单片机屏幕多级菜单系统之当前屏幕号+屏幕菜单当前深度 机制
c语言·c++·算法
mmz12072 小时前
二分查找(c++)
开发语言·c++·算法
陌路202 小时前
C++30 STL容器 -deque双端队列
开发语言·c++
AI视觉网奇2 小时前
ue 自己制作插件 c++
c++·ue5
Jayden_Ruan3 小时前
C++分解质因数
数据结构·c++·算法
微露清风3 小时前
系统性学习C++-第二十讲-哈希表实现
c++·学习·散列表
清 澜3 小时前
c++高频知识点总结 第 1 章:语言基础与预处理
c++·人工智能·面试
fqbqrr4 小时前
2601C++,模块基础
c++
带土14 小时前
6. C++智能指针(1)
开发语言·c++