Skip to main content

Chapter 37: Ray Tracing Optimization

Ray tracing 的优化对象是一条 ray 从生成、遍历加速结构、执行求交、进入 hit shader、继续发射下一条 ray、最后写回图像的完整路径。读完本章后,读者应能把一次 ray tracing 帧耗时拆成求交次数、BVH 遍历、内存访问、分支发散、递归深度、采样数和硬件并行度几个可观察因素,并据此判断优化入口。

本章用一个贯穿场景说明优化链路:一个 1080p hybrid frame 先用 rasterization 生成 depth、normal、roughness 和 material id,再用 ray tracing pass 计算 glossy reflection 和 hard shadow。每个可见像素发射一条 reflection ray,第一次命中后根据材质粗糙度决定是否继续反射;每个命中点再发射一条 shadow ray 测试小面积光源可见性。场景包含静态建筑、少量动态角色和透明植被。这个场景足够小,可以在文章里跟踪每一类成本;它又足够接近真实工程,可以承载 BLAS、TLAS、shader binding table、closest hit、any hit、fallback path 等对象。

优化的主结论是:ray tracing 帧耗时通常由“发射多少条 ray”“每条 ray 访问多少 BVH node 和 primitive”“命中后执行多少 shader 工作”“这些工作在 GPU 上是否保持并行一致”共同决定。减少 ray 数量只是第一层。更稳定的优化要把加速结构质量、ray 类型组织、shader 分支、资源布局和硬件路径一起检查。

图形 API 的文档提供了对象边界。Vulkan 的 VK_KHR_acceleration_structure 明确把 acceleration structure 作为空间组织几何并快速识别潜在求交 primitive 的设备扩展;Microsoft 的 Direct3D 12 Raytracing 文档 把 acceleration structure、DispatchRays、raytracing pipeline state 和 HLSL ray tracing shader 放在同一组 API 对象中。正文会把这些 API 对象映射到贯穿场景中的 frame、pass、buffer、shader 和性能证据。

37.1 Ray Tracing 优化目标与核心难点

Ray tracing 优化首先要定义成本单位。Rasterization 的核心工作通常沿 triangle、fragment、texture sample 展开;ray tracing 的核心工作沿 ray 展开。一条 ray 的成本来自 ray generation shader 生成起点和方向、遍历 TLAS 和 BLAS、测试 AABB 与 triangle、执行 closest hit 或 miss shader、根据材质继续派生 shadow ray 或 reflection ray。贯穿场景中,一个像素的 glossy reflection 可能对应一条 primary-like reflection ray、一次 closest hit、一次 shadow ray 和一次 miss。画面上看见的是反射颜色,GPU 上执行的是多个间接分支和随机内存访问。

优化目标应先落到可测指标。第一类指标是 ray 数量,例如每像素 reflection ray 数、shadow ray 数、bounce depth 和 sample per pixel。第二类指标是遍历成本,例如每条 ray 访问的 BVH node 数、primitive candidate 数、triangle test 数和 any hit 调用数。第三类指标是 shader 成本,例如 closest hit 中的纹理采样、BRDF 计算、材质分支和再次 TraceRay 调用。第四类指标是并行效率,例如同一 wave 或 warp 内 ray 方向是否相近、命中材质是否集中、shader 路径是否分裂、内存访问是否具有局部性。

贯穿场景的第一轮定位可以使用一个成本近似:frame_ray_cost ≈ ray_count × traversal_cost_per_ray + hit_count × hit_shader_cost + miss_count × miss_shader_cost + build_update_cost。这个式子不追求精确预测毫秒数,它的作用是阻止优化动作混在一起。开启 half resolution reflection 会直接降低 ray_count;改 BVH split 或 node layout 会改变 traversal_cost_per_ray;精简 closest hit 会改变 hit_shader_cost;动态角色的 BLAS refit 或 rebuild 会进入 build_update_cost。一次优化只应声明自己改变了哪一项。

Ray tracing 的难点来自工作负载不规则。Reflection ray 的方向由 normal 和 view vector 决定,roughness 又会扩大方向分布。Shadow ray 的长度由光源和 hit point 决定,透明植被会触发 any hit。多个像素在屏幕上相邻,进入 BVH 后访问的 node 可能很快分散。GPU 仍然以 wave 或 warp 形式批量执行 shader,ray 路径分散后,同一批 lane 会进入不同材质、不同深度、不同 hit/miss 结果,吞吐下降会直接出现在 ray tracing pass 的 GPU 时间中。

