mcpField
A
advanced-rendering

advanced-rendering

github.com/dcl-regenesislabsDevelopment1738 installsUpdated 21w ago
Repository

About advanced-rendering

advanced-rendering is a Decentraland development skill for configuring 3D visuals with the SDK's rendering components. It covers text, camera-facing entities, materials, model-node overrides, visibility, and animated textures, with guidance on authoring in main-entities.ts.

Key features

  1. 01Create camera-facing entities with Billboard
  2. 02Render in-world text with TextShape
  3. 03Configure PBR materials and textures
  4. 04Toggle entity visibility
  5. 05Override materials on GLTF nodes
  6. 06Animate material texture movement

Use cases

  1. 01Add floating labels above objects
  2. 02Create glowing or transparent surfaces
  3. 03Hide entities for conditional display
  4. 04Scroll textures on water or conveyor belts

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/dcl-regenesislabs/opendcl --skill advanced-rendering

After installing, type/advanced-renderingin Claude Code to confirm it loaded.

Details

From the project README

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.

// Example: floating label
// main-entities.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 }  // BM_ALL
  }
}

When to Use Which Rendering Feature

NeedComponentWhen
Entity faces the cameraBillboardName tags, signs, sprite-like objects
Text in the 3D worldTextShapeLabels, signs, floating text above entities
Custom material appearanceMaterial.setPbrMaterialMetallic, rough, transparent, emissive surfaces
Show/hide without removingVisibilityComponentLOD systems, toggling objects, conditional display
Modify GLTF model nodesGltfNodeModifiersOverride materials or shadow casting on specific mesh nodes

Decision flow:

  1. Need text on screen? → Use build-ui (React-ECS Label) instead
  2. Need text in 3D space? → TextShape (+ Billboard to face camera)
  3. Need glowing/transparent materials? → Material.setPbrMaterial with emissive/transparency
  4. 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)

// Rotate only on Y axis (most common — stays upright)
Billboard.create(sign, {
  billboardMode: BillboardMode.BM_Y
})
Billboard Modes
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 rotation
  • 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'

// 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
})
Transparency
// 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/cutout.png' }),
  transparencyMode: MaterialTransparencyMode.MTM_ALPHA_TEST,
  alphaTest: 0.5
})
Emissive (Glow Effects)
// 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/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'

// Hide an entity
VisibilityComponent.create(entity, { visible: false })

// Toggle visibility
const visibility = VisibilityComponent.getMutable(entity)
visibility.visible = !visibility.visible

// Useful for LOD (Level of Detail)
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:

// main-entities.ts
armored_knight: {
  components: {
    Transform: { position: { x: 8, y: 0, z: 8 } },
    GltfContainer: { src: 'models/knight.glb' },
    GltfNodeModifiers: {
      modifiers: [
        {
          path: 'RootNode/Armor',     // GLTF hierarchy path
          castShadows: false           // disable shadows for this node
        }
      ]
    }
  }
}

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:

// main-entities.ts — continuous texture scroll
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,  // EF_LINEAR
      mode: {
        $case: 'textureMoveContinuous',
        textureMoveContinuous: { direction: { x: 1, y: 0 }, speed: 0.5 }
        // movementType defaults to 0 = TMT_OFFSET; use 1 = TMT_TILING to scale instead
      }
    }
  }
}

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,    // crisp pixels (no smoothing)
    wrapMode: TextureWrapMode.TWM_REPEAT        // tile the texture
  })
})

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

Comments

Markdown supported