着色器(Shader)是图形编程中不可或缺的一部分,负责在 GPU 上执行渲染任务。本文将引导你编写第一个顶点着色器(Vertex Shader),并介绍如何在 openFrameworks 中使用它。你将学习到 GLSL 基础、顶点数据处理以及归一化设备坐标(NDC)的重要性。
顶点着色器的作用
顶点着色器是图形渲染管线中的一个关键阶段,负责处理网格的每个顶点数据。它的主要任务是将顶点位置从模型空间转换到屏幕空间(即归一化设备坐标),以便 GPU 进行光栅化和渲染。每个顶点独立处理,着色器无法直接访问其他顶点信息。
创建顶点着色器文件
文件存放规范
在 openFrameworks 中,着色器代码通常与C++代码分离存放,遵循以下约定:
-
顶点着色器:使用
.vert扩展名 -
片元着色器:使用
.frag扩展名 -
存放位置:
bin/data文件夹(资源目录)
💡 为什么分离存放? 着色器在运行时加载,属于资源文件而非编译时代码,因此与图像、网格等资源放在同一目录。
在 bin/data 文件夹中创建名为 first_vertex.vert 的新文件,准备编写我们的第一个顶点着色器,代码如下:
#version 410 // ① GLSL版本声明
in vec3 position; // ② 输入顶点位置数据
void main()
{
gl_Position = vec4(position, 1.0); // ③ 输出顶点位置
}
顶点着色器的工作原理
在深入分析代码之前,让我们先理解顶点着色器的核心职责:
-
单一职责:告诉渲染管线每个顶点在屏幕上的最终位置
-
顶点处理:每次只处理一个顶点,不知道其他顶点信息
-
位置输出:将变换后的顶点位置传递给渲染管线的下一阶段
上面的简单着色器只是获取每个顶点的原始位置,然后不加修改地传递给管线的其余部分。
GLSL语法详解
版本声明:#version 预处理器指令
#version 410 // ① 对应OpenGL 4.1版本
版本对应关系:
-
410→ OpenGL 4.1 -
330→ OpenGL 3.3 -
450→ OpenGL 4.5
💡 版本选择:本系列教程统一使用410版本,确保与我们配置的OpenGL 4.1环境匹配。
输入声明:in 关键字
in vec3 position; // ② 声明输入变量
in关键字的作用:
-
数据接收:指定着色器希望从渲染管线上一阶段接收的数据
-
管线连接:对于顶点着色器,上一阶段是GPU的顶点数据转换
-
类型声明:明确指定数据类型和变量名
数据流向:
网格顶点数据 → GPU转换 → 顶点着色器(in变量) → 处理 → 输出
GLSL向量数据类型
GLSL内置了强大的向量数据类型,无需额外的数学库:
| 类型 | 组件数 | 用途 | 访问方式 |
|---|---|---|---|
vec2 | 2个float | 2D坐标、纹理坐标 | .x .y 或 .s .t |
vec3 | 3个float | 3D位置、颜色RGB | .x .y .z 或 .r .g .b |
vec4 | 4个float | 齐次坐标、颜色RGBA | .x .y .z .w 或 .r .g .b .a |
向量构造的便利特性:
vec3 pos = vec3(1.0, 2.0, 3.0);
vec4 homogeneous = vec4(pos, 1.0); // 使用vec3构造vec4
内置变量:gl_Position
gl_Position = vec4(position, 1.0); // ③ 写入内置变量
gl_Position特点:
-
GLSL预定义的内置变量
-
每个顶点着色器都必须写入此变量
-
必须是4分量向量(齐次坐标)
-
自动传递给渲染管线的下一阶段
💡 为什么是vec4? 第4个分量(w分量)用于齐次坐标系统,对于位置数据通常设为1.0。这涉及3D图形学的数学基础,暂时记住这个规则即可。
归一化设备坐标(NDC)
如果直接运行上面的顶点着色器,结果可能不是我们期望的。这是因为:
-
默认着色器的隐藏工作:openFrameworks默认着色器自动处理坐标转换
-
我们的着色器:直接输出原始坐标,不做任何转换
什么是归一化设备坐标?
归一化设备坐标是GPU渲染管线的标准坐标系统,具有以下特点:
-
坐标范围:X、Y、Z轴都在 [-1, 1] 范围内
-
屏幕映射:自动映射到任意尺寸的屏幕
-
分辨率无关:同样的坐标在不同屏幕上显示效果一致
坐标系对比:
| 坐标系类型 | X轴范围 | Y轴范围 | 原点位置 | Y轴方向 |
|---|---|---|---|---|
| 屏幕像素坐标 | 0 → 窗口宽度 | 0 → 窗口高度 | 左上角 | 向下 |
| 归一化设备坐标 | -1 → 1 | -1 → 1 | 屏幕中心 | 向上 |
示例:将顶点映射到 NDC
假设窗口为 1024x768 像素:
-
屏幕左上角 (0, 0) → NDC (-1, 1)
-
屏幕右下角 (1024, 768) → NDC (1, -1)
下图就是将归一化设备坐标覆盖在窗口上的情形:

位置对应:
-
(-1, 1):屏幕左上角 -
(1, 1):屏幕右上角 -
(-1, -1):屏幕左下角 -
(1, -1):屏幕右下角 -
(0, 0):屏幕中心
修改顶点坐标
方案一:修改网格顶点(推荐)
将setup()函数中的顶点坐标改为归一化设备坐标:
void ofApp::setup(){
// 使用归一化设备坐标创建三角形
triangle.addVertex(glm::vec3(-1.0f, 1.0f, 0.0f)); // 左上角
triangle.addVertex(glm::vec3(-1.0f, -1.0f, 0.0f)); // 左下角
triangle.addVertex(glm::vec3(1.0f, -1.0f, 0.0f)); // 右下角
}
注意事项:
-
必须交换Y分量的符号,Y 轴方向在 NDC 中与屏幕像素坐标相反(NDC 中 Y 向上为正)。
-
确保所有坐标都在[-1, 1]范围内
方案二:在着色器中转换坐标
如果不想修改网格顶点,可以在顶点着色器中进行坐标转换:
#version 410
in vec3 position;
void main(){
// 从像素坐标转换为归一化设备坐标
float x = (position.x / 1024.0) * 2.0 - 1.0;
float y = (position.y / 768.0) * 2.0 - 1.0;
gl_Position = vec4(x, y, 0.0, 1.0);
}
转换解析:
-
position.x / 1024.0:归一化到[0, 1] -
* 2.0 - 1.0:映射到[-1, 1] -
硬编码尺寸:简化示例,实际项目中应传递动态尺寸
💡 说明:此方法将像素坐标映射到 NDC,但通常建议直接在 CPU 端使用 NDC 坐标(方案一)。
下一步
我们已经成功创建了第一个顶点着色器,但这还不足以在屏幕上看到完整的渲染效果。顶点着色器只负责顶点位置的变换,要想看到彩色的三角形,我们还需要编写片元着色器来处理像素颜色。
1737

被折叠的 条评论
为什么被折叠?