求交次数和递归深度要一起看。每多一次 bounce,ray 数量可能按命中率继续增长;每多一类光源可见性测试,shadow ray 数量也会增长。贯穿场景中,如果每个 reflection hit 都继续发射第二层 reflection,并且每层都发射 shadow ray,像素数量、bounce 深度和光源数量会形成乘法关系。常用做法是给不同 ray 类型设置预算:镜面 reflection 保留更高分辨率,粗糙 reflection 降低分辨率或使用 probe 混合,shadow ray 限制光源数量,间接 bounce 使用时域累积和 denoising 承担一部分收敛工作。

内存访问是另一条主线。BVH node、triangle index、vertex buffer、material table、texture 都会被 ray traversal 和 hit shader 访问。Primary rasterization 的访问顺序通常由屏幕和 draw call 决定,ray tracing 的访问顺序由 ray 方向、场景空间结构和命中材质共同决定。相邻像素射出的 reflection ray 如果打到不同建筑立面和动态角色,GPU 会在加速结构、geometry buffer 和 texture 之间跳转。此时继续增加算术优化收益有限,数据布局、node ordering、材质排序和 ray 分组会更接近瓶颈源头。

优化判断顺序应固定。先分离 build/update cost 和 trace cost,因为动态场景中 acceleration structure 更新可能已经占据主要成本。再分离 ray generation、traversal、hit shader 和 denoiser,因为它们对应完全不同的资源路径。之后按 ray 类型统计 reflection、shadow、ambient occlusion 或 global illumination ray 的占比。最后检查 divergence、cache miss、any hit 调用和 shader 复杂度。这个顺序能把“画面慢”转换为“哪类 ray 在哪一段路径上消耗预算”。

下面这张图只描述贯穿场景中 reflection pass 的关键成本路径,不覆盖完整 path tracer。它的目标是把可见图像、ray、acceleration structure、shader 和最终输出连成同一个 frame 内的证据链。

图中的回流说明 ray tracing pass 的成本具有递归形态。Ray Generation 读取 G-buffer,TLAS Traversal 决定进入哪个 instance,BLAS Traversal 决定访问哪些 triangle,Closest Hit Shader 决定材质计算和后续 ray。优化时要把回流次数和每次回流成本同时纳入预算。只观察最终反射图像,无法定位成本来自 ray 数量、BVH 质量还是 hit shader。

37.2 BVH, KD-Tree, and Ray Traversal Cost

BVH 和 KD-Tree 都是 acceleration structure,它们解决同一个问题:用空间层级减少 ray 与 primitive 的直接测试次数。直接遍历全场景时,一条 ray 需要测试所有 triangle;加入层级结构后,ray 先测试较粗的空间范围,再进入可能命中的子区域。优化目标从“减少 triangle test”扩展为“减少 node test、减少无效 child 访问、让内存读取更连续、让更新成本可控”。

BVH 的基本单位是 bounding volume。一个 node 包住一组 primitive 或子 node,常见包围体是 axis-aligned bounding box。ray 先与 node 的 AABB 测试,相交后再进入子 node 或 leaf。BVH 的优势是构建对象直接跟 primitive 集合绑定,移动物体可以单独更新自己的 BLAS,场景实例可以通过 TLAS 组织。贯穿场景中,静态建筑、动态角色和植被可以分别拥有 BLAS,TLAS 存放它们的 instance transform 和引用关系。

KD-Tree 的基本单位是空间切分平面。每个 node 用一个轴向平面把空间分为两侧,ray 根据进入区间依次访问子节点。KD-Tree 的优势是空间划分更明确,某些静态场景中遍历效率高;代价是 primitive 可能跨越多个 cell,构建和更新成本会升高。动态角色、skinned mesh 和大量实例会放大更新压力。实时渲染工程通常更偏向 BVH,因为 BLAS/TLAS 两级结构能把物体级更新和场景级实例管理分开。

