~3 min read

Decals — stickers in 3D

The Decal node for bullet holes, graffiti, dirt masks, footprints, and other overlays on geometry.

Decal is a node in Godot that projects a texture onto the surfaces beneath it. It’s the classic technique for:

  • Bullet holes on a wall
  • Dirt on the floor, puddles
  • Graffiti, posters
  • Tire tracks
  • Cracks, blood, scorch marks from explosions

It is implemented as a separate render pass and does not sit on the geometry — so you can “stick” it onto any surface without modifying meshes.

Web

CSS mix-blend-mode: multiply or Canvas globalCompositeOperation = 'multiply' over a background. In 3D, browsers have no equivalent — this is specific to real-time graphics.

Unity

Basic usage

  1. Add a Decal node to the scene.
  2. In the Inspector, set texture_albedo — the main texture.
  3. (Optional) texture_normal — a normal map for relief.
  4. (Optional) texture_orm — an Occlusion/Roughness/Metallic mask.
  5. Rotate and scale the node: the decal’s bounding box determines which area it projects onto.
  6. cull_mask — a filter by visual layers (only certain objects receive the decal).
World3D
├── Floor (StaticBody3D + MeshInstance3D)
└── BulletHole (Decal)
    texture_albedo: bullet_hole.png (RGBA)
    size: (0.3, 0.3, 0.3)  # 30×30 cm area
    rotation: looking at -Y (down — onto the floor)

Programmatic spawn

extends Node

@export var decal_scene: PackedScene  # a Decal as a PackedScene
@export var lifetime: float = 30.0

func spawn_bullet_hole(at: Vector3, normal: Vector3) -> void:
    var decal := decal_scene.instantiate() as Decal
    add_child(decal)
    decal.global_position = at + normal * 0.01  # slightly above the surface

    # Orient the decal so it looks "down" (along -Y) onto the normal
    decal.look_at(at - normal, Vector3.UP if abs(normal.y) < 0.9 else Vector3.BACK)

    # A little rotation randomization for a natural look
    decal.rotate_object_local(Vector3.UP, randf() * TAU)

    # Remove after lifetime — the typical approach for a "fading out" effect
    var tween := create_tween()
    tween.tween_interval(lifetime * 0.7)
    tween.tween_property(decal, "albedo_mix", 0.0, lifetime * 0.3)
    tween.tween_callback(decal.queue_free)

albedo_mix is a float 0..1 that controls the decal’s “strength”. Animate it to 0 → the decal fades out smoothly.

Decal parameters

  • size: Vector3 — the projection’s bounding box (X×Y across the surface, Z — the projection depth).
  • texture_albedo + albedo_mix — the color texture and its strength.
  • texture_normal + normal_fade — the normal map and how strongly it’s applied.
  • texture_orm — combined Occlusion/Roughness/Metallic.
  • texture_emission + emission_energy — self-illuminating decals (lights, screens).
  • modulate: Color — an overall multiply color (for recoloring without replacing the texture).
  • upper_fade / lower_fade — fade at the corners of the bounding box.
  • distance_fade_* — fade with distance (for optimization; distant decals disappear).
  • cull_mask — a bitmask of visual layers; the decal is visible only on those layers.
distance_fade — a must-have for FPS scenes

In scenes with active gunfire, hundreds of decals accumulate. Without distance_fade_enabled = true

  • sensible begin / length values, the renderer processes them all. With fade, distant ones smoothly become transparent and are skipped, leaving only the ~30-50 nearest ones visible.

Rendering requirements

  • Forward+ or the Mobile renderer. On Compatibility (WebGL), decals work, but in a reduced form and through emulation.
  • A Decal does not write to the depth buffer — it sits on top of the pixel. This means: it is not visible around a corner or through walls, and sorting with transparent objects can be tricky.

When a decal is NOT the choice

  • Large, precise markings on the floor (for example, lane markings for a render-to-map) — it may be cheaper to bake them into the floor texture or a second UV layer.
  • Geometry that should affect physics (a bulge, a figurine) — a Decal is purely visual; it does not change the collision shape.
  • Animated decals — they are supported (a sprite sheet via a UV shift in texture_albedo), but through a ShaderMaterial; the standard Decal node has no animation in the Inspector.

Comparison with Unity

Unity has an equivalent: the Decal Projector in URP (com.unity.render-pipelines.universal/Decals). The concept and API are almost 1-to-1: also a projector, texture_albedo, distance fade, cull mask. The differences are in performance details: the Unity URP Decal Projector is usually more expensive than a Godot Decal in Forward+.