学习图形学:光线追踪入门(从 0 到可用的最小实现)
0. 为什么要学光线追踪
光线追踪(Ray Tracing)的价值不只是“更真实”,而是它把渲染问题拆成一套非常统一的数学与工程模块:发射射线 → 求交 → 计算散射/发光 → 继续追踪/结束。理解这套闭环后,再回头看光栅化管线、PBR、GI、采样与降噪,会更容易形成体系。
本文目标:做出一个能渲染球体/三角形、支持阴影、反射,并能逐步扩展到路径追踪的最小实现。
1. 你需要的最小数学与约定
- 向量:点乘(投影/夹角)、叉乘(法线/面积)、归一化
- 射线: r(t) = o + t d ,其中 t > 0
- 坐标系:右手/左手都行,但要一致(相机、法线方向、叉乘方向)
- EPS:为避免自相交,常用一个很小的偏移量 \epsilon (如 1e{-4})
2. 关键模块总览(建议按这个顺序实现)
- 2.1 相机(Camera):把像素坐标映射到世界空间射线
- 2.2 几何(Hittable):射线-物体求交(球体最容易起步)
- 2.3 材质(Material / BSDF):命中点如何散射(Lambert / Metal / Dielectric)
- 2.4 光照/发光(Light / Emission):最初可以先只做“环境色 + 漫反射”
- 2.5 阴影(Shadow Ray):从命中点向光源打一根射线判断遮挡
- 2.6 递归(Recursion):镜面反射/折射靠递归追踪实现
- 2.7 加速结构(BVH):物体多了以后必须有(否则 O(N) 求交会爆炸)
- 2.8 采样与降噪:多采样、重要性采样、俄罗斯轮盘赌
3. 相机:从像素到射线
最常见的针孔相机(Pinhole Camera):
- 设定视野角(FOV)、宽高比(aspect)、相机位置
pos - 用
lookAt构造相机的正交基forward/right/up - 对每个像素 (i,j) 在成像平面上取样(可加抖动做抗锯齿)
关键点:你不是“画像素”,你是在为每个像素发射一条或多条射线。
4. 求交:从球体开始(最小闭环)
球体求交是入门必做:把射线代入球面方程,解一元二次方程,找到最近的正根 t。
你需要一个统一的“命中记录”:
t:最近命中距离p:命中点n:法线(注意朝向:与射线方向同向/反向要统一处理)materialId或指针:命中物体的材质
工程建议:把“是否命中 + 命中信息”封装成接口,例如 hit(ray, tMin, tMax, outHit)。
5. 阴影:Shadow Ray(立刻让画面“立起来”)
当你有点光源(Point Light)或方向光(Directional Light)时,阴影做法很直接:
- 主射线命中得到点
p - 从
p + n * eps朝光源方向发射一根阴影射线 - 若在到达光源前被任何物体挡住,则该光源贡献为 0
注意:阴影射线也需要 tMax(点光:到光源距离;方向光:无穷远)。
6. 递归:反射/折射与“为什么会变慢”
镜面反射(Perfect Specular):
- 反射方向 r = d - 2(d \cdot n)n
折射(Dielectric):
- 需要折射率 \eta ,并根据入射/出射介质切换 \eta_i / \eta_t
- 处理全反射(Total Internal Reflection)
- 实践中常用 Schlick 近似计算菲涅尔反射概率
性能直觉:每一次反射/折射都意味着再打一条射线,递归深度与采样数会把成本指数式推高,所以后面必须引入:BVH + 重要性采样 + 轮盘赌。
7. 从“光线追踪”到“路径追踪”:采样才是核心
如果你只做“直接光 + 阴影”,画面已经像回事,但还不算全局光照(GI)。
路径追踪(Path Tracing)的核心是:在命中点按 BSDF 分布随机采样出新的方向,累积贡献:
- 多次反弹带来间接光(Color Bleeding、软阴影、漫反射全局照明)
- 多次采样降低噪声(但需要更多时间)
最小可用策略:
- 每像素
spp(samples per pixel)多次采样 - 最大深度
maxDepth(比如 5~10) - 从某一深度开始用俄罗斯轮盘赌终止路径,保证无偏并控制成本
8. 代码最小骨架(伪代码/结构)
下面是一套“能跑起来”的结构(语言不限,C++/Rust/Python 都行),重点是模块边界清晰:
1 | Color trace(const Ray& ray, int depth) { |
如果你当前只想“学习光线追踪(不做 GI)”,也可以把 scatter 简化为:只支持镜面反射(Metal)+ 漫反射(Lambert)两种,先把闭环跑通。
9. BVH:从“能跑”到“能用”
当场景里有 10k 个三角形时,朴素求交是:
- 每根射线都要遍历全部物体:O(N)
- 每像素多采样 + 多反弹:射线数量成倍增加
BVH(Bounding Volume Hierarchy)提供的加速:
- 用 AABB 包围盒快速排除大部分物体
- 让求交接近 O(\log N)(实际依赖构建质量与场景分布)
建议实践路线:
- 先实现
AABB.hit(ray)(slab method) - 再实现 BVH 节点(left/right + box)
- 最后实现 BVH 构建(按轴排序、递归分割)
10. 学习路线(可直接照着做)
- 第 1 周:相机 + 球体求交 + 法线可视化(把法线映射到颜色)
- 第 2 周:Lambert 漫反射 + 点光源 + 阴影射线(硬阴影)
- 第 3 周:反射/折射 + Schlick 菲涅尔 + 最大递归深度控制
- 第 4 周:三角形与网格(Möller–Trumbore)+ BVH 加速
- 第 5 周+:路径追踪(GI)+ 重要性采样 + 轮盘赌 + 简单降噪
11. 常见坑(非常容易踩)
- 自相交:阴影射线/二次反弹从
p + n*eps发射 - 法线方向:统一为“与入射射线方向相反”的外法线(常见做法是根据
dot(ray.d, n)翻转) - Gamma:写入图片前做 gamma 校正(最常用 gamma=2.2)
- 能量守恒:材质参数别让反射/漫反射叠加超过 1(尤其是混合材质)
- 噪声:路径追踪早期画面一定噪,先接受它,再用采样/重要性采样解决
12. 推荐资料(入门到进阶)
- 《Ray Tracing in One Weekend》系列:最适合“先跑起来”,再逐步扩展(免费在线)
- PBRT(Physically Based Rendering):更严谨的工程与理论,适合中后期系统学习
- Real-Time Rendering:理解实时渲染体系与很多基础概念(配合光追一起看很香)
如果你愿意,我也可以在这篇文章后面继续追加一个“可运行的小项目”章节:用你偏好的语言(C++/Python/Rust),输出一张 ppm/png,并把项目结构放到仓库里(含 BVH、基础材质、以及一个 demo 场景)。
