/water-caustics-shader-unity

Water caustics shader with triplanar projection, made in Unity for URP

Primary LanguageHLSL

Water Caustics Shader

Water Caustics Shader implemented with Shader Graph in Unity 2021.3.10f1

Screenshots

Picture

7.mp4

Table of Content

Resources

Scene Setup

  • Built a basic pool and put some primitive objects inside.
  • Used the stlylized skybox to make the scene look more polished.

Picture

HLSL

Main Light

  • Based on the example for implementing custom lighting in Shader Graph, we define the custom HLSL function to get the main light.
  • SHADERGRAPH_PREVIEW will be true while developing the shader in the Shader Graph node editor.
void MainLight_half(
    in float3 WorldPos,
    out half3 Direction,
    out half3 Color,
    out half DistanceAtten,
    out half ShadowAtten
)
{
    #if SHADERGRAPH_PREVIEW
        Direction = half3(0.5, 0.5, 0);
        Color = 1;
        DistanceAtten = 1;
        ShadowAtten = 1;
    #else
        #if SHADOWS_SCREEN
            half4 clipPos = TransformWorldToHClip(WorldPos);
            half4 shadowCoord = ComputeScreenPos(clipPos);
        #else
            half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
        #endif

        Light mainLight = GetMainLight(shadowCoord);
        Direction = mainLight.direction;
        Color = mainLight.color;
        DistanceAtten = mainLight.distanceAttenuation;
        ShadowAtten = mainLight.shadowAttenuation;
    #endif
}

UV Rotation

float2 Unity_Rotate_Degrees_float(float2 UV, float2 Center, float Rotation)
{
    Rotation = Rotation * (3.1415926f/180.0f);
    UV -= Center;

    float s = sin(Rotation);
    float c = cos(Rotation);

    float2x2 rMatrix = float2x2(c, -s, s, c);

    rMatrix *= 0.5;
    rMatrix += 0.5;
    rMatrix = rMatrix * 2 - 1;

    UV.xy = mul(UV.xy, rMatrix);
    UV += Center;

    return UV;
}

Triplanar Projection

  • Based on the HLSL code from the Shader Graph Node to do Triplanar Projection.
  • The modifications for this include passing in a Speed and Rotation for the UVs.
  • The offset will be calculated with Time and Speed.
  • The rotation will be used with the previously defined HLSL function.
  • This returns a projection based on the world position of the vertices, using each plane as an UV mapping.
    • This means the projection will always be the same, no matter the shape or position of the object.
    • There will be a blending done using the Normals orientation and a blend parameter.
void TriplanarProjection_float(
    in Texture2D Texture,
    in SamplerState Sampler,
    in float3 Position,         // world space
    in float3 Normal,           // world space
    in float Tile,
    in float Blend,

    // UV manipulation
    in float Speed,
    in float Rotation,

    out float4 Out
)
{
    float3 Node_UV = Position * Tile;

    // animate UVs
    float Offset_UV = _Time.y * Speed;

    float3 Node_Blend = pow(abs(Normal), Blend);
    Node_Blend /= dot(Node_Blend, 1.0);

    float4 Node_X = SAMPLE_TEXTURE2D(Texture, Sampler, Unity_Rotate_Degrees_float(Node_UV.zy, 0, Rotation) + Offset_UV);
    float4 Node_Y = SAMPLE_TEXTURE2D(Texture, Sampler, Unity_Rotate_Degrees_float(Node_UV.xz, 0, Rotation) + Offset_UV);
    float4 Node_Z = SAMPLE_TEXTURE2D(Texture, Sampler, Unity_Rotate_Degrees_float(Node_UV.xy, 0, Rotation) + Offset_UV);

    Out = Node_X * Node_Blend.x + Node_Y * Node_Blend.y + Node_Z * Node_Blend.z;
}

Shader Graph

Caustics Tiling and Speed

  • Define a Custom Function Node using the Triplanar Projection we defined in HLSL.
  • Set a Caustics Texture, a Tiling and a Speed for the Offset of the UVs.

Picture

Caustics Texture Rotation

  • Make one of the Caustic Textures rotated by the amount determined by Caustic Texture Rotation.

Picture

Caustics Distortion

  • Use the Caustics Distortion Speed multiplied by Time, to offset a Vector2 composed by the RG channels of the World Position of the vertices.

  • Divide by Caustics Distortion Factor, to further modify the distortion.

  • Use Caustics Distortion Scale to control the scale of the noise.

Picture

5.mp4

Caustics Distorted Triplanar

  • Use the distorted World Position of the vertices as input to sample the Triplanar Projection.

Picture

Caustics End Result

  • The end result simulates the caustics of the water surface, but a much lower computational cost.

Picture

1.mp4

Additive Caustics

  • Add the caustics color to a primary Texture, to overlay on top of whatever color the object has.

Picture

6.mp4

Oclussion

  • Use a Custom Function Node to execute the MainLight HLSL function we wrote earlier.
  • Get the Direction, Color, Distance Attenuation and Shadow Attenuation for the main light.
  • Calculate a simple Lambert shading using the dot product of the light direction and the Normal Vector.
  • Multiply the Distance and Shadow Attenuations by the Color of the light.
  • Multiply everything together to calculate a colorized oclussion mask for the caustics.

Picture

Oclussion Screenshots

  • Using different angles of light.
8.mp4

Picture Picture Picture

Global Illumination

  • Use the Baked GI Node to access the Ambient/Global Illumination from the Scene.
  • Multiply it by the Albedo color coming from the Main Texture.

Picture

10.mp4

Complete Graph

  • Multiply the Oclussion by the Caustics to mask it out.
  • Multiply the Ocluded Caustics by the Albedo + Global Illumination.

Picture