I have a vertex displacement shader (in shader graph) that is working adequately to displace a surface to create ocean waves (its a basic Gerstner-like wave function).
I’m now trying to create an identical version of the math within the shader in a script so that I can get the height of the water at any position.
What I can’t make sense of is that it looks to me like the script should generate the same results as the shader but the two quickly diverge.
The Displacement method generates 5 new displacements (Vector3s) based on 5 different sets of parameters grabbed from the material (direction, amplitude and time), adds them together, and adds them to the original position.
If I run the Displacement method and the shader once they both produce the identical result (I’m using a debug node in shader graph to test this). But quickly the script generates Vector3 displacements that the majority of time are positive on the Y axis. And so, according to the script the water height is pretty much constantly rising. Whereas the shader definitely does not behave this way.
Evidently my math isn’t good enough to understanding what is causing this, nor is my understanding of shaders and coding good enough to figure out why the two are producing different results.
I appreciate this is a lot to ask. If anyone has any tips or pointers, that would be very much appreciated.
For reference, here is the script:
private void FixedUpdate()
{
testVector = Displacement(testVector);
}
private Vector3 Displacement(Vector3 position)
{
displacement1 = Wave(position, forwardBack1, amplitude1, timeScales1 * Time.time);
displacement2 = Wave(position, forwardBack2, amplitude2, timeScales2 * Time.time);
displacement3 = Wave(position, leftRight1, amplitude3, timeScales3 * Time.time);
displacement4 = Wave(position, leftRight2, amplitude4, timeScales4 * Time.time);
displacement5 = Wave(position, diagonal, amplitude5, timeScale5 * Time.time);
newDisplacement = ((displacement1 + displacement2) + (displacement3 + displacement4)) + displacement5;
newPosition = newDisplacement + position;
return newPosition;
}
private Vector3 Wave(Vector3 position, Vector3 direction, float amplitude, float time)
{
float theta = Theta(position, direction, time);
// Calculate X
float x = Mathf.Sin(theta) * WaveInput(direction, direction.x, amplitude);
float negateX = -1 * x;
// Calculate Y
float y = Mathf.Cos(theta) * amplitude;
// Calculate Z
float z = Mathf.Sin(theta) * WaveInput(direction, direction.z, amplitude);
float negateZ = -1 * z;
return new Vector3(negateX, y, negateZ);
}
private float WaveInput(Vector3 direction, float axis, float amplitude)
{
double d = amplitude / Math.Tanh(direction.magnitude * depth);
float dToFloat = Convert.ToSingle(d);
float input = (axis / direction.magnitude) * dToFloat;
return input;
}
private float Theta(Vector3 position, Vector3 direction, float time)
{
float x = (direction.x * position.x) + (direction.z * position.z);
float theta = (x - (Frequency(direction) * time)) - phase;
return theta;
}
private float Frequency(Vector3 v)
{
float vectorLength = v.magnitude;
float x = Convert.ToSingle((gravity * vectorLength) * Math.Tanh(vectorLength * depth));
float frequency = Mathf.Sqrt(x);
return frequency;
}
And the shader
As input, the shader uses:
- the absolute world position of the vertices,
- 5 different “directions” (Vector3s),
- 5 different “amplitudes” (floats)
- 5 different “time” values multiplied by Time
It adds together 5 “Waves” generated by the Wave subshader and adds that to the position. Then converts the new absolute world position back to object position.
The Wave (and WaveInput) Subshader
Theta
Frequency



