Realistic Water with Traced and Simple Reflection and Refraction (V2)

A realistic, controllable water shader with SSR, screen space refraction, fog, and Snell’s window. This one is mostly as controllable as the last shader, with some more realistic features.

Attach to a plane or quad, set height strength to 0 if you aren’t using subdivision.

This shader only works facing up.

Refraction:

  • None: Doesn’t refract, just applies fog
  • Simple: Refracts based on normal map
  • Traced: Traces (maybe) physically accurate refraction vector (WILL RESULT IN ARTIFACTS)

Reflection:

  • None: Doesn’t reflect
  • Simple: Flipped image
  • Traced: Screen-space raytracing

Absorbtion color: Mixes to that color based on color^(depth*multiplier)

 

 

Shader code
shader_type spatial;
render_mode world_vertex_coords, cull_disabled, depth_draw_always;

uniform sampler2D screen : hint_screen_texture, filter_linear_mipmap_anisotropic, repeat_disable;
group_uniforms colours;
uniform vec3 absorptioncolour : source_color;
uniform float absorptionmultiplier = 1.0;

group_uniforms material;
uniform float AirIOR = 1.0;
uniform float IOR = 1.33;

group_uniforms textures;
uniform vec2 sampler1speed = vec2(0.02, 0.0);
uniform vec2 sampler2speed = vec2(0.0, 0.02);
uniform float samplermix : hint_range(0.0, 1.0, 0.1) = 0.5;
uniform vec2 samplerscale = vec2(0.1);
uniform sampler2D normal1tex : filter_linear_mipmap_anisotropic, hint_normal;
uniform sampler2D normal2tex : filter_linear_mipmap_anisotropic, hint_normal;
uniform float normalstrength : hint_range(0.0, 5.0, 0.01) = 1.0;
uniform sampler2D height1tex : filter_linear_mipmap_anisotropic;
uniform sampler2D height2tex : filter_linear_mipmap_anisotropic;
uniform float heightstrength : hint_range(0.0, 5.0, 0.01) = 0.12;
uniform sampler2D edge1tex : filter_linear_mipmap_anisotropic;
uniform sampler2D edge2tex : filter_linear_mipmap_anisotropic;

varying vec2 position;
varying vec3 wposition;

group_uniforms refraction;
uniform int refraction_type : hint_enum("None", "Simple", "Traced") = 1;
uniform float simple_refraction_amount = 0.1;
uniform float traced_refraction_far_clip = 50.0;
uniform int traced_refraction_steps : hint_range(64, 1024, 16) = 512;

group_uniforms edge;

uniform float edge_size : hint_range(0.01, 0.5, 0.01) = 0.1;
uniform bool foam_or_fade = false;

uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap, repeat_disable;

group_uniforms screen_space_reflection;

uniform int ssr_type : hint_enum("None", "Simple", "Traced") = 2;
uniform float traced_ssr_far_clip = 50.0;
uniform int traced_ssr_steps : hint_range(64, 1024, 16) = 512;
uniform float ssr_screen_fade : hint_range(0.01, 0.5, 0.01) = 0.05;

float schlickfresnel(float ior1, float ior2, vec3 view, vec3 norm) {
	float incident = dot(view, norm);
	float reflectance = ((ior2 - ior1)/(ior2 + ior1)) * ((ior2 - ior1)/(ior2 + ior1));
	float fresnelincident = reflectance + (1.0 - reflectance) * pow(1.0 - cos(incident), 5.0);
	return clamp(fresnelincident / incident, 0.0, 1.0);
}

void vertex() {
	position = VERTEX.xz;
	UV = VERTEX.xz * samplerscale + (sampler1speed * TIME);
	UV2 = VERTEX.xz * samplerscale + (sampler2speed * TIME);
	float height = mix(texture(height1tex, UV),texture(height2tex, UV2),samplermix).x;
	VERTEX.y += (height - 0.5) * heightstrength;
	wposition = VERTEX;
	// Called for every vertex the material is visible on.
}

