Unity URP + Compute Shader 路径追踪器实战:从可用到可优化

这篇文章总结我在一个 Unity 客户端项目里实现并迭代路径追踪器的完整过程:

基于 URP ScriptableRenderPass + HLSL Compute Shader + SDF 场景表示,从"能跑"走到"可持续优化"。


一、项目目标

我想在 Unity 里做一个可控、可扩展的离线路径追踪风格渲染流程,目标是:

  • 支持基础材质:漫反射 / 金属 / 电介质(玻璃)
  • 支持发光体直接采样与多次反弹
  • 支持时间累积降噪
  • 在工程上可调参、可回退、可分阶段优化

二、整体架构(CPU + GPU)

1) CPU 侧(C# / URP Pass)

核心职责:

  • 扫描场景对象(Collider + MeshFilter)并构建 SdfShape 列表
  • 把几何和材质参数上传到 GPU(ComputeBuffer
  • 管理历史纹理、分辨率缩放、相机变化重置
  • 调度 Compute Shader 内核(单核 or 分阶段)

2) GPU 侧(Compute Shader)

核心职责:

  • 射线生成(像素抖动多样本)
  • SDF Ray March 求交 + 法线估计
  • BSDF 采样与路径积分(含俄罗斯轮盘赌)
  • 发光体直接采样 + MIS 权重
  • 当前帧结果与历史样本融合

三、渲染主流程(按帧)

下面是精简后的伪代码:

pseudo 复制代码
function ExecuteFrame():
    if needRebuildSdfBuffer():
        shapes = collectSceneShapes()
        upload(SDFShapes, shapes)

        emissiveIndices = []
        for i in range(shapes.count):
            if shapes[i].isQuadLike and shapes[i].emission > eps:
                emissiveIndices.add(i)
        upload(EmissiveIndices, emissiveIndices)

    setupRenderTargets(traceResolutionScale)
    if cameraMoved:
        resetHistorySamples()

    dispatch(CSPathTraceStage)   // 只做本帧采样
    dispatch(CSComposeStage)     // 历史融合并输出

    copyCurrentToHistory()
    blitToCameraTarget()

四、路径追踪核心(GPU)

路径积分伪代码:

pseudo 复制代码
function TracePath(ray):
    radiance = 0
    throughput = 1

    for bounce in [0..maxBounces):
        hit = rayMarchSDF(ray)
        if !hit:
            break

        mat = fetchMaterial(hit.shape)

        if mat.emissive:
            radiance += throughput * mat.emissionColor
            break

        if mat.isMetal:
            wi = reflect(ray.dir, hit.normal)
            throughput *= fresnelSchlick(...)
        else if mat.isDielectric:
            wi = reflect_or_refract_by_fresnel(...)
        else:
            radiance += sampleDirectLightMIS(hit, throughput)
            wi = cosineHemisphereSample(hit.normal)
            throughput *= mat.albedo

        if bounce >= rrStart:
            if random() > survivalProb(throughput):
                break
            throughput /= survivalProb

        ray = offsetRay(hit.pos, wi, normalBias)

    return clampHuePreserve(radiance)

五、关键技术点

  • SDF 几何表示:sphere / box / quad 统一距离场,便于在 Compute 中做统一求交
  • Ray Marching:按距离步进,命中阈值 + 偏移避免自相交
  • 材质模型
    • 漫反射:余弦半球采样
    • 金属:反射 + Schlick Fresnel
    • 电介质:折射/反射概率 + 全反射判断
  • 直接光采样:对发光面片采样点,计算面积 PDF
  • MIS(Power Heuristic):平衡"光源采样"和"BSDF采样"贡献
  • 时间累积color + sampleCount 方式做稳定融合
  • 颜色稳定性:用"亮度保持色相"的 clamp,避免 RGB 白化/偏色

六、这次优化里最有效的一步

发光索引缓存(CPU 预构建 + GPU 直接索引)

之前直接光采样每次都要在 shader 里遍历 _SDFCount 做发光体筛选,热点非常明显。

优化后流程变成:

  • CPU 构建 SDF 时顺便筛发光体索引
  • 上传 EmissiveIndices + _EmissiveCount
  • GPU 直接 chosen = EmissiveIndices[pick] 取样本对象

伪代码:

pseudo 复制代码
// CPU
emissiveIndices = filter(shapes, shape => shape.type is emissiveQuad && shape.emission > eps)
upload(EmissiveIndices, emissiveIndices)

// GPU
if _EmissiveCount == 0: return 0
pick = randomInt(0, _EmissiveCount-1)
lightIndex = EmissiveIndices[pick]
sample(lightIndex)

优点:

  • 降低 shader 中重复遍历
  • 提高直接光阶段吞吐
  • 结构清晰,后续可扩展到多类光源索引

七、技术栈清单

  • 引擎与渲染管线:Unity + URP
  • CPU 侧:C#、ScriptableRendererFeature、ScriptableRenderPass、CommandBuffer
  • GPU 侧:HLSL Compute Shader(多 kernel 分阶段)
  • 数据结构:ComputeBuffer(结构化缓冲)、RenderTexture(ARGBFloat)
  • 算法:SDF Ray Marching、Monte Carlo Path Tracing、MIS、Russian Roulette、Temporal Accumulation
  • 工程策略:相机变化检测、静态/动态 SDF 重建策略、分辨率缩放、更新帧间隔控制

八、后续优化路线

  • Step 1(推荐下一步):自适应采样(方差驱动,低噪像素降采样)
  • Step 2(大头):SDF 空间加速结构(网格分桶/BVH)降低全量遍历复杂度
  • Step 3(画质稳定):重投影历史融合(motion vector + neighborhood clamp)
相关推荐
叶帆14 天前
【YFIOs】用C#开发硬件之设备上云
开发语言·unity·c#
久数君14 天前
AI三维建模工具“造形家”:地理场景三维化的高效解决方案
unity·glb·ai算法·ai三维建模工具·地图框选·造形家·城市建筑模型
会思考的猴子15 天前
Unity VFX 属性 Postion 和 TargetPostion
unity
hai31524754315 天前
九章编程法 · 猜数字游戏 (GW-BASIC 重构版) *
人工智能·microsoft·游戏引擎·游戏程序
心前阳光15 天前
Unity资源导入之自动化资源导入
unity·自动化·游戏引擎
心前阳光15 天前
Unity之2021.3.45f2c1发布安卓程序遇到的问题
android·unity·游戏引擎
纪纯15 天前
PicoVR Unity Integration SDK 3.4 常用交互API
unity·游戏引擎·vr·pico
龙智DevSecOps解决方案15 天前
3A 游戏优化技术栈:如何打通引擎级分析工具与 DevOps 持续集成管线?
unity·性能优化·游戏开发·技术美术·perforce·unrealengine
葛兰岱尔15 天前
从 SolidWorks 到 Three.js,从 Inventor 到 Unity——制造业CAD模型“几何-语义一体化“转换,不再是天方夜谭!
开发语言·javascript·unity
鼎艺创新科技15 天前
三维电子沙盘中OSGB倾斜摄影数据的加载与渲染
游戏引擎·cocos2d