Screen Cavity (Blender-like)
I was trying to recreate the blender “cavity”. Unfortunately it consists of “screen” and “world” components.
It’s just “screen”, but I’ve added a depth buffer instead.
There are 2 modes, feel free, removing the excess.
HOW TO USE:
Create a MeshInstance3D node with a QuadMesh and place it in your scene.
Assign the material with this shader.
(Look at the 2nd screenshot.)
TESTED ENGINE VERSION: 4.0.2 / 4.2.1.stable / 4.3.stable
They helped me:
https://blender.community/c/rightclickselect/J9bbbc/?sorting=hot
https://you.com/
Shader code
shader_type spatial;
render_mode unshaded;
uniform sampler2D SCREEN_TEXTURE:hint_screen_texture, filter_linear_mipmap;
uniform sampler2D DEPTH_TEXTURE :source_color, hint_depth_texture;
uniform sampler2D NORMAL_TEXTURE : hint_normal_roughness_texture, filter_nearest;
uniform float zNear = 2;
uniform float zFar = 10.0;
uniform float width = 1.0;
uniform bool addDepth = false;
vec3 overlay(vec3 base, vec3 blend) {
return mix(2.0 * base * blend,
1.0 - 2.0 * (1.0 - base) * (1.0 - blend), clamp(base, 0.0, 1.0));
}
float textureCavity(sampler2D normal_texture, vec2 uv, vec2 screen_pixel_size){
vec4 normal = texture(normal_texture, uv);
float r_off = texture(normal_texture, uv - vec2(screen_pixel_size.x*width, 0)).r;
float g_off = texture(normal_texture, uv + vec2(0, screen_pixel_size.y*width)).g;
return (normal.r - r_off) + (normal.g - g_off) + 0.5;
}
float scaledDepth(sampler2D depth_texture, vec2 uv, mat4 inv_proj_mat){
float depth = texture(depth_texture, uv).x;
vec3 ndc = vec3(uv, depth) * 2.0 - 1.0;
vec4 view = inv_proj_mat * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = view.z;
return (zFar * zNear) / (zFar + (linear_depth * (zNear - zFar)));
}
void fragment() {
vec4 origin = texture(SCREEN_TEXTURE, SCREEN_UV);
vec2 SCREEN_PIXEL_SIZE = 1.0 / VIEWPORT_SIZE;
float res = textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE);
float scaled_depth = scaledDepth(DEPTH_TEXTURE, SCREEN_UV, INV_PROJECTION_MATRIX);
if (addDepth) {
ALBEDO = overlay(overlay(origin.rgb, vec3(res)), vec3(scaled_depth));
} else {
ALBEDO = overlay(origin.rgb, vec3(res));
//ALBEDO = vec3(scaled_depth);
}
}





Hi! working as advertised, thank you very much.
Can’t get it working, any hint of what I am doing wrong? using v4.3
Thanks heaps
Screenshot:
https://drive.google.com/file/d/1cibTlm3RnCWszKmCNzdhypILT2yiVmoC/view?usp=sharing
Very strange error. Are you using 4.3.stable?
Try it this way. I removed the unnecessary and added the useful.
Tested on 4.3.stable.official [77dcf97d8]
______________________
shader_type spatial;
render_mode unshaded;
uniform sampler2D SCREEN_TEXTURE:hint_screen_texture, filter_linear_mipmap;
uniform sampler2D DEPTH_TEXTURE :source_color, hint_depth_texture;
uniform sampler2D NORMAL_TEXTURE : hint_normal_roughness_texture, filter_nearest;
uniform float _width = 1.0;
uniform bool blur = false;
vec3 overlay(vec3 base, vec3 blend) {
return mix(2.0 * base * blend,
1.0 – 2.0 * (1.0 – base) * (1.0 – blend), clamp(base, 0.0, 1.0));
}
float textureCavity(sampler2D normal_texture, vec2 uv, vec2 screen_pixel_size, float width){
vec4 normal = texture(normal_texture, uv);
float r_off = texture(normal_texture, uv – vec2(screen_pixel_size.x * width, 0)).r;
float g_off = texture(normal_texture, uv + vec2(0, screen_pixel_size.y * width)).g;
return (normal.r – r_off) + (normal.g – g_off) + 0.5;
}
void fragment() {
vec4 origin = texture(SCREEN_TEXTURE, SCREEN_UV);
vec2 SCREEN_PIXEL_SIZE = 1.0 / VIEWPORT_SIZE;
float res = textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE, _width);
if (blur) {
res *= 0.5;
res += textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE, 3.0) * 0.3;
res += textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE, 7.0) * 0.1;
}
ALBEDO = overlay(origin.rgb, vec3(res));
}
Today I figured out how to do blur.
Actually, it’s quite simple.
You just need to output the “width” to the parameter of the “textureCavity” function.
Then call it several times, with different values.
This requires a bit of manual tweaking to suit your needs, but it seems to improve the picture.
float textureCavity(sampler2D normal_texture, vec2 uv, vec2 screen_pixel_size, float width){
vec4 normal = texture(normal_texture, uv);
float r_off = texture(normal_texture, uv – vec2(screen_pixel_size.x * width, 0)).r;
float g_off = texture(normal_texture, uv + vec2(0, screen_pixel_size.y * width)).g;
return (normal.r – r_off) + (normal.g – g_off) + 0.5;
}
void fragment() {
vec4 origin = texture(SCREEN_TEXTURE, SCREEN_UV);
vec2 SCREEN_PIXEL_SIZE = 1.0 / VIEWPORT_SIZE;
float res = textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE, 2.0);
if (blur) {
res *= 0.5;
res += textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE, 3.0) * 0.3;
res += textureCavity(NORMAL_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE, 7.0) * 0.1;
}
ALBEDO = overlay(origin.rgb, vec3(res));
}
I love this shader, is it possible for you to make a compositor effect version at some point?
Currently, this shader occludes particles, is there anyway this could be solved?
Hi! What can I do to not see shader outlines through worldenviroment’s fog?
Hey Ivan, I’m trying to use this with Mobile renderer, which doesn’t support hint_normal_roughness_texture unfortunately, so I’m getting the normals through depth but I’m having a hard time with the textureCavity function, do you happen to know a way I could make it work?
Thanks in advance!
I basically get the normals like this:
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 – 1.0, depth, 1.0);
vec3 pixel_position = upos.xyz / upos.w;
vec3 normal = normalize(cross(dFdy(pixel_position),dFdx(pixel_position)));
ok it seems like I got it working on Mobile, but I think it could do be optimized… if anyone could help me with that I’d really appreciate it!
Just replace the res variable in fragment with: