| name | advanced-rendering |
| description | Advanced rendering in Decentraland scenes. Billboard (face camera), TextShape (3D world text), PBR materials (metallic, roughness, transparency, emissive glow), GltfNodeModifiers (per-node shadow/material overrides), VisibilityComponent (show/hide entities), and texture modes. Use when the user wants billboards, floating labels, 3D text, material effects, glow, transparency, or model node control. Do NOT use for screen-space UI (see build-ui) or loading 3D models (see add-3d-models). |
Advanced Rendering in Decentraland
Authoring split
Billboard, TextShape, Material, MeshRenderer, GltfContainer, VisibilityComponent, and GltfNodeModifiers are all supported in main-entities.ts — declare the visual entity (sign, label, billboard, glowing prop, model with per-node overrides) fully there with its visual components. Examples below that show engine.addEntity() followed by Transform.create + MeshRenderer / Billboard / TextShape are pre-main-entities.ts patterns — translate them by moving the entity declaration into main-entities.ts and keeping only runtime modifications (e.g., VisibilityComponent.getMutable(entity).visible = false) in src/index.ts.
shop_label: {
components: {
Transform: { position: { x: 8, y: 3, z: 8 } },
TextShape: { text: 'OPEN', fontSize: 4, textColor: { r: 1, g: 1, b: 1, a: 1 } },
Billboard: { billboardMode: 7 }
}
}
When to Use Which Rendering Feature
| Need | Component | When |
|---|
| Entity faces the camera | Billboard | Name tags, signs, sprite-like objects |
| Text in the 3D world | TextShape | Labels, signs, floating text above entities |
| Custom material appearance | Material.setPbrMaterial | Metallic, rough, transparent, emissive surfaces |
| Show/hide without removing | VisibilityComponent | LOD systems, toggling objects, conditional display |
| Modify GLTF model nodes | GltfNodeModifiers | Override materials or shadow casting on specific mesh nodes |
Decision flow:
- Need text on screen? → Use build-ui (React-ECS Label) instead
- Need text in 3D space? →
TextShape (+ Billboard to face camera)
- Need glowing/transparent materials? →
Material.setPbrMaterial with emissive/transparency
- Need to override material on a model node? →
GltfNodeModifiers with modifiers array
Billboard (Face the Camera)
Make entities always rotate to face the player's camera:
import { engine, Transform, Billboard, BillboardMode, MeshRenderer } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const sign = engine.addEntity()
Transform.create(sign, { position: Vector3.create(8, 2, 8) })
MeshRenderer.setPlane(sign)
Billboard.create(sign, {
billboardMode: BillboardMode.BM_Y
})
Billboard Modes
BillboardMode.BM_Y
BillboardMode.BM_ALL
BillboardMode.BM_X
BillboardMode.BM_Z
BillboardMode.BM_NONE
- Prefer
BM_Y over BM_ALL for most use cases — it looks more natural and is cheaper to render.
BM_ALL is useful for particles or effects that should always directly face the camera.
TextShape (3D Text)
Render text directly in 3D space:
import { engine, Transform, TextShape, TextAlignMode } from '@dcl/sdk/ecs'
import { Vector3, Color4 } from '@dcl/sdk/math'
const label = engine.addEntity()
Transform.create(label, { position: Vector3.create(8, 3, 8) })
TextShape.create(label, {
text: 'Hello World!',
fontSize: 24,
textColor: Color4.White(),
outlineColor: Color4.Black(),
outlineWidth: 0.1,
textAlign: TextAlignMode.TAM_MIDDLE_CENTER
})
Text Alignment Options
TextAlignMode.TAM_TOP_LEFT
TextAlignMode.TAM_TOP_CENTER
TextAlignMode.TAM_TOP_RIGHT
TextAlignMode.TAM_MIDDLE_LEFT
TextAlignMode.TAM_MIDDLE_CENTER
TextAlignMode.TAM_MIDDLE_RIGHT
TextAlignMode.TAM_BOTTOM_LEFT
TextAlignMode.TAM_BOTTOM_CENTER
TextAlignMode.TAM_BOTTOM_RIGHT
Floating Label (Billboard + TextShape)
Combine Billboard and TextShape to create labels that always face the player:
const floatingLabel = engine.addEntity()
Transform.create(floatingLabel, { position: Vector3.create(8, 4, 8) })
TextShape.create(floatingLabel, {
text: 'NPC Name',
fontSize: 16,
textColor: Color4.White(),
outlineColor: Color4.Black(),
outlineWidth: 0.08,
textAlign: TextAlignMode.TAM_BOTTOM_CENTER
})
Billboard.create(floatingLabel, {
billboardMode: BillboardMode.BM_Y
})
Advanced PBR Materials
Metallic and Roughness
import { engine, Transform, MeshRenderer, Material, MaterialTransparencyMode } from '@dcl/sdk/ecs'
import { Color4, Color3 } from '@dcl/sdk/math'
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0.8, 0.8, 0.9, 1),
metallic: 1.0,
roughness: 0.1
})
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0.5, 0.5, 0.5, 1),
metallic: 0.0,
roughness: 0.9
})
Transparency
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(1, 0, 0, 0.5),
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_BLEND
})
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({ src: 'assets/cutout.png' }),
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_TEST,
alphaTest: 0.5
})
Emissive (Glow Effects)
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0, 0, 0, 1),
emissiveColor: Color3.create(0, 1, 0),
emissiveIntensity: 2.0
})
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({ src: 'assets/diffuse.png' }),
emissiveTexture: Material.Texture.Common({ src: 'assets/emissive.png' }),
emissiveIntensity: 1.0,
emissiveColor: Color3.White()
})
Texture Maps
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({ src: 'assets/diffuse.png' }),
bumpTexture: Material.Texture.Common({ src: 'assets/normal.png' }),
emissiveTexture: Material.Texture.Common({ src: 'assets/emissive.png' })
})
GltfContainer Visibility Masks
Control visibility and collision of specific mesh layers within a GLTF model using collision masks:
import { engine, Transform, GltfContainer, ColliderLayer } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const model = engine.addEntity()
Transform.create(model, { position: Vector3.create(4, 0, 4) })
GltfContainer.create(model, {
src: 'models/myModel.glb',
visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS
})
VisibilityComponent
Show or hide entities without removing them:
import { engine, VisibilityComponent } from '@dcl/sdk/ecs'
VisibilityComponent.create(entity, { visible: false })
const visibility = VisibilityComponent.getMutable(entity)
visibility.visible = !visibility.visible
function lodSystem() {
const playerPos = Transform.get(engine.PlayerEntity).position
for (const [entity, transform] of engine.getEntitiesWith(Transform, MeshRenderer)) {
const distance = Vector3.distance(playerPos, transform.position)
if (distance > 30) {
VisibilityComponent.createOrReplace(entity, { visible: false })
} else {
VisibilityComponent.createOrReplace(entity, { visible: true })
}
}
}
engine.addSystem(lodSystem)
Per-Node Modifiers (GltfNodeModifiers)
Override material or shadow casting on specific nodes within a GLTF model. Supported in main-entities.ts:
armored_knight: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/knight.glb' },
GltfNodeModifiers: {
modifiers: [
{
path: 'RootNode/Armor',
castShadows: false
}
]
}
}
}
Whole-model override: pass path: '' (empty string) to apply the modifier to every node in the model. Useful for re-skinning an entire model with a single material swap:
red_team_unit: {
components: {
Transform: { position: { x: 4, y: 0, z: 8 } },
GltfContainer: { src: 'models/unit.glb' },
GltfNodeModifiers: {
modifiers: [
{
path: '',
material: {
material: {
$case: 'pbr',
pbr: { albedoColor: { r: 1, g: 0, b: 0, a: 1 } }
}
}
}
]
}
}
}
Texture Tweens (Animate Material Textures)
Animate a material's texture offset/tiling over time — useful for water, lava, conveyor belts, scrolling backgrounds. Tween component is supported in main-entities.ts:
conveyor: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
MeshRenderer: { mesh: { $case: 'plane', plane: { uvs: [] } } },
Material: { material: { $case: 'pbr', pbr: { texture: { tex: { $case: 'texture', texture: { src: 'images/belt.png' } } } } } },
Tween: {
duration: 2000,
easingFunction: 0,
mode: {
$case: 'textureMoveContinuous',
textureMoveContinuous: { direction: { x: 1, y: 0 }, speed: 0.5 }
}
}
}
}
For a finite from-to texture move, use { $case: 'textureMove', textureMove: { start: { x: 0, y: 0 }, end: { x: 1, y: 0 }, movementType: 0 } } with a duration.
Runtime helpers (in src/index.ts): Tween.setTextureMove(entity, from, to, durationMs) and Tween.setTextureMoveContinuous(entity, direction, speed).
Avatar Texture
Generate a texture from a player's avatar:
Material.setPbrMaterial(portraitFrame, {
texture: Material.Texture.Avatar({ userId: '0x...' })
})
Texture Modes
Control how textures are filtered and wrapped:
import { TextureFilterMode, TextureWrapMode } from '@dcl/sdk/ecs'
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({
src: 'images/pixel-art.png',
filterMode: TextureFilterMode.TFM_POINT,
wrapMode: TextureWrapMode.TWM_REPEAT
})
})
Filter modes: TFM_POINT (pixelated), TFM_BILINEAR (smooth), TFM_TRILINEAR (smoothest).
Wrap modes: TWM_REPEAT (tile), TWM_CLAMP (stretch edges), TWM_MIRROR (mirror tile).
Best Practices
- Use
BillboardMode.BM_Y instead of BM_ALL — looks more natural and renders faster
- Keep
fontSize readable (16-32 for in-world text)
- Add
outlineColor and outlineWidth to TextShape for legibility against any background
- Use
emissiveColor with a dark albedoColor for maximum glow visibility
MTM_ALPHA_TEST is cheaper than MTM_ALPHA_BLEND — use cutout when smooth transparency isn't needed
- Combine Billboard + TextShape for floating name labels above NPCs or objects
- Use VisibilityComponent for LOD systems instead of removing/re-adding entities