Unity 特效扭曲效果(使用command buffer實現)

瞭解command buffer

CommandBuffer攜帶一系列的渲染命令,可以指定在相機渲染的某個點執行本身命令(包括特殊渲染,保存當前rendertexture等)

CommandBuffer可以使用的點看這裏(官網文檔

具體渲染流程和觸發點如下

 

特效扭曲的實現原理以及方案

原理:就是抓取特效後面渲染的紋理進行擾動

方案1:抓取特效後面渲染的紋理一般用grabpass,根據grabpass進行擾動

方案2:利用command buffer在渲染天空盒之後保存一張屏幕紋理(特效渲染之前的紋理),然後抓取對應位置的紋理進行擾動

方案1總結:很多手機不兼容,而且性能很一般,所以手遊暫時不可能使用

方案2總結:性能可以,有個層次問題,如果扭曲的特效在物體後面,擾動會出現前面的物體,不過爲了性能我覺得問題不大,也能達到一定效果

下面效果圖就是使用方案2

代碼實現

代碼實現難度不高

保存AfterSkybox渲染之後的紋理,然後設置全局紋理

        m_AfterSkyboxCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, m_AfterSkyboxTex);
        //設置攝像機觸發commandbuffer時機
        m_Camera.AddCommandBuffer(CameraEvent.AfterSkybox, m_AfterSkyboxCommandBuffer);
        //設置shader全局圖片,方便給扭曲效果用
        Shader.SetGlobalTexture(m_AfterSkyboxTexId, m_AfterSkyboxTex);

全局紋理的聲明

    /// <summary>
    /// 在天空盒渲染之後保存的圖片id(就是特效渲染之前)
    /// </summary>
    private static int m_AfterSkyboxTexId = Shader.PropertyToID("_AfterSkyboxTex");

shader全局紋理使用,直接shader裏面這麼聲明就可以使用

sampler2D _AfterSkyboxTex;

工程比較簡單直接放代碼,如果有需要後方再放工程

using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// Pieken 2020
/// </summary>
public class RenderMgr : MonoBehaviour
{
    /// <summary>
    /// 當前攝像機
    /// </summary>
    private Camera m_Camera;

    /// <summary>
    /// 屏幕波紋扭曲 演示屏幕後處理怎麼寫,功能是有
    /// </summary>
    public Material ScreenWaveDistortion;

    /// <summary>
    /// 在天空盒渲染之後保存的圖片id(就是特效渲染之前)
    /// </summary>
    private static int m_AfterSkyboxTexId = Shader.PropertyToID("_AfterSkyboxTex");

    /// <summary>
    /// 在天空盒渲染之後保存的圖片(就是特效渲染之前)
    /// </summary>
    private RenderTexture m_AfterSkyboxTex;

    /// <summary>
    /// 當前攝像機渲染最終的圖片
    /// </summary>
    private RenderTexture m_CameraRenderTex;

    /// <summary>
    /// 在渲染天空盒之後的commandbuff指令
    /// </summary>
    private CommandBuffer m_AfterSkyboxCommandBuffer;

    private void Start()
    {
        m_Camera = GetComponent<Camera>();
        Init();
    }

