前段时间项目升级了使用了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
文章评论