Loading...
Loading...
Advanced rendering in Decentraland scenes. Billboard, TextShape, PBR materials, GltfNodeModifiers, and VisibilityComponent. 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).
npx skill4agent add decentraland/sdk-skills advanced-rendering| Need | Component | When |
|---|---|---|
| Entity faces the camera | | Name tags, signs, sprite-like objects |
| Text in the 3D world | | Labels, signs, floating text above entities |
| Custom material appearance | | Metallic, rough, transparent, emissive surfaces |
| Show/hide without removing | | LOD systems, toggling objects, conditional display |
| Modify GLTF model nodes | | Override materials or shadow casting on specific mesh nodes |
TextShapeBillboardMaterial.setPbrMaterialGltfNodeModifiersmodifiersimport { 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)
// Rotate only on Y axis (most common — stays upright)
Billboard.create(sign, {
billboardMode: BillboardMode.BM_Y
})BillboardMode.BM_Y // Rotate on Y axis only (stays upright) — most common
BillboardMode.BM_ALL // Rotate on all axes (fully faces camera)
BillboardMode.BM_X // Rotate on X axis only
BillboardMode.BM_Z // Rotate on Z axis only
BillboardMode.BM_NONE // No billboard rotationBM_YBM_ALLBM_ALLoppositeDirectionBillboardbillboardModeQuaternion.fromEulerDegrees(0, 180, 0)targetEntityBillboardtargetEntity?: Entity// Face a specific entity instead of the camera
Billboard.create(card, { targetEntity: sphere })
// Yaw-only tracking of a target (BM_Y respected while targeting)
Billboard.create(card, { targetEntity: target, billboardMode: BillboardMode.BM_Y })
// Retarget at runtime
Billboard.getMutable(card).targetEntity = otherEntitytargetEntitytargetEntityengine.CameraEntity2billboardModeBM_YtargetEntitytargetEntitytargetEntityimport { 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
})fontSizeoutlineColoroutlineWidthTextAlignMode.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{baseDir}/references/rendering-patterns.mdimport { engine, Transform, MeshRenderer, Material, MaterialTransparencyMode } from '@dcl/sdk/ecs'
import { Color4, Color3 } from '@dcl/sdk/math'
// Shiny metal
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0.8, 0.8, 0.9, 1),
metallic: 1.0,
roughness: 0.1
})
// Rough stone
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0.5, 0.5, 0.5, 1),
metallic: 0.0,
roughness: 0.9
})// Alpha blend — smooth transparency
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(1, 0, 0, 0.5), // 50% transparent red
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_BLEND
})
// Alpha test — cutout (binary visible/invisible based on threshold)
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({ src: 'assets/Images/cutout.png' }),
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_TEST,
alphaTest: 0.5
})MTM_ALPHA_TESTMTM_ALPHA_BLEND// Glowing material (emissiveColor uses Color3, not Color4)
Material.setPbrMaterial(entity, {
albedoColor: Color4.create(0, 0, 0, 1),
emissiveColor: Color3.create(0, 1, 0), // Green glow
emissiveIntensity: 2.0
})
// Emissive with texture
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({ src: 'assets/Images/diffuse.png' }),
emissiveTexture: Material.Texture.Common({ src: 'assets/Images/emissive.png' }),
emissiveIntensity: 1.0,
emissiveColor: Color3.White()
})emissiveColoralbedoColorMaterial.setPbrMaterial(entity, {
texture: Material.Texture.Common({ src: 'assets/Images/diffuse.png' }),
bumpTexture: Material.Texture.Common({ src: 'assets/Images/normal.png' }),
emissiveTexture: Material.Texture.Common({ src: 'assets/Images/emissive.png' })
})setPbrMaterialsetBasicMaterialcastShadows: booleantruefalseMaterial.setPbrMaterial(entity, { albedoColor: Color4.Green(), castShadows: false })GltfNodeModifierscastShadows: falseimport { 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
})import { engine, VisibilityComponent } from '@dcl/sdk/ecs'
// Hide an entity
VisibilityComponent.create(entity, { visible: false })
// Toggle visibility
const visibility = VisibilityComponent.getMutable(entity)
visibility.visible = !visibility.visibleVisibilityComponent{baseDir}/references/rendering-patterns.mdpropagateToChildren: trueVisibilityComponentVisibilityComponent.create(parentEntity, { visible: false, propagateToChildren: true })1,0-visibility-comp-propagationVisibilityComponentpropagateToChildrenfalsevisibleVisibilityComponentpropagateToChildren: truevisible: truepropagateToChildren: trueVisibilityComponentTransform.parentimport { GltfNodeModifiers } from '@dcl/sdk/ecs'
GltfNodeModifiers.create(entity, {
modifiers: [
{
path: 'RootNode/Armor', // GLTF hierarchy path
castShadows: false // Disable shadow casting for this node
}
]
})path: ''material: { material: { $case: 'pbr' | 'unlit', ... } }{baseDir}/references/rendering-patterns.md74,-8-gltfnodemodifierpath/Scene_root/shark_skeleton/Sphere/Sphere.001path: ''material$case: 'pbr'albedoColoremissiveColoremissiveIntensity$case: 'unlit'diffuseColormodifierspbr: { texture: Material.Texture.Video({ videoPlayerEntity: someEntityWithVideoPlayer }) }castShadows: falsematerialmodifierspathpathGltfNodeModifiers.createOrReplace(entity, { modifiers: [...] })GltfNodeModifiers.deleteFrom(entity)Material.setPbrMaterial(portraitFrame, {
texture: Material.Texture.Avatar({ userId: '0x...' })
})import { TextureFilterMode, TextureWrapMode } from '@dcl/sdk/ecs'
Material.setPbrMaterial(entity, {
texture: Material.Texture.Common({
src: 'assets/Images/pixel-art.png',
filterMode: TextureFilterMode.TFM_POINT, // crisp pixels (no smoothing)
wrapMode: TextureWrapMode.TWM_REPEAT // tile the texture
})
})TFM_POINTTFM_BILINEARTFM_TRILINEARTWM_REPEATTWM_CLAMPTWM_MIRRORTween.setTextureMoveContinuousTween.setTextureMoveTMT_OFFSETTMT_TILINGTweenSequencewrapMode: TWM_REPEAT{baseDir}/references/rendering-patterns.mdMaterial| Method | Returns | Throws if no material? |
|---|---|---|
| Read-only | Yes |
| Read-only | No |
| Read/write | Yes |
| Read/write | No |
// Read a property safely
const src = Material.getFlatOrNull(entity)?.texture?.src
// Mutate a texture in-place without knowing PBR vs Basic
Material.getFlatMutableOrNull(entity)!.texture = Material.Texture.Common({ src: 'assets/Images/new.png' })GltfNodeModifierscastShadowscreateOrReplacedeleteFromVisibilityComponentpropagateToChildrenTween.setTextureMoveTextureMovementType.TMT_OFFSETTMT_TILINGTweenSequenceBillboardTextShapeGltfContainer