Fake light projection
Inspired by this post: https://bsky.app/profile/passivestar.bsky.social/post/3ml2x5jkfts2p made a shader that projects texture from mesh like a decal and uses normal buffer to fake the lighting.
A cheaper alternative to using projection textures on light sources (which require shadows on).
Forward+ only
To use it, add a mesh instance and and create material using this shader.
Shader code
shader_type spatial;
render_mode unshaded, blend_add;
uniform vec4 color: source_color = vec4(1,1,1,1);
uniform sampler2D shape_texture: source_color, repeat_disable;
uniform sampler2D NORMAL_TEXTURE: hint_normal_roughness_texture;
uniform sampler2D DEPTH_TEXTURE: hint_depth_texture;
uniform float uv_scale = 0.5;
uniform bool enable_y_fade = false; // Toggle for fade effect
uniform float fade_start = 0.3; // Where fading begins (0-1)
uniform float fade_end = 0.7; // Where fading ends (0-1)
uniform float fade_power = 2.0; // Curve sharpness
uniform vec3 direction;
// Credit: https://stackoverflow.com/questions/32227283/getting-world-position-from-depth-buffer-value
vec3 world_pos_from_depth(float depth, vec2 screen_uv, mat4 inverse_proj, mat4 inverse_view) {
vec4 clipSpacePosition = vec4(screen_uv * 2.0 - 1.0, depth, 1.0);
vec4 viewSpacePosition = inverse_proj * clipSpacePosition;
viewSpacePosition /= viewSpacePosition.w;
vec4 worldSpacePosition = inverse_view * viewSpacePosition;
return worldSpacePosition.xyz;
}
float smooth_fade(float y_pos) {
// Normalize Y position (-0.5 to 0.5) -> (0 to 1)
float normalized_y = clamp((y_pos + uv_scale) / (2.0 * uv_scale), 0.0, 1.0);
// Calculate fade factor
float fade_factor = smoothstep(fade_start, fade_end, normalized_y);
fade_factor = pow(fade_factor, fade_power);
return 1.0 - fade_factor;
}
void fragment() {
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
vec3 world_pos = world_pos_from_depth(depth, SCREEN_UV, INV_PROJECTION_MATRIX, INV_VIEW_MATRIX);
vec4 test_pos = (inverse(MODEL_MATRIX) * vec4(world_pos, 1.0) * uv_scale);
vec4 shape = texture(shape_texture, test_pos.xz + 0.5) * color;
vec3 screen_normal = texture(NORMAL_TEXTURE, SCREEN_UV).rgb;
screen_normal = 2. * screen_normal - 1.;
vec3 dir_view = (vec4(direction, 1.) * INV_VIEW_MATRIX).xyz;
vec3 m_normal = normalize(dir_view);
float y_fade = 1.0;
if (enable_y_fade) {
y_fade = smooth_fade(test_pos.y);
if (y_fade <= 0.0) {
discard;
}
}
float NdotN = dot(screen_normal, m_normal);
NdotN = max(0, NdotN);
float diff = NdotN * (1.0 / PI);
ALBEDO = shape.rgb;
ALPHA = shape.a * diff * y_fade;
}
