Adam Homoki did a breakdown of his beautiful Cartoon Water Shader made in UE4: waterfall, waves, caustic effect, interaction, color, and more.
Hi everybody, my name is Adam Homoki, live in Budapest, Hungary. After high school, I became a Cad-Cam technician and started working as a draftsman. It didn’t last very long…
I wanted to become an artist in the video game industry. So with the power of the internet, I started to self-study modeling. It took about one year to put together some portfolio, and find a job as an artist. Since then I worked at a few indie studios and landed a job at Gameloft where I worked on Dragon Mania Legends. In 2019 January, after six years in the game industry, I made a decision and quit my job to become an entrepreneur. This cartoon water shader was my first job as a self-employed artist.
I was inspired by Rime, the visual aesthetics are phenomenal in that game. I saw Simon Trümpler’s talk on the water created by Pablo Fernandez (see below) and I was really interested in two things. The first is the fact that the shader is unlit. Unlit materials can’t receive light information because they are self-illuminated, yet in Rime, the water is reacting to the Sun’s position. The other thing was the foam at the shoreline and around the rocks. In Rime, these foams were placed by hand, I wanted to do a system where these foams are generated immediately around every object you throw in the water.
Simon did a talk on the waterfall as well:
However, for the foams, I created have a procedural noise option. On mobile, you can’t use texture-based vertex-displacement so I created this function which can be used on mobile.
I’ve used Gerstner waves for the wave generation, this is basically a modified sine wave. You can find out more on GPU gems.
You can change four main parameters in a wave set, and it can be modified on the fly: Wavelength, Amplitude, Steepness, and Speed. The way I set these four values are for example:
I picked a value for Wavelength and Steepness.
Speed = √((Wavelength*Gravity)/2π)
Amplitude = Wavelength/(2π/Steepness)
Then I generated another set of waves with different parameters and I added them up.
For the ocean, I used 12 sets of waves, for the river and lake I used 4 sets.
How it looks on my test map with one set and 12 set of waves:
There was a little problem with the waves because Unreal doesn’t write custom depth pass on translucent materials. Basically, if you have big waves the engine can’t decide which big wave has to be rendered first. To resolve this problem, I had to place another mesh under my water and apply an opaque material to it, making sure this mesh only writes in the custom depth pass. Then I had to place these nodes into my water material, in order to work correctly.
The little sparks were mentioned in the talk, there’nothing special actually. It uses the normal map’s different channels formed into a dot product, followed by some multiplication, clamping, raising the values to a certain power.
What is really interesting is how an unlit material is capable of receiving sun rays on the surface.
I created a light control blueprint, you have to define your source of sunlight, and then the blueprint sets a material parameter collection value which is used by the shader. In this way, the shader knows about the sun direction and it can be used for controlling the reflection on the surface, including fake Subsurface Scattering color intensity based on the Sun’s orientation. If you are using a PBR material you automatically get the sun rays on the surface, but if you are using a much cheaper Unlit material in this way you also have these reflections.
For the color, I first calculate a gradient on the water surface based on my camera position.
I use this gradient to blend between the base color and a lighter blue color. Then I calculate the wave heights from the object position in world space, mask out the wave crest and trough value and blend them with the base color. For the shallow water coloration, I used a simple depth fade node.
Caustic & Wetness Effects
The caustic and wetness effects are basically a special material. The caustic texture is being distorted by another texture, then with triplanar projection applied to the mesh. I achieved the wetness effect by coloring the base texture and adjusting the roughness map value and finally adding a little sine wave animation to it. The whole thing works because, you can set up a base height for these changes in a material parameter collection, for example, everything below 0 receives the changes, while everything above 0 remains unaffected.
Reflection is just a cubemap. If you are using a PBR version of the water you can have screen space reflections as well.
For reflection and refraction, you can set the view angle parameter which is driven by a Fresnel mask. Its purpose is to hide the reflections near the player and from very steep angles.
It can be used for reducing overall noise and adds to artistic control.
Interaction with the Objects
There are two ways the water can interact with objects, first is the dynamic foam around every object, the second is buoyancy where objects can float on the water.
For the foam I created two methods – one is called distance field intersection, the other is depth based intersection.
Distance field is basically a volume texture, representing your mesh. It can be used for ambient occlusion, intersection calculation, or shadow casting. There are a few caveats using this method, the biggest one is it only supports DirectX 11 or higher.
You can find out more about distance field in the Unreal Engine documentation.
The depth-based method uses the scene depth calculating the intersection. It doesn’t look as good as distance field (for the water) but it can run on everything, even on mobile. There is a number of cases in which; using this depth method, you could achieve great intersection results such as Winston’s shield in Overwatch.
You can see that the distance field intersection starts to expand on the water surface, while depth-based begins gobbling up those rocks.
Buoyancy was a little more tricky. The wave animations in the material couldn’t be accessed by a blueprint directly. You either need a render target setup which renders the wave height into a blueprint, or you recalculate the whole wave system inside the blueprint. I went with the last option because a render target at this volume is very slow.
There is a manager blueprint for each of my water types (ocean, river, lake). This manager must be present in the scene, as it calculates an identical invisible wave on top of my waterplane which is used by another blueprint called Buoyant Actor.
You place the Buoyant Actor blueprint in the scene after you can set your own mesh within. You need to set up a number of test points which will be tested against the waves. I could have created a method which tests every triangle of the mesh against the water, but the performance impact on higher tricount would have been hefty. There is a runtime debug option in the manager blueprint so you can visualize these test points at runtime.
The most challenging thing was the buoyancy because for some reason my blueprint waves were not aligned up with my material waves, despite everything being calculated in the same way. The problem lied in the material editor’s sine and cosine function. These function periods must be set to 2π in order to give the same result as found in the blueprint.
I would advise watching Simon’s talk, even if you want to create realistic looking water. There is an abundance of information in those videos about everything, it helped me a lot! Other than that, don’t overshoot your scope. For me personally, it’s good to have a list containing everything I want to create in a project. Even if this list is always changing, it’s worth the time and helps create a nice scope for your project. But my biggest advice would be to buy this shader ? It saves you a lot of time, and any purchase would help support me financially to reach my dreams.
And don’t forget to keep an eye out for updates! I want to continue supporting this water project with many more features!
Adam Homoki, Entrepreneur
Interview conducted by Kirill Tokarev
If you found this article interesting, below we are listing several related Unity Store Assets that may be useful for you: