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.


Using Custom Vertex Streams in Unity Shaders

Custom vertex streams are a nice new addition to Unity that allows you to access per particle data in shaders. This is really nice for velocity or lifetime based shader effects.

To set it up in your particle system, go to the rendering tab and enabled custom vertex streams, then add the data that you want. In brackets, it shows the semantic that this is packed into.


Declare the semantic in your appdata, making sure that you’re using the right data type. As AgePercent is packed into TEXCOORD0, normally used for particle UVs, it defaults to a float2. Not changing this caught me out!

float4 uv : TEXCOORD0;

This then needs to go into your v2f struct so you can declare the variables you’ll want to use. For the new variable (in my case age), use a new TEXCOORD.

float2 uv : TEXCOORD0;
float age : TEXCOORD1;

This can then be output from your vertex shader, using the variable name you declared in the struct and the data from appdata that matches with the information in the custom vertex streams dropdown.

o.age = v.uv.z;

This can then be used to do whatever you want in your fragment shader!

fixed4 col = i.age;

As you can see below, the particle colour is equal to the age percentage of the particle – going from 0 when it is born to 1 when it dies.


Pretty excited to see what I can do with this!

Water Shader

Wiping your PC’s data to fix it is a good excuse to start a new project right? 😉

I began working on a stylized water shader in Unity this morning. I’m aiming for something like the images below, with depth based texture effects, reflection, refraction, fresnel and a bit of movement.

Water Moodboard

Render Type and Blend Mode

I started off by making a simple transparent shader. I used unity’s unlit preset as this will allow me to add additional outputs to the vertex to fragment function which I’ll need for getting things like screen position later.

For the blend mode I’ve set it to use traditional transparency. The generated colour is multiplied by its own alpha, and the colour already on screen is multiplied by one minus the generated colour’s alpha. This gives us traditional layered transparency where the two values add up to one. The higher the value in the source alpha, the more opaque the generated colour will appear.

I might change this to additive later depending on the type of look I want to achieve.

Tags { “RenderType”=”Transparent” “Queue” = “Transparent”}

Blend SrcAlpha OneMinusSrcAlpha


Depth Fade

The depth fade should colour pixels that are near geometry that intersects with the water plane.

The first thing I needed to do to was get the depth texture that is already being rendered as part of the inbuilt forward rendering pipeline in Unity.

uniform sampler2D _CameraDepthTexture;

After this, I added a new output from my vertex struct that would allow me to get the object’s screen position.

float4 screenPos : TEXCOORD1;

I used this in the vertex function (v2f) to get screen position, using unity’s built in function from UnityCG.cginc. This takes the vertices clip space position and uses it to work out the position on screen as a screenspace texture coordinate.

I get the clip space position by using UnityObjectToClipPos, which is in the shader by default anyway.  This transforms a position in 3D object space to the 2D clip space of the camera by multiplying it with the model, view and projection matrices.

o.vertex = UnityObjectToClipPos(v.vertex);

o.screenPos = ComputeScreenPos(o.vertex);

I then use in fragment shader to get the final colour.

Depth is computed using the LinearEyeDepth function, which when given the rendered depth texture calculates the depth between the camera each pixel. To sample the depth texture, I used an hlsl macro and gave it the screen position as a uv.

To convert this to something we can use with our objects, I took the screen position of the object (represented as a float in the 4th homogeneous coordinate) and subtracted that from the depth, allowing me to compare the depth of the object with the plane I’ve generated. 

half depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
half screenDepth = depth – i.screenPos.w;
fixed4 col = saturate(lerp(_Color, _Color2, screenDepth));

This is what screen depth looks like, represented as red = 0 and green = 1. Green are pixels who’s depth is 1 as no other pixels need to be rendered behind it.


There’s a wee gotcha here – remember to saturate (clamp 0 -1) the final colour value, otherwise you’ll end up with negative numbers inverting your colour. I think this is to do with the screen space position still being a humongous coordinate and not a screen space one, so I’ll have a look at fixing that properly.


Here’s the final result of the depth! Next I’m going to look at combining this with a texture to generate some nice edges on objects.water5