Better Water Animation, Light Support & Fixed Refraction

Since writing the last blog post I have carried out some extra work on the water shader, I’ve improved the shader’s animation a little by using a noise texture to animate it instead of using a sine wave, this creates a more realistic wave animation; I have also worked on adding light support to the shader, although the results aren’t very pretty, it is the first time I’m using lights in a fragment shader.


Refraction Fix

Although first I would like to discuss the refraction shader which I have worked on in my last blog post; in the post I describe an issue I have run into which has caused my texture to be displayed as a black and white texture instead of looking like a glass window. The fix for this is very simple, but the error is caused due to inexperience with shaders.

Fixed refraction applied to an object.
// As seen in previous post
fixed4 frag (v2f i) : SV_Target
{
    ...
    float grab = tex2Dproj(_GrabTexture, i.screenUV);

    return grab;
}

// Fixed version
fixed4 frag (v2f i) : SV_Target
{
    ...
    float4 grab = tex2Dproj(_GrabTexture, i.screenUV);

    return grab;
}

As seen in the image above, the shader is now working properly and it can be quite hard to spot the issue when reading through the shader so I have took a code snipped of where the error has been made; instead of passing the texture into a float4 (which stores 4 channels of colour data, rgba) I have passed the texture into a float (only stores 1 channel of data). For this reason, the texture displayed as black and white, once again due to inexperience I wasn’t aware of this simple mistake.


Improving Water Animation

After implementing the simple wave animation in the original shader, I didn’t like how it looked like and I wanted to improve it straight away; while working on my personal project I have stumbled upon Linder Reid’s portfolio where he describes how he has created a simple water shader in Unity. I found his wave animation implementation interesting, as he uses a noise texture, and I gave it a go implementing it into the water shader in my project.

Improved water animation with use of noise texture.
// In vertex function
float noiseSample = tex2Dlod (_NoseTex, float4(v.uv.xy, 0, 0);
o.vertex.y += sin(_Time * _WaveSpeed * noiseSample) * _WaveAmp;
o.vertex.x += cos(_Time * _WaveSpeed * noiseSample) * _WaveAmp;

The implementation of the improved water was quite simple to implement; the implementation makes use of a Perlin noise texture, originally created by Ken Perlin in 1983, I have described Perlin noise further in my previous blog post. First the noise is sampled into a float using tex2Dlod function which “performs a texture lookup with a specified level of detail…” (Nvidia, tex2Dlod, n.d.), where in my case the coordinates are the x and y coordinates of the object’s uv.

The reason why sine is used in calculation is because it “creates the oscillating up-and-down motion” (Reid, Simple Water Shader in Unity, 2017) of water, the additional parameters control how big the oscillation should be; all of those variables can be controlled by the user from inspector.

Next the noise texture is multiplied by the time value, the speed of wave, the noise sample and the wave amplitude, which results in a nicer water animation but presents another issue. As the shader wasn’t originally designed to be animated, the foam check is broken and doesn’t update as the vertices move around.

In order to fix the clipping seen in the gif above, some modification needs to be done in the vertex shader; first the noise calculation needs to be moved up in the function, when I write my shader code I add it to the bottom but this calculation needs to be done before anything else, so when it comes to the foam calculation the vertex is in the right position.

// Old implementation
vertexOutput vert(vertexInput v)
{
    vertexOutput o;
    float4 worldPos = mul(unity_ObjectToWorld, v.vertex);

    o.vertex = mul(UNITY_MATRIX_VP, worldPos);
    o.screenPosition = ComputeScreenPos(o.vertex);
    o.noiseUV = TRANSFORM_TEX(v.uv, _SurfaceNoise);
    o.distortUV = TRANSFORM_TEX(v.uv, _SurfaceDistortion);
    o.viewNormal = COMPUTE_VIEW_NORMAL;

    o.col = float4(diffuseReflection, 1.0);
    o.vertex = UnityObjectToClipPos(v.vertex);

    float noiseSample = tex2Dlod(_NoiseTex, float4(v.uv.xy, 0, 0));
    o.vertex.y += sin(_Time * _WaveSpeed * noiseSample) * _WaveAmp;
    o.vertex.x += cos(_Time * _WaveSpeed * noiseSample) * _WaveAmp;

    return o;
}
// Improved implementation
vertexOutput vert(vertexInput v)
{
    vertexOutput o;
    float4 worldPos = mul(unity_ObjectToWorld, v.vertex);

    o.vertex = mul(UNITY_MATRIX_VP, worldPos);
    
    // Wave calculation using perlin noise.
    float noiseSample = tex2Dlod(_NoiseTex, float4(v.uv.xy, 0, 0));
    o.vertex.y += sin(_Time * _WaveSpeed * noiseSample) * _WaveAmp;
    o.vertex.x += cos(_Time * _WaveSpeed * noiseSample) * _WaveAmp;

    o.screenPosition = ComputeScreenPos(o.vertex);
    o.noiseUV = TRANSFORM_TEX(v.uv, _SurfaceNoise);
    o.distortUV = TRANSFORM_TEX(v.uv, _SurfaceDistortion);
    o.viewNormal = COMPUTE_VIEW_NORMAL;

    o.col = float4(diffuseReflection, 1.0);
    //o.vertex = UnityObjectToClipPos(v.vertex);

    return o;
}

The final change that is required is the line using UnityObjectToClipPos, this line “transforms a point from object space to the camera’s clip space in homogeneous coordinates” (Unity, Built-in shader helper functions, 2019) which is one of the lines that causes the foam check to break; commenting this line out results in a water shader which updates it’s foam as it animates the waves.

Water foam is now calculated properly, and does not appear to be clipping through the sand.

There wasn’t any tutorial I have searched up to get this working properly, it was purely experimenting on my own and moving some of the code around and commenting some of the lines; I knew I had to move the noise calculation line, so that it is the first thing calculated but the effect still didn’t work properly so I went out and commented each line after that until the effect seen above.

I’m quite happy that I got this working the way I wanted, as it shows that I have made some progress in shader writing and I don’t think I would’ve been able to fix it when I have started this final project. This water shader now looks very good in my opinion, it doesn’t look like a super realistic water shader, but it has a nice style to it which can be used in more cartoony looking video games.


Adding Light

Finally, this week I have set out to add light to the shader; with the help Cg Programming book available on Wikibooks I have been able to get the shader to interact with Unity’s lights. The results do not look realistic, but this is my first time creating a shader which is affected by the lights inside of the scene.

The water shader can only be affected by the directional light that is in the scene, it will not react to any point lights as I haven’t got that far in the chapter about diffuse reflection. I will continue working on this, to try and achieve a better result.

The way the lights work in the shader currently is by getting the light source direction from Unity’s uniform parameter _WorldSpaceLightPos0 and the light colour parameter _LightColor0, these can be calculated with the surface normal, which is accessible from vertex input v.uv.

// Light calculation
float4x4 modelMatrix = unity_ObjectToWorld;
float4x4 modelMatrixInverse = unity_WorldToObject;

float3 normalDirection = normalize(_WorldSpaceLightPos0.xyz);
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float3 diffuseReflection = _LightColor0.rgb * max(0.0, dot(normalDirection, lightDirection));

o.col = float4(diffuseReflection, 1.0);

The colour value that is calculated in vertex function is then stored the in the col parameter which can be accessed in the fragment shader, and in order to get the light to affect the colour of the water it needs to be multiplied with the water colour and the foam; this can simply be done by taking the final result of frag calculation and multiplying it by col value.

Current state of the water shader with added directional light support.

Bibliography

Nvidia, n.d. tex2Dlod. [Online]
Available at: https://developer.download.nvidia.com/cg/tex2Dlod.html
[Accessed 01 04 2020].

Reid, L., 2017. Simple Water Shader in Unity. [Online]
Available at: https://lindenreid.wordpress.com/2017/12/15/simple-water-shader-in-unity/
[Accessed 02 04 2020].

Wikibook, n.d. Diffuse Reflection. [Online]
Available at: https://en.wikibooks.org/wiki/Cg_Programming/Unity/Diffuse_Reflection
[Accessed 02 04 2020].

Unity, 2019. Built-in shader helper functions. [Online]
Available at: https://docs.unity3d.com/Manual/SL-BuiltinFunctions.html
[Accessed 05 04 2020].

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s