How can I fix the proximity fade (or linear depth) distortion?
Asked Answered
M

12

0

I'm really stuck and have no idea how to fix this, I would put it under the rug if I could but it's really noticeable no matter what I do. I'm using Godot 4.0.3

I want to use this shader code I took from the proximity fade node in the visual shader:

uniform sampler2D DEPTH_TEXTURE: hint_depth_texture;
uniform vec3 water: source_color;
uniform vec3 edge_color: source_color = vec3(1.0);
uniform float edge_dis = 1.0;

void fragment() {
	float depth_tex = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r;
	
	vec4 view = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0);
	view.xyz /= view.w;
	float prox = clamp(1.0 - smoothstep(view.z + edge_dis, view.z, VERTEX.z), 0.0, 1.0);
	
	float blend = clamp(prox / edge_dis, 0.0, 1.0);
	vec3 color = mix(edge_color.rgb, water, blend);
	
	ALBEDO = vec3(color);
}

It's the "foam" of a water shader, basically an outline to any mesh in the water. The problem is, when the camera gets close to the outline, it gets distorted. And I need to use it for a beach, where the outline is veeeerry long, I have no way of hiding it.

Here is the literal code from the node:

From afar looks fine:

But up close, it gets all curved:

And on a beach, even worse:

Already searched everywhere, could not find anything. Most water shaders in godot are outdated or ignore this problem. Tried searching for unity, and there the depth texture is somehow perfectly consistent, but it's done differently so no help either.

Am I missing something? Is this a bug or an oversight from the engine?

Maryellen answered 3/6, 2023 at 5:0 Comment(0)
M
0

Well I got a solution, for anyone that finds this in the future. I posted it as an issue in Godot in case it was an actual issue. Later tried to replicate the effect in Unreal and before my pc started to melt I managed it, and it was the same result. It's not an issue with Godot but rather as Conquian says an issue with the math.

When I went to close the issue, contributor Lielay9 was kind enough to explain the math with some images and gave me a shader that works: https://github.com/godotengine/godot/issues/77798#issuecomment-1575222421

shader_type spatial;
render_mode unshaded, shadows_disabled;

uniform sampler2D DEPTH_TEXTURE: hint_depth_texture;

uniform vec3 water_color: source_color;
uniform vec3 edge_color: source_color = vec3(1.0);
uniform float edge_dis = 0.1;

void fragment() {
	float depth = texture(DEPTH_TEXTURE, SCREEN_UV,0.0).r;
	
	vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
	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 blend = clamp((vertex_y - depth_texture_y) / edge_dis, 0.0, 1.0);
	
	ALBEDO = mix(edge_color, water_color, blend);
}

As you can see, no apparent distortions:

It has limitations as Lielay9 says, but in this use case it works good enough. The actual solution is to use SDF but that's not easily done in Godot for now.

Thank you very much Conquian for your time! As you said, you need to calculate the distance from the water to the shore, but it was using INV_VIEW_MATRIX to get the world coordinates of the depth texture

Maryellen answered 4/6, 2023 at 2:42 Comment(0)
L
0

You need to make the depth linear. That way it is even all around. You can use something like this:

float linearize_depth(float d,float zNear,float zFar)
{
    return zNear * zFar / (zFar + d * (zNear - zFar));
}

Where d is the depth value between 0.0 and 1.0.

Lighten answered 3/6, 2023 at 6:23 Comment(0)
M
0

Lighten I already tried some variations of that, I can't get it to work. I actually asked here because it was the only place with a similar question that you responded.

void fragment() {
	float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV,0.0).r;
	float linear_depth = linearize_depth(depth_tex, 0.05, 100.0);
	
	vec4 view = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, linear_depth, 1.0);
	view.xyz /= view.w;
	float prox = clamp(1.0 - smoothstep(view.z + edge_dis, view.z, VERTEX.z), 0.0, 1.0);
	
	
	float blend = clamp(prox / edge_dis, 0.0, 1.0);
	vec3 color = mix(edge_color.rgb, water, blend);
	
	ALBEDO = vec3(color);
}

void fragment() {
	float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV,0.0).r;
	
	vec4 view = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0);
	view.xyz /= view.w;
	float linear_depth = linearize_depth(view.z, 0.05, 100.0);
	float prox = clamp(1.0 - smoothstep(linear_depth + edge_dis, linear_depth, VERTEX.z), 0.0, 1.0);
	
	
	float blend = clamp(prox / edge_dis, 0.0, 1.0);
	vec3 color = mix(edge_color.rgb, water, blend);
	
	ALBEDO = vec3(color);
}

void fragment() {
float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV,0.0).r;

vec4 view = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0);
view.xyz /= view.w;
float prox = clamp(1.0 - smoothstep(view.z + edge_dis, view.z, VERTEX.z), 0.0, 1.0);

