Realistic Water Shader with Foam,Droplets,Caustics,Waves

Realistic Water Shader with Foam,Droplets,Caustics,Waves

 

Godot v4.4 + ( tested in 4.5 )

 

Shader code
shader_type spatial;
//v.01
// Completely free to use by Lonely_Studios, please credit if you want.Thanks! 

// water properties
uniform vec3 primary_water_color : source_color = vec3(0.0, 0.1, 0.4);
uniform vec3 secondary_water_color : source_color = vec3(0.0, 0.4, 0.8);
uniform float water_depth = 5.0;
uniform float water_clarity = 0.5;
uniform float edge_scale = 0.1;
uniform float overall_transparency : hint_range(0.0, 1.0) = 0.9;

// Reflections and surface properties
uniform float metallic : hint_range(0.0, 1.0) = 0.1;
uniform float roughness : hint_range(0.0, 1.0) = 0.05;
uniform float specular : hint_range(0.0, 1.0) = 0.5;
uniform float fresnel_power = 5.0;
uniform float reflection_strength = 0.5;

// Wave properties
uniform sampler2D wave_height_map;
uniform sampler2D wave_normal_map1;
uniform sampler2D wave_normal_map2;
uniform float wave_speed = 0.1;
uniform float wave_height = 0.2;
uniform float wave_scale = 10.0;
uniform vec2 wave_direction1 = vec2(1.0, 0.0);
uniform vec2 wave_direction2 = vec2(0.5, 0.5);
uniform float wave_time_scale = 1.0;

// Foam properties
uniform sampler2D foam_texture : hint_default_white;
uniform vec3 foam_color : source_color = vec3(1.0, 1.0, 1.0);
uniform float foam_amount = 0.5;
uniform float foam_cutoff = 0.8;
uniform float shore_foam_distance = 1.0;

// Caustics properties
uniform sampler2D caustics_texture;
uniform float caustics_strength = 0.5;
uniform float caustics_scale = 5.0;
uniform float caustics_speed = 0.05;

// Droplet properties
uniform sampler2D droplet_normal_map;
uniform float droplet_amount : hint_range(0.0, 1.0) = 0.7;
uniform float droplet_size : hint_range(0.1, 5.0) = 1.0;
uniform float droplet_speed : hint_range(0.1, 10.0) = 2.0;
uniform float droplet_strength : hint_range(0.1, 5.0) = 2.0;
uniform float droplet_persistence : hint_range(0.1, 10.0) = 3.0;

// Refraction properties
uniform float refraction_strength = 0.1;

// Depth texture for water depth calculations
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

// Varying variables for passing data between vertex and fragment shaders
varying vec3 world_position;
varying float height_value;
varying vec3 vertex_normal;
varying vec3 binormal;
varying vec3 tangent;
varying vec3 camera_position;

// Utility functions
float fresnel_effect(float amount, vec3 normal, vec3 view) {
    return pow(1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0), amount);
}

