探索 Metal 光线追踪
Discover ray tracing with Metal
2020年6月23日
一句话判断
Metal 在 2020 年正式引入了硬件加速的光线追踪 API——如果你在做图形渲染或游戏开发,这是 Metal 从”足够好”迈向”业界一流”的关键一步。
这场 Session 讲了什么
光线追踪是图形学中模拟光线传播的技术,能产生极其逼真的光影效果(反射、折射、软阴影、全局光照)。Metal 在 2020 年引入了基于 GPU 硬件加速的光线追踪管线。
Session 从光线追踪的基本概念讲起:射线生成、射线-几何体求交、着色计算。然后介绍了 Metal 的三个核心新类型:MTRaytracingAccelerationStructure(加速结构,用于快速射线求交)、MTIntersectionFunctionTable(求交函数表,用于自定义求交逻辑)、以及 Metal Shading Language 中新增的光线追踪相关类型(如 ray、intersection_result)。
整个管线的工作方式是:在 Compute Shader 中生成射线 → 通过加速结构进行批量求交 → 在 Hit Shader 中计算着色。这个过程可以迭代多次(弹射),实现多次反射等效果。
值得深挖的点
加速结构的两层设计。 Metal 使用两层加速结构:底层(BLAS)针对每个几何体构建,顶层(TLAS)把所有实例组织在一起。这种设计的好处是:当场景中某个物体移动时,只需要重建 TLAS,而不需要重建所有 BLAS。对于动态场景(如角色移动、物体旋转),这个设计大幅降低了每帧的加速结构更新成本。
Instancing 与资源复用。 通过 TLAS 的 Instancing,同一个几何体可以在场景中出现多次而只占一份 BLAS 内存。这对于森林(重复的树木)、建筑群(重复的楼房)等场景尤其重要。
代码片段
import Metal
// 创建加速结构
let accelerationStructure = device.makeAccelerationStructure(
size: requiredSize
)
// 构建加速结构的描述符
let geometryDescriptor = MTTriangleAccelerationStructureDescriptor()
geometryDescriptor.vertexBuffer = vertexBuffer
geometryDescriptor.vertexBufferOffset = 0
geometryDescriptor.triangleCount = triangleCount
let instanceDescriptor = MTInstanceAccelerationStructureDescriptor()
instanceDescriptor.instancedAccelerationStructures = [accelerationStructure]
instanceDescriptor.instanceCount = instanceCount
instanceDescriptor.instanceBuffer = instanceBuffer
// Metal Shading Language 中的射线追踪代码
ray r;
r.origin = cameraPosition;
r.direction = normalize(pixelDirection);
r.min_distance = 0.001;
r.max_distance = 1000.0;
// 执行射线求交
intersection_result<triangle_data> result;
result = raytracing::intersect(
accelerationStructure,
r,
raytracing::intersection_query::nearest
);
if (result.type == raytracing::intersection_type::triangle) {
// 命中三角形,计算着色
float3 hitPoint = r.origin + r.direction * result.distance;
float3 normal = result.triangle_normal;
// ... 执行光照计算
}
// 在 Compute Encoder 中调度光线追踪
let commandBuffer = commandQueue.makeCommandBuffer()!
let computeEncoder = commandBuffer.makeComputeCommandEncoder()!
computeEncoder.setComputePipelineState(rayPipeline)
// 绑定加速结构
computeEncoder.setAccelerationStructure(accelerationStructure,
bufferIndex: 0)
// 调度线程组
computeEncoder.dispatchThreads(
MTLSize(width: width, height: height, depth: 1),
threadsPerThreadgroup: MTLSize(width: 8, height: 8, depth: 1)
)
computeEncoder.endEncoding()
最佳实践
- 使用两级加速结构。 BLAS 给静态几何体,TLAS 给动态场景。物体移动时只更新 TLAS,避免重建全部加速结构。
- 在后台线程构建加速结构。 加速结构的构建是计算密集型操作,不要阻塞渲染线程。可以先构建,构建完成后再在渲染中使用。
- 射线数量与质量的平衡。 每像素多条射线(如 4x MSAA)可以提升质量,但计算成本线性增长。先用 1 ray/pixel 调通,再逐步增加。
- 利用 GPU Family 的光线追踪能力。 A14 和 M1 之后的芯片有专用的光线追踪硬件,检查
supportsRaytracing属性来决定是否启用。
还有什么值得关注
- Metal Ray Tracing 同时支持三角形和自定义几何体的求交
- 可以在 Compute Shader 中混合使用光栅化和光线追踪
- 新的 Metal Performance Shaders 包含了光线追踪相关的辅助函数
- 苹果展示了实时光线追踪在 A14 GPU 上的性能数据