Object Outline Shader
Uses sobel edge detection on normal and depth textures to add an outline to objects.
Shader code
shader_type spatial;
render_mode unshaded;
/*
Normal/Depth outline shader. Apply to nodes as a next pass shader texture.
Inspired by Yui Kinomoto @arlez80, lukky_nl (YT), Robin Seibold (YT)
Uses Sobel Edge detection on a normal and depth texture
Written by William Li (LoudFlameLava)
MIT License
*/
// Might create an outline at the edge of the viewport
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;
uniform sampler2D NORMAL_TEXTURE : hint_normal_roughness_texture, filter_linear_mipmap;
uniform float normal_threshold = 0.1;
uniform float depth_threshold = 0.05;
uniform float depth_artifact_correction_coef = 2;
uniform vec3 outline_color: source_color;
const mat3 sobel_y = mat3(
vec3(1.0, 0.0, -1.0),
vec3(2.0, 0.0, -2.0),
vec3(1.0, 0.0, -1.0)
);
const mat3 sobel_x = mat3(
vec3(1.0, 2.0, 1.0),
vec3(0.0, 0.0, 0.0),
vec3(-1.0, -2.0, -1.0)
);
float edge_value_normal(sampler2D normal_tex, vec2 uv, vec2 pixel_size, mat3 sobel) {
float output = 0.0;
vec3 normal = texture(normal_tex, uv).rgb;
vec3 n = texture(NORMAL_TEXTURE, uv + vec2(0.0, -pixel_size.y)).rgb;
vec3 s = texture(NORMAL_TEXTURE, uv + vec2(0.0, pixel_size.y)).rgb;
vec3 e = texture(NORMAL_TEXTURE, uv + vec2(pixel_size.x, 0.0)).rgb;
vec3 w = texture(NORMAL_TEXTURE, uv + vec2(-pixel_size.x, 0.0)).rgb;
vec3 nw = texture(NORMAL_TEXTURE, uv + vec2(-pixel_size.x, -pixel_size.y)).rgb;
vec3 ne = texture(NORMAL_TEXTURE, uv + vec2(pixel_size.x, -pixel_size.y)).rgb;
vec3 sw = texture(NORMAL_TEXTURE, uv + vec2(-pixel_size.x, pixel_size.y)).rgb;
vec3 se = texture(NORMAL_TEXTURE, uv + vec2(pixel_size.x, pixel_size.y)).rgb;
mat3 error_mat = mat3(
vec3(length(normal - nw), length(normal - n), length(normal - ne)),
vec3(length(normal - w), 0.0, length(normal - e)),
vec3(length(normal - sw), length(normal - s), length(normal - se))
);
output += dot(sobel[0], error_mat[0]);
output += dot(sobel[1], error_mat[1]);
output += dot(sobel[2], error_mat[2]);
return abs(output);
}
float get_depth(sampler2D depth_tex, vec2 uv, mat4 inv_projection_matrix) {
float depth_raw = texture(depth_tex, uv).x;
vec3 ndc = vec3(uv * 2.0 - 1.0, depth_raw);
vec4 view = inv_projection_matrix * vec4(ndc, 1.0);
view.xyz /= view.w;
float depth_linear = -view.z;
return depth_linear;
}
float edge_value_depth(sampler2D depth_tex, vec2 uv, vec2 pixel_size, mat3 sobel, mat4 inv_projection_matrix){
float output = 0.0;
float depth = get_depth(depth_tex, uv, inv_projection_matrix);
float n = get_depth(depth_tex, uv + vec2(0.0, -pixel_size.y), inv_projection_matrix);
float s = get_depth(depth_tex, uv + vec2(0.0, pixel_size.y), inv_projection_matrix);
float e = get_depth(depth_tex, uv + vec2(pixel_size.x, 0.0), inv_projection_matrix);
float w = get_depth(depth_tex, uv + vec2(-pixel_size.x, 0.0), inv_projection_matrix);
float ne = get_depth(depth_tex, uv + vec2(pixel_size.x, -pixel_size.y), inv_projection_matrix);
float nw = get_depth(depth_tex, uv + vec2(-pixel_size.x, -pixel_size.y), inv_projection_matrix);
float se = get_depth(depth_tex, uv + vec2(pixel_size.x, pixel_size.y), inv_projection_matrix);
float sw = get_depth(depth_tex, uv + vec2(-pixel_size.x, pixel_size.y), inv_projection_matrix);
mat3 error_mat = mat3(
vec3((depth - nw)/depth, (depth - n)/depth, (depth - ne)/depth),
vec3((depth - w)/depth, 0.0, (depth - e)/depth),
vec3((depth - sw)/depth, (depth - s)/depth, (depth - se)/depth)
);
output += dot(sobel[0], error_mat[0]);
output += dot(sobel[1], error_mat[1]);
output += dot(sobel[2], error_mat[2]);
return abs(output);
}
void fragment() {
float has_outline = 0.0;
vec2 pixel_size = vec2(1.0) / VIEWPORT_SIZE;
ALBEDO = texture(SCREEN_TEXTURE, SCREEN_UV).xyz;
//ALBEDO = vec3(get_depth(DEPTH_TEXTURE, SCREEN_UV, INV_PROJECTION_MATRIX));
if (edge_value_normal(NORMAL_TEXTURE, SCREEN_UV, pixel_size, sobel_x) + edge_value_normal(NORMAL_TEXTURE, SCREEN_UV, pixel_size, sobel_y) > normal_threshold){
ALBEDO = outline_color;
has_outline += 1.0;
}
vec3 normal = texture(NORMAL_TEXTURE, SCREEN_UV).rgb;
float angle = 1.0 - dot(normalize(normal-vec3(0.5)), vec3(0.0,0.0,1.0));
if (edge_value_depth(DEPTH_TEXTURE, SCREEN_UV, pixel_size, sobel_x, INV_PROJECTION_MATRIX) + edge_value_depth(DEPTH_TEXTURE, SCREEN_UV, pixel_size, sobel_y, INV_PROJECTION_MATRIX) > depth_threshold + angle * depth_artifact_correction_coef){
ALBEDO = outline_color;
has_outline += 1.0;
}
if (has_outline < 0.1){
ALPHA = 0.0;
}
}