关于 advanced-rendering
advanced-rendering 是面向 Decentraland SDK7 场景开发者的渲染指南,介绍实体朝向、3D 文字、材质、可见性和 GLTF 节点控制。内容包含组件用法及常见限制与实现注意事项。
主要功能
- 01使用 Billboard 控制实体朝向
- 02在 3D 空间渲染文字
- 03配置 PBR、透明与发光材质
- 04切换实体可见性
- 05修改 GLTF 节点材质与阴影
适用场景
- 01创建面向摄像机的标牌
- 02为场景对象添加 3D 标签
- 03制作透明或发光表面
- 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 | 金属、粗糙、透明、自发光表面 |
| 显示/隐藏而不移除 | VisibilityComponent | LOD 系统、切换对象、条件显示 |
| 修改 GLTF 模型节点 | GltfNodeModifiers | 覆盖特定网格节点上的材质或阴影投射设置 |
决策流程:
- 需要在屏幕上显示文本?→ 改用 build-ui(React-ECS Label)
- 需要在 3D 空间中显示文本?→ 使用
TextShape(搭配Billboard使其面向摄像机) - 需要发光/透明材质?→ 使用带自发光/透明度设置的
Material.setPbrMaterial - 需要覆盖模型节点上的材质?→ 使用带有
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-explorerExplorer/Assets/DCL/SDKComponents/Billboard/System/BillboardSystem.cs验证;某个参考场景的注释称+Z轴面向摄像机,但这在 Explorerv0.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:
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/74,-8-gltfnodemodifier — 使用
GltfNodeModifiers覆盖特定 GLTF 节点上的 PBR/非光照材质、视频纹理、逐节点颜色和castShadows;使用createOrReplace/deleteFrom;错误路径控制台转储调试技巧。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/1,0-visibility-comp-propagation — 在父级/子级/孙级层级中,通过
VisibilityComponent的propagateToChildren配合运行时重新设置父级,涵盖所有覆盖组合。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/1,1-billboard-target-entity —
Billboard.targetEntity:未设置(朝向摄像机)、跟踪移动实体、目标删除后重新创建(目标缺失时朝向冻结,目标返回后恢复)、针对同一目标比较BM_Y与BM_ALL,以及targetEntity: engine.CameraEntity的行为与未设置时一致。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,3-texture-movement — 通过
Tween.setTextureMove和TextureMovementType.TMT_OFFSET、TMT_TILING实现纹理补间,并配合TweenSequence循环;还展示了Billboard与TextShape。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/52,-52-testing-gallery — 并排展示 PBR 材质参数测试(metallic/roughness/emissive/normal-map)和
GltfContainer碰撞掩码组合。