先放效果 只想要代码可以直接拉到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;
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;
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;
}
}
}
}
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返回0,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;
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);
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++;
}
}
void AddWavePos(int i)
{
Debug.Log("点击屏幕一次");
float timer = 0;
curWaveDistanceFactor[i] = WaveDistanceFactor;
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返回0,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
}
}
}
调一调参数就能产生一些比较好的效果,代码并没有非常斟酌的去写但是注释写了挺多,看懂了之后大家可以自行优化。
|