两类结构的核心差异可以放到同一组维度比较:构建成本、更新成本、内存大小、遍历一致性、primitive duplication 和 API 支持。BVH 更适合现代硬件 ray tracing API 的通用路径,KD-Tree 更常见于离线渲染器、研究实现或特定静态场景。这个判断不来自名称偏好,而来自实时 frame 中每帧更新、GPU 内存布局和硬件 traversal 单元的对象边界。

维度BVHKD-Tree对贯穿场景的影响
层级对象primitive 集合的包围盒空间切分 cell建筑和角色可按对象组织
更新方式rebuild、refit、局部更新重建压力通常更高动态角色更适合独立 BLAS
primitive 复制通常较少跨 cell 时可能复制植被和长条物体会影响内存
遍历路径node AABB 测试后进入 child沿 ray 区间访问 cellreflection ray 的方向分散会影响缓存
API 贴合度DXR/Vulkan RT 直接围绕 acceleration structure 设计多数实时硬件路径较少暴露工程实现优先走 BVH 风格对象

Ray traversal cost 由树质量和 ray 分布共同决定。树质量包含 node 的空间重叠、leaf primitive 数量、树深度和 child 排列。ray 分布包含方向相干性、长度、起点分布和 ray 类型。一个 BVH 对 camera ray 可能很好,对粗糙 reflection ray 可能成本上升,因为 reflection ray 会从表面向各种方向发散。优化 BVH 时要记录 ray 类型,不能把 primary visibility 的统计结果直接套到 shadow 或 reflection。

Surface Area Heuristic,简称 SAH,是 BVH split 的常见评价方式。它用包围盒表面积近似 ray 命中某个子节点的概率,再结合子节点 primitive 数量估算遍历代价。SAH 的工程意义是:一个 split 如果能减少高概率区域中的 primitive 测试,就值得增加少量 node 遍历。贯穿场景中,建筑墙体和室内物体如果被粗糙地塞入一个大 leaf,reflection ray 命中该 leaf 后会测试过多 triangle;更好的 split 会把空间上分离的墙体、柱子、家具分开,减少 leaf 内 candidate。

Traversal stack 是理解成本的关键对象。深度优先遍历会把远侧 child 压入 stack,先访问近侧 child。找到 closest hit 后,ray 的当前最大距离可以缩短,后续远于该距离的 node 可以被跳过。Shadow ray 通常只关心是否有遮挡,发现任何有效遮挡就可以结束。Reflection ray 通常要找最近命中,需要继续维护最近距离。不同 ray flag 和 hit 需求会改变 traversal 终止条件,进而改变 node 访问数量。

一个简化的 BVH traversal 伪代码可以帮助定位优化点。它省略硬件细节,只保留 node test、leaf test 和 closest distance 更新。

// Simplified pseudo code for explaining traversal cost.
Hit traceClosest(const Ray& ray, const BVH& bvh) {
Stack<int> stack;
stack.push(bvh.root);
Hit closest;
closest.t = ray.tMax;

while (!stack.empty()) {
int nodeId = stack.pop();
const Node& node = bvh.nodes[nodeId];

if (!intersectAabb(ray, node.bounds, closest.t)) {
continue;
}

if (node.isLeaf()) {
for (int primitiveId : node.primitives) {
closest = intersectAndKeepClosest(ray, primitiveId, closest);
}
} else {
pushChildrenNearToFar(ray, node, stack);
}
}

return closest;
}

这段伪代码对应四个成本入口。intersectAabb 次数代表 node 访问规模;leaf 内循环代表 primitive candidate;pushChildrenNearToFar 影响 early hit 后的裁剪效果;closest.t 更新影响后续 node 是否继续访问。真实 DXR 或 Vulkan ray tracing 的 traversal 由驱动和硬件实现承担,工程侧仍能通过 acceleration structure build flags、geometry flags、instance flags、ray flags 和 shader 组织影响这些入口。

KD-Tree 的优化也绕不开 ray 分布。空间切分越细,leaf 中 primitive 越少,但 ray 穿过的 cell 数量会上升。静态室内场景中,KD-Tree 可以让 shadow ray 快速穿过空区域;高度动态场景中,频繁重建会抵消遍历收益。实时工程通常采用 BVH,并通过二级结构、compaction、refit、instance mask 和 ray flags 把更新成本与遍历成本分开控制。

37.3 构建高性能 BVH 管线