float snells_window(vec3 normal, vec3 view, float ior) {
	float cos_theta = dot(normal, view);
	return step(sqrt(1.0 - cos_theta * cos_theta) * ior, 1.0);
}

float linear_depth(float nonlinear_depth, mat4 inv_projection_matrix) {
	#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
		nonlinear_depth = nonlinear_depth * 2.0 - 1.0;
	#endif
	return 1.0 / (nonlinear_depth * inv_projection_matrix[2].w + inv_projection_matrix[3].w);
}

float nonlinear_depth(float linear_depth, mat4 inv_projection_matrix) {
	#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
		linear_depth = linear_depth * 0.5 + 0.5;
	#endif
	return (1.0 / linear_depth - inv_projection_matrix[3].w) / inv_projection_matrix[2].w;
}

vec2 view2uv(vec3 position_view_space, mat4 proj_m)
{
	vec4 position_clip_space = proj_m * vec4(position_view_space.xyz, 1.0);
	vec2 position_ndc = position_clip_space.xy / position_clip_space.w;
	return position_ndc.xy * 0.5 + 0.5;
}

float remap(float x, float min1, float max1, float min2, float max2) {
	return ((x - min1) / (max1 - min1) + min2) * (max2 - min2);
}
float remap1(float x, float min1, float max1) {
	return (x - min1) / (max1 - min1);
}

float edge_fade(vec2 uv, float size) {
	float x1 = clamp(remap1(uv.x, 0.0, size), 0.0, 1.0);
	float x2 = clamp(remap1(uv.x, 1.0, 1.0 - size), 0.0, 1.0);
	float y1 = clamp(remap1(uv.y, 0.0, size), 0.0, 1.0);
	float y2 = clamp(remap1(uv.y, 1.0, 1.0 - size), 0.0, 1.0);
	return x1*x2*y1*y2;
}

