Shader开发(九)编写第一个顶点着色器

着色器(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内置了强大的向量数据类型,无需额外的数学库:

类型组件数用途访问方式
vec22个float2D坐标、纹理坐标.x .y.s .t
vec33个float3D位置、颜色RGB.x .y .z.r .g .b
vec44个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 坐标(方案一)。


下一步

我们已经成功创建了第一个顶点着色器,但这还不足以在屏幕上看到完整的渲染效果。顶点着色器只负责顶点位置的变换,要想看到彩色的三角形,我们还需要编写片元着色器来处理像素颜色。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值