How to effectively use depthTexture for water toon shader?

Here is a csb to my demo: CodeSandbox - CodeSandbox
EDIT: This csb is updated, check edit and comment below


I have been trying to create a stylized/toon/NPR water shader for fun/to learn. I started with looking at Roy’s tutorial but have been wanting to recreate Alexander’s stylized water. These are both for Unity so translating them into the three.js environment is a bit of a struggle.

It is a broad topic but the main focus to this thread is about depth textures and how to handle them. In Alexander’s, he uses the scene depth to have a constant shader that isn’t dependent on the camera’s viewing position/angle. This is the image he showcased of the node graph

I am trying to recreate this in three but not sure where to start. The code is in the demo but I will add the fragment shader here for conveniance.

uniform sampler2D u_depthTexture; // The WebGL depth texture
uniform vec2 u_winRes; // Viewport resolution
uniform float u_near;
uniform float u_far;

varying vec4 v_position; // The view position of the fragment
varying vec4 v_viewPosition; // The view position of the fragment
varying vec4 v_worldPosition; // The world position of the fragment

float linearizeDepth(float d,float zNear,float zFar) {
    return zNear * zFar / (zFar + d * (zNear - zFar));

void main() {
    vec2 screenSpace = gl_FragCoord.xy / u_winRes.xy;
    float sceneDepth = texture2D(u_depthTexture, screenSpace).r;
    sceneDepth = linearizeDepth(sceneDepth, u_near, u_far), 0.0, 1.0;

    float surfaceDepth = length(cameraPosition -;

    float thing = clamp(1.0 - (sceneDepth - surfaceDepth), 0.0, 1.0);
    thing = pow(thing, 10.0);

    vec3 water = vec3(0.4, 0.6, 0.7);
    vec3 waterdeep = vec3(0.2, 0.4, 0.6);

    vec3 finalCol;
    finalCol = mix(waterdeep, water, thing);
    gl_FragColor = vec4(finalCol, 1.0);

EDIT: I managed to get the world-space-depth section of the Alexander’s article working. Can be seen in the csb I sent above. I’ll send a reply to this message to explain what I did since I don’t want to make this too long.

1 Like

Okay so as my edit above, I took some time to try to understand some of the concepts and essentially cloned the shadergraph from the image into the fragment shader. It can be seen on my csb but i’ll put it here because that csb will probably change in the future.

// .. bunch of initialised stuff
void main() {

    vec3 worldPosition =;
    vec3 worldViewDirection = - cameraPosition;
    vec2 screenSpace = gl_FragCoord.xy / u_winRes.xy;

    vec3 deepWaterColor = vec3(0.36, 0.46, 1.00);
    vec3 shallowWaterColor = vec3(0.70, 0.83, 1.00);
    vec3 finalCol;

    float sceneDepth = texture2D(u_depthTexture, screenSpace).r;
    float eyeDepth = linearizeDepth(sceneDepth, u_near, u_far);
    float distToWater = gl_FragCoord.z / gl_FragCoord.w;
    // float waterDepth = 1.0 - depthControl((eyeDepth - distToWater)); <- old way

    // World-space water depth
    vec3 wDirDivide = worldViewDirection / distToWater;
    vec3 wDirMultiply = wDirDivide * eyeDepth;
    vec3 wDirAdd = wDirMultiply + cameraPosition;
    vec3 wDirSubtract = worldPosition - wDirAdd;

    float constWaterDepth = clamp(1.0 - wDirSubtract.g, 0.0, 1.0);
    constWaterDepth = pow(constWaterDepth, 4.0);

    finalCol = colorizeWater(deepWaterColor, shallowWaterColor, constWaterDepth);
    gl_FragColor = vec4(finalCol, 1.0);

So it works from what I can see but I have no idea what it is doing. So if anyone could answer to how it is working then please do!

TL;DR Mainly my questions are:

  1. How does the 4 lines of code work (where I commented)?
  2. Why do I have to grab the .y / .g compnonent from wDirSubtract? (Im assuming to get y-height only for the water depth)
  3. Why is distToWater calculated as gl_FragCoord.z / gl_FragCoord.w? I tried reading some articles but couldn’t really make it sink in.


Why is distToWater calculated as gl_FragCoord.z / gl_FragCoord.w ?