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)
相关推荐
天人合一peng10 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安10 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU210 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法10 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件11 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情15 小时前
android隐私合规检测
android·unity
小贺儿开发15 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区16 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader
晚枫歌F1 天前
三层时间轮的实现
网络·unity·游戏引擎