void refraction(vec3 normal, vec3 vertex, vec2 normmap, bool reverse, mat4 projection_matrix, vec3 tangent, vec3 binormal, vec2 screen_uv, vec2 viewport_size, inout vec3 emission) {
	vec3 view = normalize(-vertex);
	mat4 inv_proj_mat = inverse(projection_matrix);
	if (refraction_type == 2) {
		
		vec3 refracted = refract(view, normal, AirIOR / -IOR);
		if (reverse) {
			refracted = refract(view, normal, -IOR / AirIOR);
		}
		vec3 pos = vertex;
		float dist = 0.0;
		int curstep = 0;
		bool finished = false;
		vec2 uv;
		float currentdepth;
		float lastdepth1 = -vertex.z;
			
		while (curstep < traced_refraction_steps) {
			float step_scale = float(curstep + 1) / float(traced_refraction_steps);
			float step_dist = step_scale * step_scale * traced_refraction_far_clip;
			pos += refracted * step_dist;
			dist += step_dist;
			curstep += 1;
			float lastdepth2 = lastdepth1;
			float lastdepth1 = currentdepth;
			currentdepth = -pos.z;
			uv = view2uv(pos, projection_matrix);
			float testdepth = linear_depth(texture(DEPTH_TEXTURE, uv).r, inv_proj_mat);
			if (testdepth < lastdepth2) {
				break;
			}
			if (testdepth < currentdepth) {
				finished = true;
				break;
			}
		}
		
		
		
		
		
		
		float lineardepth = linear_depth(texture(DEPTH_TEXTURE, uv).r, inv_proj_mat);
		//float selfdepth = 1.0/(1.0 + 2.0 * distance(wposition, CAMERA_POSITION_WORLD));
		//vec3 newvolcolour = mix(volumecolour, vec3(1.0), clamp(1.0 / (depth_diff * 1.0), 0.0, 1.0));
		
		vec3 newvolcolour = vec3(1.0);
		if (reverse) {
		} else {
			newvolcolour = pow(absorptioncolour, vec3(dist * absorptionmultiplier));
		}
		emission = newvolcolour * texture(screen, uv).rgb;
	} else if (refraction_type == 1) {
		
		float lineardepth = linear_depth(texture(DEPTH_TEXTURE, screen_uv).r, inv_proj_mat);
		float selfdepth = -vertex.z;
		float depth_diff = lineardepth - selfdepth;
		float x_mult = viewport_size.y / viewport_size.x;
		vec3 tanx = binormal * (normmap.x - 0.5) * normalstrength * x_mult;
		vec3 tany = tangent * (normmap.y - 0.5) * normalstrength;
		vec2 refracted_uv = screen_uv + (tanx + tany).xy * simple_refraction_amount * depth_diff / lineardepth;
		float newdepth = linear_depth(texture(DEPTH_TEXTURE, refracted_uv).r, inv_proj_mat);
		if (newdepth >= selfdepth) {
			depth_diff = newdepth - selfdepth;
		}
		//float selfdepth = 1.0/(1.0 + 2.0 * distance(wposition, CAMERA_POSITION_WORLD));
		//vec3 newvolcolour = mix(volumecolour, vec3(1.0), clamp(1.0 / (depth_diff * 1.0), 0.0, 1.0));
		vec3 newvolcolour;
		if (reverse) {
			newvolcolour = vec3(1.0);
		} else {
			newvolcolour = pow(absorptioncolour, vec3(depth_diff * absorptionmultiplier));
		}
		emission = newvolcolour * texture(screen, refracted_uv).rgb;

		if (newdepth < selfdepth) {
			emission = newvolcolour * texture(screen, screen_uv).rgb;
		}

	} else {
		float lineardepth = linear_depth(texture(DEPTH_TEXTURE, screen_uv).r, inv_proj_mat);
		float selfdepth = -vertex.z;
		float depth_diff = lineardepth - selfdepth;
		vec3 newvolcolour;
		if (reverse) {
			newvolcolour = vec3(1.0);
		} else {
			newvolcolour = pow(absorptioncolour, vec3(depth_diff * absorptionmultiplier));
		}
		emission = newvolcolour * texture(screen, screen_uv).rgb;
	}
}

void reflection(vec3 normal, vec3 vertex, mat4 projection_matrix, inout vec3 emission, inout float specular) {
	vec3 view = normalize(-vertex);
	mat4 inv_proj_mat = inverse(projection_matrix);
	if (ssr_type == 2) {
		vec3 reflected = -reflect(view, normal);
		vec3 pos = vertex;
		int curstep = 0;
		bool finished = false;
		vec2 uv;
		float currentdepth;
		float lastdepth1 = -vertex.z;
		while (curstep < traced_ssr_steps) {
			float step_scale = float(curstep + 1) / float(traced_ssr_steps);
			float step_dist = step_scale * step_scale * traced_ssr_far_clip;
			pos += reflected * step_dist;
			curstep += 1;
			float lastdepth2 = lastdepth1;
			float lastdepth1 = currentdepth;
			currentdepth = -pos.z;
			uv = view2uv(pos, projection_matrix);
			if (!(uv.x < 1.0 && uv.y < 1.0 && uv.x > 0.0 && uv.y > 0.0)) {
				break;
			}
			float testdepth = linear_depth(texture(DEPTH_TEXTURE, uv).r, inv_proj_mat);
			if (testdepth < lastdepth2) {
				break;
			}
			if (testdepth < currentdepth) {
				finished = true;
				break;
			}
		}
		if (finished && currentdepth < traced_ssr_far_clip * 0.99) {
			specular *= 1.0 - edge_fade(uv, ssr_screen_fade);
			emission += texture(screen, uv).xyz * schlickfresnel(1.0, 1.33, view, normal) * edge_fade(uv, ssr_screen_fade);
		}
	} else if (ssr_type == 1) {
		vec3 reflected = -reflect(view, normal);
		if (reflected.z < 0.0) {
			vec2 uv = view2uv(reflected, projection_matrix);
			float testdepth = texture(DEPTH_TEXTURE, uv).r;
			
			if (testdepth > 0.0) {
				specular *= 1.0 - edge_fade(uv, ssr_screen_fade);
				emission += texture(screen, uv).xyz * schlickfresnel(1.0, 1.33, view, normal) * edge_fade(uv, ssr_screen_fade);
			}
		}
	}
	
}


