Water Shader Updates

First post here. 

Made some updates to my water shader this week!

I started with some vertex movement, with a sin wave with the x and z of the vertex added in order to get a rocking motion over the entire object. The further along in x or z the object is, the stronger the displacement, and because we have the sin wave in -1 – 1 space, this is reversed over time to get even displacement over the whole object. 

I multiplied this by a noise texture to get a nice wavy movement – the displacement will only be applied where the texture is white.

v.vertex.y += (sin(_Time.y * _Speed + v.vertex.x + v.vertex.z) * _Amount * noiseTex);


I then added some textures to get this looking a bit less like programmer art! I made use of the _ST variable available in unity, where we can access the user input tiling and offset from the texture parameter in the format (uv.x, uv.y, offset.x, offset.y). This allowed me to have a more versatile shader that’s easily manipulated  by the user.

sampler2D _EdgeTexture;
float4 _EdgeTexture_ST;

To output this to my fragment shader, I used TRANSFORM_TEX when declaring my uv.

o.uv = TRANSFORM_TEX(v.uv, _EdgeTexture);

I used this with panning in both directions and blended it with my colours to add textures to the body and edge. The edge is multiplied as I want the white in the edge texture to be coloured by the colour2 input, whereas the body is added as I want the main body to be color1 with white on top.

float2 pan_uv = i.uv + _Time.y * _Speed2;
float2 pan_uv2 = i.uv2 + _Time.y * _Speed2;
fixed4 edge = saturate(tex2D(_EdgeTexture, pan_uv).b) * _Color2;
fixed4 body = saturate(tex2D(_MainTexture, pan_uv2).b) + _Color;

I then replaced my straight lerp with a lerp between the edge and body colours added together and the body colour. This meant that my edge always stands out and no greys are introduced, while maintaining the colour the user has input.

fixed4 col = saturate(lerp(edge + body, body, screenDepth));


After that I added in a little fresnel for faked reflections. This isn’t quite finished and still needs some tweaks. Fresnel essentially describes whether you are looking directly at something.  To do this, we check the angle between the camera heading and the object’s normal, using a dot product. The dot product returns 0 when vectors are at right angle, and 1 when the vectors are perpendicular. If this is less than 0, we just return 0 using max(). 

To do this, I got normal in appdata using the semantic, then added another output to my v2f function called viewDir.

float4 normal : NORMAL;

float2 viewDir : TEXCOORD3;

I then got the camera heading by using unity’s inbuilt function ObjSpaceViewDir. This returns the object space direction  from given object space vertex position towards the camera.

o.viewDir = ObjSpaceViewDir(o.vertex);

I then use the dot product as explained above and multiply this with a texture for a more painterly result.

float fresnel = max(0, 1 – saturate(dot(i.viewDir, i.normal)));
float wobbly_fresnel = fresnel * tex2D(_Noise, i.uv).r;


This looked a bit funky – as it turns out, the view direction function returns an unnormalized vector (a vector with length), so the distance from the camera was being taken into consideration. Once I normalized this to get only the direction, I got a much subtler result.



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