I'm making a water shader with refraction, but objects above water also get refracted. I've used the depth buffer to stop this before, but I can't get it to work. I already use the depth buffer for coloring:

``````float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
depth = depth * 2.0 - 1.0;
depth = PROJECTION_MATRIX / (depth + PROJECTION_MATRIX);
depth += VERTEX.z;
depth = exp(-depth * beer_factor);
float colorDepth = clamp(1.0-depth,0.0,1.0);
``````

The variable colorDepth is simply depth inverted and clamped between 0 and 1 to use for alpha.

The refraction in SpatialMaterial also has this problem, so that's no help either. I thought refracting only if depth is less than zero would work, but that didn't do anything. Does anyone know how to do this?

• Some unity shaders might help if they were adapted, but I haven't found anything yet.

• I've collaborated with someone working on a water pack before. The project's abandoned, but the materials there can be useful, including this one.

• I assume this is the code to refract only underwater objects?

``````float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV + screen_offset).r;
vec4 world_pos = INV_PROJECTION_MATRIX * vec4((SCREEN_UV + screen_offset) * 2.0 - 1.0, depth_tex * 2.0 - 1.0, 1.0);
world_pos.xyz /= world_pos.w;
float depth = distance(VERTEX.xyz, world_pos.xyz);
{
//code for refraction
``````
• I think so.

• So is world_pos the world position of the current pixel in the depth texture?

• In this case, no. I named them wrong. That's actually the view space position.

• How come world pos is a vec4? I thought just the depth relative to the water(using VERTEX.z, I think) needs to be compared. Unless I'm mistaken, all I need to do is get the depth relative to the water plane, and only refract if depth > 0.

• The reason why it's a `vec4` is because the `w` component was needed for perspective division. It's something that must be done after transforming the normalized device coordinates to view space and vice versa. Notice the part where I divided it by `w`.

• And yeah, simply comparing the z components for depth would've sufficed too, but I believe it's more accurate if I compared it by distance.

• So what is world_pos, how does getting the distance work if it's not actually the world position?

• edited May 7

Like I said, I named it wrong. That's the position of the depth sample in `view space`. The vertex position of the water's surface is also in view space. Figuring out the distance between the two can tell you how much water is in the current fragment being rendered.

• You said it's view space position? What does that mean?

• @Dschoonmaker said:
You said it's view space position? What does that mean?

It means that the position of the fragment, or any arbitrary point really, is relative to the scene's the camera. The builtin `VERTEX` variable is in view space for example, meaning that it's position is relative to the camera.

• So it could be compared to VERTEX.z to check if that pixel is underwater.

• Correct.

• Oops, I spent several minutes trying to figure out why your solution wasn't working. Then I put in an if/else statement for debugging(make the water red if it should refract, green if it shouldn't), but the water remained a clear blue. . .

Then I realized I was typing in the shader for the bottom of the water. Godot has this weird little bug where if I click on the top of the water it selects the bottom & vice versa. Maybe it will work now • edited May 7

Thanks for your help @SIsilicon28 , but neither of your solutions(there were two different methods in the code) worked. The second almost did, but some objects too far below the surface didn't refract. I'll try to find more about depth buffer & refraction.

• edited May 7

Hmm, is your project in GLES2 or GLES3?
Also the code you were referring to in the beginning only handles fogging, not refraction. That's in a different part of the shader.

• It's GLES3.
The code I referred to does only handle fogging, but should still work.

``````void fragment(){
//  vec2 uv = UV;
//  uv.y += (0.01 * (sin(uv.x * 3.5 + (TIME*ripple_speed) * 0.35) + sin(uv.x * 4.8 + (TIME*ripple_speed) * 1.05) + sin(uv.x * 7.3 + (TIME*ripple_speed) * 0.45) / 3.0))*ripple_scale;
//  uv.x += (0.12 * (sin(uv.y * 4.0 + (TIME*ripple_speed) * 0.5) + sin(uv.y * 6.8 + (TIME*ripple_speed) * 0.75) + sin(uv.y * 11.3 + (TIME*ripple_speed) * 0.2) / 3.0))*ripple_scale;
//  uv.y += (0.12 * (sin(uv.x * 4.2 + (TIME*ripple_speed) * 0.64) + sin(uv.x * 6.3 + (TIME*ripple_speed) * 1.65) + sin(uv.x * 8.2 + (TIME*ripple_speed) * 0.45) / 3.0))*ripple_scale;
//  NORMALMAP = texture(normalmap,uv*wave_scale).xyz;
float ref_str = refraction/VERTEX.z;
vec2 ref_ofs = (NORMALMAP.xy*ref_str)-(ref_str*0.5);
//  ROUGHNESS = roughness;
//  SPECULAR = specular;
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
depth = depth * 2.0 - 1.0;
depth = PROJECTION_MATRIX / (depth + PROJECTION_MATRIX);
depth += VERTEX.z;
depth = exp(-depth * beer_factor);
float colorDepth = exp(-depth * beer_factor);
colorDepth = clamp(1.0-depth,-1.0,1.0);
//  ALBEDO = mix(color.rgb,foam.rgb,foam.a);
//  TRANSMISSION = vec3(transmission);
vec3 screenTexture;
if(depth>0.0){
depth = texture(DEPTH_TEXTURE, SCREEN_UV+ref_ofs).r;
depth = depth * 2.0 - 1.0;
depth = PROJECTION_MATRIX / (depth + PROJECTION_MATRIX);
depth += VERTEX.z;
depth = exp(-depth * beer_factor);
colorDepth = clamp(1.0-depth,0.0,1.0);
screenTexture = textureLod(SCREEN_TEXTURE,SCREEN_UV+ref_ofs,murkiness).rgb;
}
else{
screenTexture = texture(SCREEN_TEXTURE,SCREEN_UV).rgb;
}
ALBEDO = mix(screenTexture,ALBEDO,ALPHA);
ALPHA = 1.0;
}
``````

