Improved Censor Shader
A slightly improved version of the Censor Shader by Loop_Box.
1. t reliably fades out the edge of the object, at least if the object is spherical.
2. It can keep the blur amount constant regardless of viewing distance.
I’m not sure how performant it is, but it works great and is a bit less obtrusive 🙂
Shader code
/*
Unobtrusive 3x3 pixelated blur with faded edges.
Designed to be used as a censoring tool.
Usage: assign to a spherical object.
It works like this:
1. sample the screen texture to get what's drawn behind it, blurs that and draws it.
2. compute N dot L to get the objects normal relative to the camera view and fades out when that angle points away from the camera.
3. since step 2 fails when the object is partially inside another object, it also fades based on the depth texture. Essentially: if the distance between the previously drawn object and this pixel is above a certain limit, fade that.
*/
shader_type spatial;
render_mode unshaded, blend_mix;
uniform sampler2D screen_texture : hint_screen_texture, filter_linear;
uniform sampler2D depth_texture : hint_depth_texture, filter_linear;
// The size of the pixel grid.
uniform vec2 pixel_grid = vec2(64.0, 64.0);
// The amount of blur to apply.
uniform float blur_amount = 18.0;
// Whether to compensate the blur amount based on the distance from the camera.
uniform bool distance_compensated_blur = true;
// The distance from the camera to the reference distance.
uniform float reference_distance = 1.84;
// The power of the edge fading.
uniform float edge_fade_power = 1.7;
// The distance from the camera to the proximity fade distance.
uniform float proximity_fade_distance = -0.02;
// Pixelate the screen UV to the pixel grid.
vec2 pixelate(vec2 uv) {
return floor(uv * pixel_grid) / pixel_grid;
}
// Blur the screen texture
vec3 blur_3x3(sampler2D tex, vec2 uv, float blur_power, vec2 screen_size) {
float px = 1.0 / screen_size.x;
float py = 1.0 / screen_size.y;
vec3 sum = vec3(0.0);
int count = 0;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
vec2 offset = vec2(float(x) * px * blur_power, float(y) * py * blur_power);
sum += textureLod(tex, uv + offset, 0.0).rgb;
count++;
}
}
return sum / float(count);
}
void fragment() {
vec2 uv = SCREEN_UV;
// Calculate distance from camera to the vertex in view space
float dist_to_camera = length(VERTEX);
// Scale pixel grid by distance so the pixelated cells appear constant in
// world space: close up fewer/larger cells, far away more/smaller cells.
vec2 adjusted_pixel_grid = pixel_grid;
if (distance_compensated_blur) {
adjusted_pixel_grid *= (dist_to_camera / max(reference_distance, 0.001));
}
uv = floor(uv * adjusted_pixel_grid) / adjusted_pixel_grid;
// Compensate blur amount based on distance
float adjusted_blur = blur_amount;
if (distance_compensated_blur) {
// As distance decreases, blur increases to maintain visual size
adjusted_blur *= (reference_distance / max(dist_to_camera, 0.01));
}
vec3 final_color = blur_3x3(screen_texture, uv, adjusted_blur, VIEWPORT_SIZE);
// Fresnel-based edge fading (for the silhouette)
float fresnel = pow(1.0 - clamp(dot(NORMAL, VIEW), 0.0, 1.0), edge_fade_power);
// Proximity-based fading (for intersections)
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_pos = upos.xyz / upos.w;
float proximity_fade = clamp((pixel_pos.z - VERTEX.z) / proximity_fade_distance, 0.0, 1.0);
float alpha = (1.0 - fresnel) * proximity_fade;
ALBEDO = final_color;
ALPHA = alpha;
}
