先放效果
只想要代码可以直接拉到3.0
把波产生的点作为圆心,往外的半径方向作为波的传输方向,半径长度作为s波的函数变量,通过波的公式偏移uv,从而模拟波。
1.0一个从平面中心不断波动的效果
ps:不是后处理,直接作为material赋给平面使用,为了防止波变成椭圆,需要在变量中更新平面的长宽比。
Shader "MyShader/waterWave"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Strength("WaveStrength", Float) = 0.01
_Speed("WaveSpeed", Float) = 10
//waveLength越大波长越小
_WaveLength("WaveLength", Float) = 10
_PlaneWidth("PlaneWidth", Float) = 10
_PlaneLength("PlaneLength", Float) = 10
_WaveLengthChangeFactor("waveLengthChangeFactor" , Float) = 0.2
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Strength;
float _Speed;
float _WaveLength;
float _PlaneWidth;
float _PlaneLength;
float _WaveLengthChangeFactor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed2 center = fixed2(0.5, 0.5);
fixed2 offset = i.uv - center;
//保存水波形状为圆
offset *= float2(_PlaneLength / _PlaneWidth, 1);
//uv点与中心点的距离
float dist = sqrt(offset.x * offset.x + offset.y * offset.y);
fixed2 offsetDir = normalize(offset);
//以距离为函数扭曲屏幕,时间控制波往后传播,dist决定波形,实现的是不断往外波动的水波
fixed2 newUV = i.uv + _Strength * offsetDir * sin(_Time.y * _Speed + dist * _WaveLength * (1 - dist) * _WaveLengthChangeFactor);
return tex2D(_MainTex, newUV);
}
ENDCG
}
}
}
2.0
改为后处理,在鼠标点击的任意地方都会产生水波,并且会逐渐扩散消失。
原理是点击时Csharp脚本更新当前的时间差为0,随着时间差的不断增大,将时间差*时间差因子看成是截取水波的半径,在此半径外一定范围的地方才保留uv的偏移。缺点是所有的uv由统一的时间差控制,即同一时刻只有一个水波。
修改:
为了提升观感,在每一帧都线性增大水波扩散的速度并且线性增大前文提到的半径的范围。查阅资料发现水波从里到外波长和频率不变振幅变小,并且和半径成反比(1/r)
using System.Collections; using System.Collections.Generic; using UnityEngine; ////// 后处理实现水波,并且可以同时在多个点点击产生 /// public class WaterWavePostEffect : PostEffectBase { public Shader m_Shader; private Material m_Material; public Material material { get { m_Material = CheckShaderAndCreateMaterial(m_Shader, m_Material); return m_Material; } } ////// 水波的振幅 /// [SerializeField] private float WaveStrength = 0.01f; ////// 水波往外的速度 /// [SerializeField] private float WaveSpeed = 15; ////// 水波的波长(越大波长越短) /// [SerializeField] private float WaveLength = 70; ////// 水波从里到外波长减弱的因子 /// [SerializeField] private float _WaveStrengthFallFactor = 1; ////// 截取水波的半径增大的速度 /// [SerializeField] private float WaveDistanceFactor = 1; ////// 截取水波的宽度 /// [SerializeField] private float WaveWidth = 0.1f; //水波往外扩散的距离,在此范围内的uv坐标才会收到影响 private float curWaveDistance = 0; private float waveStartTime = float.MinValue; private float curWaveDistanceFactor; private Vector4 wavePos = new Vector4(0.5f, 0.5f, 0, 0); private void Awake() { curWaveDistanceFactor = WaveDistanceFactor; } private void OnRenderImage(RenderTexture source, RenderTexture destination) { if(material != null) { curWaveDistance = (Time.time - waveStartTime) * curWaveDistanceFactor; m_Material.SetFloat("_WaveSpeed", WaveSpeed); m_Material.SetFloat("_WaveStrength", WaveStrength); m_Material.SetFloat("_WaveLength", WaveLength); m_Material.SetFloat("_CurWaveDistance", curWaveDistance); m_Material.SetFloat("_WaveStrengthFallFactor", _WaveStrengthFallFactor); m_Material.SetFloat("_WaveWidth", WaveWidth); m_Material.SetVector("_WaveStartPos", wavePos); Graphics.Blit(source, destination, m_Material); } } private void Update() { if(Input.GetMouseButtonDown(0)) { float timer = 0; curWaveDistanceFactor = WaveDistanceFactor; //Input.mousePostion 获得鼠标点击位置的屏幕像素空间坐标 Vector2 mousePos = Input.mousePosition; wavePos = new Vector4(mousePos.x / Screen.width, mousePos.y / Screen.height ,0, 0); //水波产生 waveStartTime = Time.time; while(timer <= 2) { curWaveDistanceFactor += 0.2f * Time.deltaTime; timer += Time.deltaTime; } } //Debug.Log(waveStartTime); } }
Shader "MyShader/waterWavePostEffect"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _WaveStrength;
float _WaveSpeed;
float _WaveLength;
float _WaveStrengthFallFactor;
//不断延伸的半径距离,在离这个距离的_WaveWidth内才能产生水波
float _CurWaveDistance;
float _WaveWidth;
float4 _WaveStartPos;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//鼠标点击的地方为中心点
fixed2 center = fixed2(_WaveStartPos.x, _WaveStartPos.y);
fixed2 offset = i.uv - center;
//保存水波形状为圆
offset *= float2(_ScreenParams.x / _ScreenParams.y, 1);
//uv点与中心点的距离
float dist = sqrt(offset.x * offset.x + offset.y * offset.y);
//圆心往外的方向
fixed2 offsetDir = normalize(offset);
//在_CurWaveDistance的_WaveWidth距离内才会产生效果,Step(a,x),x= a 返回1
float _CurWaveWidth = _WaveWidth * (1 + dist);
float discardFactor = step(abs(dist - _CurWaveDistance), _CurWaveWidth);
//以距离为函数扭曲屏幕,时间控制波往后传播,dist决定波形,实现的是不断往外波动的水波
fixed2 offsetFactor = sin(_Time.y * _WaveSpeed + dist * _WaveLength);
//水波从里到外波长不变振幅以1/r衰减
float _CurWaveStrength = _WaveStrength * (1 / dist * _WaveStrengthFallFactor);
fixed2 newUV = i.uv + _CurWaveStrength * offsetDir * offsetFactor * discardFactor;
return tex2D(_MainTex, newUV);
}
ENDCG
}
}
}
3.0
通过数组的存储使得同一时刻多个水波能够实现,逻辑将水波产生点和时间差改为数组,在片元着色器里面for循环分别每个产生水波产生点带来的uv偏移进行叠加,for循环结束后再赋值颜色。速度和半径范围的线性增大程度只和uv位置有关无需Csharp脚本传递。
using System.Collections; using System.Collections.Generic; using UnityEngine; ////// 后处理实现水波,并且可以同时在多个点点击产生 /// public class WaterWavePostEffectPlus : PostEffectBase { public Shader m_Shader; private Material m_Material; private Vector4[] wavPoses = new Vector4[10]; public Material material { get { m_Material = CheckShaderAndCreateMaterial(m_Shader, m_Material); return m_Material; } } ////// 水波的振幅 /// [SerializeField] private float WaveStrength = 0.03f; ////// 水波往外的速度 /// [SerializeField] private float WaveSpeed = 15; ////// 水波的波长(越大波长越短) /// [SerializeField] private float WaveLength = 70; ////// 水波从里到外波长减弱的因子 /// [SerializeField] private float _WaveStrengthFallFactor = 0.2f; ////// 截取水波的半径增大的速度 /// [SerializeField] private float WaveDistanceFactor = 0.6f; ////// 截取水波的宽度 /// [SerializeField] private float WaveWidth = 0.1f; //水波往外扩散的距离,在此范围内的uv坐标才会收到影响 //for循环下每个点都是独立的,需要记录其自己的开始时间,传播距离和距离变化因子 private float[] curWaveDistance = new float[10]; private float[] waveStartTime = new float[10]; private float[] curWaveDistanceFactor = new float[10]; private Vector4 wavePos = new Vector4(0.5f, 0.5f, 0, 0); //用于指定当前赋值点的下标 private int index = 0; private void Awake() { for(int i = 0; i < curWaveDistanceFactor.Length; i++) { curWaveDistanceFactor[i] = WaveDistanceFactor; } for (int i = 0; i < curWaveDistance.Length; i++) { curWaveDistance[i] = 0; } for (int i = 0; i < curWaveDistanceFactor.Length; i++) { curWaveDistanceFactor[i] = float.MinValue; } index = 0; } private void OnRenderImage(RenderTexture source, RenderTexture destination) { if (material != null) { for(int i = 0; i< wavPoses.Length; i++) { curWaveDistance[i] = (Time.time - waveStartTime[i]) * curWaveDistanceFactor[i]; } m_Material.SetFloatArray("_CurWaveDistance", curWaveDistance); m_Material.SetVectorArray("_WaveStartPoses", wavPoses); m_Material.SetFloat("_WaveSpeed", WaveSpeed); m_Material.SetFloat("_WaveStrength", WaveStrength); m_Material.SetFloat("_WaveLength", WaveLength); m_Material.SetFloat("_WaveStrengthFallFactor", _WaveStrengthFallFactor); m_Material.SetFloat("_WaveWidth", WaveWidth); //m_Material.SetVector("_WaveStartPos", wavePos); Graphics.Blit(source, destination, m_Material); } else { Graphics.Blit(source, destination); } } private void Update() { if (Input.GetMouseButtonDown(0)) { if(index == 9) { index = 0; } AddWavePos(index); index++; } //Debug.Log(waveStartTime); } ////// 点击屏幕增加一个水波产生的原点 /// /// 水波点的下标 void AddWavePos(int i) { Debug.Log("点击屏幕一次"); float timer = 0; curWaveDistanceFactor[i] = WaveDistanceFactor; //Input.mousePostion 获得鼠标点击位置的屏幕像素空间坐标 Vector2 mousePos = Input.mousePosition; wavePos = new Vector4(mousePos.x / Screen.width, mousePos.y / Screen.height, 0, 0); wavPoses[i] = wavePos; //水波产生 waveStartTime[i] = Time.time; while (timer <= 2) { curWaveDistanceFactor[i] += 0.2f * Time.deltaTime; timer += Time.deltaTime; } } }
Shader "MyShader/waterWavePostEffectPlus"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _WaveStrength;
float _WaveSpeed;
float _WaveLength;
float _WaveStrengthFallFactor;
//不断延伸的半径距离,在离这个距离的_WaveWidth内才能产生水波
float _CurWaveDistance[10];
float _WaveWidth;
float4 _WaveStartPoses[10];
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
for (int j = 0; j < 10; j++) {
//鼠标点击的地方为中心点
fixed2 center = fixed2(_WaveStartPoses[j].x, _WaveStartPoses[j].y);
fixed2 offset = i.uv - center;
//保持水波形状为圆
offset *= float2(_ScreenParams.x / _ScreenParams.y, 1);
//uv点与中心点的距离
float dist = sqrt(offset.x * offset.x + offset.y * offset.y);
//圆心往外的方向
fixed2 offsetDir = normalize(offset);
//在_CurWaveDistance的_WaveWidth距离内才会产生效果,Step(a,x),x= a 返回1
float _CurWaveWidth = _WaveWidth * (1 + dist);
float discardFactor = step(abs(dist - _CurWaveDistance[j]), _CurWaveWidth);
//以距离为函数扭曲屏幕,时间控制波往后传播,dist决定波形,实现的是不断往外波动的水波
fixed2 offsetFactor = sin(_Time.y * _WaveSpeed + dist * _WaveLength);
//水波从里到外波长不变振幅以1/r衰减
float _CurWaveStrength = _WaveStrength * (1 / dist * _WaveStrengthFallFactor);
i.uv = i.uv + _CurWaveStrength * offsetDir * offsetFactor * discardFactor;
}
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
调一调参数就能产生一些比较好的效果,代码并没有非常斟酌的去写但是注释写了挺多,看懂了之后大家可以自行优化。



