Decal Data (managedDecalData.json and .decals.json)

This document describes the modern BeamNG decal data files used by levels:

  • art/decals/managedDecalData.json - decal type definitions
  • *.decals.json - placed decal instances, commonly main.decals.json

These files work together. managedDecalData.json defines what decal types exist, while .decals.json files define where decals are placed in the level.


Overview

Decals are projected surface details used for things such as:

  • Road markings
  • Dirt and stains
  • Cracks
  • Tire marks
  • Bullet holes
  • Signs and labels
  • Surface damage
  • Painted markings

A decal type defines the material, default size, atlas frame settings, fade behavior, and projection settings. A decal instance stores the position, normal, tangent, size, render priority, and selected texture frame.

Typical structure:

levels/example_level/
├── art/
│   └── decals/
│       └── managedDecalData.json
└── main.decals.json

File roles

File Purpose
art/decals/managedDecalData.json Defines reusable decal types.
*.decals.json Stores placed decal instances.
.decals Legacy binary decal format. Deprecated.
Modern decal instance files are JSON. Legacy binary decal files start with the TDDF identifier and should be resaved to the modern format.

managedDecalData.json

managedDecalData.json defines decal datablocks. Each entry describes one decal type.

Example

{
  "road_crack_large": {
    "class": "DecalData",
    "material": "DECAL_road_crack_large",
    "size": 5,
    "randomize": true,
    "texRows": 2,
    "texCols": 2,
    "frame": 0,
    "renderPriority": 10,
    "clippingAngle": 60,
    "fadeStartPixelSize": -1,
    "fadeEndPixelSize": 200
  }
}

The top-level key is the decal datablock name. This is the name used by .decals.json instance files.


managedDecalData.json fields

Field Type Description
class string Should be "DecalData".
material string Material used by the decal. Must reference an existing material.
size number Default decal width and height in meters before instance scaling.
renderPriority integer Draw order when decals overlap. Higher/lower priority affects which decal appears on top depending on sorting.
clippingAngle number Angle in degrees used to clip geometry facing away from the decal projection direction.
fadeStartPixelSize number Pixel size where this decal type begins to fade out. Negative values disable LOD-based fading.
fadeEndPixelSize number Pixel size where this decal type is fully faded out.
frame integer Default texture frame index used from the decal atlas.
randomize bool If true, each decal instance randomly selects a frame from the atlas.
texRows integer Atlas grid value. See note below.
texCols integer Atlas grid value. See note below.
textureCoordCount integer Number of available texture frames. Usually generated from texRows and texCols.
textureCoords RectF array Manual UV rectangles for irregular atlas layouts.
annotation string Optional annotation/debug classification.

Texture atlas frames

A decal material can use a texture atlas containing multiple decal variations.

For example, one texture may contain four crack variations in a 2×2 grid. The decal system can then select one frame per decal.

{
  "randomize": true,
  "texRows": 2,
  "texCols": 2
}

If randomize is enabled, each placed decal can use a random atlas frame.

If randomize is disabled, the frame field selects the frame.

{
  "frame": 1,
  "randomize": false
}

Manual texture coordinates

For irregular atlas layouts, use textureCoords.

Each rectangle is:

x y width height

or:

"textureCoords": [
  [0.0, 0.0, 0.5, 0.5],
  [0.5, 0.0, 0.5, 0.5]
]

Use manual coordinates only when the frames are not arranged in a regular grid.

Decal atlases support up to 16 texture frames.

.decals.json instance files

.decals.json files store placed decal instances.

Common file:

levels/<levelName>/main.decals.json

Basic structure

{
  "header": {
    "name": "DecalData File",
    "version": 2.0,
    "comments": "// Instances format: rectIdx, size, renderPriority, position.x, position.y, position.z, normal.x, normal.y, normal.z, tangent.x, tangent.y, tangent.z, uid"
  },
  "instances": {
    "road_crack_large": [
      [0, 5.0, 10, 100.0, 200.0, 0.1, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 12345]
    ]
  }
}

The instances object is grouped by decal datablock name. Each key must match a DecalData entry from managedDecalData.json.


Instance array format

In version 2.0, each decal instance is stored as an array with 13 values:

Index Field Type Description
0 rectIdx integer Texture atlas frame index.
1 size number Final decal size in meters.
2 renderPriority integer Render priority for this instance.
3 position.x number World position X.
4 position.y number World position Y.
5 position.z number World position Z.
6 normal.x number Projection normal X.
7 normal.y number Projection normal Y.
8 normal.z number Projection normal Z.
9 tangent.x number Tangent X, controls decal rotation/orientation.
10 tangent.y number Tangent Y.
11 tangent.z number Tangent Z.
12 uid unsigned integer Unique decal instance ID.

Example instance

[0, 5.0, 10, 100.0, 200.0, 0.1, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 12345]

This means:

Frame:          0
Size:           5 m
Priority:       10
Position:       [100, 200, 0.1]
Normal:         [0, 0, 1]
Tangent:        [1, 0, 0]
UID:            12345

Header

Modern .decals.json files contain a header:

"header": {
  "name": "DecalData File",
  "version": 2.0,
  "comments": "// Instances format: rectIdx, size, renderPriority, position.x, position.y, position.z, normal.x, normal.y, normal.z, tangent.x, tangent.y, tangent.z, uid"
}
Field Description
name Human-readable file identifier.
version Decal file version. Version 2.0 includes instance UIDs.
comments Optional description of the instance array layout.
Older JSON decal files without UIDs should be resaved in the editor. The modern format includes a uid value for each decal instance.

Decal materials

The material field in managedDecalData.json must point to a valid material.

