Quick summation:
I am attempting to create an ocean comprised of planes that can be easily loaded and unloaded based on distance. On this ocean I want a boat to sail with a player onboard in the first person, where I want them to experience the buoyancy of their boat relative to the surrounding waves. I am new to shadergraph and have been following several tutorials to try and create the desired effect.
These tutorials include
Catlikecoding's Wave shader https://catlikecoding.com/unity/tutorials/flow/waves/
Zicore's Gertsner wave https://www.youtube.com/watch?v=Awd1hRpLSoI&ab_channel=Zicore
Tom Weiland's dynamic water physics https://www.youtube.com/watch?v=eL_zHQEju8s&ab_channel=TomWeiland
These resources have gotten me a good chunk of the way there, but I've run into some issues regarding the boat physics specifically.
I understand the math behind simulating Gertsner waves, and have tried to set up a WaveManager that calculates the y-value of a "floater" at position (x,z).
Floater.cs
public Rigidbody rigidBody;
public float depthBeforeSubmerged = 1f;
public float displacementAmount = 3f;
public int floaterCount = 1;
public float waterDrag = 0.99f;
public float waterAngularDrag = 0.5f;
private void FixedUpdate()
{
rigidBody.AddForceAtPosition(Physics.gravity / floaterCount, transform.position, ForceMode.Acceleration);
float waveHeight = WaveManager.instance.GetWaveHeight(transform.position.x,transform.position.z);
if(transform.position.y < waveHeight)
{
float displacementMultiplier = Mathf.Clamp01((waveHeight-transform.position.y) / depthBeforeSubmerged) * displacementAmount;
rigidBody.AddForceAtPosition(new Vector3(0f, Mathf.Abs(Physics.gravity.y) * displacementMultiplier, 0f),transform.position, ForceMode.Acceleration);
rigidBody.AddForce(displacementMultiplier * -rigidBody.velocity * waterDrag * Time.fixedDeltaTime, ForceMode.VelocityChange);
rigidBody.AddTorque(displacementMultiplier * -rigidBody.angularVelocity * waterAngularDrag * Time.fixedDeltaTime, ForceMode.VelocityChange);
}
}
This is pretty much lifted directly from Tom Weiland's video. Basically, when my floatpoint dips below the calculated wave, it applies force to make it travel upwards. Following his instructions carefully yielded decent results, but the problem arose when I started using Shadergraph to create my ocean.
The main issue is I wanted the waves to be tileable across multiple planes, so I used the object position and transformed it to world position to do calculations, and then added it back to the object position before manipulating the vertices of the ocean plane.
I've tried to show it below here:
This makes the ocean plane tileable and looks great, but also enlarges it in the scene quite a bit. I've put a regular plane on top to show the difference. Both are 1x1 units in the inspector.
So this is the first problem. The calculations I do in my WaveManager aren't lining up properly with the actual visual representation of the waves.
The second problem is that I can't seem to make the calculations done in WaveManager give me the correct y-coordinates.
In the shader, the waves are animated using the Time-component.
I've found the documentation to be a bit sparse on Shadergraph components, probably because I'm self taught and have a hard time wrapping my head around some of these concepts.
I've had a hard time working out how to calculate the change in y-coordinates over time in the wavemanager-script. The different solutions I've tried have just made the y-coordinate slowly grow larger into the negative range. I just have no idea how to make my calculations match up with the ones done on the GPU. It's no important that it be super accurate, just good enough to sell the effect with small waves.
The Wavemanager code, finally.
private void Start()
{
waveLengthA = waves.GetFloat("_WaveLengthA");
waveLengthB = waves.GetFloat("_WaveLengthB");
waveLengthC = waves.GetFloat("_WaveLengthC");
waveLengthD = waves.GetFloat("_WaveLengthD");
steepnessA = waves.GetFloat("_SteepnessA");
steepnessB = waves.GetFloat("_SteepnessB");
steepnessC = waves.GetFloat("_SteepnessC");
steepnessD = waves.GetFloat("_SteepnessD");
directionA = waves.GetVector("_DirectionA");
directionB = waves.GetVector("_DirectionB");
directionC = waves.GetVector("_DirectionC");
directionD = waves.GetVector("_DirectionD");
kA = (2 * Mathf.PI) / waveLengthA;
kB = (2 * Mathf.PI) / waveLengthB;
kC = (2 * Mathf.PI) / waveLengthC;
kD = (2 * Mathf.PI) / waveLengthD;
cA = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y)/ kA);
cB = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y) / kB);
cC = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y) / kC);
cD = Mathf.Sqrt(Mathf.Abs(Physics.gravity.y) / kD);
}
private void Update()
{
offset += Time.deltaTime;
}
public float GetWaveHeight(float x,float z)
{
fA = kA*(directionA.x * x + directionA.y * z - cA * offset);
fB = kB * (directionB.x * x + directionB.y * z - cB * offset);
fC = kC * (directionC.x * x + directionC.y * z - cC * offset);
fD = kD * (directionD.x * x + directionD.y * z - cD * offset);
position += new Vector3(x + directionA.x * steepnessA / kA * Mathf.Cos(fA),steepnessA/kA*Mathf.Sin(fA),z+directionA.y*steepnessA/kA*Mathf.Cos(fA));
position += new Vector3(x + directionB.x * steepnessB / kB * Mathf.Cos(fB),steepnessB/kB*Mathf.Sin(fB),z+directionB.y*steepnessB/kB*Mathf.Cos(fB));
position += new Vector3(x + directionC.x * steepnessC / kC * Mathf.Cos(fC),steepnessC/kC*Mathf.Sin(fC),z+directionC.y*steepnessC/kC*Mathf.Cos(fC));
position += new Vector3(x + directionD.x * steepnessD / kD * Mathf.Cos(fD),steepnessC/kD*Mathf.Sin(fD),z+directionD.y*steepnessD/kD*Mathf.Cos(fD));
return position.y;
}
The code above is quite ugly with a lot of repetition, but my plan is to make a constructor at some point to make it easier to read. I grab all the values used in my shader, to make sure they match even if I change the look of the waves. Then I do the calculations from Catlikecoding and plot in the x- and z-coordinates of my floating object.
As far as I can understand, it should work if I just combine alle the calculated vectors, but obviously I'm missing something.
From what I've seen others do, they often opt to create custom planes with more vertices, that can cover their entire gameworld and avoid the problem, but I'm making a larger world and am worried about performance. (Though I don't know if I should be even.) I really like the fact that my ocean planes are tileable.
Does anyone here know of any solutions or help me solve the issue of worldspace vs objectspace, or how to accurately recreate the time-progression as seen in the shader?
Any help would be much appreciated.