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.jsonThese files work together. managedDecalData.json defines what decal types exist, while .decals.json files define where decals are placed in the level.
Decals are projected surface details used for things such as:
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 | Purpose |
|---|---|
art/decals/managedDecalData.json |
Defines reusable decal types. |
*.decals.json |
Stores placed decal instances. |
.decals |
Legacy binary decal format. Deprecated. |
TDDF identifier and should be resaved to the modern format.managedDecalData.json defines decal datablocks. Each entry describes one decal type.
{
"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.
| 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. |
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
}
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.
.decals.json files store placed decal instances.
Common file:
levels/<levelName>/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]
]
}
}
The instances object is grouped by decal datablock name. Each key must match a DecalData entry from managedDecalData.json.
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. |
[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
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. |
uid value for each decal instance.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:
Each placed decal stores:
positionnormaltangentThe 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.
renderPriority controls draw ordering when decals overlap.
This is useful for cases such as:
A consistent priority setup helps avoid flickering or incorrect overlap.
Decal types can fade based on screen size:
"fadeStartPixelSize": 100,
"fadeEndPixelSize": 20
Typical behavior:
fadeStartPixelSize: fully visiblefadeEndPixelSize: hiddenUse a negative fadeStartPixelSize to disable LOD-based fading:
"fadeStartPixelSize": -1
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.
When a level loads, the decal system roughly follows this process:
managedDecalData.json..decals.json instance files.DecalData by name.If a decal type is missing, the engine may create a temporary _missing decal datablock using WarningMaterial.
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.
.decals files, resave the level and remove the old file after verifying the new .decals.json output.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]
]
}
}
managedDecalData.json for decal definitions..decals.json for saved decal instances.clippingAngle values to avoid projection artifacts.Possible causes:
material field in managedDecalData.jsonPossible causes:
Check the tangent vector in the instance data.
Lower clippingAngle.
Check rectIdx, frame, randomize, texRows, texCols, and textureCoords.
The file may be an older format without UIDs. Resave the level to upgrade it.
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.
Was this article helpful?