For the complete documentation index, see llms.txt. This page is also available as Markdown.

性能优化

优化你的场景,使其加载更快并为所有玩家流畅运行。

你可以在场景中优化多个方面,以确保为访问它们的玩家带来尽可能好的体验。本文档介绍了一些最佳实践,它们能显著影响场景的加载速度,以及对玩家在场景内或相邻场景中时的运行流畅度。

请记住,许多玩家访问 Decentraland 时,可能使用的是非游戏硬件、通过浏览器访问,或者在 移动应用中 手机上——这些都会限制可用于你的场景的处理能力。访问你的场景时,所有人都应该获得流畅的体验。

查看 实用资源 可使用一些有帮助的工具,例如 Decentraland Scene Optimizer,它会从你的场景 3D 模型中提取、去重并压缩纹理。

📱 移动端:移动设备通常是资源最受限的客户端。如果你的场景面向移动端玩家,也请参见 为移动端构建 面向移动设备的专门指南。

Decentraland 浏览器在引擎层面强制执行了许多优化。这些优化效果显著,但要在浏览器中同时渲染多个用户生成的体验,挑战仍然很大。我们需要你的帮助,让一切顺畅运行。

时机

视频播放

播放视频是引擎最耗资源的任务之一。如果你的场景包含视频,请确保同一时间只有 一个 VideoTexture 在同时使用。多个平面可以共享同一个 VideoTexture,而不会对性能造成明显影响,但一旦添加第二个 VideoTexture,其对帧率的影响就会非常明显。

你还应避免让视频在无法被看到的区域播放。例如,如果你在室内有一个屏幕,可以根据玩家进出时触发区域来切换视频。

💡 提示:有些场景采用的一种技巧是,用多个区域流式传输同一个视频,并将这些区域以不同方式映射到不同的平面。每个视频屏幕都使用 UV 映射 ,只显示 VideoTexture 的某个独立部分。这样一来,虽然实际成本与多个 VideoTexture 不同,却看起来像是在播放多个独立视频。

💡 提示:当玩家站在你的场景外时,VideoTexture 不会在每一帧更新。这有助于减少对周围场景的影响。不过,理想做法仍然是只在玩家 走进你的场景时才开启任何视频的播放 .

延迟加载

如果你的场景很大,或者包含并非始终可见的室内区域,你可以选择不从一开始就加载整套实体。相反,随着玩家访问场景的不同区域,再按区域加载内容。这可以显著减少场景的加载时间,也能减少引擎在每一帧需要处理的纹理和 3D 内容数量。

例如,博物馆的主建筑可以从一开始就加载,但每层的画作则只会在玩家到达该层时才加载。

参见 这个示例场景 ,看看它可能如何实现。

为了尽量避免卡顿,最好通过切换实体形状的 可见的 属性为 false 来隐藏实体。采用这种方式时,你在创建它们时就把它们加入引擎,但只是让它们的模型不可见。

另一种方法是,直到需要时才把实体添加到引擎中。这可能会在实体第一次出现时造成一些卡顿,而且它们也可能需要几秒钟才能变得可见。这种方法的好处在于,它是绕过 场景限制。请记住,场景限制计数针对的是任一时刻在场景中被渲染的内容,而不是可能被渲染的全部内容。通过加载和卸载场景的一部分,你应该能够绕开这些限制。

你也可以对距离较远或被遮挡的实体切换动画开关。例如,对于播放非常细微待机动画的 NPC,你可以让它只在玩家距离小于 20 米时播放该动画。围绕 NPC 使用触发区域,并据此开启或关闭其动画。

💡 提示:当实体距离很远且足够小时,引擎会将其剔除。这种剔除有助于降低 drawcall 成本;不过,直接将实体从引擎中移除总是更好。此剔除也不会考虑其他实体的遮挡,因此那些虽然不大但被墙挡住的实体仍然会被渲染。

异步块

……的代码块 异步代码 在等待时不会阻塞其他所有内容的进度:场景会继续运行,而异步块会在其等待的响应到达后继续执行。请注意,场景在单线程上运行,因此这不是并行处理;它只是避免在等待外部响应时让场景停顿。

任何依赖异步服务响应的过程,例如 getPlayerData() 或 getRealm() 都应始终运行在异步块中,否则它们会在等待响应时阻塞场景其余部分的加载。对任何第三方服务器调用也同样适用。

请注意,场景会在所有非异步内容完成后被视为完全加载。玩家进入场景时,异步进程可能仍在运行。避免出现这样的情况:某个异步进程会加载一个实体,而该实体可能会让玩家卡在其几何体内部。

依赖事件

尽量让场景逻辑依赖于监听 事件 ,而不是每帧都运行检查。

