1401 字
7 分钟
光线追踪基础——从光栅化到 RTX

一、光栅化做不到的事#

光栅化管线是一个”一次只看一个三角形”的模型——每个三角形独立投影、独立光栅化。这让它很快,但也让它天生看不见全局。以下效果在光栅化里特别难做:

效果为什么光栅化难
镜面反射需要知道”从镜子出发,光打到对面墙上什么位置”
软阴影需要知道”面光源被物体遮挡了多少”(不是简单的二元遮挡)
全局光照需要知道光线反弹后的颜色贡献
折射需要跟踪光线穿过不同介质时的路径

这些效果都需要递归地追踪光线在场景中的路径——这正是光线追踪做的事情。


二、光线追踪的基本原理#

1 反向追踪#

真实世界:光源发出光子 → 光子弹射 → 进入人眼。但这太浪费——大部分光子你永远看不到。

图形学反过来:从眼睛出发,向每个像素射一条光线,看它打到什么,再递归追踪反射方向的光线。

眼睛 → 像素 (x,y) → 光线 → 打到物体A(红色)
→ 反射光线 → 打到物体B(蓝色)
→ 像素最终颜色:红 × 0.7 + 蓝 × 0.3

2 核心操作: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 为什么只在特定材质上开光追,而不是全场景替换。

光线追踪基础——从光栅化到 RTX
https://www.m4doka.xyz/posts/cg/cg-11-raytracing/
作者
m4doka
发布于
2026-03-29
许可协议
CC BY-NC-SA 4.0