The generation of simple water ripples only needs the center position information, and the rest of the artistic effects can be achieved in the shader.
Since the shader cannot temporarily store the information, the script needs to pass the character position information to the shader.
(Can the geometry shader store information?)
Method
-
Store character positions in an array at intervals
Vector4 waterRipples[]
-
Store individual water ripple lifetimes in
waterRipples.w
(Time preferably needs to take into account whether the character moves, jumps, etc.)
-
Pass
waterRipples
to Shader -
Calculate all water ripples in the shader
Custom Fuction:
-
A single water ripple code is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
float RipplesTransform(float time, float3 positionWS, float4 center, float radius = 3, float speed = 0.5, float noise = 0) { float dist = distance((center.xyz + float3(0, 0.5, 0)), (positionWS + noise) * float3(1, 1, 2)); float changingRadius = center.w; changingRadius = 1 - changingRadius; changingRadius = 1 - pow(changingRadius,3); float opacity = sin(changingRadius * 3.1415926); float rings = abs(frac(dist - (time * speed)) - 0.5) * 2.0; rings = pow(rings,4); float mask = RemapFloat01(dist, float2(0, radius * changingRadius)); mask = 1.0 - mask; mask *= opacity; return rings * mask; }
-
dist
is the distance to the center point, wherefloat3(0, .5, 0)
is the height offset of the center point, used to adjust the height of the center position of the water ripple.float3(1, 1, 2)
is It is used to scale the distance in the z direction, so that an elliptical water ripple is obtained (if the character is not on the plane with the z axis of 0, it cannot be used directly) -
RemapFloat01()
maps a range to 01 (custom) -
changingRadius
is a value from 0 to 1 that controls the radius change and transparency.The change of radius needs to be fast and then slow (blue curve), and the change of transparency also needs to be fast, then slow and finally disappear (orange curve).
-
rings
is to loop throughfrac()
andabs()
.
-
void SetWaterRipples()
is used to perform calculations and output results to shadergraph.1 2 3 4 5 6 7 8
void SetWaterRipple_float( float3 posWS, float time, float noise, out float WaterRipples) { for (int i = 0; i < _ripplesParticlesSize; i++) { WaterRipples = lerp(RipplesTransform(time, posWS, _waterRipples[i], 7, 0.36, noise), 1, WaterRipples); } }
The Custom Function reference code is as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//-----Unity ShaderGraph Custom Function-----
uniform float4 _waterRipples[10];
uniform int _ripplesParticlesSize;
float RemapFloat01(float In, float2 InMinMax)
{
return clamp((In - InMinMax.x) / (InMinMax.y - InMinMax.x), 0, 1);
}
//generate according to center pos
float RipplesTransform(float time, float3 positionWS, float4 center, float radius = 3, float speed = 0.5, float noise = 0)
{
float dist = distance((center.xyz + float3(0, 0.5, 0)), (positionWS + noise * .7) * float3(1, 1, 2));
float changingRadius = center.w;
changingRadius = 1 - changingRadius;
changingRadius = 1 - pow(changingRadius,3);
float opacity = sin(changingRadius * 3.1415926);
float rings = abs(frac(dist - (time * speed)) - 0.5) * 2.0;
rings = pow(rings,4);
float mask = RemapFloat01(dist, float2(0, radius * changingRadius));
mask = 1.0 - mask;
mask *= opacity;
return rings * mask;
}
void SetWaterRipple_float( float3 posWS, float time, float noise,
out float WaterRipples)
{
for (int i = 0; i < _ripplesParticlesSize; i++)
{
WaterRipples = lerp(RipplesTransform(time, posWS, _waterRipples[i], 7, 0.36, noise), 1, WaterRipples);
}
}
Script:
-
ripplesParticlesSize
andwaterRipples[]
need to be passed to the shader. WherewaterRipples[].xyz
represents the center position of the water ripple, andwaterRipples[].w
represents the survival time of the water ripple; -
waterRipples[].w
can be affected bytime_dissolving
andtime_jumping
,time_jumping
is determined bytime_grounded
andtime_air
.In this way, the value of
waterRipples[].w
can be roughly used to represent the different states of the character when moving, when taking off, when in the air and when landing.
reference code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//-----C# Script-----
public class SetWaterRipples : MonoBehaviour
{
private static int ripplesParticlesSize = 9;
private int count;
private Vector4[] waterRipples = new Vector4[ripplesParticlesSize];
private bool[] startSpreading = new bool[ripplesParticlesSize];
private float[] time_ripple = new float[ripplesParticlesSize];
[SerializeField] private float interval = .2f;
private float duration;
private float time;
private float time_dissolving;
private Transform playerTr;
private Vector3 playerPos;
private CharacterMovement _characterMovement;
private Controller _controller;
void Start()
{
playerTr = GameObject.FindWithTag("Player").transform;
_characterMovement = playerTr.gameObject.GetComponent<CharacterMovement>();
_controller = playerTr.gameObject.GetComponent<Controller>();
duration = (ripplesParticlesSize) * interval;
Shader.SetGlobalInt("_ripplesParticlesSize", ripplesParticlesSize);
for (int i = 0; i < ripplesParticlesSize; i++)
{
startSpreading[i] = false;
}
}
void Update()
{
playerPos = playerTr.position;
Timer();
if (time >= interval)
{
waterRipples[count] = playerPos + new Vector3(Random.Range(-.3f, .3f), 0f, Random.Range(-1f, 1f));
startSpreading[count] = true;
count++;
if (count > ripplesParticlesSize-1)
{
count = 0;
}
time = 0f;
}
Shader.SetGlobalVectorArray("_waterRipples", waterRipples);
}
private bool isGrounded = false;
private float time_grounded;
private float time_air;
private float time_jumping;
void Timer()
{
time += Time.deltaTime;
for (int i = 0; i < ripplesParticlesSize; i++)
{
if (startSpreading[i])
{
time_ripple[i] += Time.deltaTime;
//combination of ripple appreance
waterRipples[i].w = Mathf.Clamp01(Mathf.InverseLerp(0f, duration, time_ripple[i])) * Mathf.Lerp(time_dissolving, 1f,
(1f - time_jump) * Mathf.Sin(Mathf.PI * (1f - Mathf.Pow((1f - time_jump), 3f)) ));
if (time_ripple[i] >= duration)
{
startSpreading[i] = false;
time_ripple[i] = 0f;
}
}
}
//appear when jumping
isGrounded = _controller.State.IsGrounded;
if (isGrounded)
{
time_air = 0f;
time_grounded += Time.deltaTime * .6f;
if (time_grounded >= 1f)
{
time_grounded = 1f;
}
}
else
{
time_grounded = 0f;
time_air += Time.deltaTime * 1f;
if (time_air >= 1f)
{
time_air = 1f;
}
}
time_jumping = Mathf.Lerp(time_grounded, 1f, time_air);
//appear when moving
if (_characterMovement._horizontalMovement != 0f)
{
time_dissolving += Time.deltaTime * 1f;
if (time_dissolving >= 1f)
{
time_dissolving = 1f;
}
}
else
{
time_dissolving -= Time.deltaTime * 0.3f;
if (time_dissolving <= 0f)
{
time_dissolving = 0f;
}
}
}
}
Extends
Add sparkling effect to water surface
The effect is more noticeable at a distance.
This can be done using positionWS.z
combined with Screen Position
to add noise at the end
(Below is the reference of screen space near and far)