    private void Init()
    {
        //獲取一張紋理
        m_AfterSkyboxTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.Default);
        m_AfterSkyboxTex.name = "AfterSkyboxTex";
        //新建一個commandbuffer
        m_AfterSkyboxCommandBuffer = new CommandBuffer();
        m_AfterSkyboxCommandBuffer.name = "AfterSkyBox_CommandBuffer";
        //buffer之類把當前渲染出來的圖片保存到m_AfterSkyboxTex
        m_AfterSkyboxCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, m_AfterSkyboxTex);
        //設置攝像機觸發commandbuffer時機
        m_Camera.AddCommandBuffer(CameraEvent.AfterSkybox, m_AfterSkyboxCommandBuffer);
        //設置shader全局圖片,方便給扭曲效果用
        Shader.SetGlobalTexture(m_AfterSkyboxTexId, m_AfterSkyboxTex);
    }

    private void OnPreRender()
    {
        if (QualitySettings.antiAliasing == 0)
        {
            m_CameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default);
        }
        else
        {
            m_CameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default, QualitySettings.antiAliasing);
        }
        m_Camera.targetTexture = m_CameraRenderTex;
    }

    private void OnPostRender()
    {
        m_Camera.targetTexture = null;
        //測試後處理,屏幕波紋扭曲 正常這麼寫Graphics.Blit(m_CameraRenderTex, null as RenderTexture);
        Graphics.Blit(m_CameraRenderTex, null as RenderTexture, ScreenWaveDistortion);
        RenderTexture.ReleaseTemporary(m_CameraRenderTex);
    }

    /// <summary>
    /// 刪除CommandBuffer相關
    /// </summary>
    private void DestroyCommandBuffer()
    {
        Destroy(m_AfterSkyboxTex);
        if (m_AfterSkyboxCommandBuffer != null)
        {
            m_Camera.RemoveCommandBuffer(CameraEvent.AfterSkybox, m_AfterSkyboxCommandBuffer);
            m_AfterSkyboxCommandBuffer.Dispose();
            m_AfterSkyboxCommandBuffer = null;
        }
    }

    private void OnDestroy()
    {
        DestroyCommandBuffer();
    }
}

扭曲使用的shader

Shader "Effect/Particle/Distortion/Add"
{
	Properties
	{
		_MainTex("Particle Texture", 2D) = "white" {}
		//噪音圖
		_Noise("Noise", 2D) = "white" {}
		_distortFactorTime("FactorTime",Range(0,5)) = 0.5
		_distortFactor("factor",Range(0.04,1)) = 0
	}
	SubShader
	{
		Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" }
		Blend SrcAlpha One
		LOD 100
		Cull Off Lighting Off ZWrite Off
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0
			#pragma multi_compile_particles
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float4 grabPos:TEXCOORD1;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _Noise;
			float4 _Noise_ST;
			fixed _distortFactorTime;
			fixed _distortFactor;
			sampler2D _AfterSkyboxTex;
			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.grabPos = ComputeGrabScreenPos(o.vertex);
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				float2 tempuv = i.uv + _Time.xy*_distortFactorTime;
				fixed4 col = tex2D(_Noise, tempuv);
				i.grabPos.xy += col.xy*_distortFactor;
				half4 color = tex2D(_MainTex, i.uv) * 2;
				half4 bgcolor = tex2Dproj(_AfterSkyboxTex, i.grabPos) * 0.5;
				color.a = saturate(color.a);
				bgcolor.a = color.a;
				return bgcolor * color;
			}
			ENDCG
		}
	}
}

總結

這種使用commandbuffer獲取一張紋理進行扭曲,雖然層次會出現問題,但是性能可以也能達到一定效果

下面是幾年前崩壞3技術分享的原話

我們在崩壞3的場景中較多的使用了屏幕扭曲效果,比如刀劍的拖尾特效,時空斷裂效果,水流瀑布及其他場景效果。在渲染扭曲效果的過程中,我們使用3個通道來存儲扭曲的渲染結果,兩個用於存儲uv偏移,另一個用於存儲扭曲強度mask,扭曲強度mask用於執行深度剪裁和基於距離的強度控制。使用單獨的pass渲染扭曲結果到幀緩衝紋理對於移動平臺來說開銷較大,所以我們在最終的後處理中整合應用了扭曲效果,相比前者要快很多。 但這種方法也可能導致靠前面的物體由於沒有分層處理而混入後面扭曲材質的問題,不過考慮到移動平臺的性能限制,相對於整體效果而言這種妥協是值得的。

 

最近研究開放世界場景加載,應該一段時間不寫博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章