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;
}
Live Preview
Tags
decal, lighting, projected, projection, uv
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Rytelier

Related shaders

guest

0 Comments
Oldest
Newest Most Voted