Godot Version
4.6.1
Question
I’m using the following edge detection shader (by Leo Peltola) in my game to create a colored outline around objects.
shader_type spatial;
render_mode unshaded;
// MIT License. Made by Leo Peltola
// Inspired by https://threejs.org/examples/webgl_postprocessing_pixel.html
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
uniform sampler2D NORMAL_TEXTURE : hint_normal_roughness_texture, filter_nearest;
uniform sampler2D mask_texture : hint_default_black; // Assign your SubViewport Texture here
uniform bool shadows_enabled = true;
uniform bool highlights_enabled = true;
uniform float shadow_strength : hint_range(0.0, 1.0, 0.01) = 0.4;
uniform float highlight_strength : hint_range(0.0, 10.0, 0.01) = 0.1;
uniform vec3 highlight_color : source_color = vec3(1.);
uniform vec3 shadow_color : source_color = vec3(0.0);
varying mat4 model_view_matrix;
uniform float depth_threshold : hint_range(0.0, 10.0, 0.001) = 0.0f;
float getDepth(vec2 screen_uv, sampler2D depth_texture, mat4 inv_projection_matrix){
// Credit: https://godotshaders.com/shader/depth-modulated-pixel-outline-in-screen-space/
float raw_depth = texture(depth_texture, screen_uv)[0];
vec3 normalized_device_coordinates = vec3(screen_uv * 2.0 - 1.0, raw_depth);
vec4 view_space = inv_projection_matrix * vec4(normalized_device_coordinates, 1.0);
view_space.xyz /= view_space.w;
return -view_space.z;
}
void vertex(){
model_view_matrix = VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0], INV_VIEW_MATRIX[1], INV_VIEW_MATRIX[2], MODEL_MATRIX[3]);
}
void fragment() {
vec2 e = vec2(1./VIEWPORT_SIZE.xy);
// Shadows
float depth_diff = 0.0;
float neg_depth_diff = .5;
if (shadows_enabled) {
float depth = getDepth(SCREEN_UV, DEPTH_TEXTURE, INV_PROJECTION_MATRIX);
float du = getDepth(SCREEN_UV+vec2(0., -1.)*e, DEPTH_TEXTURE, INV_PROJECTION_MATRIX);
float dr = getDepth(SCREEN_UV+vec2(1., 0.)*e, DEPTH_TEXTURE, INV_PROJECTION_MATRIX);
float dd = getDepth(SCREEN_UV+vec2(0., 1.)*e, DEPTH_TEXTURE, INV_PROJECTION_MATRIX);
float dl = getDepth(SCREEN_UV+vec2(-1., 0.)*e, DEPTH_TEXTURE, INV_PROJECTION_MATRIX);
depth_diff += clamp(du - depth, 0., 1.);
depth_diff += clamp(dd - depth, 0., 1.);
depth_diff += clamp(dr - depth, 0., 1.);
depth_diff += clamp(dl - depth, 0., 1.);
neg_depth_diff += depth - du;
neg_depth_diff += depth - dd;
neg_depth_diff += depth - dr;
neg_depth_diff += depth - dl;
neg_depth_diff = clamp(neg_depth_diff, 0., 1.);
neg_depth_diff = clamp(smoothstep(0.5, 0.5, neg_depth_diff)*10., 0., 1.);
depth_diff = smoothstep(0.2, 0.3, depth_diff);
// ALBEDO = vec3(neg_depth_diff);
}
vec3 original_color = texture(SCREEN_TEXTURE, SCREEN_UV).rgb;
vec3 final_shadow_color = mix(original_color, shadow_color, shadow_strength);
vec3 final = original_color;
if (shadows_enabled) {
final = mix(final, final_shadow_color, depth_diff);
}
ALBEDO = final;
float alpha_mask = depth_diff * float(shadows_enabled);
ALPHA = clamp((alpha_mask) * 5., 0., 1.);
}
And it works great! Except, I’m trying to modify my custom grass shader to improve visuals, and it’s conflicting with the above code. Specifically, I’m trying to enable depth on my grass blades, instead of making them be considered transparent, both for performance and aesthetic reasons. I’m doing this by using ALPHA_SCISSOR_THRESHOLD = 0.5; on a render_mode depth_draw_opaque; enabled shader (I’ve tried all other depth related render modes and none did what I needed). But, as expected, now the edge shader detects the grass depth and draws an outline on it.
Before:
After:
I wanted to keep the grass with the depth effect, since it solves some weird visual quirks it had for being transparent.
Before, with sorting issues due to transparency:
After, with proper sorting between blades (all post processing effects disabled):
I’ve tried rendering objects that should receive the outline in a different Viewport and use it as a mask to enable/disable where to apply the post-processing, but it had it’s own caveats I couldn’t solve. I have no idea how to properly do this. Maybe this is an instance of using a per-object next-pass shader? Or is there a way of excluding select objects from the edge detection effect?





