白泽图

  • 文章
    • Unity渲染
    • Unity项目开发
    • 工具
    • 数学
    • 算法
    • 网站搭建
    • 网络&操作系统
蒋程个人博客
互联网技术经验总结&分享
  1. 首页
  2. Unity渲染
  3. 正文

URP自定义后期实现方法

2021-01-11 2877点热度 1人点赞 0条评论

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

标签: 暂无
最后更新:2021-01-12

蒋程

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

COPYRIGHT © 2023 白泽图. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

登录
注册|忘记密码?