上一篇文章(草地的頂點動畫實現風吹草動)中我們通過模型的頂點動畫來模擬了風吹草動的效果。今天我們在給他加上一個交互的效果,就是有角色或者其他物體在草地上走過時,草地會向周圍散開。我們先看下草不動的時候向周圍散開的樣子。
其實實現方法也很簡單,只要向Shader傳遞一下物體的坐標信息,然後在shader中用草的頂點坐標減去傳遞進來的物體坐標就可以得到草每個頂點散開的方向了。然後再通過一個半徑的範圍值來控制交互影響的範圍,當然還是要通過UV的V方向的值來控制根部不動。
v2f vert (appdata v) { v2f o; float4 worldPos = mul(unity_ObjectToWorld, v.vertex); //得到物體和草模型頂點之間的距離 float dis = distance(_PlayerPos, worldPos); //通過影響範圍,還有強度和UV的值來控制草的彎曲強度 float pushDown = saturate((1 - dis + _PushRadius) * v.uv.y * _Strength); //計算出每個頂點散開的方向並做歸一化處理 float3 direction = normalize(worldPos.xyz - _PlayerPos.xyz); //減弱一些y軸向上的影響,否則很多草頂點會穿過地面 direction.y *= 0.5; worldPos.xyz += direction * pushDown; o.pos = mul(UNITY_MATRIX_VP, worldPos); o.uv = v.uv; return o; }
是不是很簡單,接下來我們再把這個加在之前的風吹草動上面就行了。shader代碼如下:
Shader "Custom/GrassVertexAniInteractive" { Properties { _MainTex ("Texture", 2D) = "white" {} _Noise("Noise", 2D) = "black" {} _WindControl("WindControl(x:XSpeed y:YSpeed z:ZSpeed w:windMagnitude)",vector) = (1,1,1,0.5) //前面幾個分量表示在各個軸向上自身擺動的速度, w表示擺動的強度 _WaveControl("WaveControl(x:XSpeed y:YSpeed z:ZSpeed w:worldSize)",vector) = (1,0,1,1) //前面幾個分量表示在各個軸向上風浪的速度, w用來模擬地圖的大小,值越小草擺動的越凌亂,越大擺動的越整體 //_PlayerPos("PlayerPos", vector) = (0,0,0,0) //物體的位置坐標,需要在運行時通過C#代碼傳入,所以這裡注釋掉,把這個參數作為全局控制的參數 _Strength("Strength", float) = 1 //草地彎曲的強度 _PushRadius("PushRadius", float) = 1 //交互的範圍 } SubShader { Tags { "RenderType"="Opaque" } LOD 100
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; };
sampler2D _MainTex; sampler2D _Noise; half4 _WindControl; half4 _WaveControl;
float4 _PlayerPos; half _Strength; half _PushRadius;
v2f vert (appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); //草地自身風吹草動的計算 float4 worldPos = mul(unity_ObjectToWorld, v.vertex); float2 samplePos = worldPos.xz / _WaveControl.w; samplePos += _Time.x * -_WaveControl.xz; fixed waveSample = tex2Dlod(_Noise, float4(samplePos, 0, 0)).r; worldPos.x += sin(waveSample * _WindControl.x) * _WaveControl.x * _WindControl.w * v.uv.y; worldPos.z += sin(waveSample * _WindControl.z) * _WaveControl.z * _WindControl.w * v.uv.y; //草地交互的計算 float dis = distance(_PlayerPos, worldPos); float pushDown = saturate((1 - dis + _PushRadius) * v.uv.y * _Strength); float3 direction = normalize(worldPos.xyz - _PlayerPos.xyz); direction.y *= 0.5; worldPos.xyz += direction * pushDown;
o.pos = mul(UNITY_MATRIX_VP, worldPos); o.uv = v.uv;
return o; }
fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
在C# 代碼中我們只要在Update裡面把物體的位置傳遞給Shader 的_PlayerPos參數就行了
void Update() { playerPos = transform.position; Shader.SetGlobalVector("_PlayerPos" , playerPos); }
最後如果是大面積的草地頂點數量很多的時候,可以寫兩份Shader,一個是帶交互的,一個是不帶交互的。然後把草地分塊再加上Trigger,默認都使用不帶交互的Shader,當角色或物體進入到某塊草地的Trigger時動態把這塊草地的Shader替換成帶交互的,這樣Trigger以外的頂點都不會參與計算了。
推薦閱讀: