(Updated) Decal with PBR – Godot 4.6
NOTE: THIS SHADER IS MENT TO BE TAKEN APPART AND USED IN YOUR OWN PROJECT SO DON’T DIRECTLY COPY AND PASTE AND EXPECT IT TO WORK!
The only new feature it has vs the default decal is it supports height maps.
Shader code
// Based of carpenterblue's shader: https://godotshaders.com/shader/decal-shader-for-4-3-compatibility-renderer-transparency-support-no-repeat/
shader_type spatial;
render_mode depth_draw_opaque;
uniform float blend_material : hint_range(0.0, 1.0, 0.01) = float(1.0);
group_uniforms albedo;
uniform vec4 albedo_color : source_color = vec4(1.0);
uniform sampler2D albedo_texture : source_color, repeat_disable;
group_uniforms normal_mapping;
uniform sampler2D normal_map_texture : hint_normal, repeat_disable;
group_uniforms height_mapping ;
uniform sampler2D texture_heightmap : source_color, repeat_disable;
uniform float heightmap_scale : hint_range(0.0, 64.0, 0.01);
uniform bool use_heightmap = false;
const int HEIGHTMAP_MAX_LAYERS = 16;
const int HEIGHTMAP_MIN_LAYERS = 4;
uniform float roughness : hint_range(0.0, 1.0, 0.01) = 1.0;
uniform float metallic : hint_range(0.0, 1.0, 0.01) = 0.0;
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture;
uniform sampler2D NORMAL_TEXTURE : hint_normal_roughness_texture;
const float CUBE_HALF_SIZE = 0.5;
const float DISTANCE = 0.1;
varying mat4 INV_MODEL_MATRIX;
void vertex(){
INV_MODEL_MATRIX = inverse(MODEL_MATRIX);
NORMAL = vec3(0.0, 0.0, 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_projection, mat4 inverse_view) {
float z = depth;
vec4 clip_space_position = vec4(screen_uv * 2.0 - 1.0, z, 1.0);
vec4 view_space_position = inverse_projection * clip_space_position;
view_space_position /= view_space_position.w;
vec4 world_space_position = inverse_view * view_space_position;
return world_space_position.xyz;
}
vec2 apply_heightmap(vec2 uvs, vec3 view_dir, float camera_distance){
if ((use_heightmap == true) && (camera_distance < 10.0)) {
float num_layers = mix(float(HEIGHTMAP_MAX_LAYERS), float(HEIGHTMAP_MIN_LAYERS), abs(dot(vec3(0.0, 0.0, 1.0), view_dir)));
float new_heightmap_scale = heightmap_scale;
new_heightmap_scale *= 1.0 - pow(min(camera_distance / 10.0, 1.0), 2.0);
float layer_depth = 1.0 / num_layers;
float current_layer_depth = 0.0;
vec2 p = view_dir.xy * new_heightmap_scale * 0.01;
vec2 delta = p / num_layers;
vec2 ofs = uvs;
float depth = 1.0 - texture(texture_heightmap, ofs).r;
float current_depth = 0.0;
while (current_depth < depth) {
ofs -= delta;
depth = 1.0 - texture(texture_heightmap, ofs).r;
current_depth += layer_depth;
}
vec2 prev_ofs = ofs + delta;
float after_depth = depth - current_depth;
float before_depth = (1.0 - texture(texture_heightmap, prev_ofs).r) - current_depth + layer_depth;
float weight = after_depth / (after_depth - before_depth);
ofs = mix(ofs, prev_ofs, weight);
return ofs;
}
return uvs;
}
void fragment() {
float camera_distance = distance(CAMERA_POSITION_WORLD, NODE_POSITION_WORLD);
vec3 view_dir = normalize(normalize(-VERTEX + EYE_OFFSET) * mat3(TANGENT, -BINORMAL, NORMAL));
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 = (INV_MODEL_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) > DISTANCE){
discard;
}
vec2 decal_uvs = (test_pos.xy * vec2(1.0, -1.0)) + 0.5;
decal_uvs = apply_heightmap(decal_uvs, view_dir, camera_distance);
vec4 color = texture(albedo_texture, decal_uvs) * albedo_color;
vec4 height_map = texture(texture_heightmap, decal_uvs);
ALBEDO = color.rgb * COLOR.rgb;
ALPHA = color.a * COLOR.a;
vec4 normals_view_remapped = (texture(NORMAL_TEXTURE, SCREEN_UV));
float new_blend_material = blend_material;
if (use_heightmap == true) {
new_blend_material *= max(dot(view_dir, normals_view_remapped.xyz), 0.0);
}
METALLIC = metallic;
NORMAL_MAP = texture(normal_map_texture, decal_uvs).xyz;
NORMAL_MAP_DEPTH = 1.0;
ROUGHNESS = mix(roughness, normals_view_remapped.w, new_blend_material);
}

