Edge Detection Shader
This shader is actually not, well, one shader. It’s two!
The shader code below contains two shaders separated by a comment – copy them into separate shaders files and add shader pass 1 to a material, in that material, select “next pass” and add the shader pass 2.
Shader code
//---------------------------------------------
// Edge-Detection Shader Pass 1
//
// Here we simply pass the vertex normals to the albedo
// so we can access it through the SCREEN_TEXTURE in our Shader Pass 2
// LICENSE: MIT
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_toon,specular_disabled,shadows_disabled;
varying vec3 world_normal;
void vertex() {
world_normal = NORMAL;
}
void fragment() {
ALBEDO = world_normal.rgb;
}
// END OF SHADER PASS 1
//---------------------------------------------
//---------------------------------------------
// Edge-Detection Shader Pass 2
//
// Here's our fully lit and shaded model,
// but through the SCREEN_TEXTURE, we also have the world normals
// of all the visible parts of our model that the first pass gives us.
// LICENSE: MIT
shader_type spatial;
render_mode blend_mix,depth_draw_alpha_prepass,cull_back,diffuse_lambert,specular_disabled;
uniform vec4 albedo : hint_color;
uniform sampler2D texture_albedo : hint_albedo;
uniform float specular;
uniform float metallic;
uniform float roughness : hint_range(0,1);
uniform float edge_strength : hint_range(0,1) = 0.2;
uniform vec4 edge_color : hint_color = vec4(0.5, 0.5, 0.5, 1.0);
// essentially a cheap "lightness" function
// returns the average of red, green and blue color channels
float vec3_avg(vec3 color) {
return (color.r + color.g + color.b) / 3.0;
}
// transform a pixel coordinate to screen UV
vec2 pixel_to_screen_uv(vec2 viewport_size, vec2 pixel) {
return vec2(pixel.x / viewport_size.x, pixel.y / viewport_size.y);
}
void fragment() {
vec4 albedo_tex = texture(texture_albedo, UV);
vec2 iuv = vec2(SCREEN_UV.x * VIEWPORT_SIZE.x, SCREEN_UV.y * VIEWPORT_SIZE.y);
vec3 neighbour_left = texture(SCREEN_TEXTURE, pixel_to_screen_uv(VIEWPORT_SIZE, iuv + vec2(0, 0))).rgb;
vec3 neighbour_right = texture(SCREEN_TEXTURE, pixel_to_screen_uv(VIEWPORT_SIZE, iuv + vec2(0.5, 0))).rgb;
vec3 neighbour_top = texture(SCREEN_TEXTURE, pixel_to_screen_uv(VIEWPORT_SIZE, iuv + vec2(0, 0.0))).rgb;
vec3 neighbour_bottom = texture(SCREEN_TEXTURE, pixel_to_screen_uv(VIEWPORT_SIZE, iuv + vec2(0, 0.5))).rgb;
ALBEDO = albedo.rgb * texture(texture_albedo, UV).rgb;
// compare normals: if they differ, we draw an edge
// by mixing in the edge_color, by edge_strength amount
// feel free to try other ways to mix, such as multiply for more textured objects.
if (abs(vec3_avg(neighbour_left) - vec3_avg(neighbour_right)) > 0.0) {
ALBEDO = mix(ALBEDO, edge_color.rgb, edge_strength);
}else if (abs(vec3_avg(neighbour_top) - vec3_avg(neighbour_bottom)) > 0.0) {
ALBEDO = mix(ALBEDO, edge_color.rgb, edge_strength);
}
METALLIC = metallic;
ROUGHNESS = roughness;
SPECULAR = specular;
}
// END OF SHADER PASS 2
//---------------------------------------------
This will have issues due to the vec3 avg color from the normals. For example a normal of (1, 0, 0) and (0, 1, 0) and (0, 0, 1) will all have the resulting avg value of 0.33, even though they are vastly different normals, they are considered the same in comparison.
Thanks!
I don’t think this works anymore in Godot 4, either that or I’m missing something haha. Anyone know how this is implemented properly?