很多刚踏入游戏开发的朋友,总会被 3D 坐标系、矩阵变换、渲染管线一堆概念搞得头昏脑胀🤯。明明代码复制过来,模型旋转却歪到姥姥家;坐标一顿计算,物体直接飞到屏幕外面。今天我们就从引擎最基础的底层原理出发,拆解 3D 空间变换、渲染流水线、API 与引擎工作流,帮你打通游戏图形的任督二脉。

Bilibili 同步视频

游戏引擎原理与实践 03:空间变换与3D渲染流水线入门

📐 搞懂坐标系:一切 3D 运算的起点

3D 坐标系由一个原点 + 三组互相正交的基向量构成,空间内任意一点,都可以依靠这三根轴来描述。这里就有一个新手高频踩坑点:左手坐标系 VS 右手坐标系。

图 1:左手坐标系与右手坐标系示意。不同游戏引擎会选用不同坐标系,选错坐标系,后续旋转、矩阵计算全部会出错。

不少引擎采用左手坐标系,也有引擎使用右手坐标系;轴向上也有区分,有的引擎 Y 轴向上,也存在 Z 轴向上的设计。不用死记硬背手指姿势,只要记住:开发前,先确认引擎的坐标系约定,这是所有运算正确的大前提。

欧拉角也是和坐标系强绑定的概念,比如 Yaw(偏航)、Pitch(俯仰)、Roll(翻滚)。旋转顺序直接决定最终结果,本书示例引擎的顺序是 Roll→Pitch→Yaw,也就是 z 轴 → x 轴 → y 轴。同时旋转方向也要留意:本文约定正角度,顺着轴向看过去是逆时针旋转。

🔄 空间变换:线性变换与仿射变换

空间变换是 3D 引擎的灵魂,也是大部分人卡壳的重灾区。我们先分清两个核心概念:线性变换、仿射变换。

1. 线性变换

线性变换 F F F必须满足两条铁律:

简单理解:向量相加再变换,等价于分别变换再相加;向量数倍后变换,等价于变换后再数倍。像 F ( x ) = 3 x F(x)=3x F(x)=3x 就是典型的线性变换。

3D 中两大经典线性变换:

  1. 缩放变换:把点 / 向量的 x、y、z 各个分量分别乘上缩放系数。⚠️注意:原生线性缩放,永远是相对于坐标原点执行。如果想要相对于某一个自定义点缩放,单纯线性变换做不到。

  2. 旋转变换:可以写成 F ( a ) = a M F(a)=aM F(a)=aM, M M M为 3×3 正交旋转矩阵。任意绕任意方向轴的旋转,都可以拆解成绕 x/y/z 三个坐标轴的组合旋转。

看下这个直观例子:同样是放大 2 倍,一个相对于原点,另一个相对于点 ( 2 , 0 ) (2,0) (2,0)。

图 2:(a) 为相对于原点放大 2 倍;(b) 相对于 (2,0) 点放大 2 倍。后者不再属于线性变换,属于仿射变换。

想要实现非原点的缩放,需要:先把物体平移,把目标点挪到原点 → 执行缩放 → 再平移还原位置。这一套组合,就是仿射变换。

2. 仿射变换:游戏物体的万能变换

仿射变换公式:
F ( a ) = M a + T F(a)=Ma+T F(a)=Ma+T
也就是线性变换之后,追加一次平移。游戏里绝大多数物体变换都是仿射变换。

💡小知识:投影变换不属于仿射变换。

仿射变换有个非常关键的执行顺序。不管你代码写的顺序是平移再缩放再旋转,数学上都可以等价转换为:先缩放 → 再旋转 → 最后平移。几乎所有游戏引擎都遵循这套标准顺序。
F ( a ) = a S M + T F(a)=aSM+T F(a)=aSM+T
S S S代表缩放矩阵, M M M代表旋转矩阵, T T T代表平移向量。

齐次 4×4 矩阵可以把缩放、旋转、平移全部打包到一个矩阵内:
M t r a n s f o r m = M s c a l e t i m e s M r o t a t e t i m e s M t r a n s l a t e M_{transform}=M_{scale} times M_{rotate} times M_{translate} Mtransform​=Mscale​timesMrotate​timesMtranslate​

✨小补充:数据存储上,骨架层级计算常用矩阵;动画数据会分开存储:缩放 (3D 向量)、旋转 (四元数)、平移 (3D 向量),GPU 蒙皮计算的时候,再统一转换成矩阵。
另外还有左乘右乘大坑!本文示例全部采用**向量右乘矩阵 ** v ′ = v M v'=vM v′=vM,不同引擎约定不同,抄矩阵代码务必核对。

