第一部分:从3D空间到屏幕坐标
一个三角形顶点,如何在几微秒内从虚拟世界飞到你的显示器上
① 问题登场:一张3D画面,到底是怎么算出来的?
打开任何一款3D游戏,你看到的每一帧画面——角色、建筑、光影——背后都是数十万个三角形。显卡每秒处理这些三角形,把它们变成屏幕上的像素。
但一个三角形在3D世界里的位置——比如"在(x=3.7, y=1.2, z=-8.5)处有一个顶点"——怎么最终变成你显示器上 "第 847 行、第 523 列的那个像素" 呢?
整篇文章就是为了解开中间那个 ? —— GPU 的整个数学流水线
⚠ 问题来了
你的显示器是 2D 的(只有行和列),但 3D 世界有 x、y、z 三个维度。怎么把 3D 坐标变成 2D 像素位置? 而且——远处的物体看起来更小,这个"近大远小"怎么变成数学?
② 第一站:一个"点"的数学表达
在 3D 世界里,任何物体——角色、建筑、云——都由三角形的顶点组成。我们先聚焦一个顶点。
一个点 P 在 3D 空间中的位置,用三个数表示:P = (x, y, z)。但更优雅的写法是把它看成一个列向量:
P = xyz
向量不仅可以表示"位置",还可以表示方向、位移、法线——这些在后续的光照计算中至关重要。
点 P(3, 2, 1) —— 三个数,对应三个坐标轴上的投影
💡 关键事实 ①
一个顶点 = 一个 3D 列向量。整个三角形的三个顶点,就是三个这样的向量。显卡对它们做的事,本质上就是对向量做数学运算。
③ 发现一:平移——把点搬到新位置
你移动游戏角色:按一下右键,角色瞬间从 (3, 2, 1) 位移到 (9, 2, 1)。这很简单:
x'y'z' =
xyz +
ΔxΔyΔz
每个分量加一个平移量,完事。
平移 = 每个坐标分量加上一个偏移量
✅ 记下结果
平移 = 向量加法。P' = P + T。简单直观,三个分量各加各的。
⚠ 但有个麻烦
平移是加法,旋转和缩放却是乘法。三种操作形式不统一——每次变换都要判断"这次用加法还是乘法?" 后面我们会用一个巧妙的方法把它们全部统一成矩阵乘法。
④ 发现二:旋转——矩阵的魔法
旋转比平移复杂。绕 z 轴旋转 θ 角:x 和 y 坐标会互相"掺和",但 z 坐标不变。
x'y'z' =
cos θ−sin θ0
sin θcos θ0
001
xyz
这就是矩阵 × 向量。矩阵的每一行和向量的每个分量"一一配对、乘起来、加起来":
x' = cos θ · x − sin θ · y
y' = sin θ · x + cos θ · y
绕原点旋转 θ 角——矩阵乘法一步完成
💡 关键事实 ②
旋转 = 矩阵 × 向量。矩阵乘法天然适合旋转:新坐标变成旧坐标的线性组合(加权求和)。缩放也是矩阵乘法——对角矩阵。
🤔 为什么用矩阵?
矩阵乘法可以把一堆操作"攒起来":先旋转再平移?两个矩阵相乘,得到一个新矩阵,然后一次乘法搞定。这叫变换的复合——GPU 最擅长的就是矩阵乘法。
⑤ 发现三:缩放——对角线上的力量
缩放最简单:每个轴乘以一个系数。用矩阵写就是:
sx00
0sy0
00sz
三个缩放因子坐在对角线上。矩阵乘法自动变成:x' = sx·x, y' = sy·y, z' = sz·z。
现在三种基本变换都写成了矩阵:
| 变换 | 形式 | 矩阵大小 |
| 缩放 | 矩阵 × 向量 | 3×3 |
| 旋转 | 矩阵 × 向量 | 3×3 |
| 平移 | 向量加法 | — |
⚠ 不和谐
缩放和旋转是 3×3 矩阵乘法,平移却是加法。三种操作没法合并成一个矩阵。这是接下来要解决的核心问题。
⑥ 发现四:齐次坐标——用 4 个数表示 3D 点
解决方案出奇地优雅:给每个 3D 向量加一个"第四分量",称为 w。
P = xyz1
普通 3D 点的 w = 1;方向向量(不需要平移的向量,如法线)的 w = 0。
现在平移也能写成 4×4 矩阵乘法的形式了:
x'y'z'1
=
100Δx
010Δy
001Δz
0001
xyz1
看!第四行把 w=1 原样带到结果中;而最右边一列(蓝色)的三个数正是平移量——它们在矩阵乘法中和 w=1 相乘后被加到 x', y', z' 上。
💡 关键事实 ③
齐次坐标 (x, y, z, w=1) 让平移变成了一次矩阵乘法。从此以后,缩放、旋转、平移,全都是 4×4 矩阵乘法——统一了!
✅ 记下结果
4×4 齐次矩阵 = 显卡的"通用语言"。所有空间变换,不论类型,都是 矩阵 × 4D 向量。GPU 的硬件就是为这种运算设计的——几千个核心并行做矩阵乘法。
⑦ 发现五:MVP——三步合成一步
一个三角形顶点从"模型文件里的坐标"到"屏幕上的位置",要经过三个矩阵变换。GPU 把它们缩写为 M·V·P:
① Model 矩阵(模型变换)
把顶点从"物体自身坐标系"搬到"世界坐标系"
物 → 世:包含缩放、旋转、平移
② View 矩阵(视图变换)
把世界坐标搬到"摄像机坐标系"
世 → 眼:相当于把摄像机挪到原点
③ Projection 矩阵(投影变换)
把摄像机看到的3D场景"压扁"到2D裁剪空间
眼 → 屏:近大远小在这里编码
三件事,但 GPU 只做一次矩阵乘法:
vclip = P · V · M · vlocal
GPU 先把三个矩阵乘成一个大矩阵 MVP = P·V·M,然后对每个顶点只做一次 4×4 × 4×1 的乘法。
🤔 为什么先乘矩阵?
矩阵乘法满足结合律:(P·V)·M = P·(V·M)。一帧有几十万个顶点,每个顶点都做三次矩阵乘法太慢。先把三个矩阵乘好(只需算 1 次),然后每个顶点用这个大矩阵乘 1 次——节省 67% 的运算。
⑧ 发现六:透视投影——"近大远小"怎样变成数学
这是显卡数学中最巧妙的部分。你的眼睛(或摄像机)是一个点,物体越远看起来越小。这个效果怎么用矩阵编码?
视锥体 (Frustum):眼睛看到的金字塔形空间
核心公式——投影矩阵的精髓在最后一行:
投影矩阵 P =
…………
…………
…………
00−10
→
它把 z 坐标搬到了 w 分量!
💡 关键事实 ④
投影矩阵不直接做除法——它把"深度信息"藏进了 w 分量。离摄像机越远的点,w 越大。接下来的一步——透视除法——才真正让远处物体变小。
⑨ 发现七:透视除法——除以 w 的神奇效果
投影矩阵乘完后,我们得到了一个 4D 向量 (xc, yc, zc, wc)。最后一步:
xndcyndczndc =
xc / wcyc / wczc / wc
因为投影矩阵的设计,wc ≈ 原来的 z(深度)。所以:z 越大 → w 越大 → 除以更大的数 → x, y 变小 → 远处物体缩小!
透视除法:GPU 硬件自动执行——每个顶点必经的最后一步
✅ 记下结果
经过 MVP 矩阵 + 透视除法,一个 3D 世界中的顶点 (x, y, z) 变成了归一化设备坐标 NDC:x, y 都在 [-1, 1] 范围内,z 在 [0, 1] 范围内。最后再映射到屏幕像素(如 1920×1080),就叫视口变换——简单的线性映射。
🤔 NDC 为什么是 [-1, 1]?
它让 GPU 与分辨率无关。不管你屏幕是 720p 还是 4K,裁剪和背面剔除都在 [-1, 1] 的 NDC 空间完成——这是硬件固定的"标准尺"。
第二部分:三角形内部——从轮廓到像素
三个顶点确定了屏幕上的位置,但三角形里面的成千上万个像素怎么办?
⑩ 发现八:光栅化——三角形变成像素
三个顶点在屏幕上有了位置(比如 (100, 200), (350, 100), (280, 400)),但三角形内部怎么填充?这就是光栅化 (Rasterization)。
GPU 使用边缘函数 (edge function)来判断一个像素在三角形内还是外:
EAB(P) = (Bx − Ax)(Py − Ay) − (By − Ay)(Px − Ax)
这是二维叉积的符号。三条边各算一次,如果三个结果同号——像素就在三角形内。
边缘函数:三边各判断一次,全同号 = 在三角形内
💡 关键事实 ⑤
边缘函数是光栅化的核心公式——它把一个几何问题("点在三角形内吗?")变成了一个代数问题("三个表达式同号吗?")。GPU 硬件里专门有电路加速这个计算。
⑪ 发现九:重心坐标——三个顶点的"份额"
一个像素在三角形内了,但它的颜色、深度、纹理坐标怎么确定?答案:重心坐标 (Barycentric Coordinates)。
P = α · A + β · B + γ · C
其中 α + β + γ = 1,且 α, β, γ ≥ 0
α, β, γ 是三个顶点的"权重"。像素离 A 越近,α 越大;在 A 点上,α = 1 且 β = γ = 0。
重心坐标:像素与对面顶点围成的面积,占总面积的比例 = 该顶点的权重
💡 关键事实 ⑥
任何一个顶点的属性(颜色、纹理坐标、法线方向),都可以用这三个权重在三角形内部平滑插值。这就是为什么一个三角形的三个顶点有三种颜色时,中间能看到渐变。
⑫ 扩展一:纹理映射——给三角形"贴纸"
每个顶点不仅有 3D 坐标,还有纹理坐标 (u, v)。u 和 v 在 [0, 1] 范围内,表示在纹理图片上的位置。
像素 (xscreen, yscreen) 已知 α, β, γ 三个权重后,它的纹理坐标自然就是三个顶点纹理坐标的加权平均:
uv =
α · uAvA +
β · uBvB +
γ · uCvC
但有一个陷阱:透视变换会让纹理坐标的线性插值失真。直接用屏幕坐标的 α,β,γ 插值 (u,v) 是不对的! 正确的做法是先对 (u/w, v/w, 1/w) 插值,再除以 1/w 恢复——这叫透视校正插值。
🤔 为什么不能直接插?
透视变换是非线性的。远处纹理变密、近处纹理变疏。不用 w 校正的话,纹理看起来会"扭曲"——就像老式 PS1 游戏那种"波浪"效果。现代 GPU 在硬件层面自动处理这件事。
⑬ 扩展二:光照——法向量、点积与颜色
一个像素的颜色不光来自纹理——还有光照。核心公式:
亮度 = N · L
其中 N 是表面法向量(单位长度),L 是光源方向(单位长度)。它们的内积 = |N|·|L|·cos(夹角)。
当 N 和 L 方向一致时(正对光源,夹角 = 0°)→ cos(0) = 1 → 最亮。当夹角为 90° 时 → cos(90°) = 0 → 全黑。
点积 = 光照的数学本质:两个方向"多一致"
| 夹角 θ | N·L = cos(θ) | 效果 |
| 0°(正对光源) | 1.0 | 最亮 |
| 45° | ≈ 0.71 | 中等 |
| 60° | 0.5 | 较暗 |
| 90° | 0 | 全黑 |
| > 90° | < 0 → 钳位到 0 | 光源在背面 |
🤔 法向量也要矩阵变换吗?
是的——但用的是不同的矩阵。法向量用 Model 矩阵的逆转置矩阵(即 M−T)变换,保证变换后依然是单位向量且垂直于表面。GPU 在顶点着色器中自动处理。
⑭ 全篇地图:一个顶点的生命史
现在回头看——一个从 3D 模型文件里读出的顶点,到底经历了什么?
① 局部坐标 (x, y, z, 1)
模型文件中的原始位置
② × Model 矩阵 → 世界坐标
缩放 + 旋转 + 平移 = 一把到位
③ × View 矩阵 → 摄像机坐标
世界以摄像机为原点重新定位
④ × Projection 矩阵 → 裁剪空间
深度藏入 w,准备好被透视
⑤ 透视除法 (/w) → NDC [-1,1]
近大远小在此生效
⑥ 视口变换 → 屏幕坐标 (像素)
[-1,1] 线性映射到 [0, 1920]×[0, 1080]
⑦ 光栅化 → 三角形内像素
边缘函数判断内外
⑧ 重心坐标插值 → 片元属性
颜色/纹理/法线 = 三个顶点的加权平均
⑨ 纹理采样 + 光照 → 最终像素颜色 RGB
N·L 点积决定亮度,纹理查找决定色调
✅ 一条公式统治全部
从 3D 顶点到屏幕像素的完整旅程,可以压缩成:
屏幕颜色 =
纹理( 插值( 视口( /w( P·V·M · v局部 ) ) ) ) · N·L
核心运算:矩阵乘法、透视除法、边缘判断、权重插值、点积——全部是线性代数
⑮ 彩蛋:这些数学不只属于显卡
显卡管线的数学工具箱——矩阵、齐次坐标、重心坐标、点积——在看似完全不同的领域反复出现:
| GPU 中的概念 | 跨领域等价物 | 领域 |
| 4×4 齐次变换矩阵 | 机器人运动学中的 DH 矩阵 | 机器人学 |
| MVP = P·V·M 矩阵复合 | 坐标系链式变换 | 物理/工程 |
| 重心坐标插值 | 有限元方法中的形函数 | 计算力学 |
| N·L 漫反射点积 | 两个向量的余弦相似度 | 机器学习/NLP |
| 透视投影矩阵 | 相机标定中的内参矩阵 | 计算机视觉 |
| 齐次坐标 (x,y,z,w) | 射影几何中的齐次表示 | 纯数学 |
| 光栅化边缘函数 | 计算几何中的点在多边形内判断 | GIS/CAD |
💡 主角登场:线性代数
显卡背后的数学本质上就是线性代数在 3D 渲染这个具体问题上的应用。你学会的不只是"显卡怎么画三角形",更是如何用矩阵和向量来描述、变换、投影空间中的物体——这是现代科技(图形学、机器人、AI、物理模拟)的通用语言。
✦ 你现在已经能从零推导出这一切了
回头看第 ① 节的那个问号:3D 三角形顶点 → 屏幕像素。
你现在知道:
1
用齐次坐标 (x, y, z, 1) 表示顶点,用 4×4 矩阵 做平移、旋转、缩放
3
投影矩阵把深度藏进 w 分量,除以 w 实现近大远小
4
屏幕上的三个顶点围成三角形,边缘函数 判断哪些像素在里面
5
重心坐标 给三个顶点分配权重,三角形内部属性平滑过渡
✅ 你手里的工具箱
矩阵乘法、齐次坐标、透视除法、边缘函数、重心坐标、点积——这六个概念,就是现代 GPU 每秒处理数十亿个三角形的全部数学基础。你不用记 API 的名字(OpenGL?Vulkan?DirectX?——它们只是同一套数学的不同方言),因为你已经懂了里面的数学。