欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[Unity Shader]凌波微步效果

程序员文章站 2024-03-22 13:17:22
...

[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 是为了即时将变化在屏幕上显示出来。

我们先看下效果

[Unity Shader]凌波微步效果

我们创建两个材质球,第一个材质球不做任何处理,然后将第二个材质球的Direction变量的X修改为2,将两个物体做个对比观察。

[Unity Shader]凌波微步效果

我们发现物体向右边移动了。接下来我们想要的残影效果还没有,我们使用噪波算法实现随机偏移的效果。

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

[Unity Shader]凌波微步效果

我们看到物体是整体都会被拉伸了,但是我们只需要根据他的移动方向做拉伸就好,也就是他的前进方向做拉伸,背面不做拉伸。怎么做呢?

物体在阳光下会有投影,物体的投影,也就是他的反射光线是可以根据入射光线以及他的法线来算出。这里就可以将他的不做拉伸的背面理解为他的反射光线。

那么我们就将这个反射光线作为参数传入进去

//变换拉伸
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;

一般情况下移动方向是新位置减去之前的位置,但是这样会导致残影优先移动了过去,什么意思呢?就是下面这个情况

[Unity Shader]凌波微步效果

我们的移动方向是向右边,但是残影的方向其实应该是向左边,也就是反过来,这样才是对的。

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

欢迎关注微信公众号:Unity游戏开发笔记
[Unity Shader]凌波微步效果
QQ群:
[Unity Shader]凌波微步效果