Software Rasterizer: Triangles to Pixels
一条可编程渲染管线的各阶段:
顶点数据 → 顶点着色 → 裁剪 → 光栅化 → 片元着色 → 帧缓冲
| URP 阶段 | 本项目对应 |
|---|---|
| 顶点着色器 | vertex_shader 函数 |
| 裁剪 | ClipWithPlane |
| 片元着色器 | fragment_shader |
本渲染器把 URP 的每个阶段都用 CPU 代码实现了一遍,骨架完全照搬 URP,只是从 GPU 搬到 CPU——目的是可逐行调试、让 URP 里看不见的中间值全部可见。
| 层 | 职责 | Unity 对应 |
|---|---|---|
| core | 管场景(相机、光源、物体) | Scene |
| Render | 管渲染管线 | URP 可编程管线 |
| gpu | 管着色(Shader 函数库) | URP 的 Shader |
实现了 Lit、SimpleLit、UnLit 三种,名字与 Unity URP 完全一致:
这种分层让”场景描述 / 管线调度 / 着色计算”职责分离,和商业引擎的架构一致。
本渲染器场景用 JSON 描述,但每个数字都对齐 Unity 的约定:相机位置/旋转/视野角、near/far,以及坐标系和旋转顺序。
Unity 用左手坐标系(DirectX 风格):+x 右、+y 上、+z 向前。本渲染器照此对齐。这与 OpenGL/smallpt 的右手系相反——坐标系不同会导致矩阵和裁剪的符号约定不同。
Unity 用欧拉角表示旋转,旋转顺序固定(ZXY)。旋转顺序不同,同样的欧拉角会产生不同的最终朝向——所以必须严格对齐,否则相机视角对不上。
只有坐标系、旋转顺序、投影矩阵全部一致,“比画面”才有意义。差一个符号或顺序,整个画面就错了。
渲染器每画一帧做五件事:
本质和 URP 里一次 DrawCall 做的事相同,区别是 URP 在 GPU 黑盒里跑,这里摊开成可逐行调试的 C++。
最后一步的 sRGB 转换容易被忽略:光照计算在线性空间,但显示器用 sRGB(gamma ≈ 2.2)。不做这步转换,画面会偏暗或偏亮。