Example decal material:

"DECAL_road_crack_large": {
  "name": "DECAL_road_crack_large",
  "mapTo": "unmapped_mat",
  "class": "Material",
  "Stages": [
    {
      "baseColorMap": "/levels/example/art/decals/road_crack_b.color.png",
      "normalMap": "/levels/example/art/decals/road_crack_nm.normal.png",
      "opacityMap": "/levels/example/art/decals/road_crack_o.data.png",
      "roughnessMap": "/levels/example/art/decals/road_crack_r.data.png"
    },
    {},
    {},
    {}
  ],
  "translucent": true,
  "translucentZWrite": true,
  "version": 1.5
}

Decal materials commonly use:

  • Base color
  • Opacity
  • Normal
  • Roughness
  • Ambient occlusion, when needed
Decal rendering uses a decal-specific material path and supports decal fading. Clear coat is not used for decals.

Projection orientation

Each placed decal stores:

  • position
  • normal
  • tangent

The normal defines the projection direction. The tangent defines rotation/orientation on the surface.

For a decal on a flat horizontal surface:

normal  = [0, 0, 1]
tangent = [1, 0, 0]

If a decal appears rotated incorrectly, check the tangent vector.


Render priority

renderPriority controls draw ordering when decals overlap.

This is useful for cases such as:

  • Road markings over asphalt
  • Dirt over road markings
  • Cracks over painted lines
  • Layered damage decals

A consistent priority setup helps avoid flickering or incorrect overlap.


Fading

Decal types can fade based on screen size:

"fadeStartPixelSize": 100,
"fadeEndPixelSize": 20

Typical behavior:

  • Above fadeStartPixelSize: fully visible
  • Between start and end: fading
  • Below fadeEndPixelSize: hidden

Use a negative fadeStartPixelSize to disable LOD-based fading:

"fadeStartPixelSize": -1

Clipping angle

clippingAngle controls how much surrounding geometry can receive the projected decal.

Lower values limit the decal to surfaces facing the projection direction more closely. Higher values allow projection onto steeper angles.

Example:

"clippingAngle": 60

Use this to avoid decals wrapping too far around corners or projecting onto unwanted surfaces.


Loading process

When a level loads, the decal system roughly follows this process:

  1. Load decal type definitions from managedDecalData.json.
  2. Load materials referenced by each decal type.
  3. Load .decals.json instance files.
  4. For each instance group:
    • Find the matching DecalData by name.
    • Read each instance array.
    • Create decal instance with position, normal, tangent, size, frame, and UID.
  5. Add decals to spatial decal containers for rendering and editing.

If a decal type is missing, the engine may create a temporary _missing decal datablock using WarningMaterial.


Legacy binary .decals files

Older decal files used a binary format starting with:

TDDF

These files are deprecated.

When loaded, the engine attempts to convert them internally and marks the decal data as dirty so it can be resaved in the modern JSON format.

If a level still uses old binary .decals files, resave the level and remove the old file after verifying the new .decals.json output.

Minimal example

art/decals/managedDecalData.json

{
  "road_crack_large": {
    "class": "DecalData",
    "material": "DECAL_road_crack_large",
    "size": 5,
    "randomize": true,
    "texRows": 2,
    "texCols": 2,
    "renderPriority": 10,
    "clippingAngle": 60,
    "fadeStartPixelSize": -1,
    "fadeEndPixelSize": 200
  }
}

main.decals.json

{
  "header": {
    "name": "DecalData File",
    "version": 2.0,
    "comments": "// Instances format: rectIdx, size, renderPriority, position.x, position.y, position.z, normal.x, normal.y, normal.z, tangent.x, tangent.y, tangent.z, uid"
  },
  "instances": {
    "road_crack_large": [
      [0, 5.0, 10, 100.0, 200.0, 0.1, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 12345],
      [2, 4.0, 10, 110.0, 205.0, 0.1, 0.0, 0.0, 1.0, 0.707, 0.707, 0.0, 12346]
    ]
  }
}

Best practices

  • Use managedDecalData.json for decal definitions.
  • Use .decals.json for saved decal instances.
  • Keep decal material names valid and unique.
  • Use atlases for decal variations instead of many separate materials.
  • Keep atlas frame count at or below 16.
  • Use opacity maps to control decal shape.
  • Use appropriate clippingAngle values to avoid projection artifacts.
  • Use render priorities consistently for overlapping decals.
  • Resave old decal files to upgrade them to JSON format.
  • Use cooked DDS textures for decal materials.

Common issues

Decals appear as warning material

Possible causes:

  • Missing decal material
  • Invalid material field in managedDecalData.json
  • Material failed to load

Decals do not appear

Possible causes:

  • Missing decal datablock
  • Instance group name does not match any decal type
  • Opacity map is black
  • Decal is clipped by surface angle
  • Fade settings hide the decal
  • Invalid instance array

Decal appears rotated incorrectly

Check the tangent vector in the instance data.

Decal projects onto unwanted surfaces

Lower clippingAngle.

Atlas frame is wrong

Check rectIdx, frame, randomize, texRows, texCols, and textureCoords.

Editor asks to resave decals

The file may be an older format without UIDs. Resave the level to upgrade it.


Summary

BeamNG decal data uses two main files:

  • managedDecalData.json defines decal types and materials.
  • .decals.json stores placed decal instances.

The modern instance format is JSON, grouped by decal type, with each decal stored as a compact numeric array containing frame, size, priority, transform vectors, and UID.

Last modified: May 28, 2026

Any further questions?

Join our discord
Our documentation is currently incomplete and undergoing active development. If you have any questions or feedback, please visit this forum thread.