Unity Shader: Toon Water Shader

 


I'm a huge fan of Studio Ghibli movies and I've always wanted to replicate their visual style in a video game.
In this post I'll tell you how I emulate as close as possible, the look of the water we all love in Studio Ghibli movies.
Before we continue, I'd like to thank Roystan and Harry Alisavakis for their tutorials on toon water and grab pass shaders which gave me the kickstart to develop my own water shader.

Features

I wanted my shader to be quick and easy to insert in a scene, so all you need is just a flat plane that will be our water surface and some kind of terrain with depth to contain our water.


The shader will have the following features:
  • animated vertex displacement
  • animated fake refraction
  • selective planar reflection
  • animated normal maps
  • animated foam

How it works

We can subdivide the shader into 5 steps:
  1. Vertex displacement - use a simple vertex displacement map to generate waves moving the vertices of the flat plane along the y-axis.
  2. Depth based water colors - water changes color based on its depth. This is due to the fact that light is absorbed when it passes through water.
    Use camera depth texture to get the depth values of the objects based on their distance from the camera.
    Then use these depth values to interpolate between the three different colors of the water.
  3. Grab-pass animated fake refraction - grab-pass takes everything behind an object and renders it onto a texture. If you project this texture on a surface, it's just like seeing objects behind a glass. Because it's a texture, we can distort and animate its UVs to get a fake animated refraction effect.
  4. Planar animated mirror reflection - to reflect the skybox and certain objects in the scene, I used an old script I found on Unity Wikipedia that basically acts like a perfect mirror. It works only on planar surfaces. I just tweaked it a bit so that the reflection texture it returns, is a global texture available to all the shaders called _WorldReflectionTex. That way, I can attach the script to a single object but use the reflection texture on multiple water surfaces.
  5. Intersection animated foam - use a noise texture to simulate foam and change its UVs to animate it. Also use the depth values coming from the camera depth texture to intensify foam presence around objects that intersects the water surface.
Let's go over each step in detail.

1.Vertex displacement

Lines 126, 127 - pretty self-explanatory. Sample the vertex displacement map in the vertex shader and use its y-values to change the y-value of each vertex.

2.Depth based water color

Line 108 - camera depth texture is available globally to all shaders. All you have to do is declare a variable called _CameraDepthTexture in your shader.
Line 132 - since_CameraDepthTexture is a full-screen texture, sample it using the screen space position of the vertex (stored in the built-in variable screenPos).
Line 133 - convert camera depth texture values from non-linear to linear.
Line 134 - you'll want to know how deep are the depth values relative to the water surface. To find out, you need to know the depth of the water surface which is stored in the w component of the screenPos built-in variable.
Then you just subtract the w component from the linear depth values.
Line 135 - you'll also want to know how deep depth values are compared to our maximum depth (_DeepThreshold), percentage-wise. Use the saturate function to clamp the values in the range [0, 1].
This value will be used to get the gradient from the water surface color to the water color at its deepest end.
Line 136 - same as on Line 135, but this time compute the value you use to get the gradient from the water surface color to the water color when it's near the shoreline.
Line 137 - finally, interpolate _WaterShoreColor, _WaterSurfaceColor and _WaterDeepColor using two lerp functions to get the final depth based water color.

3.Grab-Pass animated fake refraction

Line 54 - store the grab pass into _GrabTexture.
Lines 121, 122 - in the vertex shader, compute the UVs used to project the _GrabTexture.
Line 137 - sample a normal map to make the surface curlier. Also use the built-in variable _Time.y and _NormalSpeed property to animate the normal map.
Line 138 - use a second normal map to fake the refraction. You will use the values of this normal map to distort the grabPassUV you get from the vertex shader.
Lines 139, 140 - arbitrary way to distort the grabPassUV to fake refraction.
Line 142 - sample the _GrabTexture using the grabPassUV so when you'll apply the texture onto the water plane, anything below it will look as if it has been refracted.

4.Planar animated mirror reflection

First, here is the link to the tweaked C# script to get planar reflection
Attach it to the water flat plane and it will reflect just the skybox if you select Nothing in the Reflection Layers dropdown menu, or you can select a specific layer where you have put all the objects you want refracted.
As I mentioned, this script returns a global texture called _WorldReflectionTex, so you just have to attach it once on a single object to get multiple water surfaces.

Line 107 - reference that texture as a uniform variable.
Line 143 - sample _WorldReflectionTex using grabPassUV so it has the same animation and distortion applied.

5.Intersection animated foam

Lines 146, 147 - use noise texture _FoamTex to generate the foam on the water surface. To give it a cartoon-like look, apply a cutoff threshold. Any pixel with a value below the cutoff threshold won't be rendered.
The cutoff threshold is represented by the _FoamCutoff property.
You'll also want to intensify foam presence around an object when it intersects the water surface. To get this effect, modulate the cutoff threshold based on the water depth.
Line 148 - to get a better look, I also use a two-channel texture (red & green) _FoamDistortionTex to distort the foam texture. Sample the texture using its own UVs declared in the Input structure.
As a texture, the x and y values are in the range [0, 1] but we want the value to be in the range [-1, 1]: that's why you have to multiply by 2 and subtract 1 from the sample.
Line 149 - animate foamUV using _FoamSpeed property and apply distortion by using foamDistTex you just sampled.
Line 151 - apply the modulated cutoff threshold to get the final look of the foam.

Textures

Normal Texture

Refraction Distortion Texture







 









Vertex Displacement Texture / Foam Texture



















Foam Distortion Texture


Comments

Popular posts from this blog

GLSL Shader in Maya Part 1

Unity Shader: Spherical Mask Dissolve