高性能 BVH 管线从 primitive bounds 开始。Primitive bounds 是每个 triangle、curve、AABB procedural primitive 的空间范围。bounds 错误会直接破坏 traversal:过大的 bounds 会增加 false positive,过小的 bounds 会漏交。贯穿场景中,skinned character 的 BLAS 如果使用过宽的保守 bounds,reflection ray 会频繁进入角色节点;植被 alpha geometry 如果 bounds 粗糙,shadow ray 会触发大量 any hit。构建管线的第一步是为每类几何生成紧致、稳定、可更新的 bounds。

BLAS 和 TLAS 的分层决定更新边界。BLAS 通常保存 mesh 或 procedural geometry 的局部 acceleration structure;TLAS 保存 instance、transform、mask 和 BLAS 引用。静态建筑 BLAS 可以离线或加载阶段构建并压缩;动态角色 BLAS 根据骨骼变形 rebuild 或 refit;TLAS 每帧根据可见 instance、transform 和 mask 更新。这个分层让场景级变化不必重建所有 triangle 层级,也让动态对象的成本独立统计。

BVH build 有两个目标经常冲突:构建快和遍历快。SAH split 通常追求更低 traversal cost,但构建开销高;linear BVH,简称 LBVH,常用 Morton code 或空间排序快速生成树,构建速度好,树质量可能低于高质量 SAH。实时工程常用混合策略:静态资产使用高质量 build,动态或短生命周期对象使用 fast build 或 refit,重要反射对象使用更高质量结构,远处小物体使用 instance mask 或 LOD 降低参与 ray tracing 的成本。

Refit 是动态场景的常用更新方式。它保留树拓扑,只根据变形后的 primitive bounds 自底向上更新 node bounds。Refit 的优点是更新快,代价是树质量会随形变下降。一个角色摆臂后,原有 split 可能无法贴合新的空间分布,node overlap 增大,reflection ray 和 shadow ray 会访问更多 child。工程判断可以用“refit 多帧后 traversal cost 是否上升”作为证据;如果上升稳定出现,就在关键帧或形变幅度超过阈值时 rebuild。

Compaction 负责降低 acceleration structure 的内存占用。构建后的 BVH 可能包含临时空间或保守布局,API 通常提供查询 compacted size 和 copy compacted structure 的路径。静态建筑和长期存在的场景资产适合 compaction,因为一次构建后会被多帧复用。每帧重建的动态 BLAS若立即 compact,copy 成本可能超过内存收益。判断顺序应先看结构生命周期,再看显存压力和 traversal 缓存表现。

下面的构建流程把贯穿场景拆成可执行管线。它适用于 DXR、Vulkan ray tracing 或引擎自有抽象,具体 API 名称可以替换,数据依赖保持一致。

这条流程的关键是回路。Traversal Statistics 回到 Object Lifetime,表示 build 策略要接受帧内证据修正。静态建筑如果占据大部分 reflection 命中,使用高质量 BLAS build 可能降低长期成本。动态角色如果只覆盖少量屏幕像素,fast build 加 instance mask 可能比高质量 rebuild 更稳定。植被如果 any hit 成本高,可以用 ray tracing 专用简化几何或 opacity micromap 类机制承接透明度判断,前提是目标 API 和硬件路径支持。

Traversal stack 和 node ordering 影响运行时访问顺序。Near-to-far child 排列可以让 closest hit 更早更新 tMax,减少远侧 node 访问。宽 BVH 可以降低树深度,增加单 node 的 child 测试数量;二叉 BVH 控制简单,树深可能更高。硬件实现的内部布局不会完全暴露给应用,但应用能影响输入 primitive 顺序、geometry 分组、instance 分组和 build flag。对于贯穿场景,按材质、空间块和动态状态组织 geometry,能减少 hit shader 中的材质跳转,也能让 TLAS instance 更接近真实空间分布。

Build/update 分离是 BVH 管线的预算核心。加载阶段、关卡切换、摄像机切换和每帧更新属于不同时间尺度。静态 BLAS build 可以进入 asset pipeline,动态 BLAS update 进入 frame graph,TLAS update 靠近 ray tracing pass。这样做的收益是资源状态和 barrier 更清楚:build input buffer 只读,acceleration structure storage 写入,trace pass 读取,compact copy 走 copy 或 compute 相关路径。Vulkan 的 acceleration structure 扩展把 build command、copy command、size query 和 descriptor type 作为 API 对象暴露,正是为了让这些生命周期可被命令缓冲表达。

一个工程化 BVH 管线可以使用如下检查顺序。先确认每类 geometry 的 bounds 是否紧致且覆盖变形范围。再确认静态、动态、实例化对象是否分离到不同 BLAS/TLAS 更新路径。然后比较 fast build、high quality build、refit 和 periodic rebuild 的帧时间。接着检查 compaction 是否用于长生命周期结构。最后用 ray 类型统计验证 traversal node 数和 any hit 调用是否下降。每一步都要回到 reflection pass 的 GPU 时间和画面正确性,防止把构建优化做成孤立资产处理。

37.4 并行求交与缓存友好策略

并行求交的关键问题是 ray coherence。Ray coherence 指一组同时执行的 ray 在起点、方向、长度、命中对象和 shader 路径上具有相似性。Camera ray 在屏幕邻域内通常相干,shadow ray 面向同一光源时也可能相干,glossy reflection ray 随 roughness 增大而快速发散。贯穿场景中,低 roughness 金属墙面的 reflection ray 方向集中,高 roughness 地板的 reflection ray 方向分散。两者使用同一种 tracing 策略,会让后者拖低 wave 内并行效率。

Ray packet 是早期 CPU 和 SIMD ray tracer 中常见的组织方式。它把多条方向相近的 ray 打包,用同一次 node 测试或 SIMD lane 执行多个 ray。GPU 硬件 ray tracing 的底层调度不完全由应用控制,但思想仍然有用:把相似 ray 类型分到相同 pass、相同 dispatch 或相同 shader 路径,可以减少 wave 内分支分裂。贯穿场景中,reflection ray、shadow ray 和 ambient occlusion ray 的用途不同,长度、hit 需求和 shader 行为也不同,分 pass 或分队列组织通常比在一个 mega shader 中混合所有 ray 更容易优化。

Wavefront path tracing 是更适合 GPU 的组织方式。它把路径追踪拆成多个队列阶段:生成 ray、求交、材质计算、生成下一批 ray、阴影测试、累积结果。每个阶段处理相似工作,减少单个 shader 内的递归和分支。代价是需要队列 buffer、compaction、prefix sum 和多次 dispatch。贯穿场景如果从一次 reflection pass 扩展到多 bounce path tracing,wavefront 结构可以把 hit shader 中的材质分支搬到后续 material queue,让 traversal 阶段和 shading 阶段分别优化。

数据布局决定 cache 友好程度。BVH node 应尽量紧凑,child bounds 和 child index 应让 traversal 读取少量连续 cache line。Triangle 数据应按 leaf 或空间邻域重排,让访问 leaf 后读取的 vertex 和 index 更接近。Material table 应让常用字段连续,hit shader 先读取必要字段,再按材质类型读取扩展数据。Texture 访问应通过 mip、roughness-aware LOD 和 bindless 或 descriptor indexing 策略降低随机绑定成本。贯穿场景中,反射打到建筑立面时,如果 material id 指向分散的大量 texture,closest hit 成本会从 traversal 迁移到 texture fetch。

Node ordering 和 geometry ordering 要服务 ray 类型。Shadow ray 通常带有最大距离和 early termination 需求,适合快速判断遮挡;closest reflection ray 需要最近命中,适合 near-to-far traversal 和 tighter bounds。透明植被会触发 any hit,any hit 中如果执行 alpha texture 采样,shadow ray 的成本会显著上升。工程上可以给 shadow ray 使用 instance mask,排除不影响阴影的装饰物;给 reflection ray 使用 ray tracing LOD;给透明物体使用更紧凑的代理几何或 API 支持的透明度加速机制。

SIMD/GPU divergence 需要从两个层级理解。Traversal divergence 发生在同一批 ray 访问不同 node 和 leaf 时;shader divergence 发生在命中后进入不同材质、不同分支和不同 texture 路径时。前者通常通过 ray 分组、BVH 质量和 scene layout 改善;后者通过材质分层、hit group 简化、shader specialization 和 shader execution reordering 类能力改善。DXR 1.2 和部分硬件生态提供的 SER 思路就是把相似 shader 工作重新组织,以提高 ray tracing shader 阶段的执行一致性;使用它仍要以平台支持和实际 profiler 证据为前提。