// Hash function for random number generation
float hash21(vec2 p) {
    p = fract(p * vec2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}

// Additional hash function for more variation
float hash22(vec2 p) {
    vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

// Function to create ripple droplet shape
float droplet_ripple(vec2 uv, float size, float time_factor) {
    // Distance from center
    float dist = length(uv - 0.5);
    
    // Basic droplet shape
    float droplet = smoothstep(0.5 * size, 0.0, dist);
    
    // Add ripple rings that expand over time
    float ripple_speed = 3.0 * time_factor;
    float ripple = sin(dist * 50.0 - ripple_speed) * 0.5 + 0.5;
    
    // Add second ripple ring for more complexity
    float ripple2 = sin(dist * 30.0 - ripple_speed * 0.7) * 0.5 + 0.5;
    
    // Fade ripple at edges
    ripple *= smoothstep(0.5 * size, 0.0, dist);
    ripple2 *= smoothstep(0.5 * size, 0.0, dist);
    
    // Combine base droplet with ripple effects
    return droplet * (0.6 + 0.2 * ripple + 0.2 * ripple2);
}

void vertex() {
    // Store camera position for fragment shader
    camera_position = CAMERA_POSITION_WORLD;
    
    // Calculate world position for use in fragment shader
    world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
    
    // Sample wave height map for vertex displacement
    vec2 wave_uv = world_position.xz / wave_scale;
    vec2 time_offset = TIME * wave_speed * wave_time_scale * vec2(1.0, 1.0);
    
    // Sample height map at two different offsets and blend
    float height1 = texture(wave_height_map, wave_uv + time_offset * wave_direction1).r;
    float height2 = texture(wave_height_map, wave_uv * 0.5 + time_offset * wave_direction2).r;
    height_value = mix(height1, height2, 0.5) * 2.0 - 1.0; // Convert to -1 to 1 range
    
    // Apply height displacement to vertex
    VERTEX.y += height_value * wave_height;
    
    // Calculate normal, tangent, and binormal for normal mapping
    vertex_normal = normalize(NORMAL);
    // Fix for the .a error - use a simpler approach for binormal calculation
    binormal = normalize(cross(NORMAL, TANGENT.xyz));
    tangent = normalize(TANGENT.xyz);
    
    // Adjust normal based on wave height gradient for more realistic wave shape
    vec2 du = vec2(0.01, 0.0);
    vec2 dv = vec2(0.0, 0.01);
    
    float h_u1 = texture(wave_height_map, (world_position.xz + du) / wave_scale + time_offset * wave_direction1).r;
    float h_u2 = texture(wave_height_map, (world_position.xz + du) * 0.5 + time_offset * wave_direction2).r;
    float h_u = mix(h_u1, h_u2, 0.5) * 2.0 - 1.0;
    
    float h_v1 = texture(wave_height_map, (world_position.xz + dv) / wave_scale + time_offset * wave_direction1).r;
    float h_v2 = texture(wave_height_map, (world_position.xz + dv) * 0.5 + time_offset * wave_direction2).r;
    float h_v = mix(h_v1, h_v2, 0.5) * 2.0 - 1.0;
    
    vec3 grad = vec3((h_u - height_value) / du.x, 1.0, (h_v - height_value) / dv.y);
    NORMAL = normalize(grad);
}

void fragment() {
    // Calculate view direction using the passed camera_position
    vec3 view_dir = normalize(camera_position - world_position);
    
    // Calculate depth for water effects
    float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV).r;
    vec3 depth_world_pos = (INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0)).xyz;
    float depth_distance = distance(depth_world_pos, world_position);
    
    // Calculate water depth factor
    float water_depth_factor = 1.0 - exp(-depth_distance * water_clarity);
    
    // Calculate shore proximity for foam
    float shore_proximity = 1.0 - smoothstep(0.0, shore_foam_distance, depth_distance);
    
    // Calculate wave normals
    vec2 wave_uv = world_position.xz / wave_scale;
    vec2 time_offset = TIME * wave_speed * wave_time_scale * vec2(1.0, 1.0);
    
    // Sample normal maps with different scales and directions
    vec3 normal1 = texture(wave_normal_map1, wave_uv + time_offset * wave_direction1).rgb * 2.0 - 1.0;
    vec3 normal2 = texture(wave_normal_map2, wave_uv * 0.5 + time_offset * wave_direction2).rgb * 2.0 - 1.0;
    
    // Blend normal maps
    vec3 blend_normal = normalize(mix(normal1, normal2, 0.5));
    
    // Create droplet effect
    // Initialize droplet normal and intensity
    vec3 droplet_normal = vec3(0.0, 0.0, 1.0);
    float total_droplet_intensity = 0.0;
    
    // Constants for droplet sizes
    float large_droplet_count = 8.0 * droplet_size;
    float medium_droplet_count = 15.0 * droplet_size;
    float small_droplet_count = 25.0 * droplet_size;
    
    // Large droplets
    {
        vec2 tiled_uv = UV * large_droplet_count;
        vec2 tile_id = floor(tiled_uv);
        vec2 tile_uv = fract(tiled_uv);
        
        float rand_val = hash21(tile_id);
        float rand_val2 = hash22(tile_id);
        
        if (rand_val < 0.3 * droplet_amount) {
            float drop_time_offset = rand_val2 * 10.0;
            float drop_lifetime = 1.0 + rand_val * 2.0 * droplet_persistence;
            float drop_time = fract((TIME * droplet_speed + drop_time_offset) / drop_lifetime);
            
            float drop_fade_in = smoothstep(0.0, 0.1, drop_time);
            float drop_fade_out = 1.0 - smoothstep(0.6, 1.0, drop_time);
            float drop_intensity = drop_fade_in * drop_fade_out;
            
            vec2 drop_center = vec2(0.2 + 0.6 * hash21(tile_id + 0.1), 
                                    0.2 + 0.6 * hash22(tile_id + 0.2));
            vec2 centered_uv = tile_uv - drop_center;
            
            float drop_shape = droplet_ripple(tile_uv, 0.4 + 0.2 * rand_val, drop_time * 3.0);
            
            vec2 droplet_uv = centered_uv + 0.5;
            vec3 large_droplet_normal = texture(droplet_normal_map, droplet_uv).rgb * 2.0 - 1.0;
            
            large_droplet_normal.xy *= 1.5 * droplet_strength;
            large_droplet_normal = normalize(large_droplet_normal);
            
            float large_intensity = drop_intensity * drop_shape * 1.2;
            droplet_normal = mix(droplet_normal, large_droplet_normal, large_intensity);
            total_droplet_intensity += large_intensity;
        }
    }
    
    // Medium droplets
    {
        vec2 tiled_uv = UV * medium_droplet_count;
        vec2 tile_id = floor(tiled_uv);
        vec2 tile_uv = fract(tiled_uv);
        
        float rand_val = hash21(tile_id + vec2(33.33, 66.66));
        float rand_val2 = hash22(tile_id + vec2(11.11, 22.22));
        
        if (rand_val < 0.25 * droplet_amount) {
            float drop_time_offset = rand_val2 * 15.0;
            float drop_lifetime = 0.8 + rand_val * 1.5 * droplet_persistence;
            float drop_time = fract((TIME * droplet_speed + drop_time_offset) / drop_lifetime);
            
            float drop_fade_in = smoothstep(0.0, 0.05, drop_time);
            float drop_fade_out = 1.0 - smoothstep(0.5, 0.9, drop_time);
            float drop_intensity = drop_fade_in * drop_fade_out;
            
            vec2 drop_center = vec2(0.3 + 0.4 * hash21(tile_id + 0.3), 
                                   0.3 + 0.4 * hash22(tile_id + 0.4));
            vec2 centered_uv = tile_uv - drop_center;
            
            float drop_shape = droplet_ripple(tile_uv, 0.3 + 0.15 * rand_val, drop_time * 2.5);
            
            vec2 droplet_uv = centered_uv + 0.5;
            vec3 medium_droplet_normal = texture(droplet_normal_map, droplet_uv).rgb * 2.0 - 1.0;
            
            medium_droplet_normal.xy *= 1.3 * droplet_strength;
            medium_droplet_normal = normalize(medium_droplet_normal);
            
            float medium_intensity = drop_intensity * drop_shape * 0.8;
            droplet_normal = mix(droplet_normal, medium_droplet_normal, medium_intensity);
            total_droplet_intensity += medium_intensity;
        }
    }
    
    // Small droplets
    {
        vec2 tiled_uv = UV * small_droplet_count;
        vec2 tile_id = floor(tiled_uv);
        vec2 tile_uv = fract(tiled_uv);
        
        float rand_val = hash21(tile_id + vec2(77.77, 88.88));
        float rand_val2 = hash22(tile_id + vec2(99.99, 44.44));
        
        if (rand_val < 0.2 * droplet_amount) {
            float drop_time_offset = rand_val2 * 20.0;
            float drop_lifetime = 0.5 + rand_val * droplet_persistence;
            float drop_time = fract((TIME * droplet_speed + drop_time_offset) / drop_lifetime);
            
            float drop_fade_in = smoothstep(0.0, 0.03, drop_time);
            float drop_fade_out = 1.0 - smoothstep(0.4, 0.8, drop_time);
            float drop_intensity = drop_fade_in * drop_fade_out;
            
            vec2 drop_center = vec2(0.4 + 0.2 * hash21(tile_id + 0.5), 
                                   0.4 + 0.2 * hash22(tile_id + 0.6));
            vec2 centered_uv = tile_uv - drop_center;
            
            float drop_shape = droplet_ripple(tile_uv, 0.2 + 0.1 * rand_val, drop_time * 2.0);
            
            vec2 droplet_uv = centered_uv + 0.5;
            vec3 small_droplet_normal = texture(droplet_normal_map, droplet_uv).rgb * 2.0 - 1.0;
            
            small_droplet_normal.xy *= 1.2 * droplet_strength;
            small_droplet_normal = normalize(small_droplet_normal);
            
            float small_intensity = drop_intensity * drop_shape * 0.6;
            droplet_normal = mix(droplet_normal, small_droplet_normal, small_intensity);
            total_droplet_intensity += small_intensity;
        }
    }
    
    // Ensure droplet normal is properly normalized
    droplet_normal = normalize(droplet_normal);
    
    // Combine wave normals with droplet normals
    float final_droplet_influence = min(total_droplet_intensity, 1.0) * droplet_amount * 0.7;
    vec3 final_normal = normalize(mix(blend_normal, droplet_normal, final_droplet_influence));
    
    // Transform normal from tangent space to world space
    mat3 TBN = mat3(tangent, binormal, vertex_normal);
    vec3 world_normal = normalize(TBN * final_normal);
    
    // Calculate fresnel effect for reflections
    float fresnel_factor = fresnel_effect(fresnel_power, world_normal, view_dir);
    
    // Calculate foam
    vec2 foam_uv = world_position.xz / (wave_scale * 0.5);
    vec2 foam_offset1 = TIME * wave_speed * 0.05 * wave_direction1;
    vec2 foam_offset2 = TIME * wave_speed * 0.07 * wave_direction2;
    
    float foam_noise1 = texture(foam_texture, foam_uv + foam_offset1).r;
    float foam_noise2 = texture(foam_texture, foam_uv * 2.0 + foam_offset2).r;
    float foam_blend = mix(foam_noise1, foam_noise2, 0.5);
    
    // Enhance foam near shores and on wave crests
    float height_foam = smoothstep(0.6, 1.0, height_value + 0.5); // Foam on wave crests
    float shore_foam = shore_proximity * foam_amount * 2.0;
    float final_foam = smoothstep(foam_cutoff, 1.0, foam_blend * (shore_foam + height_foam));
    
    // Calculate caustics
    vec2 caustics_uv = depth_world_pos.xz / caustics_scale;
    vec2 caustics_offset = TIME * caustics_speed * vec2(0.3, 0.4);
    float caustics1 = texture(caustics_texture, caustics_uv + caustics_offset).r;
    float caustics2 = texture(caustics_texture, caustics_uv * 1.5 - caustics_offset * 0.8).r;
    float caustics_blend = max(caustics1, caustics2) * caustics_strength;
    
    // Apply caustics only to underwater areas
    caustics_blend *= (1.0 - shore_proximity) * (1.0 - water_depth_factor);
    
    // Calculate refraction offset
    vec2 refraction_offset = world_normal.xz * refraction_strength;
    vec2 refracted_uv = SCREEN_UV + refraction_offset;
    
    // Sample screen texture with refraction offset
    vec3 refracted_color = texture(SCREEN_TEXTURE, refracted_uv).rgb;
    
    // Apply caustics to refracted color
    refracted_color += caustics_blend * vec3(0.5, 0.7, 1.0);
    
    // Blend between deep and shallow water colors based on depth
    vec3 water_color = mix(secondary_water_color, primary_water_color, water_depth_factor);
    
    // Blend water color with refracted color based on depth
    vec3 final_color = mix(refracted_color, water_color, water_depth_factor * overall_transparency);
    
    // Add foam
    final_color = mix(final_color, foam_color, final_foam);
    
    // Apply fresnel reflections
    final_color = mix(final_color, vec3(1.0), fresnel_factor * reflection_strength);
    
    // Apply final color and material properties
    ALBEDO = final_color;
    METALLIC = metallic;
    ROUGHNESS = roughness - (final_droplet_influence * 0.05); // Reduce roughness where droplets are
    SPECULAR = specular + (final_droplet_influence * 0.2); // Enhance specular where droplets are
    
    // Apply normal mapping
    NORMAL_MAP = final_normal;
    
    // Set transparency
    ALPHA = min(overall_transparency + fresnel_factor * 0.2, 1.0);
}
Tags
caustics, droplets, foam, rain, shader, Spatial, water
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

Foam Edge Water Shader

Water with foam and depth

Water Shader for Realistic Look

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments