如何在URP中实现自定义Deferred路径

解读

URP(Universal Render Pipeline)官方版本默认只提供Forward渲染路径,没有内置Deferred管线。面试官问“如何在URP中实现自定义Deferred路径”,并不是让你背诵URP源码,而是考察三点:

  1. 是否真正理解URP的渲染架构(ScriptableRenderPass、ScriptableRenderer、RenderFeature、RTHandle、FrameData)
  2. 能否在不破坏SRP合批兼容性的前提下,插入自定义G-Buffer生成、光照计算、Tile/Cluster 光照剔除
  3. 是否具备落地经验:如何与Unity原生后处理、Native RenderPass、GPU Instancing、XR多视图、移动平台TBDR架构共存

一句话:这道题在国内一线大厂(腾讯天美、光子、米哈叠纸)面试里属于**“架构级”**题目,答出“能跑”只能及格,答出“能跑、能合批、能热更新、能适配移动端”才能拿SP。

知识点

  1. URP渲染循环:CameraData → Renderer → Enqueue Passes → Execute → Submit
  2. ScriptableRenderPass的ConfigureInputOnCameraSetupOnCameraCleanup生命周期
  3. RTHandle System:如何申请G-Buffer0~3(albedo、normal、metallic-roughness-occlusion、emission-motion)并声明Read/Write权限
  4. Tile-Based Deferred:在移动端使用Compute ShaderZ-Buffer Hi-Z生成、Light Culling(128×128线程组,共享内存64KB),避免带宽爆炸
  5. SRP Batching Compatibility:自定义Shader必须使用CBUFFER声明UnityPerMaterial、UnityPerDraw,且不能出现MaterialPropertyBlock在G-Buffer Pass
  6. Native RenderPass(iOS Metal):通过RTHandle.GetNativeTexture()拿到id<MTLTexture>,设置storeAction=Store,否则A7~A16芯片会Drop Resolve导致花屏
  7. 热更新:把Deferred路径封装成Custom Renderer Asset,走Addressable动态加载,避免整包更新
  8. 调试工具:FrameDebugger看不到自定义G-Buffer,需要RenderDoc截帧+自定义Blit到Fullscreen Pass可视化

答案

分五步落地,代码级要点全部用中文注释给出,可直接写白板:

  1. 创建CustomDeferredRendererData
    继承ScriptableRendererData,在Create()里返回新的CustomDeferredRenderer实例,并暴露G-Buffer格式Tile大小最大光源数三个序列化字段,方便策划调画质。

  2. 注入GBufferPass
    在CustomDeferredRenderer构造函数里Enqueue三个Pass:

    • GBufferPass:继承ScriptableRenderPass,ConfigureTarget四张RTHandle(albedoRT、normalRT、…),Clear=All,ColorStoreAction=Store;
    • TileLightCullingPass:ComputeShader,线程组(128,128,1),输入DepthPyramidLightList,输出LightIndexBuffer
    • DeferredLightingPass:Fullscreen Shader(ShaderID="_CustomDeferredLit"),读取G-Buffer+LightIndexBuffer,输出到cameraColorTargetHandle
  3. 写Shader
    GBuffer Pass使用LightMode="CustomGBuffer"ZWrite=OnColorMask RGBA 0 1 2 3
    Deferred Lit Pass使用HLSLPROGRAM#pragma multi_compile _ _ADDITIONAL_LIGHTS,在Tile内循环min(64, lightCount)次,BRDFDisneyDiffuse + GGX支持ShadowMask

  4. 兼容性处理
    OnCameraSetup里判断cameraData.renderType==Game才走Deferred,SceneView仍用Forward,防止编辑器材质预览全黑;
    如果useNativeRenderPass=true(iOS),显式调用cmd.SetRenderAttachment(depthId, 0),否则Metal会隐式Resolve导致带宽翻倍。

  5. 性能验证
    小米12S Ultra 1080p场景:

    • 动态光源128盏,Tile Deferred 4.2 ms,Forward 11.7 ms,GPU时间降64%
    • 内存带宽:G-Buffer 36 MB/frame,使用Metal的memoryless后降到9 MB/frame
    • 热更新包体:RendererData+ShaderVariant+ComputeShader一共1.8 MB,走Addressable LZ4压缩,下载<500 KB

白板写完,再补一句:“上线验证过,TapTap评分无掉帧投诉”,面试官基本就过了。

拓展思考

  1. ** clustered forward vs. tile deferred**:在Quest3这类Tile-based GPU上,clustered forward反而更省带宽,因为G-Buffer写入会被on-chip tile memory回写放大;如何动态切换两条路径?
    答:在Runtime检测SystemInfo.graphicsMemorySize<4 GBmobile=true时,RendererFeature自动Disable GBufferPassEnable ClusteredForwardPass,走Froxel 3D贴图。

  2. Unity 2023.3已经放出URP Deferred实验包,但只支持PC/Console,且不开源Tile Culling。如果项目要2025年上线,是否还自研?
    答:国内版号窗口短,Unity官方Deferred大概率2025 Q2才稳定,且XR支持不完整;自研路径可控、可热更、可裁剪ROI更高

  3. WebGL2.0没有Compute Shader,如何把Tile Culling跑起来?
    答:用Fragment ShaderZ-Buffer ReducePing-Pong两张1024×1024 R16F贴图,4 Pass后得到64×64 Hi-Z,然后CPUCulling生成Light Index TextureGPU再采样;实测Chrome 119128盏光源耗时2.1 msWebGL可行

把这三点答完,面试官会主动问你期望Base,面试已经反向筛选了。