mcpField
A
advanced-rendering

advanced-rendering

github.com/decentraland开发4280 次安装今天更新
代码仓库

关于 advanced-rendering

advanced-rendering 是面向 Decentraland SDK7 场景开发者的渲染指南,介绍实体朝向、3D 文字、材质、可见性和 GLTF 节点控制。内容包含组件用法及常见限制与实现注意事项。

主要功能

  1. 01使用 Billboard 控制实体朝向
  2. 02在 3D 空间渲染文字
  3. 03配置 PBR、透明与发光材质
  4. 04切换实体可见性
  5. 05修改 GLTF 节点材质与阴影

适用场景

  1. 01创建面向摄像机的标牌
  2. 02为场景对象添加 3D 标签
  3. 03制作透明或发光表面
  4. 04按条件隐藏场景实体

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/decentraland/sdk-skills --skill advanced-rendering

安装后在 Claude Code 中输入/advanced-rendering确认已加载。

附带文件references/rendering-patterns.md

详细介绍

Decentraland 中的高级渲染

何时使用哪种渲染功能

需求组件适用场景
实体面向摄像机Billboard名牌、标志、类似精灵的对象
3D 世界中的文本TextShape标签、标志、实体上方的浮动文本
自定义材质外观Material.setPbrMaterial金属、粗糙、透明、自发光表面
显示/隐藏而不移除VisibilityComponentLOD 系统、切换对象、条件显示
修改 GLTF 模型节点GltfNodeModifiers覆盖特定网格节点上的材质或阴影投射设置

决策流程:

  1. 需要在屏幕上显示文本?→ 改用 build-ui(React-ECS Label)
  2. 需要在 3D 空间中显示文本?→ 使用 TextShape(搭配 Billboard 使其面向摄像机)
  3. 需要发光/透明材质?→ 使用带自发光/透明度设置的 Material.setPbrMaterial
  4. 需要覆盖模型节点上的材质?→ 使用带有 modifiers 数组的 GltfNodeModifiers

SDK7 不支持反射、镜面和渲染到纹理。 没有反射探针、镜面或摄像机到纹理组件(@dcl/ecs 组件列表,7.30;Material.reflectivityColor 是镜面反射色调,并非反射)。镜面需要通过在带色调、略微透明的玻璃平面后方复制反射几何体来伪造,并驱动所有移动物体的副本(玩家副本见 npcs →“玩家的副本”)。规划好空间:玻璃后方的反射空间需要与其前方的房间一样深;地块外的内容会被裁切(add-3d-models),所以位于地块边缘的镜面意味着需要添加一个地块。在侧墙、地板和天花板与真实场景的交界处裁掉反射侧的部分——重叠副本会在接缝处发生 Z 冲突(2026-10-12 观察所得)。

Billboard(面向摄像机)

使实体始终旋转并面向玩家的摄像机:

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 模式
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
  • 大多数情况下优先使用 BM_Y 而非 BM_ALL——看起来更自然,渲染成本也更低。
  • BM_ALL 适用于应始终直接面向摄像机的粒子或特效。
  • 实体的局部 −Z 轴面向摄像机。 Explorer 会使局部 +Z 轴背向观看者——Quaternion.LookRotation(billboardPos − cameraPosition)——因此,在使用 Billboard 的父实体上,局部 +Z 处的子实体从所有视角看都位于其后方,而局部 −Z 处的子实体则会渲染在其前方。已对照 unity-explorer Explorer/Assets/DCL/SDKComponents/Billboard/System/BillboardSystem.cs 验证;某个参考场景的注释称 +Z 轴面向摄像机,但这在 Explorer v0.186.0-alpha 中是错误的。
  • 默认情况下,Billboard 面向的是摄像机,而非化身——即活动虚拟摄像机的世界坐标位置,见同一文件。在第三人称视角下,摄像机和玩家的位置不同,看到的朝向也不同,因此请从玩家自己的摄像机视角验证躲到障碍物后/探身而出的效果,切勿通过自由摄像机巡视来验证。若要面向其他对象——玩家的化身(targetEntity: engine.PlayerEntity)或任何其他实体——请设置 targetEntity(见下文 面向另一个实体——targetEntity)。
  • 没有 oppositeDirection 标志。 SDK7 的 Billboard 组件只公开 billboardMode 和 targetEntity——无法反转模型面向摄像机的方向。如果模型显示的是背面而非正面,请将模型绕 Y 轴旋转 180°(Quaternion.fromEulerDegrees(0, 180, 0))。如果采用父实体挂载 Billboard、子实体挂载模型的设置,请将旋转应用到子实体——父实体的旋转由 Billboard 控制。
  • 迁移说明:从 SDK6 迁移到 SDK7 时,有时会发现模型的 Billboard 朝向与 SDK6 中正确的表现相反,背对摄像机。这两个 SDK 对 Billboard 朝向摄像机的模型面似乎存在分歧。解决方法相同——将显示的模型绕 Y 轴旋转 180°。背景信息请参阅 [[migrate-sdk6-to-sdk7]](常见问题)。
面向另一实体 — targetEntity

Billboard 有一个可选的 targetEntity?: 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 = otherEntity
  • 未设置(默认) → 面向主摄像机,与之前完全相同。targetEntity 完全向后兼容。
  • 将 targetEntity 设置为摄像机保留实体(engine.CameraEntity,ID 为 2),效果等同于不设置。
  • billboardMode 仍然适用:设置了 targetEntity 的 BM_Y 只会绕 Y 轴旋转以面向目标。
  • 注意:如果引用的目标实体不存在或已被删除,Billboard 将停止重新定向(实体会保持最后的朝向),直到目标再次存在。
  • 多人游戏:面向摄像机的 Billboard 会在每位玩家本地计算(每位玩家看到的都是面向自己的 Billboard,不会同步)。而设置了 targetEntity 的 Billboard 会对所有玩家保持相同的朝向,因为目标的位置属于场景状态。如果需要确保所有玩家看到相同朝向,请使用 targetEntity。

TextShape(3D 文本)

在 3D 空间中直接渲染文本:

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: 5,  // world units: 10 ≈ 1 m tall, so this is about 0.5 m
  textColor: Color4.White(),
  outlineColor: Color4.Black(),
  outlineWidth: 0.1,
  textAlign: TextAlignMode.TAM_MIDDLE_CENTER
})
  • fontSize 的单位是世界单位,而不是像素:10 会让文本约有 1 m 高(SDK 文档;Explorer 会将其直接传给世界空间中的 TextMeshPro,即 TMPProSdkExtensions.cs),并且会受到父级 Transform.scale 的影响。16–32 适合用作能从一个地块另一端读到的大型标牌;显示器、铭牌或标签需要约 1–4,或在缩放过的父级下使用更小的尺寸。显示器模型中的文本溢出(2026-10-08)是单位不匹配所致,并非布局错误。
  • 不要将 TextShape(或带纹理的平面)用作规则手册:在游戏世界中,长段落太小、太长,难以阅读,玩家也不会停下来眯着眼看。每块标牌只表达一个观点,最多 10 个词,字体要大;其余内容应融入视觉效果和玩法中(参见 game-design →“规则:展示,而非说教”)。
  • 务必添加 outlineColor 和 outlineWidth,确保文本在任何背景下都清晰易读。
  • 要使用自定义字体,请将 fontSrc 设置为场景中的 .ttf 文件,并将 font 设为备用字体。SDK 版本、Explorer 和移动端支持情况以及预览方法,参见 fonts。
  • **TextShape 的正面朝向其局部 -Z 方向。**站在实体的 -Z 侧的观察者才能正向阅读。因此,要在模型的 +Z 面上添加标签,请将标签设为模型的子级,在 +Z 方向稍微偏移,并绕 Y 轴旋转 180°。此现象在游戏世界中观察到两次(SDK 7.30.1,2026-10-12);方向弄错后,标牌会镜像显示,或从玩家接近的一侧看不见。
  • 空格无法对齐列,即使使用 Font.F_MONOSPACE 也不行。用空格填充行来构建表格会导致间距收缩(在一个三列公告板上观察到此现象,SDK 7.30.1,2026-10-12)。请每列使用一个 TextShape,分别放置在各自的 x 坐标上,并使用 TAM_MIDDLE_LEFT / TAM_MIDDLE_RIGHT 对齐;每列中的各行则用 \n 分隔。
文本对齐选项
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

浮动标签模式(将 Billboard 与 TextShape 组合成面向摄像机的标签)请参见 {baseDir}/references/rendering-patterns.md 中的 浮动标签(Billboard + TextShape) 一节。

将屏幕或文本适配到模型的显示区域

直接将平面或 TextShape 放在显示器模型的原点,可能会让它位于边框前、边框后或横跨边框(2026-10:一个屏幕平面比边框高出 6 cm,文本宽度则是显示区域的两倍)。首先检查几何体:使用 add-3d-models 中的包围盒脚本导出 GLB 的顶点(筛选法线朝向观察者的面),并测量凹陷显示区域的矩形及其深度——当时测量的显示器,其显示区域为 0.654 × 0.497 m,且位于边框表面后方 1.6 cm。将平面调整为与该矩形相同的尺寸,并放在显示表面前方 2–5 mm 处(仍位于边框表面后方);对于文本,将 fontSize、width / height 和 textWrapping 设置为与该矩形相符的值,而不是凭感觉猜测后再让项目所有者检查。

高级 PBR 材质

金属度和粗糙度
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
})
透明度
// 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_TEST 比 MTM_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()
})
  • 使用 emissiveColor,并将 albedoColor 设为深色,以获得最明显的发光效果。
纹理贴图
Material.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' })
})
castShadows

setPbrMaterial 和 setBasicMaterial 都接受 castShadows: boolean(默认值为 true)。将其设为 false,即可在不改变表面外观的情况下阻止其投射阴影:

Material.setPbrMaterial(entity, { albedoColor: Color4.Green(), castShadows: false })

若要禁用 GLTF 模型中特定节点的阴影,请改用 GltfNodeModifiers 并设置 castShadows: false(见下文)。

GltfContainer 碰撞掩码

使用碰撞掩码来控制哪些碰撞层会响应 GLTF 模型中的不同网格层。GLTF 模型有两种网格层:可见网格(玩家看到的渲染内容)和不可见层(碰撞体网格,内部名称中带有 _collider):

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

显示或隐藏实体,而不将其移除:

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

若要实现基于距离、每帧切换 VisibilityComponent 的 LOD(细节层次)系统,请参阅 {baseDir}/references/rendering-patterns.md 中的 通过 VisibilityComponent 实现 LOD 部分。

propagateToChildren

在 VisibilityComponent 上将 propagateToChildren: true,即可一次性将可见性应用于层级中的所有子级。这样就不必逐个标记每个子实体:

VisibilityComponent.create(parentEntity, { visible: false, propagateToChildren: true })

规则(已在 1,0-visibility-comp-propagation 测试场景中验证):

  • 如果子级有自己的 VisibilityComponent,则无论祖先传播了什么值,都以子级自身的值为准——即使子级自身的 propagateToChildren 为 false,它也会保持自身的 visible 值,不会重新继承父级的设置。
  • 如果子级没有 VisibilityComponent,则会继承最近一个设置了 propagateToChildren: true 的祖先的值。
  • 子级可以覆盖不可见父级,将自身设为 visible: true;它还可以设置 propagateToChildren: true,从而再次强制显示自身子树——每个带有 VisibilityComponent 的节点都会重新评估传播结果。
  • 传播遵循实时的 Transform.parent 层级:运行时重新设置实体的父级,会改变应用于该实体的祖先传播可见性。
按节点修改(GltfNodeModifiers)

覆盖 GLTF 模型中特定节点的材质或阴影投射设置:

