Strange SSR Water Refraction

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;
}