如何在URP中实现自定义Deferred路径
解读
URP(Universal Render Pipeline)官方版本默认只提供Forward渲染路径,没有内置Deferred管线。面试官问“如何在URP中实现自定义Deferred路径”,并不是让你背诵URP源码,而是考察三点:
- 是否真正理解URP的渲染架构(ScriptableRenderPass、ScriptableRenderer、RenderFeature、RTHandle、FrameData)
- 能否在不破坏SRP合批兼容性的前提下,插入自定义G-Buffer生成、光照计算、Tile/Cluster 光照剔除
- 是否具备落地经验:如何与Unity原生后处理、Native RenderPass、GPU Instancing、XR多视图、移动平台TBDR架构共存
一句话:这道题在国内一线大厂(腾讯天美、光子、米哈叠纸)面试里属于**“架构级”**题目,答出“能跑”只能及格,答出“能跑、能合批、能热更新、能适配移动端”才能拿SP。
知识点
- URP渲染循环:CameraData → Renderer → Enqueue Passes → Execute → Submit
- ScriptableRenderPass的ConfigureInput、OnCameraSetup、OnCameraCleanup生命周期
- RTHandle System:如何申请G-Buffer0~3(albedo、normal、metallic-roughness-occlusion、emission-motion)并声明Read/Write权限
- Tile-Based Deferred:在移动端使用Compute Shader做Z-Buffer Hi-Z生成、Light Culling(128×128线程组,共享内存64KB),避免带宽爆炸
- SRP Batching Compatibility:自定义Shader必须使用CBUFFER声明UnityPerMaterial、UnityPerDraw,且不能出现MaterialPropertyBlock在G-Buffer Pass
- Native RenderPass(iOS Metal):通过RTHandle.GetNativeTexture()拿到id<MTLTexture>,设置storeAction=Store,否则A7~A16芯片会Drop Resolve导致花屏
- 热更新:把Deferred路径封装成Custom Renderer Asset,走Addressable动态加载,避免整包更新
- 调试工具:FrameDebugger看不到自定义G-Buffer,需要RenderDoc截帧+自定义Blit到Fullscreen Pass可视化
答案
分五步落地,代码级要点全部用中文注释给出,可直接写白板:
-
创建CustomDeferredRendererData
继承ScriptableRendererData,在Create()里返回新的CustomDeferredRenderer实例,并暴露G-Buffer格式、Tile大小、最大光源数三个序列化字段,方便策划调画质。 -
注入GBufferPass
在CustomDeferredRenderer构造函数里Enqueue三个Pass:- GBufferPass:继承ScriptableRenderPass,ConfigureTarget四张RTHandle(albedoRT、normalRT、…),Clear=All,ColorStoreAction=Store;
- TileLightCullingPass:ComputeShader,线程组(128,128,1),输入DepthPyramid、LightList,输出LightIndexBuffer;
- DeferredLightingPass:Fullscreen Shader(ShaderID="_CustomDeferredLit"),读取G-Buffer+LightIndexBuffer,输出到cameraColorTargetHandle。
-
写Shader
GBuffer Pass使用LightMode="CustomGBuffer",ZWrite=On,ColorMask RGBA 0 1 2 3;
Deferred Lit Pass使用HLSLPROGRAM,#pragma multi_compile _ _ADDITIONAL_LIGHTS,在Tile内循环min(64, lightCount)次,BRDF用DisneyDiffuse + GGX,支持ShadowMask。 -
兼容性处理
在OnCameraSetup里判断cameraData.renderType==Game才走Deferred,SceneView仍用Forward,防止编辑器材质预览全黑;
如果useNativeRenderPass=true(iOS),显式调用cmd.SetRenderAttachment(depthId, 0),否则Metal会隐式Resolve导致带宽翻倍。 -
性能验证
小米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评分无掉帧投诉”,面试官基本就过了。
拓展思考
-
** clustered forward vs. tile deferred**:在Quest3这类Tile-based GPU上,clustered forward反而更省带宽,因为G-Buffer写入会被on-chip tile memory回写放大;如何动态切换两条路径?
答:在Runtime检测SystemInfo.graphicsMemorySize<4 GB且mobile=true时,RendererFeature自动Disable GBufferPass,Enable ClusteredForwardPass,走Froxel 3D贴图。 -
Unity 2023.3已经放出URP Deferred实验包,但只支持PC/Console,且不开源Tile Culling。如果项目要2025年上线,是否还自研?
答:国内版号窗口短,Unity官方Deferred大概率2025 Q2才稳定,且XR支持不完整;自研路径可控、可热更、可裁剪,ROI更高。 -
WebGL2.0没有Compute Shader,如何把Tile Culling跑起来?
答:用Fragment Shader做Z-Buffer Reduce,Ping-Pong两张1024×1024 R16F贴图,4 Pass后得到64×64 Hi-Z,然后CPU端Culling生成Light Index Texture,GPU再采样;实测Chrome 119上128盏光源耗时2.1 ms,WebGL可行。
把这三点答完,面试官会主动问你期望Base,面试已经反向筛选了。