[Unity Shader]凌波微步效果

[Unity Shader]凌波微步效果

相信很多人都看過天龍八部,裏面的段譽有一個技能就是凌波微步:移動的時候人先到,衣角跟隨其後。說白了就是移動時有一個殘影跟着他。下面先看下最終效果

在這裏插入圖片描述

下面我們看如何實現上面的效果。

思路:

1.既然需要移動,那麼就需要一個3維(x,y,z三個方向)的數據存儲,同時還需要一個變量用來表示偏移強度。

2.需要一個2d貼圖來做採樣

因此Shader代碼很快就出來了

Shader "QShader/UnlitShader_04_1"
{
	Properties
	{
		_MainTex ("MainTex", 2d) = "white"{}
		_Direction ("Direction", vector) = (0,0,0,1)
	}
	SubShader
	{		
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

            #include "UnityCG.cginc"

			sampler2D _MainTex;
			half4 _Direction;
 			float4 _MainTex_ST;

			struct appdata
			{
				float4 position : POSITION;
				float2 uv : TEXCOORD0;
			};
			
			struct v2f
			{
				float4 position : SV_POSITION;
				float2 uv:TEXCOORD0;
			};
		 
			v2f vert (appdata v)
			{
				v2f o;
				v.position.xyz += _Direction.xyz * _Direction.w;
				o.position = UnityObjectToClipPos(v.position);
				o.uv = TRANSFORM_TEX(v.uv,_MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{ 
				fixed4 col = tex2D(_MainTex,i.uv);
				return col;
			}
			ENDCG
		}
	}
}

注意裏面的 TRANSFORM_TEX 是爲了即時將變化在屏幕上顯示出來。

我們先看下效果

在這裏插入圖片描述

我們創建兩個材質球,第一個材質球不做任何處理,然後將第二個材質球的Direction變量的X修改爲2,將兩個物體做個對比觀察。

在這裏插入圖片描述

我們發現物體向右邊移動了。接下來我們想要的殘影效果還沒有,我們使用噪波算法實現隨機偏移的效果。

//噪波算法
float noise = frac(sin(dot(v.uv.xy, float2(12.9898, 78.233))) * 43758.5453);

在這裏插入圖片描述

我們看到物體是整體都會被拉伸了,但是我們只需要根據他的移動方向做拉伸就好,也就是他的前進方向做拉伸,背面不做拉伸。怎麼做呢?

物體在陽光下會有投影,物體的投影,也就是他的反射光線是可以根據入射光線以及他的法線來算出。這裏就可以將他的不做拉伸的背面理解爲他的反射光線。

那麼我們就將這個反射光線作爲參數傳入進去

//變換拉伸
fixed NdotD = max(0,dot(_Direction,v.normal));
			v2f vert (appdata v)
			{
				v2f o;	 
				//噪波算法
				float noise = frac(sin(dot(v.uv.xy, float2(12.9898, 78.233))) * 43758.5453);
				//變換拉伸
				fixed NdotD = max(0,dot(_Direction,v.normal));
				v.position.xyz += _Direction.xyz * _Direction.w * noise * NdotD;
				o.position = UnityObjectToClipPos(v.position);
				o.uv = TRANSFORM_TEX(v.uv,_MainTex);
				return o;
			}

實際就如上所示。至此完整的Shader代碼已經出來了。我們增加了一個Color變量用來在貼圖上面添加一個好看的顏色,這裏僅是爲了美觀,可以去掉。

Shader "QShader/UnlitShader_04_2"
{
	Properties
	{
		_Color ("Color",Color) = (0,0,0,1)
		_MainTex ("MainTex", 2d) = "white"{}
		_Direction ("Direction", vector) = (0,0,0,1)
	}
	SubShader
	{		
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

            #include "UnityCG.cginc"

			sampler2D _MainTex;
			half4 _Direction;
 			float4 _MainTex_ST;
			float4 _Color;

			struct appdata
			{
				float4 position : POSITION;
				float2 uv : TEXCOORD0;
				half3 normal:NORMAL;
			};
			
			struct v2f
			{
				float4 position : SV_POSITION;
				float2 uv:TEXCOORD0;
			};
		 
			v2f vert (appdata v)
			{
				v2f o;	 
				//噪波算法
				float noise = frac(sin(dot(v.uv.xy, float2(12.9898, 78.233))) * 43758.5453);
				//變換拉伸
				fixed NdotD = max(0,dot(_Direction,v.normal));
				v.position.xyz += _Direction.xyz * _Direction.w * noise * NdotD;
				o.position = UnityObjectToClipPos(v.position);
				o.uv = TRANSFORM_TEX(v.uv,_MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{ 
				fixed4 col = tex2D(_MainTex,i.uv);
				col+=_Color;
				return col;
			}
			ENDCG
		}
	}
}

這個時候我們需要一個腳本文件來將物體移動的方向作爲參數傳給Shader的Direction變量,用來動態顯示殘影。因此新建AfterglowEffect.cs代碼如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AfterglowEffect : MonoBehaviour {

    private Material[] mats;
    private Vector3 prePosition;
    private Vector3 curPosition;
    private float deltaTime;

    // Use this for initialization
    void Start()
    {
        prePosition = curPosition = transform.position;
        Renderer[] renderers = transform.GetComponentsInChildren<Renderer>();
        mats = new Material[renderers.Length];
        for (int i = 0; i < renderers.Length; i++)
        {
            Renderer renderer = renderers[i];
            mats[i] = renderer.sharedMaterial;
        }
    }

    // Update is called once per frame
    void Update()
    {
        curPosition = transform.position;

        if (curPosition == prePosition)
        {
            deltaTime = 0;
            return;
        }

        deltaTime += Time.deltaTime;
        prePosition = Vector3.Lerp(prePosition,curPosition,deltaTime);

        Vector3 direction = prePosition- curPosition;
        for (int i = 0; i < mats.Length; i++)
        {
            mats[i].SetVector("_Direction", new Vector4(direction.x, direction.y, direction.z, mats[i].GetVector("_Direction").w));
        }
    }
}

這裏有兩個需要注意的地方

prePosition = Vector3.Lerp(prePosition,curPosition,deltaTime);

我們根據之前的位置和當前的位置通過Lerp函數做插值,動態傳入就讓殘影移動的比較平滑。還有一個要注意的是我們的移動方向

Vector3 direction = prePosition- curPosition;

一般情況下移動方向是新位置減去之前的位置,但是這樣會導致殘影優先移動了過去,什麼意思呢?就是下面這個情況

在這裏插入圖片描述

我們的移動方向是向右邊,但是殘影的方向其實應該是向左邊,也就是反過來,這樣纔是對的。

參考:https://connect.unity.com/p/shaderan-li-ding-dian-yun-dong-mo-hu

歡迎關注微信公衆號:Unity遊戲開發筆記
在這裏插入圖片描述
QQ羣:
在這裏插入圖片描述

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