下面的简化队列结构展示了 wavefront 思路。它是用于解释阶段边界的最小伪代码,只表达 ray 类型分离和阶段分离。

// Simplified pseudo code for a wavefront ray tracing frame.
RayQueue reflectionQueue = generateReflectionRays(gbuffer);
RayQueue shadowQueue;
ColorBuffer reflectionResult;

for (int bounce = 0; bounce < maxReflectionBounce; ++bounce) {
HitQueue hits = traceRays(reflectionQueue);
ShadeQueue shadeItems = compactValidHits(hits);

shadowQueue = generateShadowRays(shadeItems);
VisibilityBuffer visibility = traceVisibility(shadowQueue);

reflectionResult.accumulate(shadeItems, visibility);
reflectionQueue = generateNextBounceRays(shadeItems);
}

这段伪代码把 traceRaysshadeItems 分开,表达的是优化边界。Traversal 阶段关注 BVH、ray flags、ray coherence 和内存访问;shading 阶段关注材质读取、纹理采样和下一条 ray 生成。真实硬件 ray tracing pipeline 可以通过 ray generation、closest hit、miss、any hit 和 shader binding table 完成类似逻辑,也可以在 compute shader 中管理队列。选择哪条路径取决于 API 支持、工程复杂度和目标平台。

缓存友好策略应按证据排序。先看 ray 类型是否混杂,因为混杂会同时放大 traversal 和 shader divergence。再看 hit shader 是否读取过多材质字段,因为随机材质访问会破坏缓存局部性。然后看 geometry 是否按空间或 leaf 重排,因为 leaf 命中后的 vertex/index 读取需要连续。接着看 texture 采样是否使用合适 LOD 和压缩格式。最后看 denoiser 与 ray pass 的数据流是否重复读取大 buffer。这个顺序比直接修改某个 shader 函数更稳定。

并行求交也受 sample strategy 影响。上一章讨论过采样策略,本章只保留优化结论:采样分布越随机,ray coherence 越低;采样越集中,噪声模式和偏差风险需要用时域累积、空间滤波和 material-aware denoising 管理。贯穿场景中,粗糙 reflection 使用蓝噪声方向采样可以改善视觉噪声分布,但 traversal coherence 可能下降。优化时要同时观察 ray tracing pass 时间、denoiser 时间和最终反射稳定性。

37.5 RTX 硬件加速机制与兼容实践

RTX 硬件加速在工程语境中指一组围绕 acceleration structure traversal 和 ray-primitive intersection 的专用路径。不同厂商和 API 的名称不同,通用抽象是:应用构建 BLAS/TLAS,ray generation shader 发射 ray,硬件或驱动加速遍历和求交,命中后调度 closest hit、any hit、miss 等 shader。应用仍负责资源生命周期、shader 复杂度、ray 数量、denoising、fallback path 和跨平台能力查询。

BLAS 和 TLAS 是兼容实践的中心对象。BLAS 保存 triangle 或 procedural primitive 的底层几何结构,TLAS 保存 instance。一个 scene 可以有多个 BLAS,被多个 TLAS instance 引用。贯穿场景中,建筑 BLAS 被多个楼层 instance 复用,动态角色 BLAS 每帧更新,透明植被 BLAS 使用单独 instance mask。Ray generation shader 通过 TraceRay 或等价 API 访问 TLAS,硬件 traversal 根据 TLAS 找到实例,再进入对应 BLAS。

Shader binding table,简称 SBT,是 ray 命中后选择 shader 记录和局部数据的表。它把 ray generation、miss、hit group 和局部 root data 连接起来。SBT 的性能风险来自记录过多、布局分散、hit group 粒度过细和局部数据过大。贯穿场景中,如果每种材质都拥有独立 hit group,命中建筑、角色、玻璃、植被时会频繁跳转。更稳的做法是把 hit group 按几何行为和 ray 类型分组,把材质参数放在结构化 buffer 或 bindless resource 中,由少量 specialization 控制主要分支。

