My focus is not on realistic refraction, but rather on cheaply computed distortion of screen texture on objects that looks like refraction as in the image. It looks good if applied on ocean shaders, glasses or crystals in games.
Cheap Refraction
The simulation is done by simply distorting screen UVs using the normal map. And then sampling the screen texture on object with these UVs.
uv = uv + length(normal_vector) * strength
Since the length of normal vector is different on different parts on normal map in screen coordinates, this uneven values when added to UVs produce distortion.
Later, for more control we add strength1; a variable that simply produce more distortion as we see deeper and produces no (or very minimal) refraction as we go upwards. this is because it has been multiplied with UV.y (effect made stronger by raising UV.y to some power).
vec2 refract_uv(vec2 uv, float strength, vec3 normal){
float strength1 = strength * (
is_depth_based ? pow(uv.y, 4.0) * 5.0 : 1.0
);
uv += strength1 * length(normal) - strength1 * 1.2;
return uv;
}
Full Refraction Shader
We finally add contribution of object’s original albedo color, and its alpha along with the refraction.
shader_type spatial;
uniform vec3 albedo : source_color;
uniform sampler2D normalmap;
uniform float alpha : hint_range(0.0, 1.0, 0.01) = 0.8;
uniform float refraction_strength : hint_range(0.0, 8.0, 0.001) = 0.5;
uniform sampler2D screen_texture : hint_screen_texture;
uniform sampler2D depth_texture : hint_depth_texture;
uniform bool is_depth_based = false;
vec2 refract_uv(vec2 uv, float strength, vec3 normal){
float strength1 = strength * (is_depth_based ? pow(uv.y, 4.0) * 5.0 : 1.0);
uv += strength1 * length(normal) - strength1 * 1.2;
return uv;
}
void fragment() {
vec3 nmap = texture(normalmap, UV).rgb;
ALBEDO = mix(albedo, texture(screen_texture, refract_uv(SCREEN_UV, refraction_strength, nmap)).rgb, 1.0 - alpha);
}