原始顶点v

缩放S

旋转M

叠加平移T

变换后顶点v'

图 3:仿射变换标准执行流程,先缩放、再旋转、最后平移。

🚀3D 渲染流水线:三角形如何变成屏幕画面

一个模型上的三角形,是怎么一步步出现在你的显示器上面?这就是 3D 流水线干的活。就算有可编程着色器改写部分逻辑,整体的基础脉络依然不变。

模型空间
原始顶点、UV、法线

世界空间
物体摆放到场景

相机空间
以摄像机为原点观察场景,剔除不可见面

投影空间
近大远小投影变换

屏幕空间
转为2D像素,深度测试、混合、光照计算

输出屏幕画面

图 4:经典 3D 渲染流水线流程示意图

  1. 模型空间:美术在建模软件制作模型时的坐标系。每个模型顶点都基于自身原点定义。

  2. 世界空间:把模型放到游戏场景,施加缩放旋转平移,把顶点换算到整个游戏大世界的坐标系。

  3. 相机空间(观察空间):切换视角,把所有物体转换到以摄像机为原点的坐标系,同时把完全看不到的面片做剔除。

  4. 投影空间:执行透视或者正交投影,实现我们熟悉的近大远小效果。

  5. 屏幕空间:把三维投影结果映射到二维屏幕像素。这里会做深度测试处理物体遮挡,执行模板测试、Alpha 混合、雾化、光照等像素阶段运算。

📌着色器出现之后,流水线不再完全死板。顶点、像素阶段逻辑都可以自定义,比如光照可以放到顶点着色器,也可以放到片段着色器。

🎮图形 API 与引擎工作流

DirectX & OpenGL

DirectX (D3D)、OpenGL 并不是游戏引擎,它们是和显卡硬件打交道的底层函数库。它们封装显卡能力,给上层提供调用接口,榨干 GPU 性能。

💡学习小技巧:如果直接啃 D3D/OpenGL 觉得晦涩难懂,可以先尝试手写一个 CPU 软渲染器,模拟显卡的管线逻辑。吃透原理再上手硬件 API,难度会大幅下降。

汇编指令在图形开发的妙用

汇编在现代图形开发依旧有三大实用场景:

  1. SIMD 单指令多数据流指令,加速海量向量、矩阵数学运算;

  2. 程序崩溃没有调试符号时,通过崩溃处汇编、寄存器值定位 BUG;

  3. 着色器反编译,逆向学习商业游戏的特效实现思路。

完整引擎工作流

游戏资源不是凭空在引擎内生成,是一套多软件协作的流水线:

  • 建模软件 (3ds Max):产出模型三角面数据

  • 贴图软件 (Photoshop):制作纹理贴图

  • 高模雕刻软件 (ZBrush):烘焙法线贴图

  • 动作软件:制作骨骼蒙皮、动画片段

外部资源导入引擎之后,引擎内部再拆分出材质编辑器、特效编辑器、场景编辑器、资源管理器等模块,把模型、贴图、动画组装成场景、游戏对象,再叠加物理、AI、脚本、UI,最终输出成品游戏。

游戏引擎内部

外部美术软件

建模软件

贴图软件

ZBrush高模

动作制作软件

模型

贴图

动作

材质

场景编辑器

动画树

游戏对象

脚本

AI

物理

最终游戏

图 5:游戏引擎资源工作流,美术资源导入引擎,经过组合加工产出游戏内容

📝课后小思考题

读完这篇,不妨可以尝试思考几个问题,检验自己的理解程度:

  1. 如何计算一个点到平面的距离?

  2. 已知两个向量,怎么求出它们之间的夹角?

  3. 为什么三角形和相机近裁剪面相交的时候,需要对三角形做切割?那和远平面相交?和视口左右上下边界相交呢?

  4. 可以拿 Unity 简单做个 Demo,完整走一遍从导入模型到产出简单小游戏的完整流程,感受整套工作流。


小结✨

空间变换、渲染流水线是游戏图形的地基。坐标系、矩阵、仿射变换看着公式吓人,但只要理清顺序、分清各个空间,就会发现逻辑并不复杂。很多开发踩坑,本质都是忽略坐标系差异、变换顺序搞错。理解底层原理,不管是使用现成引擎,还是尝试自研引擎,都会事半功倍。

游戏引擎原理与实践 03:空间变换与3D渲染流水线入门

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