This is my code, irrelevant parts are commented out. colorDepth is what I use for depth coloring, for now I leave depth the same(it could maybe be used).
Note that

``````if(depth>0.0){
``````

is a placeholder, I tried a lot of different solutions but reverted to this when they didn't work. I know it won't work, I just have it there for now.

I think it should still work, especially because I've been declaring separate variables for the depth test(for debugging purposes).

• If I use depth as albedo, it looks correct: Deep areas are white, and shallow areas are black. It would seem that if I only refract if depth is greater than zero, it should work. But it doesn't. Also, if I only refract if depth is greater than 0.5, I can see that deep pixels get refracted and shallow pixels don't. That's expected, but objects above the surface also get refracted. It behaves as if I took the absolute value of depth, but I don't, and I don't square it either. All I do is:

``````float depthTest = texture(DEPTH_TEXTURE,SCREEN_UV).r;
depthTest = depthTest * 2.0 - 1.0;
depthTest = PROJECTION_MATRIX / (depthTest + PROJECTION_MATRIX);
depthTest += VERTEX.z;
if(depthTest>0.0){
//refract
``````
• In line 20 of the shader fragment you posted, it shows that you were applying beer's law twice on the depth value. Maybe that's the problem.

• I noticed that also & removed it. That could be the problem, except depthTest is a new variable; everything I do to it is included in the code I posted(expect ALBEDO = vec3(depthTest), but that doesn't change it).

• Could you post your shader code again, so that I know what you have so far?

• ``````shader_type spatial;
render_mode depth_draw_alpha_prepass,specular_schlick_ggx;

uniform vec2 uv_scale = vec2(1);

uniform vec4 color : hint_color;
uniform float roughness : hint_range(0,1);

uniform sampler2D normal1 : hint_normal;
uniform sampler2D normal2 : hint_normal;
uniform float normal_scale;

uniform vec2 wave1_scroll;
uniform vec2 wave2_scroll;

uniform float beer_factor;
uniform float min_alpha : hint_range(0,1);
uniform float murkiness;
uniform float refraction;

uniform float specular_power;
uniform float specular_strength;

void vertex(){
UV *= uv_scale;
}

void fragment(){
vec3 wave1 = texture(normal1,UV+(wave1_scroll*TIME)-(wave1_scroll*0.5*TIME)).xyz*2.0-1.0;
vec3 wave2 = texture(normal2,UV+(wave2_scroll*TIME)-(wave2_scroll*0.5*TIME)).xyz*2.0-1.0;
NORMALMAP = normalize(vec3(wave1.xy+wave2.xy,wave1.z))*0.5+0.5;
NORMALMAP_DEPTH = normal_scale;
float fresnel = sqrt(1.0-dot(NORMAL,VIEW));
float ref_str = refraction/VERTEX.z;
vec2 ref_ofs = (NORMALMAP.xy*ref_str)-(ref_str*0.5);
ROUGHNESS = roughness*(1.0-fresnel);
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
depth = depth * 2.0 - 1.0;
depth = PROJECTION_MATRIX / (depth + PROJECTION_MATRIX);
depth += VERTEX.z;
float colorDepth = exp(-depth * beer_factor);
depth = exp(-depth * beer_factor);
colorDepth = clamp(1.0-colorDepth,0.0,1.0);
ref_str *= clamp(colorDepth,0,0.1);
ref_ofs = (NORMALMAP.xy*ref_str)-(ref_str*0.5);
ALBEDO = color.rgb;
vec3 screenTexture;
float depthTest = texture(DEPTH_TEXTURE,SCREEN_UV).r;
depthTest = depthTest * 2.0 - 1.0;
depthTest = PROJECTION_MATRIX / (depthTest + PROJECTION_MATRIX);
if(depthTest>VERTEX.z){
depth = texture(DEPTH_TEXTURE, SCREEN_UV+ref_ofs).r;
depth = depth * 2.0 - 1.0;
depth = PROJECTION_MATRIX / (depth + PROJECTION_MATRIX);
depth += VERTEX.z;
depth = exp(-depth * beer_factor);
colorDepth = clamp(1.0-depth,0.0,1.0);
screenTexture = textureLod(SCREEN_TEXTURE,SCREEN_UV+ref_ofs,colorDepth+murkiness).rgb;
}
else{
screenTexture = textureLod(SCREEN_TEXTURE,SCREEN_UV,colorDepth+murkiness).rgb;
}
float alpha = clamp(colorDepth+min_alpha,min_alpha,1.0);
ALBEDO = mix(screenTexture,ALBEDO,alpha);
}

void light() {
DIFFUSE_LIGHT += max(dot(NORMAL, LIGHT),0.0) * ATTENUATION * ALBEDO * LIGHT_COLOR;
vec3 H = normalize(VIEW + LIGHT);
float NdotH = max(0, dot(NORMAL, H));
float specular = pow(NdotH, specular_power) * specular_strength * ATTENUATION.x;
SPECULAR_LIGHT += specular*LIGHT_COLOR;
}
``````