Metal 光线追踪指南
Your guide to Metal ray tracing
2023年6月5日
一句话判断
Metal 光线追踪今年新增了曲线基元(Curve Primitives)——专门用于渲染毛发和植被的几何类型,内存占用更小、加速结构构建更快,还能在相机靠近时保持曲线平滑。
这场 Session 讲了什么
GPU 软件工程师 Pawel Szczerbuk 详细介绍了 Metal 光线追踪的使用方法和今年新增的特性。
光线追踪的基本工作流是:定义场景几何体 -> 构建加速结构(Acceleration Structure)-> 在 GPU 函数中创建光线并查询交点 -> 基于交点信息着色。加速结构通过递归分区几何体来快速排除不相交的区域。
Metal 支持三种几何描述符:三角形(最常用的图元)、包围盒(自定义交叉函数)和今年新增的曲线(Curve Primitives)。曲线基元专为毛发、皮毛和植被等大量细小几何体设计。
Session 用迪士尼的 Moana 岛屿场景作为渲染示例,详细讲解了加速结构的创建流程、实例化(Instancing)机制、以及光线交叉查询的实现方式。
曲线基元支持四种基函数:Bezier、Catmull-Rom、B-Spline 和 Linear。每个曲线段由 2-4 个控制点定义,控制点半径沿曲线插值。默认以 3D 圆柱体截面渲染,远距离时可以切换为扁平模式提升性能。
值得深挖的点
曲线基元的内存优势:传统方式用三角形近似曲线需要大量几何体,内存占用大。曲线基元直接用数学定义曲线,相邻曲线段共享控制点(通过索引缓冲区),内存占用显著减少。
加速结构的分配策略:Metal 允许从堆(Heap)分配加速结构,而不是直接从设备分配。从堆分配可以减少资源管理开销。Metal 还提供了查询堆大小和对齐需求的方法。
曲线的远/近优化:近距离观察时使用 3D 圆柱体截面(round curves),保证视觉质量;远距离时切换为扁平模式(flat curves),减少计算量。这种自适应策略在毛发等密集场景中很有用。
实例化(Instancing)的复用能力:同一个加速结构可以被多次引用,每次使用不同的变换矩阵。这在场景中有大量重复物体时(如森林中的树木)能大幅减少内存和构建时间。
代码片段
// 曲线几何描述符设置
MTLCurveGeometryDescriptor *curveDesc = [MTLCurveGeometryDescriptor curveGeometryDescriptor];
curveDesc.controlPointBuffer = controlPointBuffer; // 控制点缓冲
curveDesc.radiusBuffer = radiusBuffer; // 半径缓冲
curveDesc.controlPointIndexBuffer = indexBuffer; // 控制点索引
curveDesc.controlPointCount = numControlPoints;
curveDesc.curveCount = numCurveSegments;
curveDesc.curveType = MTLCurveTypeRound; // 圆柱体截面
curveDesc.curveBasis = MTLCurveBasisBezier; // Bezier 基函数
curveDesc.curveEndCaps = MTLCurveEndCapsSphere; // 球形端盖
// 加速结构分配
// 1. 计算所需空间
let sizes = device.accelerationStructureSizes(
descriptor: accelerationDescriptor
)
// 2. 从堆分配(推荐,减少开销)
let accelStruct = heap.makeAccelerationStructure(
size: sizes.accelerationStructureSize
)
// 3. 分配临时缓冲区(构建时使用)
let scratchBuffer = device.makeBuffer(
length: sizes.buildScratchBufferSize,
options: .storageModePrivate
)
// 4. 在 GPU 上构建
let commandBuffer = commandQueue.makeCommandBuffer()!
let encoder = commandBuffer.makeAccelerationStructureCommandEncoder()!
encoder.build(accelerationStructure: accelStruct,
descriptor: accelerationDescriptor,
scratchBuffer: scratchBuffer,
scratchBufferOffset: 0)
encoder.endEncoding()
commandBuffer.commit()
// GPU 着色器中的光线交叉查询
ray r;
r.origin = rayOrigin;
r.direction = rayDirection;
r.min_distance = 0.001;
r.max_distance = FLT_MAX;
intersector<triangle_data, curve_data> itr;
intersection_result<triangle_data, curve_data> result;
// 执行交叉查询
result = itr.intersect(r, accelerationStructure, 0);
if (result.type == intersection_type::triangle) {
// 处理三角形交叉
float2 bary = result.triangle_barycentric_coord;
} else if (result.type == intersection_type::curve) {
// 处理曲线交叉
float u = result.curve_parameter;
}
最佳实践
- 毛发和植被场景使用曲线基元:比三角形近似更省内存、构建更快,还能保持曲线在放大时的平滑度。
- 从堆分配加速结构:减少资源管理开销,特别是在需要频繁重建加速结构的动态场景中。
- 远距离用扁平曲线模式:如果毛发只在远景中出现,flat curves 能显著提升性能。
- 利用实例化减少重复:场景中大量重复的几何体应该使用实例化而不是复制多份。
- 预计算加速结构大小:在构建之前先查询所需空间,避免运行时分配失败。
还有什么值得关注
- 曲线基元支持球形端盖(sphere end caps),让曲线两端看起来更自然。
- 加速结构支持引用(refitting),当几何体位置变化时可以更新而不是完全重建。
- 光线交叉查询支持自定义交叉函数,可以用于体渲染等特殊效果。
- Metal 的光线追踪 API 设计考虑了动态场景——加速结构可以在 GPU 上异步构建,不会阻塞渲染管线。