深入理解 URP 渲染管线的可编程注入点,避免采样黑屏与时序错误,掌握自定义 Pass 的正确姿势
什么是 ScriptableRenderFeature?
ScriptableRenderFeature 是 Unity URP(Universal Render Pipeline)提供的核心扩展机制。它允许开发者在渲染管线的任意阶段插入自定义的渲染逻辑,实现诸如后处理效果、深度/法线抓取、自定义光照等高级功能。
核心概念
ScriptableRenderFeature =功能定义(何时执行?)+ ScriptableRenderPass =执行逻辑(做什么?)。二者配合使用,构成了 URP 渲染管线的插件系统。
URP 渲染管线阶段一览
注入点资源可用性对照表
不同的注入点对渲染资源的可用性各不相同。以下表格详细列出了各注入点可以安全访问的纹理资源:
| 注入点 (RenderPassEvent) | 深度图 _CameraDepthTexture | 颜色图(Opaque) _CameraOpaqueTexture | 颜色图(Transparent) |
|---|---|---|---|
| BeforeRenderingOpaques | ✓ 已完成 | ✓ 已完成 | ✗ 未写入 |
| AfterRenderingOpaques | ✓ 已完成 | ✓ 已完成 | ✗ 未写入 |
| BeforeRenderingTransparents | ✓ 已完成 | ✓ 已完成 | △ 部分可用 |
| AfterRenderingTransparents | ✓ 已完成 | ✓ 已完成 | ✓ 已完成 |
| BeforeRenderingPostProcessing | ✓ 已完成 | ✓ 已完成 | ✓ 已完成 |
| AfterRenderingPostProcessing | ✓ 已完成 | ✓ 已完成 | ✓ 已完成 |
关键提醒
深度图和颜色图的抓取需要在 UniversalRenderer Data 资产中启用相应选项。默认情况下,这些纹理可能不会被生成。
完整代码示例:创建自定义 Render Feature
第一步:定义 ScriptableRenderPass
using UnityEngine.Rendering.Universal; using UnityEngine.Rendering; public class CustomRenderPass : ScriptableRenderPass { // Pass 运行时数据 private RenderTargetIdentifier tempRT; private Material customMaterial; // 设置材质和其他资源 public void Setup(Material mat) { customMaterial = mat; } // 核心执行逻辑 public override void Execute( ScriptableRenderContext context, ref RenderingData<Rendering cameraTargetSettings> renderingData) { if (customMaterial == null) return; var cmd = new CommandBuffer(); cmd.name = "Custom Effect Pass"; // 在此处添加渲染逻辑 // 例如:Blit、绘制全屏Quad等 context.ExecuteCommandBuffer(cmd); cmd.Release(); } }第二步:创建 ScriptableRenderFeature
using UnityEngine; using UnityEngine.Rendering.Universal; public class CustomRenderFeature : ScriptableRendererFeature { // ★ 核心:选择正确的注入点 // 可选:BeforeRenderingOpaques / AfterRenderingOpaques / // BeforeRenderingTransparents / AfterRenderingTransparents / // BeforeRenderingPostProcessing / AfterRenderingPostProcessing public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingOpaques; private CustomRenderPass customPass; public Material customMaterial; // 初始化 Pass 实例 public override void Create() { customPass = new CustomRenderPass(); // 设置 Pass 的注入点和渲染优先级 customPass.renderPassEvent = passEvent; } // 将 Pass 添加到渲染管线 public override void AddRenderPasses( ScriptableRenderer renderer, ref RenderingData renderingData) { // 配置 Pass 并入队 customPass.Setup(customMaterial); renderer.EnqueuePass(customPass); } }第三步:在 Pass 中正确采样纹理
public override void Execute( ScriptableRenderContext context, ref RenderingData renderingData) { var cameraData = renderingData.cameraData; var cmd = new CommandBuffer(); // ===================================== // ★ 核心:安全采样渲染资源 // ===================================== // 1. 采样深度图(如果需要) // 在 AfterRenderingOpaques 时深度图已可用 if (cameraData.camera.depthTextureMode.HasFlag(DepthTextureMode.Depth)) { var depthTex = Shader.GetGlobalTexture("_CameraDepthTexture"); cmd.SetGlobalTexture("_MyDepthTex", depthTex); } // 2. 采样不透明颜色图(如果需要) // 使用 _CameraOpaqueTexture 采样不透明物体渲染结果 var opaqueTex = Shader.GetGlobalTexture("_CameraOpaqueTexture"); cmd.SetGlobalTexture("_MyOpaqueTex", opaqueTex); // 3. 应用自定义效果(使用 Blit) var tempRT = RenderTexture.GetTemporary( cameraData.camera.pixelWidth, cameraData.camera.pixelHeight, 0, RenderTextureFormat.Default ); cmd.Blit(RenderTargetIdentifier.None, tempRT, customMaterial); cmd.Blit(tempRT, cameraData.targetTexture); RenderTexture.ReleaseTemporary(tempRT); context.ExecuteCommandBuffer(cmd); cmd.Release(); }常见错误与解决方案
问题一:在透明物体 Pass 中采样透明颜色图得到黑屏
错误代码位置:在BeforeRenderingTransparents或更早的位置尝试采样透明物体的渲染结果。
原因:透明物体的颜色缓冲在BeforeRenderingTransparents时还未被写入,此时读取只能得到黑屏或旧数据。
解决方案
将采样逻辑移到AfterRenderingTransparents注入点:
class TransparentGrabPass : ScriptableRenderPass { public override void Execute(ScriptableRenderContext context, Ref<RenderingData> renderingData) { // ✅ 在 AfterRenderingTransparents 时可以安全采样 var cmd = new CommandBuffer(); var transparentTex = RenderingUtils.GetTransparentTexture(renderingData); // 现在 transparentTex 包含正确的透明物体渲染结果 } }问题二:在 Opaque 渲染前采样颜色图获得的是上一帧数据
错误理解:以为BeforeRenderingOpaques时颜色图已经是当前帧的内容。
实际原因:颜色图是在上一帧的AfterRenderingOpaques阶段写入的,并非当前帧。
解决方案
如果需要当前帧的不透明物体结果,在AfterRenderingOpaques之后使用:
AfterRenderingOpaques- 采样当前帧不透明结果(推荐)BeforeRenderingTransparents- 同样可用AfterRenderingTransparents- 全部渲染完成后
问题三:深度图在某些设备上不可用
表现:在 Editor 中正常,但在某些 Android/iOS 设备上出现黑屏或深度效果错误。
原因:未在 Renderer Data 中正确配置深度纹理,或设备不支持所需格式。
解决方案
在 URP Asset 中启用 Depth Texture 选项:
// 在 Pass 中检查深度图可用性 public override void Execute(ScriptableRenderContext context, Ref<RenderingData> renderingData) { var cameraData = renderingData.cameraData; if (!cameraData.camera.depthTextureMode..HasFlag(DepthTextureMode.Depth)) { Debug.LogWarning("Depth texture not available! Enable it in Renderer Data."); return; } // 安全使用深度图 var depthTex = Shader.GetGlobalTexture("_CameraDepthTexture"); }最佳实践总结
Opaque 物体效果
深度图可用
颜色图(Opaque)可用
颜色图(Transparent)不可用
使用注入点:AfterRenderingOpaques
透明物体效果
深度图可用
颜色图(Opaque)可用
颜色图(Transparent)可用
使用注入点:AfterRenderingTransparents
全场景后处理
深度图可用
颜色图(Opaque)可用
颜色图(Transparent)可用
使用注入点:Before/AfterRenderingPostProcessing
延迟写入
所有纹理可用
最终合成的完整画面
无法再修改透明物体
使用注入点:AfterRenderingPostProcessing