3.1 顶点着色器剖析与坐标变换


3.1 顶点着色器剖析与坐标变换

本节摘要:顶点着色器的使命可以浓缩成一句话——把每个顶点从模型局部坐标系搬运到裁剪空间,同时把光照需要的属性打包外送。本节逐行拆解一份生产级顶点着色器,讲透局部、世界、观察、裁剪四个空间的来龙去脉,并给出平移、旋转、缩放矩阵的构造直觉。

学习目标

读完本节,你应当能够:独立写出接收位置、UV、法线的完整顶点着色器;对任意一个坐标说出它当前所处的空间;亲手构造平移与缩放矩阵;用"逐空间排查法"定位模型显示异常。

最小可用的顶点着色器

下面这份代码覆盖了实际项目里九成场景的需求,先整体看一遍,再逐块拆:

#version 330 core layout (location = 0) in vec3 aPos; // 顶点位置:模型局部坐标 layout (location = 1) in vec2 aUv; // 纹理坐标 layout (location = 2) in vec3 aNormal; // 法线:光照要用 uniform mat4 uModel; // 局部 -> 世界 uniform mat4 uView; // 世界 -> 观察 uniform mat4 uProj; // 观察 -> 裁剪 out vec2 vUv; // 交给片元:纹理坐标 out vec3 vNormal; // 交给片元:世界空间法线 out vec3 vWorldPos; // 交给片元:世界空间位置(算光照方向用) void main() { vec4 world = uModel * vec4(aPos, 1.0); // 先变到世界空间 vWorldPos = world.xyz; vNormal = mat3(uModel) * aNormal; // 方向向量只吃旋转缩放,不吃平移 vUv = aUv; // UV 原样传递 gl_Position = uProj * uView * world; // 一路推到裁剪空间 }

注意代码里的分工:gl_Position 是唯一的"规定动作",必须填写;三个 out 变量是"自选动作",按下游需要配置。矩阵乘法顺序从右往左读——先模型、再观察、最后投影,恰好是空间流转的先后。vec4(aPos, 1.0) 里的 1.0 不是凑数:齐次坐标的 w 分量让平移得以用矩阵乘法表达,点用 1.0、方向向量用 0.0,这个约定贯穿全部图形代码。

图:坐标空间流转:从建模软件到屏幕像素

图:坐标空间流转:从建模软件到屏幕像素

各个空间为什么存在

每个空间都服务于一个具体问题,不是数学家故意添乱。

局部空间解决"建模方便"。美术师做一把椅子,自然以椅子自身为中心记坐标,不必关心它将来摆在场景哪个角落。

世界空间解决"场景组装"。模型矩阵回答"这把椅子搬到哪、转多少度、放大几倍"。世界空间也是光照计算的自然舞台——光源位置、阴影判断都在这一坐标系里进行,所以示例着色器特意输出 vWorldPos

观察空间解决"从相机看"。把世界坐标乘上观察矩阵,相当于把整个世界反向搬到相机面前:相机永远坐在原点看向负方向。所谓移动相机,本质是反向移动全世界。

裁剪空间解决"视野裁剪与透视"。投影矩阵把观察空间做成透视效果:远的物体坐标被压缩。此时坐标变成四维齐次形式,硬件接下来统一除以 w(透视除法),z 值随距离自然衰减,视野锥体外的顶点在裁剪阶段被丢弃。这就是开篇导读里那堵"裁剪空间之墙"的真相——w 分量不是噪音,透视效果全靠它。

最后硬件把 NDC(标准化设备坐标,各分量落在固定范围内)线性映射到视口矩形,顶点才算真正落到像素网格上。这条链路里你只需要负责"乘到裁剪空间为止",后面全自动。

三大变换矩阵的构造直觉

平移与缩放的矩阵构造简单到可以默写,关键是记住 GLSL 列主序、平移量在最后一列:

mat4 translate(vec3 t) { return mat4(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, t.x, t.y, t.z, 1.0); // 平移住在最后一列 } mat4 scaleUniform(float s) { return mat4(s, 0.0, 0.0, 0.0, 0.0, s, 0.0, 0.0, 0.0, 0.0, s, 0.0, 0.0, 0.0, 0.0, 1.0); }

旋转矩阵以绕 z 轴为例:mat4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1),其中 c 是余弦、s 是正弦——记忆方法是"xy 平面内的二维旋转披上齐次外衣",绕 x、绕 y 同理可得。实战中这些矩阵几乎都由数学库在 CPU 侧构造后作为 uniform 上传,着色器内手写矩阵主要出现在顶点动画这类小位移场景。

⚠️ 常见坑:模型"消失"的原因清单,按出现频率排序——矩阵乘法顺序写反(应 proj * view * model);忘记把局部坐标包成 vec4(pos, 1.0);投影矩阵近远平面参数写错(近平面大于远平面);观察矩阵把相机放到了模型内部或背后。逐空间排查:把 uProj 换成单位阵看有没有输出,再逐个换 uView、uModel,哪一步消失问题就在哪一步。

投影矩阵的参数直觉

透视投影矩阵在 CPU 侧由参数生成,常用的四个参数每个都对应一类视觉症状,值得单独点名。

视场角(fov):广角镜头,值越大视野越宽、边缘拉伸越明显。第一人称游戏常用大视场营造冲击感,第三人称用小视场保留体型。调它等于换镜头。

宽高比(aspect):视口宽除以高。与窗口比例不一致时画面被拉伸压扁——圆变椭圆。窗口尺寸变化的回调里忘记更新这个参数,是"画面变形"故障的头号原因。

近平面(near)与远平面(far):裁剪的两个深度边界。近平面必须大于零,否则透视除法除零;远近平面的比值过大则深度缓冲精度告急,两个交叠的表面开始"闪烁打架"(深度冲突,z-fighting)。经验值是让 far 只比场景最远可视距离略大,near 尽量取大一点——深度精度集中在近平面附近,near 每放大一档,中远距离的精度都在受益。

// 透视矩阵参数与症状的对应(由数学库生成,此处示意参数含义) // perspective(radians(60.0), 16.0/9.0, 0.1, 100.0) // 视场角 宽高比 近 远

💡 关键直觉:把投影矩阵当成"相机的参数面板"——视场角是镜头,宽高比是相纸,近远平面是对焦范围。画面里的透视畸变、比例失调、深度打架,全部可以沿着这四根旋钮排查。

本节要点回顾

  • 着色器管辖到裁剪空间为止gl_Position = proj * view * model * vec4(pos, 1.0) 是标准终局写法;
  • 空间各有使命:局部利于建模、世界利于组装与光照、观察服务相机、裁剪承载透视与裁剪;
  • 齐次坐标的 w:点带 1.0、方向带 0.0;透视除法由硬件完成,透视效果全在 w 里;
  • 矩阵构造:列主序、平移在末列、旋转是二维旋转的推广,生产中由 CPU 侧数学库统一构造上传。

位置链路通了,下一节解决另一半:顶点携带的属性如何跨越光栅化、稳稳交到片元手里。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U