Cubemap Reflective Water Surface

Hey guys,

A reflective water shader that I modified and used for my own project:

 

The original idea was to create a shader that by inputing an HDRI could simulate some sort of natural water with reflection and lighting physics that looks like real water but in 2d.

In the end I found that it works better with cubemap than HDRI, that was originally supported in godot.

After some optimization. now it could run 120fps on my old m1 macbook laptop(not owning a windows laptop these days is really a headache sometimes, but talking about the quality of windows11 today…).

 

iChannel0 would be your screen texture you can put anything under the shader, then it will look like under the water surface.

iChannel1 need to be an rgb noise(if you dont have it you can use the third pic in the screenshots, or you can generate one yourself)

iChannel2 is where you put your cubemap(If you don’t have any on hand, you can download one from here: 

https://www.humus.name/index.php?page=Textures&start=0

this guy has a lot cool stuff on his website :)

 

BTW:

This shader only runs well in Forward+!!(DEMO project below since it’s rendered on web it’s in compatible mode so the reflection was a bit broken)

 

Here’s my project:

Tranquil Koi: https://store.steampowered.com/app/3184080/Tranquil_Koi/

Original shader:

https://www.shadertoy.com/view/ldB3Wd

Shader code
shader_type canvas_item;

// Uniforms - External parameters
uniform sampler2D iChannel0 : hint_screen_texture, repeat_disable, filter_nearest; // Screen texture
uniform sampler2D iChannel1; // Animation texture (contains position, time offset, speed parameters)
uniform samplerCube iChannel2; // Cubemap for reflection
uniform vec4 tint_color : source_color; // Tint color

// ========== Utility Functions ==========

// Bias function - Adjusts curve offset
float bias(float x, float b) {
    return x / ((1.0 / b - 2.0) * (1.0 - x) + 1.0);
}

// Gain function - Adjusts curve gain
float gain(float x, float g) {
    float t = (1.0 / g - 2.0) * (1.0 - (2.0 * x));
    return x < 0.5 ? (x / (t + 1.0)) : (t - x) / (t - 1.0);
}

// Degamma correction - Convert from linear to sRGB space
vec3 degamma(vec3 c) {
    return pow(c, vec3(2.2));
}

// Gamma correction - Convert from sRGB to linear space
vec3 gamma(vec3 c) {
    return pow(c, vec3(1.0 / 1.5));
}

// Grid division count
#define DIVS 4

void fragment() {
    // Calculate UV coordinates (flip Y-axis and correct aspect ratio)
    vec2 uv = SCREEN_UV;
    uv.y = 1.0 - uv.y;
    uv.x *= (1.0 / SCREEN_PIXEL_SIZE).x / (1.0 / SCREEN_PIXEL_SIZE).y;

    float h = 0.0; // Water surface height
    float time = TIME; // Current time

    // Iterate through all wave source points
    for (int iy = 0; iy < DIVS; iy++) {
        for (int ix = 0; ix < DIVS * 2; ix++) {
            // Read wave source parameters from animation texture
            vec4 t = texture(iChannel1, vec2(float(ix), float(iy)) * (4.0 / 256.0), 100.0);

            // Calculate wave source position (with animation offset)
            vec2 p = vec2(float(ix), float(iy)) * (1.0 / float(DIVS - 1));
            p += (0.75 / float(DIVS - 1)) * (t.xy * 2.0 - 1.0);

            // Calculate distance to current pixel
            vec2 v = uv - p;
            float d = dot(v, v);
            d = pow(d, 0.7);

            // Wave propagation calculation
            float life = 10.0; // Wave lifecycle
            float n = time * 2.0 * (t.w + 0.05) - t.z * 10.0; // Normalized time
            n *= 0.01 + t.w; // Apply speed factor
            n = mod(n, life * 0.5 + t.z * 3.0 + 2.0); // Loop time

            float x = d * 99.0; // Scaled distance
            float T = x < (2.0 * PI * n) ? 1.0 : 0.0; // Wave front threshold
            float e = max(1.0 - (n / life), 0.0); // Decay factor
            float F = e * x / (2.0 * PI * n); // Amplitude

            // Sine wave calculation
            float s = sin(x - (2.0 * PI * n) - PI * 0.5);
            s = s * 0.5 + 0.5;
            s = bias(s, 0.5);

            // Accumulate wave height
            s = (F * s) / (x + 1.1) * T;
            h += s * 100.0 * (0.5 + t.w);
        }
    }

    // Calculate water surface normal (for reflection and refraction)
    vec3 n = vec3(dFdx(h), 15.0, dFdy(h));
    n = normalize(n);

    // Calculate view vector
    vec3 e = normalize(vec3(-uv.y * 1.0 + 1.0, 1.0, uv.x * 1.0 - 1.0));

    // Calculate reflection vector and reflection color
    vec3 rv = reflect(-e, n);
    vec3 reflect_color = degamma(texture(iChannel2, rv).xyz);

    // Calculate refraction vector and LOD level
    vec3 fn = refract(vec3(0.0, 1.0, 0.0), n, 2.5);
    float lod = length(fn.xz) * 10.0;

    // Apply refraction and sample background
    vec3 c = degamma(textureLod(iChannel0, SCREEN_UV + fn.xz * 0.05, lod).xyz);

    // Mix reflection and refraction based on water height
    c *= 1.0 - h * 0.0125;
    c += reflect_color * 0.2;

    // Apply lighting
    vec3 L = normalize(vec3(1.0, 1.0, 1.0));
    float dl = max(dot(n, L), 0.0) * 0.7 + 0.3;
    c *= dl;

    // Apply gamma correction and output
    c = gamma(c);
    COLOR = vec4(c, 1.0);
}
Live Preview
Tags
2d, cubemap, dripples, reflection, water, wave, wipples
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments