接上篇,這篇詳細寫寫水面的環形漣漪模擬的shader。
所有的shader實驗源碼都可以在GitHub找到。
C#腳本
首先把碰撞檢測的C#腳本寫了
C#腳本的主要任務:
1、碰撞檢測
2、如果發生碰撞,把碰撞點傳給shader,作爲環形漣漪模擬的圓心
3、如果發生碰撞,首先傳一個開始範圍值,過一定時間後再傳一個結束範圍值,這樣就可以模擬人物踩上水後,先出現外圍漣漪,再從內圈開始消失。
using System.Collections;
using UnityEngine;
public class SimpleWater : MonoBehaviour
{
[Range(0,0.5f)]
public float waveWidth = 0.1f;
[Range(0.01f, 0.1f)]
public float timeMince = 0.02f;
[Range(0,1)]
public float waveDissolveRatioTime = 0.5f;
private Vector3 point = new Vector3(0, 0, 0);
private Material material;
private float panelWidth;
private float panelHeight;
private bool isCollision = false;
private void Awake()
{
material = GetComponent<MeshRenderer>().material;
if (material == null)
Debug.Log("material is null!");
panelWidth = transform.localScale.x * 5;
panelHeight = transform.localScale.z * 5;
material.SetVector("_wavePos", new Vector4(0, 0, 0, 0));
material.SetFloat("_StartWaveWidth", 0);
material.SetFloat("_EndWaveWidth", 0);
}
private void OnCollisionEnter(Collision collision)
{
ContactPoint contact = collision.contacts[0];
//碰撞點座標
Vector3 pos = contact.point;
pos -= transform.position;
pos.x /= panelWidth;
pos.z /= panelHeight;
pos.x = pos.x / 2 + 0.5f;
pos.z = pos.z / 2 + 0.5f;
isCollision = true;
StartCoroutine(ChangeWaveWidth("_StartWaveWidth", 0.8f,true));
//把panel的長寬也傳遞過去,以消除縮放影響
material.SetVector("_wavePos", new Vector4(1 - pos.x, 1 - pos.z, panelWidth, panelHeight));
}
private IEnumerator ChangeWaveWidth(string name, float time, bool isStartWaveWidth)
{
//把設定的waveWidth每timeMince傳給shader一次,每傳一次加一點,從0逐漸接近設定的waveWidth值
for (int i = 0; i < time / timeMince; i++)
{
material.SetFloat(name, waveWidth * i / (time * 50));
//在設定的wave消失時間(比例)時,開始給shader的EndWaveWidth,這樣就可以模擬波從內圈開始消失了
if (Mathf.Abs(i - waveDissolveRatioTime * time / timeMince) <= 0.05f)
{
if (isStartWaveWidth)
StartCoroutine(ChangeWaveWidth("_EndWaveWidth", 1f, false));
}
yield return new WaitForSeconds(timeMince);
}
StopCoroutine(ChangeWaveWidth(name, time, isStartWaveWidth));
}
}
主要是用協程開啓動畫傳值,關鍵部分都詳細寫了註釋,就不再重複了。
然後就是重頭戲——Shader的部分。
Shader
要模擬環形漣漪,首先計算各點到圓心的向量,用點的uv座標減去圓心uv座標即可
float2 dv = i.uv.xy - float2(_wavePos.x, _wavePos.y);
dv = dv * float2(_wavePos.z / _wavePos.w, 1);//消除scale縮放影響
然後計算點到圓心的距離,在距離上使用正弦公式計算採樣的offset
float distance = sqrt(dv.x * dv.x + dv.y * dv.y);
float sinFactor = sin(_Frequency * distance - _Time.y);
計算振幅amplitude,決定波的上下起伏大小,同時使用_StartWaveWidth和_EndWaveWidth來限制波的範圍,用smoothstep而不是clamp是爲了邊緣過渡平滑
float amplitude = _Amplitude * smoothstep(0, _StartWaveWidth, _StartWaveWidth - distance.x) * smoothstep(0, _EndWaveWidth, distance.x - _EndWaveWidth);
計算圓心到點的方向,再乘上上述計算結果,就得到了採樣貼圖的offset。在uv座標上加上offset和上一篇文章提到的折射偏移speed就可以了。
float2 direction = normalize(dv);
float2 wave_offset = normalize(dv) * sinFactor * amplitude;
fixed3 bump = UnpackNormal(tex2D(_Bump, i.uv.zw + speed + wave_offset)).rgb;
源碼
Shader "MyShaderTest/7_SimpleWater"
{
Properties
{
_MainTex("Main Tex",2D) = "white"{}
_MainColor("Main Color",Color) = (1,1,1,1)
_Bump("Bump",2D) = "bump" {}
_BumpScale("Bump Scale",float) = 1
_BumpSpeed("Bump Speed",Range(0,2)) = 1
_ReflectionColor("Reflection Color",Color) = (1,1,1,1)
_RefractionScale("Refraction Scale",float) = 1
_Frequency("Frequency",float) = 0
_Amplitude("Amplitude",float) = 1
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
Pass
{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 Ttw0 : TEXCOORD2;
float4 Ttw1 : TEXCOORD3;
float4 Ttw2 : TEXCOORD4;
};
sampler2D _MainTex;
half4 _MainTex_ST;
half4 _MainTex_TexelSize;
half4 _MainColor;
sampler2D _Bump;
half4 _Bump_ST;
half _BumpScale;
half _BumpSpeed;
fixed4 _ReflectionColor;
half _RefractionScale;
float _Frequency;
float _Amplitude;
float4 _wavePos;
float _StartWaveWidth;
float _EndWaveWidth;
v2f vert(a2v v)
{
v2f o;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBitangent = cross(worldNormal, worldTangent) * v.tangent.w;
o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1));
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _Bump);
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0.0)
{
o.uv.y = 1.0 - o.uv.y;
o.uv.w = 1.0 - o.uv.w;
}
#endif
o.Ttw0 = float4(worldTangent.x, worldBitangent.x, worldNormal.x, worldPos.x);
o.Ttw1 = float4(worldTangent.y, worldBitangent.y, worldNormal.y, worldPos.y);
o.Ttw2 = float4(worldTangent.z, worldBitangent.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 worldPos = float3(i.Ttw0.w,i.Ttw1.w,i.Ttw2.w);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = half2(_BumpSpeed, _BumpSpeed) * _Time.y;
float2 dv = i.uv.xy - float2(_wavePos.x, _wavePos.y);
dv = dv * float2(_wavePos.z / _wavePos.w, 1);//消除scale縮放影響
float distance = sqrt(dv.x * dv.x + dv.y * dv.y);
float sinFactor = sin(_Frequency * distance - _Time.y);
float amplitude = _Amplitude * smoothstep(0, _StartWaveWidth, _StartWaveWidth - distance.x) * smoothstep(0, _EndWaveWidth, distance.x - _EndWaveWidth);
float2 direction = normalize(dv);
float2 wave_offset = normalize(dv) * sinFactor * amplitude;
fixed3 bump = UnpackNormal(tex2D(_Bump, i.uv.zw + speed + wave_offset)).rgb;
bump.xy *= _BumpScale;
bump.z = sqrt(1 - saturate(dot(bump.xy, bump.xy)));
bump = normalize(half3(dot(bump, i.Ttw0.xyz), dot(bump, i.Ttw1.xyz), dot(bump, i.Ttw2.xyz)));
float2 mainTex_uv_offset = bump.xy * _MainTex_TexelSize.xy * _RefractionScale;
fixed3 refrColor = tex2D(_MainTex, i.uv.xy + mainTex_uv_offset).rgb * _MainColor;
half3 reflColor = dot(worldViewDir, bump) * _ReflectionColor;
return fixed4(reflColor + refrColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}