Decal Example

A relatively simple decal shader that uses the depth buffer to draw over the geometry in the scene. The shader works great with a default Godot cube.

One thing to note: The shader does NOT take the normals of the scene into account and will draw over anything within the cube. I tried writing the shader so it would reconstruct the normals from the depth buffer (which kinda worked) but the normals were relative to the camera rotation and not the world position and I couldn’t figure out how to transform it for use with decals.

For the drawing over anything within the cube, color masking could be used for this! I didn’t implement it in the shader, but I have written a tutorial on colormasking on my website RandomMomentania, which may be helpful as a reference.

 

This shader is not optimized! I just wrote it as a shader experiment/test and wanted to share the results.

Credit to Andon M. Coleman on StackOverflow for posting the snippet for getting a world position from the depth buffer. Also, credit to Ronja’s tutorials for the inspiration to make this little shader experiment.

Shader code
shader_type spatial;
render_mode world_vertex_coords, unshaded;
uniform vec4 albedo : hint_color;
uniform sampler2D texture_albedo : hint_albedo;

uniform float cube_half_size = 1.0;


// 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) {
	float z = depth * 2.0 - 1.0;
	
	vec4 clipSpacePosition = vec4(screen_uv * 2.0 - 1.0, z, 1.0);
	vec4 viewSpacePosition = inverse_proj * clipSpacePosition;
	
	viewSpacePosition /= viewSpacePosition.w;
	
	vec4 worldSpacePosition = inverse_view * viewSpacePosition;
	
	return worldSpacePosition.xyz;
}

void fragment() {
	float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
	vec3 world_pos = world_pos_from_depth(depth, SCREEN_UV, INV_PROJECTION_MATRIX, (CAMERA_MATRIX));
	vec4 test_pos = (inverse(WORLD_MATRIX) * vec4(world_pos, 1.0));
	
	if (abs(test_pos.x) > cube_half_size ||abs(test_pos.y) > cube_half_size || abs(test_pos.z) > cube_half_size) {
		discard;
	}
	ALBEDO = texture(texture_albedo, (test_pos.xz * 0.5) + 0.5).rgb * albedo.rgb;
}
Tags
decal, godot
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.

Related shaders

Texture mix example

Raymarched 3D Noise Decal

Quick Example Fresnel Outline

Subscribe
Notify of
guest

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
NekotoArts
3 years ago

icon.png has seen better days XD. Nice work tho!

SonnyBonds
SonnyBonds
2 years ago

Great starting point for decal shaders!

An optimization tip is to inverse the world matrix in the vertex shader and send it to fragment in a varying mat4 instead of inverting it in the fragment shader. This yields a massive speedup, at least on the fairly bad graphics card I’ve got in this laptop…

bengtsts
1 year ago
Reply to  SonnyBonds

Thanks! With unoptimised heavy use, I got about 10 fps on a low end machine with just that tip.

Huraqan
Huraqan
1 year ago

Cool! How can you apply it to only certain objects?

TheYellowArchitect
9 months ago

Is this more performant than using a Decal?

redston
redston
8 months ago

im using it bc i want to use opengl and in opengl theres no decals other then that im not sure