前段时间项目升级了使用了URP后,收到了一些关于后期的BUG,Debug后发现URP改变了渲染管线导致原先BuildIn的一些机制无法使用了,比如OnRenderImage、OnPreRender等函数失效,所以之前BuildIn的写的一些效果都不能用了,于是源看了一下URP源码,也查找网上的一些相关资料,有了个大致的理解,先把使用方式记录下来,之后抽空再写一篇URP源码设计防止遗忘
这里拿项目中的一个实例来说明使用方式,功能描述: 角色XXX释放技能后,除了场上的角色与特效,其他可见物都变成深红色(用于加强角色技能的影响感受)
新建一个工程用于单独实现的URP管线下的LUT效果
以下Shader是一个简单LUT的效果
Shader "Custom/LUT" { Properties { _MainTex("Main Texture", 2D) = "white" {} _Color("Color",Color) = (1,1,1,1) _Brightness("Brightness",Range(1,5)) = 1 } SubShader { Tags{ "Queue" = "Geometry" "RenderType" = "Opaque" "IgnoreProjector" = "True" } Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; fixed4 color : COLOR; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Color; float _Brightness; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } float4 frag(v2f i) : SV_Target { fixed4 mainTexColor = tex2D(_MainTex,i.uv); float step = mainTexColor.r * _Brightness; return fixed4(step, step, step, 1) * _Color; } ENDCG } } Fallback Off }
我们在URP中应用上面的Shader做后期效果需要处理以下几点
一、扩展ScriptableRendererFeature与ScriptableRenderPass
LUTRenderFeature.cs代码
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class LUTRenderFeature : ScriptableRendererFeature { public RenderPassEvent mEvent = RenderPassEvent.AfterRenderingTransparents; private LUTRenderPass mPass; private LUT mLUT; public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { var stack = VolumeManager.instance.stack; mLUT = stack.GetComponent<LUT>(); if (!mLUT.IsActive()) //控制开关 return; var cameraColorTarget = renderer.cameraColorTarget; //设置当前需要后期的画面 mPass.Setup(cameraColorTarget, mLUT); //添加到渲染列表 renderer.EnqueuePass(mPass); } public override void Create() { mPass = new LUTRenderPass(mEvent, Shader.Find("Custom/LUT")); } }
LUTRenderPass.cs代码
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class LUTRenderPass : ScriptableRenderPass { private const string mPostProcessingTag = "Render LUT Effects"; private const string mTempTexName = "Render LUT Temp Texture"; private RenderTargetHandle mTempTex_Handle; private Material mMat; private LUT mLUT; private FilterMode mFilterMode = FilterMode.Bilinear; private RenderTargetIdentifier mSourceRT_Identifier; public LUTRenderPass(RenderPassEvent @event, Shader shader) { this.renderPassEvent = @event; mTempTex_Handle.Init(mTempTexName); mMat = CoreUtils.CreateEngineMaterial(shader); } public void Setup(in RenderTargetIdentifier sourceRT,LUT lut) { mSourceRT_Identifier = sourceRT; mLUT = lut; } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var cmd = CommandBufferPool.Get(mPostProcessingTag); RenderImage(cmd, ref renderingData); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } private void RenderImage(CommandBuffer cmd,ref RenderingData renderingData) { if (mMat == null) { Debug.LogError("LUTRenterPass mMat can not be null!"); return; } mMat.SetColor("_Color", mLUT.mColor.value); mMat.SetFloat("_Brightness", mLUT.mBrightness.value); RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor; //获取临时RT cmd.GetTemporaryRT(mTempTex_Handle.id, opaqueDesc, mFilterMode); //将当前相机的RT经过处理后,存入临时RT Blit(cmd, mSourceRT_Identifier, mTempTex_Handle.Identifier(), mMat, 0); //将处理后的RT赋值给相机RT Blit(cmd, mTempTex_Handle.Identifier(), mSourceRT_Identifier); } public override void FrameCleanup(CommandBuffer cmd) { //释放临时RT cmd.ReleaseTemporaryRT(mTempTex_Handle.id); } }
解说:在URP中ScriptableRendererFeature是URP框架提供给使用者扩展自定义效果的一个方式, 它与ScriptableRenderPass是一对组合:
1. 以上的代码中最核心的点:LUTRenderFeature中的 第26行代码 renderer.EnqueuePass(mPass) ,意思是将自定义的Pass添加到渲染列表中,如果不执行这句话,pass中的Execute函数不会被调用
2. ScriptableRenderPass的LUTRenderPass的 Execute函数 是使用Shader实现最终效果的地方
3. URP本质上对CommandBuffer的一个封装,所以需要使用CommandBuffer去实现最终效果(URP框架封装了CommandBufferPool用于提升效率,注意不要自己new)
4. 需要指定一个Pass的执行时机,即RenderPassEvent,可以和BuildIn中CommandBuffer的使用基本一致
5. 如果中途产生额外开销,pass需要实现FrameCleanup回收
二、上面的代码中的LUT对象里存放的是Shader相关的参数,使用方式如下:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; [Serializable, VolumeComponentMenu("PF/LUT")] public class LUT : VolumeComponent, IPostProcessComponent { public bool mIsEnable = false; public ColorParameter mColor = new ColorParameter(Color.white,true); public ClampedFloatParameter mBrightness = new ClampedFloatParameter(1f, 1f, 5f,true); public bool IsActive() { return mIsEnable && active; } public bool IsTileCompatible() { return false; } }
LUT.cs代码解说如下:
1.[Serializable,VolumeComponentMenu("PF/LUT)]的作用分别为可序列化,和可以用于在GameObject "Global Volume" 中的菜单“AddOverride”按钮添加,具体说明如下(Global Volume 创建方式为 :在Hierarchy栏中右键 Volume\Global Volume,然后选中后在Inspector中点击 New 按钮)
2.参数如ColorParameter,ClampedFloatParameter等定义在VolumeParameter.cs中,我们也可以通过继承VolumeParameter扩展自定义结构,它的作用就是给你写好一套美观的Inspector面板显示,如下图
(当然如果觉得不符合你的审美,可以通过继承VolumeComponentEditor对象在OnInspectorGUI()函数中自己实现,方式和平时写编辑器时自定义Inspector面板显示差不多)
3.继承IPostProcessComponent实现两个接口 是否激活效果与 IsActive() 与 是否兼容平铺 IsTitleCompatible()
三、建立关系以及开关说明
1.选中项目的UniversalRenderPipelineAsset_Renderer.asset,在Inspector面板中点击"Add Renderer Feature"添加LUTRenderFeature
2.添加完成后LUTRenderFeature中的AddRenderPasses函数就会被每帧调用,我们可以通过开关来控制是否将LUTRenderPass添加到渲染本帧的渲染列表中,控制开关就是LUT中的IsActive(),其中active变量指上图LUTRenderFeature前面的激活选项,如果关闭则LUTRenderFeature中的AddRenderPasses函数也不会被执行,所以通常默认选中,代码上我在LUT中额外添加了一个变量mIsEnable来控制效果的开启与关闭,运行时开关代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UISwitch : MonoBehaviour { private LUT mLUT; void Start() { mLUT = UnityEngine.Rendering.VolumeManager.instance.stack.GetComponent<LUT>(); } void OnGUI() { var buttonName = mLUT.mIsEnable ? "关" : "开"; if (GUI.Button(new Rect(0,0,150,50), buttonName)) { mLUT.mIsEnable = !mLUT.mIsEnable; } } }
Demo地址: https://pan.baidu.com/s/1trr5lhjtMjRL44xXbI5oIQ 提取码: iukv
文章评论