Keeping Water Out of a Boat

Here is a long overdue post I wanted to make about the game I worked on for Global Game Jam 2017, Wrath of Poseidon. When we first placed the boat in the water, the water plane clipped right though it, as seen below. This results in a pretty sad looking boat, so we needed to find a way to make sure the water is only drawn around the boat.

water clipping through boat

The solution I thought of was to create a "cover" over the boat, above the water level. The cover would be fully transparent, but still be drawn to the depth buffer. First the boat would be drawn, then the cover, and lastly the water. When the water was drawn it would fail the depth test in the areas under the cover, leaving the inside of the boat dry.

The cover mesh

Here we have the cover mesh, without the transparent material applied. Note that the cover is flat instead of following the top surface of the boat. This avoids an issue where otherwise, from certain camera angles, the cover could obscure water that is outside of the boat.

cover without material

Transparent shader for the cover

Here is the code for the shader, which has three parts of interest. The first is "Queue"="Geometry+1", which causes the cover to be drawn after other geometry; we would end up with a hole in the boat if the cover is drawn first. The second is Blend SrcAlpha OneMinusSrcAlpha, which enables alpha blending, and the third is return fixed4(0, 0, 0, 0);, which uses a fully transparent color for the cover.

Shader "Custom/BoatWaterOcclude"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+1" }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            float4 vert (appdata v) : SV_POSITION
            {
                return UnityObjectToClipPos(v.vertex);
            }

            fixed4 frag () : SV_Target
            {
                return fixed4(0, 0, 0, 0);
            }
            ENDCG
        }
    }
}

Draw the water after the cover

The last step is to ensure the water is drawn after the cover. Since the cover shader uses Geometry+1 for the queue, we will just use Geometry+2 in the water shader. The rest of the water shader has been omitted since it is not relevant.

Tags { "RenderType"="Opaque" "Queue"="Geometry+2" }

Results

Here is the finished product. The boat is now seaworthy!

finished result

Cheers,

Jeff

Previous Post Fixing metadata.wsdl