![[Unity] URP Renderer Feature之基类封装](http://pic.xiahunao.cn/yaotu/[Unity] URP Renderer Feature之基类封装)
什么是Renderer Feature在unity urp中官方提供Renderer Feature方便我们自定义Feature添加到渲染管线中而无需重构整个渲染管线。我们可以通过feature创建pass实现传值pass提交绘制命令实现各种各样的后处理效果如Bloom、模糊、风格化渲染。本文解决了什么问题编写后处理效果时有部分重复操作故本文将材质检查、Pass 入队、临时纹理管理与全屏绘制等重复初始操作封装为基类供具体效果继承复用以便统一资源管理。目录源码一、Renerer Feature创建、入队与释放Pass1.1 基础属性1.2 Create()Feature创建时初始化函数1.3 CreatePass()1.4 AddRenderPasses(ScriptableRenderer, ref RenderingData)1.5 Dispose()二、Render Pass资源准备与全屏绘制2.1 基础属性2.1.1 ProfilerTag2.2 渲染上下文参数2.2.1 RenderingData2.2.2 ScriptableRenderContext2.3 OnCameraSetup(CommandBuffer, ref RenderingData)2.4 Execute(ScriptableRenderContext context, ref RenderingData renderingData)2.5 BeforeBlit()2.6 Dispose()参考资料源码using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; // 支持性审查、pass挂进渲染队列 public abstract class PostEffectFeatureBase : ScriptableRendererFeature { [SerializeField] protected Material material; [SerializeField] protected RenderPassEvent renderPassEvent RenderPassEvent.BeforeRenderingPostProcessing; private ScriptableRenderPass pass; // 支持性审查 public override void Create() { if (pass is System.IDisposable disposablePass) disposablePass.Dispose(); pass null; if (material null) { Debug.LogWarning($[{GetType().Name}] material is not assigned, effect disabled., this); return; } if (material.shader null || !material.shader.isSupported) { Debug.LogWarning($[{GetType().Name}] shader is not supported on this platform, effect disabled., this); return; } pass CreatePass(); } protected abstract ScriptableRenderPass CreatePass(); public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (pass ! null renderingData.cameraData.cameraType CameraType.Game) renderer.EnqueuePass(pass); } protected override void Dispose(bool disposing) { if (pass is System.IDisposable disposablePass) disposablePass.Dispose(); pass null; } } // 全屏后处理pass每帧逻辑blit框架子类传入职责、逻辑 public abstract class PostEffectPassBase : ScriptableRenderPass, System.IDisposable { protected Material material; private RTHandle temporaryColorTexture; protected abstract string ProfilerTag { get; } protected virtual int ShaderPass 0; // 传材质、设置后处理插队时机 public PostEffectPassBase(Material material, RenderPassEvent renderPassEvent) { this.material material; this.renderPassEvent renderPassEvent; ConfigureInput(ScriptableRenderPassInput.Color); } // 资源初始化 public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { RenderTextureDescriptor descriptor renderingData.cameraData.cameraTargetDescriptor; descriptor.depthBufferBits 0; descriptor.msaaSamples 1; RenderingUtils.ReAllocateIfNeeded(ref temporaryColorTexture, descriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: $_{ProfilerTag}_TemporaryColor); } // 每帧注入时调用的绘制逻辑 public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (material null || temporaryColorTexture null) return; CommandBuffer cmd CommandBufferPool.Get(ProfilerTag); // 取绘制命令 try { BeforeBlit(); // 传入子类材质参数 RTHandle source renderingData.cameraData.renderer.cameraColorTargetHandle; // 读取读取获取当前相机颜色目标 Blitter.BlitCameraTexture(cmd, source, temporaryColorTexture, material, ShaderPass); // 组装绘制命令 Blitter.BlitCameraTexture(cmd, temporaryColorTexture, source); context.ExecuteCommandBuffer(cmd); // 提交给GPU用材质重新绘制原像素 } finally { CommandBufferPool.Release(cmd); // 归还命令给下个pass用 } } // 传入Feature参数 protected virtual void BeforeBlit() { } // 释放Pass长期持有的纹理 // 有virtual允许子类重写释放逻辑重写时需调用 base.Dispose() 释放基类资源 public virtual void Dispose() { temporaryColorTexture?.Release(); temporaryColorTexture null; } }为了方便pass的复用将feature和pass拆开分别管理。一、Renerer Feature创建、入队与释放Pass继承自ScriptableRendererFeature类ScriptableRendererFeature继承于ScriptableObject。ScriptableObject 是 Unity 提供的一个保存数据的类但保存为资产而不是像 MonoBehaviour 那样将数据保存在场景中。1.1 基础属性[SerializeField] protected Material material; [SerializeField] protected RenderPassEvent renderPassEvent RenderPassEvent.BeforeRenderingPostProcessing;职责material提供使用的 Shader 及材质参数具体执行哪个 Shader Pass由ShaderPass索引指定RenderPassEvent。定义在何时机插入渲染管线。1.2 Create()Feature创建时初始化函数public override void Create() { if (pass is System.IDisposable disposablePass) disposablePass.Dispose(); pass null; if (material null) { Debug.LogWarning($[{GetType().Name}] material is not assigned, effect disabled., this); return; } if (material.shader null || !material.shader.isSupported) { Debug.LogWarning($[{GetType().Name}] shader is not supported on this platform, effect disabled., this); return; } pass CreatePass(); }职责重复创建Pass释放旧Pass防止RT资源泄漏负责支持性审查Pass为空、审查保障材质非空、材质对应shader非空、材质对应shader支持在目标系统运行创建Pass。1.3 CreatePass()protected abstract ScriptableRenderPass CreatePass();职责具体Feature实现创建并返回Pass实例。1.4 AddRenderPasses(ScriptableRenderer, ref RenderingData)public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (pass ! null renderingData.cameraData.cameraType CameraType.Game) renderer.EnqueuePass(pass); }职责Pass非空且当前相机类型为Game 可调整时可注入一个/多个Pass。PS避免在此方法中访问相机目标可能尚未分配。1.5 Dispose()protected override void Dispose(bool disposing) { if (pass is System.IDisposable disposablePass) disposablePass.Dispose(); pass null; }职责调用Pass 的Dispose()清除对该Pass的引用。Feature创建并持有PassURP会调用Feature的生命周期方法而不会自动调用Pass的自实现因此由他负责通知Pass释放资源。二、Render Pass资源准备与全屏绘制继承ScriptableRenderPass实现System.IDisposable。2.1 基础属性2.1.1 ProfilerTagprotected abstract string ProfilerTag { get; }创建一个ProfilerTag在子类定义如Bloom Effect的标记protected override string ProfilerTag Bloom Effect;方便后续在 CommandBufferPool 中去获取到即可在在 FrameDebugger 中也可以找到它去分析性能。2.2 渲染上下文参数2.2.1 RenderingDatapublic struct RenderingData { internal CommandBuffer commandBuffer; public CullingResults cullResults; public CameraData cameraData; public LightData lightData; public ShadowData shadowData; public PostProcessingData postProcessingData; public bool supportsDynamicBatching; public PerObjectData perObjectData; public bool postProcessingEnabled; }职责当前相机本次渲染所需的上下文数据包括剔除结果、相机、灯光、阴影、后处理及批处理配置供渲染 Pass 使用。2.2.2 ScriptableRenderContext职责当定义一个自定义渲染管线时使用ScriptableRenderContext来安排并将状态更新和绘制命令并提交到GPU。2.3 OnCameraSetup(CommandBuffer, ref RenderingData)public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { RenderTextureDescriptor descriptor renderingData.cameraData.cameraTargetDescriptor; descriptor.depthBufferBits 0; descriptor.msaaSamples 1; RenderingUtils.ReAllocateIfNeeded(ref temporaryColorTexture, descriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: $_{ProfilerTag}_TemporaryColor); }职责配置渲染目标及其清除状态创建临时渲染目标纹理。此方法由渲染器在渲染相机之前调用。2.4 Execute(ScriptableRenderContext context, ref RenderingData renderingData)public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (material null || temporaryColorTexture null) return; CommandBuffer cmd CommandBufferPool.Get(ProfilerTag); // 取绘制命令 try { BeforeBlit(); // 传入子类材质参数 RTHandle source renderingData.cameraData.renderer.cameraColorTargetHandle; // 读取获取当前相机颜色目标 Blitter.BlitCameraTexture(cmd, source, temporaryColorTexture, material, ShaderPass); // 组装绘制命令 Blitter.BlitCameraTexture(cmd, temporaryColorTexture, source); context.ExecuteCommandBuffer(cmd); // 提交给GPU用材质重新绘制原像素 } finally { CommandBufferPool.Release(cmd); // 归还命令给下个pass用 } }职责检查资源材质或临时 RT 不可用时跳过。获取命令缓冲区从池中取出CommandBuffer记录绘制命令。更新材质参数调用BeforeBlit()传入具体效果的参数。处理画面用指定材质和 Shader Pass 处理相机颜色写入临时 RT。写回结果将临时 RT 复制回相机颜色目标避免直接读写同一张纹理。提交与清理向渲染上下文提交命令并通过finally保证命令缓冲区归还池中。2.5 BeforeBlit()protected virtual void BeforeBlit() { }职责子类重写接收Feature传入参数绘制前写入材质2.6 Dispose()public virtual void Dispose() { temporaryColorTexture?.Release(); temporaryColorTexture null; }职责具体清理释放自己持有的 RTHandle 等资源。参考资料Class ScriptableRendererFeature | Universal RP | 12.0.0编写 Scriptable Render Pass | Universal RP | 14.1.0(52 封私信 / 81 条消息) URP系列教程 | 如何使用Scriptable Renderer Feature来自定义后处理效果 - 知乎(52 封私信 / 81 条消息) Unity URP 自定义RendererFeature笔记 - 知乎