shader_type spatial;
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, repeat_disable, filter_nearest;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
uniform float refraction = 0.075;
varying mat4 inv_mvp;
uniform float absorbance : hint_range(0.0, 10.0) = 2.0;
uniform vec3 shallow_color : source_color = vec3(0.22, 0.66, 1.0);
uniform vec3 deep_color : source_color = vec3(0.0, 0.25, 0.45);
uniform float foam_amount : hint_range(0.0, 2.0) = 0.2;
uniform vec3 foam_color : source_color = vec3(1);
uniform sampler2D caustic_texture : hint_default_white,repeat_enable,filter_linear_mipmap;
uniform sampler2D foam_texture : hint_default_white,repeat_enable,filter_linear_mipmap;
uniform float roughness : hint_range(0.0, 1.0) = 0.05;
uniform float wave_scale = 4.0;
varying vec3 uv_world_pos;
uniform sampler2D normal1 : hint_normal,repeat_enable,filter_linear_mipmap;
uniform vec2 wave_dir1 = vec2(1.0, 0.0);
uniform sampler2D normal2 :hint_normal,repeat_enable,filter_linear_mipmap;
uniform vec2 wave_dir2 = vec2(0.0, 1.0);
uniform float wave_speed : hint_range(0.0, 0.2) = 0.015;
vec3 screen(vec3 base, vec3 blend){
return 1.0 - (1.0 - base) * (1.0 - blend);
}
void vertex() {
uv_world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
inv_mvp = inverse(PROJECTION_MATRIX * MODELVIEW_MATRIX);
}
void fragment()
{
vec2 normal_offset1 = (TIME * wave_dir1) * wave_speed;
vec2 normal_offset2 = (TIME * wave_dir2) * wave_speed;
vec3 normal_blend = mix(texture(normal1, uv_world_pos.xz / wave_scale + normal_offset1), texture(normal2, uv_world_pos.xz / wave_scale + normal_offset2), 0.5).rgb;
vec3 ref_normalmap = normal_blend * 2.0 - 1.0;
ref_normalmap = normalize(TANGENT * ref_normalmap.x + BINORMAL * ref_normalmap.y + NORMAL *ref_normalmap.z);
vec2 ref_uv = SCREEN_UV + (ref_normalmap.xy);
float depth_raw = texture(DEPTH_TEXTURE, ref_uv).r * 2.0 - 1.0;
float depth = PROJECTION_MATRIX[3][2] / (depth_raw + PROJECTION_MATRIX[2][2]);
float depth_blend = exp((depth+VERTEX.z + absorbance) * -absorbance);
depth_blend = clamp(1.0-depth_blend, 0.0, 1.0);
float depth_blend_pow = clamp(pow(depth_blend, 2.5), 0.0, 1.0);
float depth1 = texture(DEPTH_TEXTURE, SCREEN_UV, 0.0).r;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth1);
vec4 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 vertical_depth = vertex_y - depth_texture_y;
// Makes the water more transparent as it gets more shallow
float alpha_blend = -vertical_depth * absorbance;
alpha_blend = clamp(1.0 - exp(alpha_blend), 0.0, 1.0);
vec2 distortUV = (ref_normalmap.xy + SCREEN_UV) * refraction * 2.0;
vec3 refractiontex = texture(SCREEN_TEXTURE, distortUV).xyz;
vec3 screen_color = refractiontex;
// Small layer of foam
float foam_blend = clamp(1.0 - (vertical_depth / foam_amount), 0.0,1.0);
vec4 foamtex = texture(foam_texture,uv_world_pos.xz / wave_scale + normal_offset1 + normal_offset2);
vec3 foam = foam_blend * foam_color * foamtex.rgb;
// Mix them all together
vec3 color_out = mix(deep_color, shallow_color, alpha_blend);
color_out = screen(color_out, foam);
color_out = mix(color_out, screen_color, .5);
mediump float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));
// Caustic screen projection
vec4 caustic_screenPos = vec4(ref_uv*2.0-1.0, depth_raw, 1.0);
vec4 caustic_localPos = inv_mvp * caustic_screenPos;
caustic_localPos = vec4(caustic_localPos.xyz/caustic_localPos.w, caustic_localPos.w);
vec2 caustic_Uv = caustic_localPos.xz / vec2(1024.0) + 0.5;
vec4 caustic_color = texture(caustic_texture, caustic_Uv*300.0);
color_out *= 1.0 + pow(caustic_color.r, 1.50) * (1.0-alpha_blend) * 6.0;
// Set all values:
ALBEDO = color_out;
METALLIC = 0.05;
ROUGHNESS = roughness;
SPECULAR = 0.2 + depth_blend_pow * fresnel;
NORMAL_MAP = normal_blend;
ALPHA = alpha_blend;
}
how to add refraction to a water shader?
you would use a screen buffer copy as a texture and warp(multiply by some color noise function corresponding with surface normals for the waves) the screen UV coordinates that you'd map the screen buffer copy by to the surface.
Megalomaniak how to setup a screen buffer?
- Edited
The root viewport that outputs the final screen state already keeps track of and provides a UI-less buffer to the shader system. See fragment built-ins in the docs here:
https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html#fragment-built-ins
In the list there you find both sampler2D SCREEN_TEXTURE
and in vec2 SCREEN_UV
. I've linked latest stable documentation since I presume you are still using 4.0 for now but if you are moving to 4.1 there might perhaps be some further changes though I doubt this specifically has changed in any way.
As the sampler2D SCREEN_TEXTURE
note says: "Removed in Godot 4. Use a sampler2D with hint_screen_texture instead." So presumedly you would create a uniform variable for sampler2D like you would for any other texture but give it the mentioned hint.
Megalomaniak have u looked at my shader code? im allready using those but not in the correct way i think
DJM Then could you share some images of your shader, are you sure it's not working alright and if it isn't...how?
Megalomaniak
the ground below the water doesnt get refracted
- Edited
You sure? I'd place a straight pillar or something in the water one end sticking out for a sanity check. Also, I'd check via running project not just in editor.
- Edited
im sure, did lots of tests, refraction isnt working
what if you try to temporarily pipe screen_color
to the albedo output, so without the caustics result.
Megalomaniak allready tried that
DJM Have you tried removing the filter_linear_mipmap
from the uniform? Perhaps that messes with it.
The shader is long, and I'd have to test it myself. It's too complex to understand just looking at the code.
cybereality Yeah, I'd probably try creating a separate shader focusing on just getting the refraction bit working then revisit the more complete shader to fix it.
Megalomaniak tried it, doesnt make a difference
Can you upload a sample project with just this shader and some demo assets. I could take a look, but it would help to have your setup and not have to recreate it.
cybereality ok will do, give me a bit time
- Best Answerset by DJM
So it's not actually using the refraction reading from the screen texture. Because of the alpha blending, it's just invisible.
I'll show you the lines to change, but you will need to tweak a lot to get the colors you need.
alpha_blend = clamp(1.0 - exp(alpha_blend), 0.6, 1.0);
vec2 distortUV = SCREEN_UV + ref_normalmap.xy * refraction * 0.2;
ALBEDO = screen_color;