Godot Version
Godot 4.6.1
Question
Hi, I’m developing a water shader with SSR refractions for my 3D game.
The problem is that when I start the game, the water SSR refraction looks strange. The refraction disappears when I move the player upwards or left to right.
shader_type spatial;
// === Couleur & Texture ===
uniform vec4 water_color: source_color;
uniform vec4 fresnel_color: source_color;
uniform vec4 depth_color: source_color;
uniform float roughness = 0.05;
// === Normalmap ===
uniform float normalmap_a_str: hint_range(0.0, 1.0, 0.01) = 0.5;
uniform float normalmap_b_str: hint_range(0.0, 1.0, 0.01) = 0.25;
uniform sampler2D normalmap_a: hint_normal;
uniform sampler2D normalmap_b: hint_normal;
uniform vec2 movement_direction = vec2(0.2, 0.1);
uniform float movement_strength = 0.12;
// === Vagues ===
uniform sampler2D wave;
uniform float time_scale: hint_range(0.0, 0.2, 0.005) = 0.025;
uniform float noise_scale = 10.0;
uniform float height_scale = 0.05;
// === Fresnel ===
uniform float fresnel_scale = 0.05;
uniform float near = 1.0;
uniform float far = 100.0;
// === UV ===
uniform float uv_scale = 1.0;
// === Profondeur ===
uniform float depth_distance: hint_range(0.0, 20.0, 0.1) = 2.0;
uniform float beers_law: hint_range(0.0, 20.0, 0.1) = 1.0;
uniform float water_transparency: hint_range(0.0, 1.0, 0.01) = 0.1;
// === Réfraction ===
uniform float refraction_strength: hint_range(0.0, 8.0, 0.01) = 0.05;
// === SSR (Screen Space Reflections) ===
uniform float ssr_intensity: hint_range(0.0, 1.0, 0.01) = 0.6;
uniform float ssr_distortion: hint_range(0.0, 0.1, 0.001) = 0.02;
// === Textures système ===
uniform sampler2D depth_texture: hint_depth_texture, repeat_disable, filter_nearest;
uniform sampler2D screen_texture: hint_screen_texture, repeat_disable, filter_nearest;
varying float height;
varying vec3 world_pos;
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
height = texture(wave, world_pos.xz / noise_scale + TIME * time_scale).r;
VERTEX.y += height * height_scale;
}
float fresnel(float amount, vec3 normal, vec3 view) {
return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0)), amount);
}
float edge(float depth) {
depth = 2.0 * depth - 1.0;
return near * far / (far + depth * (near - far));
}
void fragment() {
// === Profondeur de bord ===
float z_depth = edge(texture(depth_texture, SCREEN_UV).x);
float z_pos = edge(FRAGCOORD.z);
float z_dif = z_depth - z_pos;
// === Mouvement des UVs (normalmaps) ===
vec2 uv = UV * uv_scale;
vec2 uv_movement = movement_direction * TIME * movement_strength;
// === Blend des normalmaps ===
vec3 normalmap = texture(normalmap_a, UV + uv_movement).rgb * normalmap_a_str;
normalmap += texture(normalmap_b, UV - uv_movement).rgb * normalmap_b_str;
// === Normale perturbée pour la réfraction ===
vec3 ref_normalmap = normalmap * 2.0 - (normalmap_a_str + normalmap_b_str);
vec3 ref_normal = mix(NORMAL, TANGENT * ref_normalmap + BINORMAL * ref_normalmap + NORMAL * ref_normalmap, 1.0);
vec2 ref_ofs = SCREEN_UV - ref_normal.xy * refraction_strength;
// === Screen Space Reflection (SSR) ===
// 1. Réfléchir le vecteur VIEW autour de la normale (espace vue)
vec3 ssr_normal = normalize(NORMAL + ref_normalmap * ssr_distortion * 10.0);
vec3 reflect_dir = reflect(-VIEW, ssr_normal);
// 2. Projeter la direction réfléchie en espace clip
vec4 reflect_clip = PROJECTION_MATRIX * vec4(reflect_dir, 0.0);
vec2 reflect_uv = reflect_clip.xy / reflect_clip.w * 0.5 + 0.5;
// 3. Atténuer vers les bords pour éviter les artefacts
vec2 edge_fade = smoothstep(0.0, 0.1, reflect_uv) * smoothstep(1.0, 0.9, reflect_uv);
float ssr_fade = edge_fade.x * edge_fade.y;
// 4. Échantillonner la réflexion
vec3 ssr_color = textureLod(screen_texture, reflect_uv, ROUGHNESS * 2.0).rgb;
// === Depth & Screen textures ===
float depth_clean = textureLod(depth_texture, SCREEN_UV, 0.0).r;
float depth = textureLod(depth_texture, ref_ofs, 0.0).r;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
vec3 ndc_clean = vec3(SCREEN_UV * 2.0 - 1.0, depth_clean);
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = -view.z + VERTEX.z;
vec4 world;
if (linear_depth < 0.00001) {
world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc_clean, 1.0);
} else {
world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
}
float depth_texture_y = world.y / world.w;
float vertex_y = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).y;
float depth_blend = clamp((vertex_y - depth_texture_y) / depth_distance, 0.0, 1.0);
depth_blend = exp(-depth_blend * beers_law);
// === Fresnel ===
float fres = fresnel(4.0, NORMAL, VIEW);
// === Couleurs de surface ===
vec4 surface_color = mix(water_color, fresnel_color, fres);
vec4 blended_depth_color = mix(depth_color, surface_color, depth_blend);
vec3 adjusted_depth_color = mix(fresnel_color, blended_depth_color, step(fresnel_scale, z_dif)).rgb;
// === Réfraction ===
vec3 refraction_texture;
if (linear_depth < 0.00001) {
refraction_texture = textureLod(screen_texture, SCREEN_UV, ROUGHNESS * 2.0).rgb;
} else {
refraction_texture = textureLod(screen_texture, ref_ofs, ROUGHNESS * 2.0).rgb;
}
vec3 final_color = mix(adjusted_depth_color, refraction_texture, water_transparency * depth_blend);
// === Mix SSR avec Fresnel ===
// Plus l'angle est rasant (fresnel fort), plus la réflexion est visible
float ssr_blend = fres * ssr_intensity * ssr_fade;
final_color = mix(final_color, ssr_color, ssr_blend);
ALBEDO = final_color;
ROUGHNESS = roughness * (1.0 - fres);
NORMAL_MAP = normalmap;
}