import { 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.md 中的 GltfNodeModifiers — 整个模型材质覆盖 部分。

修改器详情(来自 74,-8-gltfnodemodifier 测试场景):

  • path 是 GLTF 节点层级路径,以 / 分隔(例如 Scene_root/shark_skeleton/Sphere/Sphere.001)。path: '' 指向整个模型;嵌套路径则指向一个节点及其子级。
  • material 接受 $case: 'pbr'(完整 PBR:albedoColor、emissiveColor、emissiveIntensity、纹理等)或 $case: 'unlit'(diffuseColor 等)。同一个 modifiers 数组中的不同节点可以使用不同的类型。
  • 此处也支持纹理,包括视频:pbr: { texture: Material.Texture.Video({ videoPlayerEntity: someEntityWithVideoPlayer }) }。
  • 按节点设置 castShadows: false(无需设置 material)即可仅禁用该节点的阴影投射。
  • 一次调用中的一个 modifiers 数组可以包含多条条目,每条指向不同的 path。
  • 调试技巧:传入不存在的 path,会将模型完整的 GLTF 节点层级记录到场景控制台中——可故意传入错误路径,以查找正确的节点名称。
  • 使用 GltfNodeModifiers.createOrReplace(entity, { modifiers: [...] }) 更新;使用 GltfNodeModifiers.deleteFrom(entity) 移除所有覆盖。

节点路径是嵌入 GLB 中、引擎可见的名称,并非任意指定的名称。如果目标节点没有所请求类型的材质,则可能会忽略覆盖。

头像纹理

根据玩家的头像肖像生成纹理:

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_POINT(像素化)、TFM_BILINEAR(平滑)、TFM_TRILINEAR(最平滑)。 包裹模式:TWM_REPEAT(平铺)、TWM_CLAMP(拉伸边缘)、TWM_MIRROR(镜像平铺)。

盒体和平面图元的默认 UV

MeshRenderer.setBox / setPlane 未指定 uvs 时,会使用桌面版 Explorer 的内置布局(unity-explorer Utility/Primitives/BoxFactory.cs 和 PlaneFactory.cs,于 2026 年 10 月查看);而且对于盒体,每个面都不同。盒体未旋转时,“正向”指观察者面对该面时,纹理的顶行位于顶部:

面纹理显示方向
−z正向
+z旋转 180°
−x 和 +x旋转 90°(纹理的 u 轴沿垂直方向)
顶面(+y)和底面(−y)旋转 90°(u 沿 z 方向,v 沿 x 方向;观察者面向 +z)

因此,任何带有方向性的纹理——文字、砖块层、木纹、地板图案——都会在四面墙中的两面以及地板和天花板上旋转(2026-10 在世界中观察到:“墙面和天花板都旋转了”)。将盒体绕 y 轴旋转 90°,只会让问题转移到另一对墙面。修复方法,按优先顺序排列:传入显式的 uvs——48 个浮点数,即 6 个面 × 4 个角 ×(u、v);辅助方法见文档 Materials 页面的 Set UVs;或者使用 setPlane 实体构建表面,每个面一个;或者提供 GLB。

平面是双面图元:u 沿局部 +x 方向,v 沿局部 +y 方向,因此从 −z 侧看时,纹理正向且未镜像;从 +z 侧看时,纹理左右镜像。[推导结果,未经世界内检查:使用 Quaternion.fromEulerDegrees(90, 0, 0) 平放时,未镜像的一面朝上,纹理顶部指向世界 +z。]

纹理补间

有关动画纹理图案(Tween.setTextureMoveContinuous 连续滚动、Tween.setTextureMove 单次滑动、TMT_OFFSET 与 TMT_TILING 移动类型,以及通过 TweenSequence 循环)的内容——这些功能都要求纹理使用 wrapMode: TWM_REPEAT——请参阅 {baseDir}/references/rendering-patterns.md 中的 Texture Tweens 部分。

FlatMaterial 访问器

Material 组件提供了快捷方法,可跳过嵌套联合结构,让材质访问更加便捷:

方法返回值没有材质时是否抛出异常?
Material.getFlat(entity)只读 FlatMaterial是
Material.getFlatOrNull(entity)只读 FlatMaterial | null否
Material.getFlatMutable(entity)可读写 FlatMaterial是
Material.getFlatMutableOrNull(entity)可读写 FlatMaterial | null否
// 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' })

示例场景

引擎团队测试场景:针对真实运行时测试这些 API:

评论

支持 Markdown