该 update() 函数的数据存储在一个 系统 会在每一帧运行,理想情况下每秒 30 次。如果可以通过订阅事件来替代,尽量不要做反复检查。

例如,与其不断检查玩家的穿戴物品,不如订阅 onProfileChanged 事件,并且只在穿戴物品发生变化时检查玩家的穿戴物品。

如果你必须使用系统,也不要在每一帧都进行检查或调整。你可以在 update 函数中加入一个计时器,只在每隔整整一秒,或按其他合适的周期时执行一次检查。

优化 3D 模型

你的 3D 模型可以通过多种方式优化得更轻量。

在使用该功能时 Creator Hub,你可以查看场景中 3D 模型所使用资源的统计信息,以及它们是否超过了任何 场景限制.



你可以展开此菜单以查看详情。



以下是一些改进这些指标的提示:

  • 在可能的情况下,在多个 3D 模型之间共享纹理。一个好做法是使用单张纹理作为图集贴图,并在场景中的所有模型之间共享。与其使用多张小纹理,不如使用一张 1024x1024 像素的大共享纹理。

    注意:不要让同一个图像文件同时作为材质的 albedo 纹理、normal map 或 emissive map。即使内容相同,也请使用不同文件。将同一个图像文件分配给不同类型的纹理属性,在压缩为资源包时可能会引入不希望出现的视觉瑕疵。

  • .glb 是一种压缩格式,因此它的体积总会比一个 .gltf。另一方面,在 .gltf 通过将纹理导出为单独文件,很容易共享纹理图像。你可以通过使用 以下工作流程,从而同时拥有 .glb 带外部纹理文件的模型。

  • 避免使用混合透明。混合透明必须绕过不少渲染优化。如果可能,优先使用不透明或 alpha 测试几何体。

  • 避免使用蒙皮网格。它们会显著拖慢性能。