Ray generation shader 是 ray tracing pass 的入口。它通常读取 G-buffer、camera 参数、blue noise、frame index 和 output texture,然后生成 ray origin、direction、tMin、tMax、ray flags 和 payload。优化 ray generation 要控制 payload 大小和 ray flags。Payload 越大,寄存器压力越高,wave occupancy 可能下降;ray flags 如果能表达“只需要遮挡”“跳过 closest hit”“接受第一个 hit”等意图,traversal 和 shader 调度可以减少多余工作。Shadow ray 的 payload 应小于 reflection ray 的 payload,因为它只需要可见性结果。

Closest hit shader 负责命中后的材质计算。它应读取最小必要数据:primitive id、barycentric、instance data、material id、normal、roughness 和必要 texture。复杂 BRDF、多层材质和多次 texture lookup 会使 ray tracing pass 从 traversal-bound 转向 shader-bound。Any hit shader 用于透明、alpha test 或自定义过滤,它的调用次数可能很高,尤其是植被和毛发。Any hit 中的 texture 采样会放大 shadow ray 成本,因此透明场景需要专门预算。

Miss shader 负责 ray 没有命中场景时的颜色或可见性。Reflection ray 的 miss 可能采样 environment map;shadow ray 的 miss 表示无遮挡。Miss shader 通常成本低,但大量 miss ray 仍会访问 TLAS 和部分 BVH node。远距离天空反射、室外大场景和开放空间会增加 miss 占比。优化时要区分“miss shader 成本低”和“miss ray traversal 成本低”这两个判断,后者仍需要 profiler 或统计证据。

下面这张图描述硬件 ray tracing pipeline 的对象关系。它对 DXR、Vulkan ray tracing 和引擎抽象都成立,具体类型名和绑定方式按平台变化。

图中的 SBT 和 TLAS 是两个不同控制面。TLAS 控制 ray 会访问哪些空间对象,SBT 控制命中后执行哪段 shader。一个优化动作要说明自己改变了哪一面:instance mask 改变 TLAS 可见性;hit group 合并改变 SBT 选择;payload 精简改变 shader 寄存器压力;ray flag 改变 traversal 和 shader 调度意图。

Fallback path 是跨平台渲染系统的必要设计。支持硬件 ray tracing 的平台走 DXR、Vulkan ray tracing、Metal ray tracing 或厂商 API;支持不足的平台可以使用 screen-space reflection、reflection probe、shadow map、signed distance field tracing 或 compute BVH traversal。Fallback 属于能力 profile 的一部分,承担低端和受限平台上的效果替代。工程上应把效果目标拆成质量层级:高端 profile 使用硬件 reflection 和 ray traced shadow,中端 profile 使用 half resolution ray tracing 加 denoising,低端 profile 使用 SSR、probe 和 shadow map 组合。

兼容实践从能力查询开始。API 层需要查询 ray tracing pipeline、acceleration structure、descriptor indexing、buffer device address、shader recursion depth、SBT alignment、payload size、geometry type 和 build flags。引擎层需要把这些能力收敛成 profile,例如 HardwareRT_HighHardwareRT_LimitedComputeFallbackRasterFallback。Profile 决定 ray 数量、分辨率、bounce depth、透明参与方式、denoiser 强度和材质功能。这样可以让内容生产、画质设置和性能预算使用同一组约束。

RTX 类硬件路径仍受数据和 shader 约束。硬件可以加速 BVH traversal 和 ray-triangle intersection,但它不会自动降低 ray 数量,也不会自动精简材质 shader。一个 4K 反射 pass 如果发射大量粗糙 reflection ray,并在 closest hit 中执行复杂多层材质,硬件 traversal 只能解决其中一段。工程判断应写成“硬件 RT 让某些 traversal 和 intersection 工作进入专用路径;帧预算仍由 ray 预算、acceleration structure 质量、shader 路径、denoising 和同步共同决定”。

最终的兼容检查顺序是:先查询平台能力和限制,再选择 BLAS/TLAS 构建策略,然后为 ray generation、closest hit、any hit 和 miss 设置最小 payload 与 ray flags,接着按 ray 类型组织 SBT 和 pass,之后提供 SSR、probe 或 shadow map fallback,最后用工具观察 trace pass、AS build、hit shader、any hit 和 denoiser 的时间分布。这个顺序能把“是否有 RTX”转换成“当前 profile 能承担哪一级 ray tracing 效果”。

