01渲染管线总览Built-in vs URP/HDRPUnity 渲染管线历史上经历了两个重要时代。早期的Built-in Render Pipeline内置管线也称 Legacy Pipeline是随 Unity 3/4/5 时代共同成长的老一代渲染架构功能齐全但定制空间有限。2018 年起Unity 引入了基于Scriptable Render PipelineSRP的全新框架官方同时推出了面向移动/主机的轻量化方案 URPUniversal RP和面向高端机器的 HDRPHigh Definition RP。维度Built-in PipelineURPHDRP正式名称Legacy / Built-in RPUniversal Render PipelineHigh Definition RP主要定位全平台通用老项目移动、主机、PC 中低端PC、主机高端写实Shader 语言CG / HLSLSurface ShaderHLSLShaderLab URP库HLSLShaderLab HDRP库默认光照模型Forward / Deferred 可选ForwardURP 12 支持 DeferredDeferred 为主SRP Batcher不支持原生支持原生支持多 Pass Shader完整支持受限见第5章受限后处理系统Post Processing Stack v2独立包内置 Volume 框架内置 Volume 框架自定义渲染特性Camera Render / CommandBufferScriptableRendererFeatureCustom Pass更细粒度关键认知URP 和 HDRP 本质上是 SRP 框架的两种官方实现。SRP 框架允许开发者完全自定义 GPU 命令提交顺序而 Built-in 管线的渲染逻辑深埋在 C 引擎层可定制程度低。02架构差异Forward、Deferred 与 SRP Batcher2.1 Forward Rendering前向渲染前向渲染是最直白的渲染路径每个物体对每盏灯光都执行一次完整的顶点片元着色结果直接写入帧缓冲。复杂度是O(物体数 × 灯光数)灯光多时性能急剧下降。Built-in 的 Forward 路径通过ForwardAddPass 为每盏附加光再走一遍 ShaderURP 则将所有实时灯数据打包进常量缓冲CBUFFER使用UniversalForward单 Pass 一次性计算。2.2 Deferred Rendering延迟渲染延迟渲染先把几何信息写入多个G-Buffer几何缓冲区再在屏幕空间一次性完成所有光照计算彻底解耦了物体数与灯光数的性能耦合。复杂度降为O(物体数 灯光数)但无法支持 MSAA 且半透明物体仍需前向渲染。URP 12 开始支持 Deferred 路径HDRP 默认使用 Deferred。2.3 SRP Batcher渲染批次的革命Built-in 管线的一大性能瓶颈在于SetPass Call——每次切换材质CPU 都要重新上传 Shader 参数产生昂贵的 GPU 状态切换。SRP Batcher 通过以下机制彻底改变了这一现状⚡SRP Batcher 核心思想SRP Batcher 不减少 Draw Call 数量而是将所有使用同一 Shader Variant 的物体的材质属性Per-Material 数据和物体变换矩阵Per-Object 数据各自打包进专用的持久化 GPU 缓冲区CBUFFER。一旦数据已在 GPU 上后续帧无需重复上传大幅降低 CPU 开销。Shader Custom/SRPBatcherDemo { Properties { _BaseColor (Base Color, Color) (1,1,1,1) _BaseMap (Base Map, 2D) white {} } SubShader { HLSLPROGRAM // ✅ SRP Batcher 要求所有 per-material 属性必须在统一 CBUFFER 中声明 CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _BaseMap_ST; // 纹理 ST 也必须在此 CBUFFER 内 CBUFFER_END // ❌ 错误示例在 CBUFFER 外声明属性会破坏 SRP Batcher 兼容性 // float4 _SomeValue; ← 这行会让 SRP Batcher 标红 ENDHLSL } }特性Built-in Dynamic BatchingGPU InstancingSRP Batcher减少内容Draw CallDraw CallSetPass CallCPU 开销要求顶点数 900相同材质相同 Mesh 材质相同 Shader VariantCBUFFER 声明规范多 Pass 兼容受限受限第一个 Pass 才兼容动态合批是否是Shader 层面03SubShader 与多 Pass 执行顺序3.1 SubShader 选择机制当一个物体需要渲染时Unity 依次遍历 Shader 中所有SubShader块选中第一个满足当前平台硬件能力Tags LOD的 SubShader 执行。Shader Custom/PipelineSelect { // SubShader 0高端平台支持几何着色器 / SM5.0 SubShader { Tags { RenderPipelineUniversalPipeline RenderTypeOpaque } LOD 300 // URP Pass... } // SubShader 1Built-in 管线前向渲染 SubShader { Tags { RenderTypeOpaque } LOD 200 // Built-in Pass... } // Fallback最低要求任何平台均可执行 Fallback Diffuse } // Unity 从 SubShader 0 开始选中第一个满足平台能力和 LOD 要求的3.2 多 Pass 在 Built-in 中的执行顺序在 Built-in 管线中一个 SubShader 内的多个 Pass 按从上到下、顺序执行的原则运行。每个 Pass 会独立完成一次完整的绘制并可通过ZTest、Blend、Stencil等状态控制写入目标。Built-in 多 Pass 执行时序单物体Pass #0Vertex→Fragment→Write Depth ColorPass #1Vertex→Fragment→Blend over Pass#0Pass #2 …依序执行各自独立的渲染状态⚠️注意顺序影响结果Pass 的执行顺序直接影响Blend、Stencil的累积结果。例如先写深度再做描边是常见模式顺序颠倒会导致描边被遮挡。3.3 Pass 的 LightMode 如何影响执行时机除了物体渲染时顺序执行的逻辑每个 Pass 还通过Tags { LightMode ... }声明自己属于哪个渲染阶段。渲染管线会在特定阶段主动查找并调用对应 LightMode 的 Pass而不是简单从头跑到尾。SubShader { // Pass A主光照 —— 前向渲染阶段被调用 Pass { Tags { LightMode UniversalForward } // HLSL 光照计算... } // Pass B阴影投射 —— Shadow Map 阶段被调用与 A 独立 Pass { Tags { LightMode ShadowCaster } // 输出深度... } // Pass C深度预写 —— Depth Prepass 阶段被调用 Pass { Tags { LightMode DepthOnly } // 仅输出深度不写颜色... } } // 三个 Pass 分属不同渲染阶段由管线按需调用互不干扰04LightMode Tag 完全手册LightModeTag 是管线与 Shader Pass 之间的约定协议 管线在每个渲染阶段扫描场景中所有材质的 Pass只执行匹配当前阶段 LightMode 的那个 Pass。4.1 Built-in 管线常用 LightModeForwardBase前向基础 Pass处理场景中的主方向光Directional Light、环境光Ambient和所有 Baked 阴影。每个物体只执行一次。Built-in 专用ForwardAdd前向附加 Pass每盏实时点光、聚光灯都触发一次此 Pass叠加到 ForwardBase 结果上。灯越多Draw Call 越高。Built-in 专用ShadowCaster投影 Pass将物体深度信息写入 Shadow Map。Built-in 和 URP/HDRP 通用可在此 Pass 中实现 Alpha 裁剪阴影。通用Deferred延迟渲染 Pass将 Albedo / Normal / 金属度 / 粗糙度等信息写入 G-Buffer供后续 Lighting Pass 读取。Built-in DeferredPrepassBase旧式延迟遗留旧版 Legacy Deferred已废弃的法线/深度预处理 Pass仅在 Legacy Deferred 路径中使用。已废弃Always始终执行不受渲染路径影响无论何种管线都会执行。常用于特效、调试或需要无条件写入某 Buffer 的场景。通用4.2 URP 专属 LightMode TagUniversalForwardURP 主光照 PassURP 前向渲染阶段的核心 Pass处理所有实时灯光主平行光 多盏点光/聚光灯打包在一起。每个物体仅执行第一个带此 Tag 的 Pass。URP 专用UniversalForwardOnly仅前向执行URP 12 引入在 Deferred 路径下依然强制走前向渲染。适合不支持 G-Buffer 写入的特殊材质如半透明。URP 12ShadowCaster阴影投射 Pass同 Built-in写入 Shadow Map。在 URP 中可复用也支持 _ALPHATEST_ON 关键字驱动的 Alpha Cutout 阴影。通用DepthOnly深度预写 PassURP Depth Prepass 阶段使用仅写入深度缓冲不写颜色。为后续 SSAO、Depth of Field 等后处理提供 _CameraDepthTexture。URP 专用DepthNormalsOnly深度法线预写URP 14 新增同时写入深度和视空间法线到 _CameraDepthNormalsTexture供 SSAO 等使用比 DepthOnly 携带更多信息。URP 14Universal2D2D 渲染 Pass配合 2D Renderer 使用URP 2D 渲染器在 Sprite 渲染阶段调用此 Pass。3D 项目几乎用不到。URP 2DSubShader { Tags { RenderPipeline UniversalPipeline RenderType Opaque Queue Geometry } // ── Pass 1主渲染 ────────────────────────────── Pass { Name URPForward Tags { LightMode UniversalForward } // ✅ URP 主光照计算 } // ── Pass 2阴影投射 ──────────────────────────── Pass { Name ShadowCaster Tags { LightMode ShadowCaster } ZWrite On ZTest LEqual ColorMask 0 // 不写颜色 // 写深度到 Shadow Map } // ── Pass 3深度预写 ───────────────────────────── Pass { Name DepthOnly Tags { LightMode DepthOnly } ZWrite On ColorMask 0 // 仅写 _CameraDepthTexture后处理 SSAO/DOF 需要 } }✅LightMode 匹配规则当 Pass 没有声明 LightMode Tag 时默认值为Always。在 URP 中没有 LightMode 或 LightMode 不被识别的 Pass 会被直接忽略不会执行。05URP 多 Pass 限制与解决方案5.1 核心限制URP 默认只执行第一个 UniversalForward Pass这是 URP 与 Built-in 管线最容易踩坑的差异之一。在 Built-in 管线中一个 SubShader 中的多个 Pass 会依序全部执行而 URP 的渲染器ForwardRenderer / UniversalRenderer在处理不透明物体时只会为每个物体调用第一个标记为UniversalForward的 Pass。SubShader { Tags { RenderPipeline UniversalPipeline } // ✅ Pass #0 —— URP 会执行这个 Pass { Tags { LightMode UniversalForward } // 主渲染逻辑... } // ❌ Pass #1 —— URP 会直接跳过同类型第二个 Pass 不执行 Pass { Tags { LightMode UniversalForward } // 描边逻辑在 URP 中永远不会运行 Cull Front // ... } } // 解决方案使用 ScriptableRendererFeature 替代第二个 UniversalForward Pass为什么 URP 要这样设计URP 引入SRP Batcher时要求每个物体在一次 Draw Call 中处理完所有光照天然与多 Pass 分多次绘制相矛盾。 同时URP 的ScriptableRenderPass系统将附加效果的职责从 Shader Pass 转移到了ScriptableRendererFeature 让多 Pass 效果以 Render Feature 的形式由 CPU 侧调度而非硬塞在同一个 Shader 里。5.2 各 Pass 类型在 URP 中的执行情况Pass LightModeBuilt-in 中URP 中备注UniversalForward不识别执行仅第一个多个只执行 index0ShadowCaster执行执行阴影阶段独立调用DepthOnly不识别执行Depth Prepass 阶段DepthNormalsOnly不识别执行URP 14提供法线信息给 SSAOForwardBase执行忽略URP 不认 Built-in TagForwardAdd执行忽略URP 不使用逐灯 Pass无 Tag / Always执行忽略URP 严格按 LightMode 匹配5.3 解决方案如何在 URP 中实现多 Pass 效果方案一使用 ScriptableRendererFeature推荐将附加效果描边、X光、毛发 shell layer拆出为独立的ScriptableRenderPass 以ScriptableRendererFeature注入到 URP Renderer 的特定阶段BeforeRenderingOpaques、AfterRenderingOpaques 等。 这是 URP 官方推荐的扩展方式与 SRP Batcher 完全兼容。using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; // 1. 继承 ScriptableRendererFeature在 URP Asset 的 Renderer 上挂载 public class OutlineFeature : ScriptableRendererFeature { private OutlineRenderPass _pass; public override void Create() { _pass new OutlineRenderPass(); // 指定此 Pass 注入到哪个渲染阶段 _pass.renderPassEvent RenderPassEvent.AfterRenderingOpaques; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { renderer.EnqueuePass(_pass); } } // 2. 继承 ScriptableRenderPass在 Execute 中用 CommandBuffer 绘制描边 public class OutlineRenderPass : ScriptableRenderPass { public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var cmd CommandBufferPool.Get(Outline Pass); // 在此使用 cmd.DrawRenderer / cmd.DrawMesh 绘制描边 context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }方案二Stencil Buffer 分帧实现描边等效果利用 Stencil 测试第一个UniversalForwardPass 写入 Stencil 值然后通过 Renderer Feature 对全屏 Stencil 区域做后处理实现类似多 Pass 的视觉效果。方案三使用 UsePass 引用其他 Shader 的 PassUsePass指令可以直接引用另一个 Shader 中特定名称的 Pass避免代码重复。注意 Pass 名称需要全大写。SubShader { Tags { RenderPipeline UniversalPipeline } Pass { Name MY_FORWARD // Pass 名称必须全大写才能被 UsePass 引用 Tags { LightMode UniversalForward } // ... } // 直接复用另一个 Shader 中名为 SHADOWCASTER 的 Pass UsePass Universal Render Pipeline/Lit/SHADOWCASTER UsePass Universal Render Pipeline/Lit/DEPTHONLY }方案四材质堆叠Multi-Material Renderer在MeshRenderer组件上挂载多个材质Materials 数组Unity 会为每个 Sub-Mesh 或使用第一个 Sub-Mesh 的情况下为每个材质分别执行 Draw Call等效于多 Pass 效果且完全兼容 SRP Batcher。06实战代码URP 多 Pass Shader 模板下面是一个在 URP 中正确实现不透明 PBR 深度预写 阴影投射三 Pass 组合的完整 Shader 模板 同时满足 SRP Batcher 兼容条件所有 per-material 属性在CBUFFER_START(UnityPerMaterial)中声明。Shader Custom/URPMultiPassTemplate { Properties { _BaseMap (Base Map, 2D) white {} _BaseColor (Base Color, Color) (1, 1, 1, 1) _Metallic (Metallic, Range(0,1)) 0.0 _Smoothness(Smoothness,Range(0,1)) 0.5 _Cutoff (Alpha Cutoff, Range(0,1)) 0.5 } SubShader { Tags { RenderPipeline UniversalPipeline RenderType Opaque Queue Geometry } // ════════════════════════════════════════════════ // PASS 1 —— UniversalForward主光照 // ════════════════════════════════════════════════ Pass { Name URPForward Tags { LightMode UniversalForward } ZWrite On ZTest LEqual Cull Back HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl // ✅ SRP Batcher所有 per-material 属性必须在此 CBUFFER 内 CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _BaseMap_ST; float _Metallic; float _Smoothness; float _Cutoff; CBUFFER_END TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float3 normalWS : TEXCOORD0; float2 uv : TEXCOORD1; float3 positionWS : TEXCOORD2; }; Varyings vert(Attributes IN) { Varyings OUT; VertexPositionInputs posInputs GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs nrmInputs GetVertexNormalInputs(IN.normalOS); OUT.positionCS posInputs.positionCS; OUT.positionWS posInputs.positionWS; OUT.normalWS nrmInputs.normalWS; OUT.uv TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; } half4 frag(Varyings IN) : SV_Target { half4 texColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); half4 baseColor texColor * _BaseColor; InputData inputData (InputData)0; inputData.normalWS normalize(IN.normalWS); inputData.positionWS IN.positionWS; inputData.viewDirectionWS GetWorldSpaceNormalizeViewDir(IN.positionWS); inputData.shadowCoord TransformWorldToShadowCoord(IN.positionWS); SurfaceData surfaceData (SurfaceData)0; surfaceData.albedo baseColor.rgb; surfaceData.metallic _Metallic; surfaceData.smoothness _Smoothness; surfaceData.alpha baseColor.a; surfaceData.normalTS half3(0,0,1); return UniversalFragmentPBR(inputData, surfaceData); } ENDHLSL } // ════════════════════════════════════════════════ // PASS 2 —— ShadowCaster阴影投射 // ════════════════════════════════════════════════ Pass { Name ShadowCaster Tags { LightMode ShadowCaster } ZWrite On ZTest LEqual ColorMask 0 Cull Back HLSLPROGRAM #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment #include Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl ENDHLSL } // ════════════════════════════════════════════════ // PASS 3 —— DepthOnly深度预写 // ════════════════════════════════════════════════ Pass { Name DepthOnly Tags { LightMode DepthOnly } ZWrite On ColorMask 0 Cull Back HLSLPROGRAM #pragma vertex DepthOnlyVertex #pragma fragment DepthOnlyFragment #include Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl ENDHLSL } } FallBack Hidden/Universal Render Pipeline/FallbackError }SRP Batcher 兼容性检查在 Unity Editor 中选中 Shader 资产Inspector 面板最下方会显示SRP Batcher: compatible或列出不兼容原因通常是有属性未放入 UnityPerMaterial CBUFFER。07总结与迁移建议核心要点速查知识点Built-inURP多 Pass 执行全部顺序执行每类 LightMode 仅执行第一个主光照 Pass TagForwardBaseUniversalForward附加光 PassForwardAdd逐灯打包进 UniversalForward无 ForwardAddSRP Batcher不支持原生支持需 CBUFFER 声明规范描边等多 Pass 效果第二个 Pass 直接写改用 ScriptableRendererFeature无 LightMode Pass当作 Always 执行被忽略不执行深度纹理生成Camera DepthTextureModeDepthOnly Pass URP Asset 开启从 Built-in 迁移到 URP 的检查清单迁移 Shader 时需逐项确认① 将所有ForwardBase→UniversalForward删除ForwardAddPass② 所有 per-material 属性放入CBUFFER_START(UnityPerMaterial) ... CBUFFER_END③ 将UnityCG.cginc引用改为Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl④ Surface Shader 无法在 URP 中使用需改写为手写顶点/片元 Shader⑤ 多 Pass 描边/外发光效果改用ScriptableRendererFeature实现⑥ 检查GrabPass用法——URP 不支持改用_CameraOpaqueTexture