float linear_depth = linearize_depth(prox, 0.05, 100.0);
float blend = clamp(linear_depth / edge_dis, 0.0, 1.0);
vec3 color = mix(edge_color.rgb, water, blend);

ALBEDO = vec3(color);

}

Maryellen answered 3/6, 2023 at 6:49 Comment(0)
M
0

Maryellen other shaders get the linear depth like this:

https://godotshaders.com/shader/high-quality-post-process-outline/

float getLinearDepth(float depth, vec2 uv, mat4 inv_proj) {
	vec3 ndc = vec3(uv * 2.0 - 1.0, depth);
	vec4 view = inv_proj * vec4(ndc, 1.0);
	view.xyz /= view.w;
	return -view.z;
}

But it still has the same distortion

Maryellen answered 3/6, 2023 at 7:28 Comment(0)
M
0

Lighten also here:
https://github.com/paddy-exe/ShaderFunction-Extras/blob/main/addons/ShaderFunction-Extras/Utility/utility.gdshaderinc
same thing, all the vulkan ones do basically the same, and give the same result.

I think it just can't be resolved. Must be an issue with Vulkan or Godot implementation of the depth texture.

Maryellen answered 3/6, 2023 at 8:10 Comment(0)
P
0

Have you tried checking out other working water shader tutorial and see how it implement? No idea how shader works but I'm currently using this shader and it seems to work ok.

Pome answered 3/6, 2023 at 8:50 Comment(0)
M
0

Pome Nope. Already tried that one.

uniform float edge_dis = 0.5;
uniform float near = 1.0;
uniform float far = 100.0;

float edge(float depth){
	depth = 2.0 * depth - 1.0;
	return near * far / (far + depth * (near - far));
}

void fragment() {
	float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV,0.0).r;
	
	float z_depth = edge(depth_tex);
	float z_pos = edge(FRAGCOORD.z);
	float z_dif = z_depth - z_pos;
	
	float blend = step(edge_dis, z_dif);
	vec3 color = mix(edge_color.rgb, water, blend);
	
	ALBEDO = vec3(color);
}

Maryellen answered 3/6, 2023 at 8:59 Comment(0)
L
0

Can you upload a minimum project of the effect? I could probably fix it if I can mess with the code.

Lighten answered 3/6, 2023 at 9:20 Comment(0)
M
0

Lighten

min-water.zip
4kB

I placed the two shaders

Maryellen answered 3/6, 2023 at 9:58 Comment(0)
L
0

Okay, so I tried but I wasn't able to fix it. However, I think I see what the problem is.

You are working in screen space. And the depth you are getting is from the screen to the floor of the sand. But the value you want is from the distance from the water to the sand. This is not the same, though may be close at far angles which is why it appears to work. You should be able to get the water pixel using FRAGCOORD. I tried to hack it, but it wasn't working right. But FRAGCOORD give you the pixel (screenspace) position of the current fragment. So that must be part of it.

Lighten answered 3/6, 2023 at 10:37 Comment(0)
M
0

Lighten I'm not sure, I also tried float depth = texture(DEPTH_TEXTURE, FRAGCOORD.xy / VIEWPORT_SIZE).r;
it was the same

Maryellen answered 3/6, 2023 at 10:56 Comment(0)
M
0

Well I got a solution, for anyone that finds this in the future. I posted it as an issue in Godot in case it was an actual issue. Later tried to replicate the effect in Unreal and before my pc started to melt I managed it, and it was the same result. It's not an issue with Godot but rather as Conquian says an issue with the math.

When I went to close the issue, contributor Lielay9 was kind enough to explain the math with some images and gave me a shader that works: https://github.com/godotengine/godot/issues/77798#issuecomment-1575222421

shader_type spatial;
render_mode unshaded, shadows_disabled;

uniform sampler2D DEPTH_TEXTURE: hint_depth_texture;

uniform vec3 water_color: source_color;
uniform vec3 edge_color: source_color = vec3(1.0);
uniform float edge_dis = 0.1;

void fragment() {
	float depth = texture(DEPTH_TEXTURE, SCREEN_UV,0.0).r;
	
	vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
	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 blend = clamp((vertex_y - depth_texture_y) / edge_dis, 0.0, 1.0);
	
	ALBEDO = mix(edge_color, water_color, blend);
}

As you can see, no apparent distortions:

It has limitations as Lielay9 says, but in this use case it works good enough. The actual solution is to use SDF but that's not easily done in Godot for now.

Thank you very much Conquian for your time! As you said, you need to calculate the distance from the water to the shore, but it was using INV_VIEW_MATRIX to get the world coordinates of the depth texture

Maryellen answered 4/6, 2023 at 2:42 Comment(0)
P
0

I tried it with the shader I got and it works! Thanks for sharing!

Pome answered 4/6, 2023 at 4:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.