void fragment() {

	vec3 onorm = NORMAL;

	vec2 normmap = mix(texture(normal1tex, UV),texture(normal2tex, UV2),samplermix).xy;
	NORMAL += TANGENT * (normmap.x - 0.5) * normalstrength;
	NORMAL += BINORMAL * (normmap.y - 0.5) * normalstrength;

	vec3 wnorm = (vec4(NORMAL, 0.0) * VIEW_MATRIX).xyz;
	vec3 wview = (vec4(VIEW, 0.0) * VIEW_MATRIX).xyz;
	ROUGHNESS = 0.05;
	METALLIC = 0.0;
	ALBEDO = vec3(0.0);
	if (FRONT_FACING) {
		float fres = clamp(schlickfresnel(AirIOR, IOR, VIEW, NORMAL), 0.0, 1.0);

		float lineardepth = linear_depth(texture(DEPTH_TEXTURE, SCREEN_UV).r, INV_PROJECTION_MATRIX);
		float selfdepth = -VERTEX.z;
		float depth_diff = lineardepth - selfdepth;
		// REFRACTION
		
		refraction(NORMAL, VERTEX, normmap, false, PROJECTION_MATRIX, TANGENT, BINORMAL, SCREEN_UV, VIEWPORT_SIZE, EMISSION);
		EMISSION *= 1.0 - fres;
		// SSR
		reflection(NORMAL, VERTEX, PROJECTION_MATRIX, EMISSION, SPECULAR);
		// EDGE EFFECT

		float distfromedge = depth_diff * dot(normalize(NORMAL), normalize(-VERTEX)) / VIEW.z;
		if (distfromedge < edge_size) {
			distfromedge /= edge_size;
			if (foam_or_fade) {
				ALPHA = distfromedge;
			} else {
				float edgetex = mix(texture(edge1tex, UV).r, texture(edge2tex, UV2).r, samplermix);
				if (edgetex > distfromedge) {
					ALBEDO = vec3(1.0);
					ROUGHNESS = 1.0;
					METALLIC = 1.0;
					EMISSION = vec3(0.0);
					NORMAL = onorm;
				}
			}
		} else {
			ALPHA = 1.0;
		}

	} else {

		// SNELLS WINDOW
		float window = dot(refract(VIEW, NORMAL, IOR / AirIOR), -NORMAL);

		if (window >= 1.00) {
			SPECULAR = 0.0;
			refraction(NORMAL, VERTEX, normmap, true, PROJECTION_MATRIX, TANGENT, BINORMAL, SCREEN_UV, VIEWPORT_SIZE, EMISSION);
		} else if (window <= 0.0) {
			// SSR

			reflection(NORMAL, VERTEX, PROJECTION_MATRIX, EMISSION, SPECULAR);

		} else {
			SPECULAR *= clamp(1.0 - window, 0.0, 1.0);

			refraction(NORMAL, VERTEX, normmap, true, PROJECTION_MATRIX, TANGENT, BINORMAL, SCREEN_UV, VIEWPORT_SIZE, EMISSION);
			EMISSION *= window;
			// SSR

			reflection(NORMAL, VERTEX, PROJECTION_MATRIX, EMISSION, SPECULAR);
		}

	}
	ALPHA = clamp(ALPHA, 0.0, 1.0);
}
Live Preview
Tags
raytraced, realistic, reflection, refraction, snell's law, SSR, Transparency, underwater, 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.

More from smallcableboi

Related shaders

guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
arnau
arnau
5 days ago

can you specify what exactly you did to make it look like in the images? looks amazin but can’t manage to recreate, don’t know exactly how i have to setup the textures