一、光栅化做不到的事
光栅化管线是一个”一次只看一个三角形”的模型——每个三角形独立投影、独立光栅化。这让它很快,但也让它天生看不见全局。以下效果在光栅化里特别难做:
| 效果 | 为什么光栅化难 |
|---|---|
| 镜面反射 | 需要知道”从镜子出发,光打到对面墙上什么位置” |
| 软阴影 | 需要知道”面光源被物体遮挡了多少”(不是简单的二元遮挡) |
| 全局光照 | 需要知道光线反弹后的颜色贡献 |
| 折射 | 需要跟踪光线穿过不同介质时的路径 |
这些效果都需要递归地追踪光线在场景中的路径——这正是光线追踪做的事情。
二、光线追踪的基本原理
1 反向追踪
真实世界:光源发出光子 → 光子弹射 → 进入人眼。但这太浪费——大部分光子你永远看不到。
图形学反过来:从眼睛出发,向每个像素射一条光线,看它打到什么,再递归追踪反射方向的光线。
眼睛 → 像素 (x,y) → 光线 → 打到物体A(红色) → 反射光线 → 打到物体B(蓝色) → 像素最终颜色:红 × 0.7 + 蓝 × 0.32 核心操作:Ray-Triangle Intersection
追踪中 90% 的计算都在做”光线和三角形求交”。场景里有几百万三角形,暴力遍历不可行。所以需要加速结构——BVH(层包围盒,场景管理篇提过)。
Ray 进入 BVH: ├── 和包围盒相交?→ 否 → 跳过整棵子树 ├── 和包围盒相交?→ 是 → 继续向下 │ ├── 叶子节点 → 逐个三角形求交 │ └── 内部节点 → 递归检查子节点DXR 的硬件加速做的是 BVH 遍历 + 光线-三角形求交——不需要你在 Shader 里自己写这套遍历逻辑。
三、DXR 的 Shader 管线
DXR 定义了一套全新的 Shader 类型,和光栅化管线的 VS/PS 完全独立:
[应用程序] ↓ DispatchRays()[Ray Generation Shader] ← 入口:对每个像素发射光线 ↓ TraceRay()[加速结构遍历 + 三角形求交] ← 硬件做 ↓[Closest Hit Shader] ← 光线打到最近的三角形时执行 │ ↓ TraceRay()(递归:反射/折射) │ [Closest Hit Shader] ↓[Miss Shader] ← 光线没打中任何东西时执行(返回天空盒颜色) ↓[Ray Generation Shader 收到结果] → 写 Render Target关键 Shader 类型
// Ray Generation Shader:每个像素的入口[shader("raygeneration")]void RayGen() { RayDesc ray; ray.Origin = CameraPos; ray.Direction = GetRayDirection(DispatchRaysIndex().xy); ray.TMin = 0.001; ray.TMax = 10000.0;
Payload payload; TraceRay(Scene, RAY_FLAG_NONE, ~0, 0, 0, 0, ray, payload); Output[DispatchRaysIndex().xy] = payload.Color;}
// Closest Hit Shader:打到物体时[shader("closesthit")]void ClosestHit(inout Payload payload, in IntersectionAttributes attr) { // 计算直接光照 payload.Color = CalculateLighting(hitPosition, hitNormal);
// 如果是镜面,递归追踪反射光线 if (IsSpecular(material)) { RayDesc reflRay = CreateReflectionRay(hitPosition, hitNormal, rayDir); TraceRay(Scene, RAY_FLAG_NONE, ~0, 0, 0, 0, reflRay, payload); }}
// Miss Shader:没打中任何东西[shader("miss")]void Miss(inout Payload payload) { payload.Color = SkyBoxColor; // 天空盒颜色}TraceRay 是递归的——在 ClosestHit 里可以再次调用 TraceRay 追踪反射光线。这和光栅化管线的”当前像素没有历史”完全不同。
四、混合渲染:光栅化 + 光追
现在的做法不是”全光追替代光栅化”——而是光栅化画基础,光追补充最难的效果:
1. 光栅化:GBuffer Pass(延迟渲染的几何阶段)2. Compute:用光栅化结果做大部分直接光照3. 光追: ├── 镜面/光滑材质 → 反射光线(替代 Screen Space Reflection) ├── 面光源阴影 → 软阴影光线(替代 Shadow Map 的硬边) ├── 漫反射 GI → 全局光照光线(替代烘焙 Lightmap) └── 透明材质 → 折射光线4. 混合 → 最终画面UE5 的 Hybrid Ray Tracer(混合光追器)就在做这件事。 在 Post Process Volume 里开启 Ray Tracing Reflections / Shadows / Ambient Occlusion 时,引擎在光栅化结果之上追加光追效果。
五、光追的性能:为什么不能全做
| 瓶颈 | 说明 |
|---|---|
| BVH 构建 | 每帧需要为动态物体重建 BVH——场景有 10000 个三角形移动时,构建开销不可忽略 |
| 光线数量 | 1080p × 1 ray/pixel × 2 bounces = 4M 光线——每帧。每加一次反射光线翻数倍 |
| Incoherent Rays | 相邻像素的光线可能打到完全不同的物体 → Cache 命中率低 → GPU Occupancy 暴跌 |
| 材质复杂度 | Hit Shader 里跑复杂的 PBR 计算——如果每个 Hit Shader 都做了 1000 行 Shader 代码,光线追踪的”并行优势”被 Shader 复杂度吃掉 |
所以现代游戏光追 = 低采样率 + 降噪(Denoising)。 只追踪少量光线(如 1/4 分辨率),然后用时域/空间降噪算法补足。DLSS Ray Reconstruction 就是干这个的。
六、总结
| 概念 | 一句话 |
|---|---|
| 光追核心 | 眼睛发出一条光线 → 递归追踪弹射 → 收集颜色 |
| BVH | 光追的加速结构,硬件做遍历+求交 |
| DXR 管线 | RayGen → TraceRay/Hit/Miss——独立的 Shader 类型 |
| 混合渲染 | 光栅化做基础 + 光追做反射/阴影/GI |
| 降噪 | 光追采样率很低,靠时间/空间降噪补足 |
| 性能 | BVH 构建 + 光线数 + Incoherence → 全光追还早 |
光线追踪不是光栅化的替代品——它是补充武器,专打光栅化打不准的目标。理解 DXR 的 Shader 模型和性能瓶颈,你才知道 UE 为什么只在特定材质上开光追,而不是全场景替换。