最小自检任务

给定一个 hybrid renderer:主场景用 rasterization 绘制,反射使用 hardware ray tracing。画面中有静态建筑、一个动态角色、一片 alpha-test 植被和一个小面积光源。当前 1080p 帧中,reflection pass 从 2.1 ms 上升到 6.8 ms。关闭动态角色后变为 5.9 ms,关闭植被后变为 3.2 ms,把 reflection 分辨率降到 half resolution 后变为 2.0 ms。请判断主要瓶颈可能位于哪些路径,并给出排查顺序。

答案要点

主要瓶颈首先指向 ray 数量和透明植被路径。Half resolution 后从 6.8 ms 降到 2.0 ms,说明 ray_count 对总耗时有强影响;关闭植被后从 6.8 ms 降到 3.2 ms,说明 alpha-test 植被触发的 any hit、透明度测试、shadow ray 或 reflection ray traversal 带来大量额外成本。关闭动态角色只降到 5.9 ms,角色 BLAS update 或角色命中路径存在成本,但当前证据显示它属于次级项。

排查顺序应先拆分 AS build/update 与 trace pass。读取 GPU marker 或 frame graph 计时,确认动态角色的 BLAS refit/rebuild 和 TLAS update 占比。然后按 ray 类型统计 reflection ray、shadow ray 和 miss ray 数量,验证 half resolution 的收益是否来自 ray 数量下降。接着单独查看植被 instance 的 any hit 调用次数、alpha texture 采样和 instance mask,判断 shadow ray 与 reflection ray 是否都访问植被。之后检查 SBT hit group 粒度和 closest hit payload,确认材质分支是否放大 shader divergence。最后根据 profile 选择优化动作:植被使用 ray tracing LOD、instance mask、透明度加速能力或 shadow 专用代理;reflection 使用 half resolution、roughness cutoff、时域累积和 denoising;动态角色使用 refit 阈值和周期性 rebuild。

核心结论是:这帧的首要优化入口集中在 ray 数量、植被 any hit 路径和 BVH/TLAS 可见性控制,单个 shader 算术指令属于后续精修对象。角色更新成本需要继续监控,但当前证据显示它属于次级瓶颈。

本章知识点总结

  • 成本单位:Ray tracing 优化应以 ray 路径为单位,把 ray generation、traversal、hit shader、miss shader 和 build/update 分开计量。
  • Ray 预算:每像素 ray 数、bounce depth、shadow ray 数和 sample count 会共同决定总求交规模。
  • 遍历成本:BVH node 访问、leaf primitive candidate 和 triangle test 决定 traversal 的主要压力。
  • 树质量:SAH、leaf 大小、node overlap、child ordering 和 bounds 紧致度会改变每条 ray 的访问路径。
  • 两级结构:BLAS 管理几何层级,TLAS 管理 instance 层级,二者分离后才能控制静态资产和动态对象的更新预算。
  • Refit 边界:Refit 适合快速更新动态 bounds,但形变持续扩大时会降低树质量,需要用 traversal 统计触发 rebuild。
  • 并行一致:Ray coherence 决定 wave 内执行效率,reflection、shadow 和 AO 等 ray 类型应按用途组织。
  • 缓存路径:Node layout、triangle 重排、material table 和 texture 访问会影响 ray traversal 与 hit shader 的实际吞吐。
  • Any Hit 成本:透明、alpha test、植被和毛发会放大 any hit 调用,应单独设置 ray tracing 预算和可见性策略。
  • SBT 作用:Shader binding table 连接命中对象和 shader 记录,过细 hit group 和过大局部数据会增加调度与缓存压力。
  • Payload 压力:Ray payload 越大,寄存器压力越高;shadow ray、reflection ray 和 GI ray 应使用不同的最小数据结构。
  • 硬件边界:RTX 类硬件路径加速 traversal 和 intersection,帧预算仍受 ray 数量、shader、denoising 和同步影响。
  • Fallback 设计:跨平台渲染应把 hardware RT、compute fallback、SSR、probe 和 shadow map 放入同一套 profile 管理。
  • 排查顺序:先分离 build/update 与 trace,再按 ray 类型统计,随后检查 BVH 质量、any hit、hit shader、SBT 和 denoiser。