💡 提示:进一步阅读 [3D 建模部分](/creator/3d-modeling/3d-models 中的 3D 模型最佳实践

在 Blender 中抢救一个沉重的模型

当统计数据指向某一个特定模型时,通常修复模型比在代码中绕开它更快。在 Blender 或任何类似工具中,最有用的编辑包括:

  • 减少三角形数量 使用 Decimate 修改器。这是一个用于处理你已拥有模型的抢救工具。新模型最好从一开始就直接建模为低多边形。

  • 调整纹理大小 为 2 的幂次大小,1024x1024 或更小,并将它们重新打包进 .glb.

  • 删除玩家永远看不到的面,比如道具的底部和背面。开启 背面剔除 ,而不是把几何体加倍。

  • 合并材质 为一个,使用单张图集纹理,而不是每个部分一张纹理。

  • 从 _collider 网格中移除灯光、摄像机和材质。 引擎会忽略它们,因此它们纯属负担。

在导出回同一文件之前,检查修改前后的三角形数量和材质数量。如果预览正在运行,它会随你的更改重新加载。

多次重复使用同一个模型

场景里常常充满重复内容:街道上的路灯、房间里的椅子、公园里的树木。构建这些内容的最佳方式其实最简单—— 给每个副本分配各自的实体,并让它们都指向同一个 .glb 文件。

引擎会识别出这些实体共享同一个来源。该文件只会下载一次,只会转换为资源包一次,而且其网格和纹理只会在内存中保留一份——第 20 个路灯除了它在世界中的位置之外,几乎不会带来额外成本。这在不同场景之间也同样适用:如果相邻场景使用同一个文件,它已经在内存中了。导出二十个几乎相同的 .glb 文件,则意味着二十次下载和内存中的二十份副本。

这 不会 节省的是 draw call。无论 20 个路灯来自 20 个实体还是来自一个包含 20 个路灯建模在一起的 .glb 模型,20 个路灯都意味着 20 个需要绘制的对象。减少该数量的唯一办法,是在建模工具中把网格合并起来;这确实是个权衡——参见 实例化 vs 复制 vs 合并 ,了解在什么情况下这样做值得。

还有几个相关提示:

  • 按簇分组,而不是做成一个巨大的模型。 如果你有很多零散的道具,一个不错的折中方案是每组一个 .glb ——比如一段街区或一个房间的一套家具——而不是每个道具一个模型,或者整个场景一个模型。这样既减少了对象数量,又能让每个簇保持足够小,从而让剔除仍然能发挥作用。

  • 一次生成很多副本? 先用 AssetLoad 组件预先加载模型。随后这些副本会基于已经在内存中的模型创建,而不是每个都在等待同一次下载。构建每个副本仍然需要工作,所以一次性大量生成无论如何都可能拖慢一帧——预加载消除了等待文件的时间,而不是创建副本的成本。

  • 一个非常大的模型比许多小模型更让引擎吃力。 引擎会把构建场景的工作分摊到多帧,以避免卡顿,但它无法拆分一个单独的超大模型——它会落在同一帧里。许多较小的部分会平滑加载;一个庞大的整体块更容易造成可见的卡顿。

  • 重复一个模型不会引入新的纹理或几何体。 由一个模型构建的 20 个路灯会共享该模型的网格和纹理,而不是为集合增加新的内容——第 20 个在纹理内存上几乎没有成本。材质是个例外:引擎会为每个被渲染的对象创建一个材质实例,以应用你场景的边界裁剪,因此材质数量取决于你渲染的对象数量,而不是你使用的模型数量。这些实例仍然共享相同的纹理和着色器变体,所以它们消耗的是少量内存,而不是帧时间。会把场景推向纹理 限制 的原因之一,是拥有许多 不同的 模型,每个模型都有自己专属的一套内容——因此围绕更少的可复用模型库来整合场景会有所帮助。

背面剔除

为了进行性能优化,Backface Culling 将被设置为 开启 位于 所有 模型材质在引擎中渲染后,无论其设置如何都如此。

如果你预计会看到模型的背面或内部,请复制这些面并反转法线。

故障排除

要验证场景是否存在材质 Backface Culling 问题,请按以下步骤操作:

  1. 打开 调试 面板。

  • 如果场景已发布,请在聊天中输入 /debug 命令。

  • 如果你处于场景的预览模式,请点击错误图标(

    调试图标

    ),它位于屏幕右上角。

  1. 调试面板将出现在屏幕右下角。

  2. 在 当前场景,点击 背面调试器 按钮。


  1. 切换 强制背面剔除:它会显示启用了 Backface Culling 的材质。这是优化在生产环境中启用后的实际渲染效果。切换开关可找出需要修复的材质。

  2. 切换 背面调试器 ,即可轻松找出关闭 Backface Culling 的材质。它会高亮显示:

  • 红色:未将 Backface Culling 设置为 开启.

  • 绿色:启用 Backface Culling 的材质 开启.

Backface Culling 关闭的材质(左)与开启的材质(右)

资源包转换

每次发布场景时,Decentraland 内容服务器都会将其中的每个 .gltf 和 .glb 模型压缩为资源包格式。该格式 显著 更轻,因此场景的加载速度更快、运行也更流畅。转换会在每次部署后立即开始。通常只需几秒钟,但对于非常大的场景,或服务器繁忙时,则可能需要更久,因为转换会针对每个平台单独运行,并且可能排在其他场景后面。你可以 直接检查转换状态 。

在开发场景时也在本地运行同样的转换。这会让预览更流畅,并让你在发布前检查压缩模型是否存在问题。参见 使用优化后的资源预览.

💡 提示:在为 Decentraland 中的活动做计划时,最好至少提前一小时发布最终版本,这样到那时所有模型都已转换为资源包,而且你还有时间修复任何意外问题。如果你不想在活动前泄露惊喜,可以部署一个场景版本,其中项目文件夹里包含所有最终的 3D 模型,但这些模型不可见,或者它们的大小设置为 0。

连接性

如果你的场景连接到任何第三方服务器,或使用 messagebus 在玩家之间发送消息,也有一些事项需要注意。

  • 你的场景同一时间应只保持一个活动的 WebSockets 连接。

  • HTTP 调用会由引擎汇聚处理,因此同一时间只会处理一个。任何额外请求都会在内部排队,并必须等到其他请求完成后才能继续。这个排队过程由系统自动处理,你无需做任何事情。

  • 当使用 messagebus 在玩家之间发送消息时,请注意所有消息都会发送给服务器岛内的所有其他玩家。避免出现“接收到一条消息就直接发送另一条消息”的情况,因为当场景里人多时,消息数量会迅速呈指数增长。

场景 UI

当场景 UI 由许多个独立元素组成时,渲染成本会变得很高。请记住,每个 UI 元素都需要引擎单独执行一次 drawcall。

💡 提示:尽量将多个元素合并成一张图像。例如,如果你有一个包含多个文本元素的菜单,最好把磁贴中的文字和任何额外图像都烘焙到背景图像中。这样可以避免引擎为每个文本元素每帧额外执行一次 drawcall。

避免在每一帧都调整 UI,这样尤其耗性能,而且可能会被排队处理。例如,如果你的 UI 中有一个应随时间缩小的生命值条,玩家可能分辨不出它以 10 FPS 更新还是以 30 FPS(每帧)更新有什么区别。更新这条条形的系统可以使用一个计时 100 毫秒的短计时器,只有当计时器归零时才影响 UI。

避免存在大量隐藏的 UI 元素,即使它们未被渲染,也会对性能产生影响。在可能的情况下,尽量按需创建 UI 组件。

World 中的地形

发布到 Decentraland World 会被自动生成的草地、树木和海景所环绕。渲染这些地形会消耗玩家的一部分渲染预算。如果你的场景不需要它,可以 禁用地形 在你的 scene.json 中禁用,以便把这些资源留给你场景自己的内容。

监控性能

衡量场景性能最好的指标是 FPS(每秒帧数)。在预览中,你可以在调试面板里看到当前场景的 FPS。你的目标应始终保持 30 FPS 或更高。

在已部署的场景中,你可以通过输入 /showfps 来切换显示这些指标的面板。

场景统计面板

在桌面客户端的预览中运行场景时,你可以打开一个面板,显示场景内容的实时统计数据。点击屏幕右上角、控制台和调试图标旁边的统计图标。

场景统计面板

该面板会将场景的三角形、实体、刚体和纹理与它们的限制值进行对照,显示各项预算的使用百分比。某项达到其限制的 80% 后,数值会变为橙色。它还会显示材质、几何体、碰撞体以及外部视频或音频的纯计数。将鼠标悬停在每项指标旁边的信息图标上,可查看其统计内容及重要性的说明。

随着内容的加载和卸载,这些数字会实时更新,因此该面板非常适合用来检查诸如 延迟加载 之类的策略如何影响引擎在任一时刻实际处理的内容。参见 场景限制 了解每项限制的详细信息。请记住,这些都是软限制:超过其中一项不会阻止你的场景,但会降低玩家的性能体验。

场景与引擎之间的消息

场景性能的主要瓶颈之一,通常是场景代码与引擎之间发送消息。

在预览中运行场景时,请注意右上角会显示“Y = Toggle Panel”。按下键盘上的 Y 即可打开一个面板,其中包含一些实时更新的有用信息。

当你与涉及 SDK 和引擎之间消息的内容交互时,你会注意到“Processed Messages”的数字在增长。你需要密切关注“Pending on Queue”的数字,它应始终为 0 或接近 0。这表示有多少消息尚未被处理,而是被推入了队列。如果“Pending on Queue”的数值开始增长,那么你已经进入危险区域,应考虑对场景进行更多优化。

请记住,你在预览中体验到的性能可能与生产环境中的不同:

  • 周围相邻场景可能会产生负面影响

  • 将场景的 3D 模型压缩为资源包可能会带来正面影响

  • 访问你场景的一些玩家可能正在使用性能较弱的硬件

先尝试将场景部署到 Decentraland World ,再进行更全面的测试,这始终是个好做法。

始终向玩家征求反馈。不要想当然地认为你体验场景的方式与其他所有人都一样。

使用 AI 优化

AI 代理可以为你处理本页所述的大部分测量和修复工作。Decentraland 提供 AI 技能 ,这些内容会教会编码代理(如 Claude Code 或 Cursor)如何优化场景,而桌面客户端还包含一个 MCP 服务器 ,让代理能够直接检查并测量你正在运行的场景。

借助 optimize-scene 和 unity-explorer-mcp 已安装的技能和已连接的 MCP 服务器,代理可以:

  • 根据内容预算检查场景:它会读取与场景统计面板相同的实时数据,并将其与 场景限制进行比较,从而告诉你是哪一项预算接近上限。

  • 找出责任资产:它会根据场景中每个 3D 模型贡献的三角形数量、材质数量和 draw call 数量进行排序,其中包括从特定视角可见的内容,因此它能准确知道应先优化哪些模型。

  • 测量真实帧率:它会将玩家定位到场景中的任意位置,并采样玩家在该处实际体验到的 FPS,包括瞬时卡顿。

  • 验证自己的工作:每次更改后它都可以重新测量,因此其建议和修复都有真实数据支撑,而不是猜测。

这让优化变成了你可以直接提出的需求:

“检查我的场景是否在限制范围内,找出在出生点附近拖慢帧率的任何因素,修复它,并向我展示修复前后的数据。”

结合 Blender MCP 服务器使用

Decentraland 工具会告诉代理哪些模型最重,但它们不会直接编辑模型本身。若想更进一步,你可以尝试将它们与 Blender MCP 服务器结合起来,它会让代理获得访问 Blender 编辑工具的能力。这使代理能够在 Blender 中打开一个重量较大的模型,减少其三角形数量、合并其材质或缩小其纹理,然后将结果作为一个 .glb 文件导出回场景中,随后再在世界内重新测量帧率。

请记住,自动化的网格编辑可能会明显改变模型外观,因此务必检查结果并保留原始文件的备份。

最后更新于