Zi Profile picture
Zi
Indie VR gamedev working on a penguin VR game 🐧 in Godot! Past: @OwlchemyLabs. Husband of @synecdoki. Proud shiba dad.

Jan 16, 2024, 19 tweets

Wanna make your own Wind Waker inspired ocean effect in Godot? Here's a quick and dirty guide!
#GodotEngine #gamedev

First, procure your ocean mesh(es). This can be a huge plane, or a disk, or many tiles. But it does need to be flat (y=0), and have enough subdivisions for you to see waves. The more subdivisions, the smoother the wave, so optimize for your needs.

Next: The foam texture! This is the SOUL of the effect, so spend time making it "you". Use a soft airbrush under a contrast adjustment layer. It gives clean edges while smoothing out sloppy brushwork. Feels satisfying, like molding clay.

This technique also makes it easier to fix edge discontinuities and make your texture seamless. Just apply an offset to your image, and paint your "repairs" over the discontinuities. They will automatically be smoothed out.

Now for some shadering! I'll show bits of code, but you can totally do this in a shader graph too. First, make sure you enable the render mode "world_vertex_coords". This lets us move or scale the ocean mesh (e.g. follow the player camera) without moving the effect itself.

In the vertex shader, pass VERTEX to the fragment shader via a world_pos varying. In the frag shader, use world_pos to build a UV to sample your foam texture (add a param to scale it to your liking). You now have a black and white ocean that stays fixed to the world.

Add two shader params for the water color and the light foam color, and reroute your previous foam texture sample to instead be a mix ratio between these colors. Now you should have a blue and white ocean pattern, or whatever colors you chose!

Add another shader param for the DARK foam color. In the frag shader, sample the foam texture a second time, with a (0.5, 0.5) offset from the light foam sample. Use this sample to do dark foam mix before you do a light foam mix. Now you should have two layers of foam!

The foam texture looks great at close and mid range, but far away, it looks busy and repetitive. We can calculate lateral-distance-from-camera in the vertex shader, pass it as a varying to the frag shader, and use it to fade away the foam. We can also expose params to adjust it.

If you made it this far, good time to mention that a huge part of the look of an ocean comes from its interplay with the sky, directional light, ambient light, and fog. Make sure to play around with these. I don't tend to use postprocessing effects, but you can add those too.

Next, create a distortion texture. This will be used to make the foam lines "ripple and wiggle". Combine a noisy red channel and noisy green channel. They represent motion along the X and Z axes, respectively. 128px is a good size for this.

You'll want to make sure your distortion texture matches the kind of distortion you want. Want big, sweeping line movements? Make a smooth texture. Want small ripples? Make a more noisy texture. Want both? Make your texture have big and small features.

In the frag shader, sample the distortion texture using world_pos. Take the sampled distortion value (R and G) and add it to the foam UVs from before. Make the strength and scale adjustable, but don't need to adjust them yet. Now you will have a distorted, static ocean! Yay!

Add a UV scroll to the sampling of the distortion texture (any direction works). Make the speed adjustable. Now you can easily adjust your distortion params because you can see the animation. You can use one distortion for both foams for performance, or use two for a nicer look.

We need a height function that tells how high the surface is at any point on the ocean. You can use any wave function you want (like a trochoid), but I chose to just average two sinusoids. For this, you'll need 4 shader params:
- speed
- frequency
- amplitude

Now just plug it into VERTEX.y in your vertex shader, and voila! Waves! Adjust the settings to your liking. We're almost there! But notice how the waves don't respect lighting - they just appear a uniform color no matter what. We need to give them normals that make sense.

In the vert shader, query the ocean height two more times - once a bit to the right, and once a bit forward. Take the cross product of the vectors formed by the three sampled surface positions, normalize it, and that is your new surface normal!

Now just throw in some pizzazz. Maybe make the waves dissipate far away from the camera, etc. If you need ocean surface gameplay logic (like buoyancy), you'll need to sync the ocean time by creating a global shader var. You can animate this in the editor using a tool script.

And that's it! Hope you have fun making this, and making it your own. 🐧🌊

Share this Scrolly Tale with your friends.

A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.

Keep scrolling