我做了一个 3D 虚拟世界基底,在上面再搭 3D 世界或元宇宙,事半功倍

下面这些数字全部来自我这个基底的内部实现。因为是我自己踩过的坑,所以可以讲得具体一点:每一条都给出机制和实测值,而不是"建议优化性能"这种正确的废话。

这篇讲三件 3D 前端最容易踩、而且不会报错的事:draw call 堆高、点光源数量变化触发全场景重编译、着色器编译阻塞主线程。顺带说说颜色正确性------它也不报错,但不报错不等于不严重。

标签 :Three.js WebGL 性能优化 3D 前端


先说一个判断:性能问题里,最难缠的不是最慢的那个

新手调 3D 性能时,第一反应是"帧率低就降画质"。但实际做过一轮就会发现:真正难缠的,是那些帧率看着还行、但埋着隐患的问题。

它们的特点是------不报错。你在开发机上测,一切正常;换台机器、换个场景、用户一多就出问题,而且查不到原因。

下面三件是我实测撞过并且解掉的,都属于这类。

一、draw call:合批才是根治,删模型不是

机制

每次绘制调用(draw call)都有固定开销。场景里 3000 个物体、每个一次调用,光是"发起调用"这一项就吃掉大量 CPU 时间。

把同一批材质的东西合并成一次调用,调用次数降下来了,但这一次调用里要处理的顶点数据变多了------所以不是无限合批,通常要在"调用次数"和"单次调用数据量"之间找平衡点。

实测

我们做过一次几何合批:

场景 合批前 draw call 合批后 降幅
密集区 3064 837 −73%
加载期占位场 2150 956 −56%

密集区是 3D 项目里最容易崩的地方------用户视角转过去就是一片密集模型。这个数字说明:合批之后,密集区的调用开销降到了原来的四分之一不到。

但更值钱的是"占位场"那一行

加载期占位场那个 −56% 容易被忽略,但它解决的是一个体验问题:加载的时候先用一个全图占位场撑住,一个 draw call 覆盖 1071/1071 个对象,模型就位后再用 250 毫秒渐缩退场。

用户永远先看到一个完整的世界,而不是一堆散件。 这一点比帧率数字更影响观感。

⚠️ 不要用"删场景"救帧率

帧率不够就删模型,是把技术问题换成了产品问题。性能问题有治理路径,删内容只是把账欠着------而且用户会感觉到"东西变少了",只是说不清为什么。

二、点光源数量变化:一次改动,8989 毫秒全场冻结

机制

这是三件事里最隐蔽的一个。

场景里用了点光源(PointLight)之后,每当你增删一个点光源,所有材质都要重新编译着色器。因为每个材质的渲染逻辑取决于"当前场景里有几盏灯、分别是什么类型"。

而着色器编译在不同平台上行为不一样:有些平台(Windows 的 ANGLE/D3D11 路径上)是同步且不可中断的。也就是说,编译期间主线程被死死占住,画面完全冻住。

实测

复制代码

八个多秒的卡死。如果用户正在操作、或者正在加载进一个房间,就是一次"页面死了"级别的体验。

而且它没有任何日志。 帧率监控只会告诉你"这里卡了",不会告诉你是灯的问题。

解法:灯池

思路很简单:不要让灯的数量变化。

固定 12 盏灯常驻在场景里,永远不增不减。需要更多灯的时候,从池子里"租"一盏,设定它的位置、颜色、强度;不用了"归还"------归还时不是移除,而是把强度设为 0 并移到场景外。

因为灯的数量恒定,材质重编译的次数归零。

复制代码

这个改动的成本很低(就是维护一个灯对象的数组),收益是消除了一类最严重的卡顿。

三、着色器编译阻塞主线程:给编译上"预算"

机制

新网格第一次进入视野时,需要现场编译它的着色器。这个编译是同步的,编译期间画面是死的。

场景小的时候,编译一两个网格感觉不出来。但如果是一次进一个房间,或者角色换装、模型批量入场------几十个网格排队等着编译,画面就一卡一卡地顿。

解法:预热预算

两件事:

  1. 未预热的网格不入屏 ------ 还没编译完的,不要让它出现在画面里(宁可先不显示)
  1. 编译后按每帧固定份额结算 ------ 编译完成后,不是立刻全部入屏,而是按每帧 170 毫秒的份额,一个一个入屏

实测

复制代码

关键不是"最坏情况 386 毫秒"这个数字,而是 "没有任何一帧超过 500 毫秒" 。因为 500 毫秒以上用户能明显感觉到"顿了一下",而 386 毫秒在正常交互里是可以接受的。

四、怎么知道该优化哪一项

上面三件事有个共同点:你不会自动收到提示。 所以需要一个主动的归因手段。

我用的做法是三个诊断接口:

接口 回答什么问题
onBeforeRender 逐对象计数 现在这一帧有哪些对象在画、各自调用了几次
diagTurnLag 转视角归因 转视角卡顿是哪些对象造成的
diagTurnSweep 全场扫描 整个场景里谁最重

为什么必须自己建这个能力:性能问题没有报错日志,你不主动去问,就只能靠猜。而猜的成本极高------你会花一天怀疑模型数量,第二天怀疑贴图,第三天才想到灯。

有了归因接口,能直接看到"密集区 3064 次调用"、"这一帧有 12 盏灯"------问题从"有点卡"变成"这里"。

五、颜色正确性:另一个不会报错的问题

顺手提一个和性能无关、但同样不报错的坑。

Three.js 早期版本有个常见做法:输出按 sRGB 处理,贴图却按线性着色。两者叠加就是双重 gamma,画面会整体偏亮、偏粉,阴影发灰。

这个 bug 不产生任何报错,只表现为"质感差了点"。如果验收标准是"能跑起来",它会一直留在产品里。

验证方式不能靠眼睛。我们做版本升级时的做法是:

  1. 先立 10 张统一口径的基线截图(固定相机位置、固定参数)
  1. 写一个 PSNR / diff 工具逐张比对
  1. 把差异逐项归因

实测后台界面 43.4dB 一致,主世界的差异能全部归因到颜色管线修正,构图和角色像素级一致。

顺带做了一件事:加载前检测 WebGL2 支持,不支持时给一句明确的提示,而不是白屏或控制台刷错。

小结

这几件事的共同点值得再说一遍:

问题 为什么难发现 解法
draw call 堆高 帧率只是"慢慢降" 几何合批(实测 −73%)
灯数变化 一次冻 8 秒多,无日志 灯池(灯数恒定,重编译归零)
着色器编译 一卡一卡,不易关联 预热预算(每帧 170 毫秒份额)
颜色双重 gamma 画面"看着不对",不报错 基线截图 + PSNR 比对

它们都不会告诉你在哪里出错,所以你需要自己造工具去问。

六、为什么上面这些值得一次做完

标题里说「事半功倍」,这里讲清楚是哪一半------因为这句话很容易被读成广告。

上面这四类工作,你换成任何一个项目都要重做一遍。 一个展厅要做一遍,一个培训环境要再做一遍,一个带 AI 角色的空间还是要做一遍。它们和你的业务内容毫无关系:不管你这个世界里放的是展品、课程还是角色,draw call 怎么降、灯池怎么设计、着色器编译怎么摊预算,答案都是同一个。

所以把这四类成本一次做完、放进一个可复用的基底里,所有上层场景共享------这就是标题那句话的意思。省下来的是第二阶段的排查时间,不是第一阶段的编码时间。

(我做的就是这样一个基底:浏览器端、可部署在自有服务器上的 3D 虚拟世界。上面这些数字都出自它的内部实现,技术细节都在代码里。)

需要说清另一半:省下来的不是全部。 按层分一下的话,上面这四类属于底下那几层(渲染与资产管线、性能与多端、实时通信),它们和你的业务无关;最上面那层业务逻辑------你这个空间里放什么、访客能做什么、怎么算完成------该你写还是你写,一个字段都不会因为有基底而少写。 任何"有了基底就不用做业务了"的说法都是错的。**

这里也不给任何时间倍数。 要估的是:你这个场景里那四类问题各占多少工时。说得出数字,才知道哪些该自己写、哪些该拿现成的------这份清单同时也是验收清单。

至于什么时候应该反过来自己写、不用任何基底,下面几种情况是:只有一个静态页面且不需要联机(一个模型加一个网页就够了)、你学的正是这些机制本身(写一遍是最有效的学习方式)、约束是极低运行环境(通用方案塞不进去)、或者产品形态就是一次性交付(基底的价值在第三个月,做完就下线的话收不回来)。

七、"元宇宙"这几个字,具体落在哪些活上

标题里出现了元宇宙,拆开看其实很清楚:

元宇宙里常被说成什么 实际对应哪些活 谁负责
"能进去" 浏览器打开就能走、不用装客户端 基底
"有空间" 3D 场景、模型加载、资产分级、远近裁剪 基底
"能一起玩" 多人同步、移动与转向一致、掉线恢复、近距离语音 基底
"能一直在线" 账号与数据、资产管理、后台、不依赖第三方 基底
"有内容" 这个空间里放什么、访客能做什么、怎么算完成 你
"有身份" 角色身份:我是谁、别人怎么认出我、怎么持久化 你
"有经济系统" 虚拟资产、交易规则、结算逻辑 你
"能跨空间" 世界间互通、身份和资产怎么带过去 协议层,规则由你定

规律很清楚:被宣传时最强调的往往是最上面那几行,而最花时间的是下面那几行。 "有内容""有身份""经济系统"听起来最有想象力,实际上它们是业务逻辑,写起来快、也没有性能坑;反过来"能进去""有空间""能一直在线"这些听起来平淡的活,占了大半工期,还全都不会报错。

所以一个可复用的基底做的事,恰好是把下面那几行从你的待办里划掉。 你拿到的不是一个现成的世界,而是一块已经通电的地基------上面盖什么,地基是同一块。

边界提前说清:跨空间做的是协议层 (怎么握手、身份怎么迁移、同名怎么处理,实测 4/4 自动改名),规则归你。经济系统的结算、内容审核这类东西我们完全没做,也不打算做。

最后交代边界:上面所有数字都来自我们自己的项目和场景(浏览器端 3D 世界,一台服务器自部署),换个场景数值会不同。能复用的是这些机制和这套归因思路,不是这些数字------你的场景要自己测一遍。文中提到的移动端和 XR 表现我们尚未完整实测,也不做推断。

本文为开发过程中的技术复盘,数据来自项目内验收脚本,可复跑核验。列出的机制均为当前实现的如实描述,不作为后续承诺。

相关推荐
Csvn1 小时前
从 request_id 到可视化 trace:Langfuse 全链路追踪实战(O02)
人工智能·aigc
回眸&啤酒鸭1 小时前
【回眸】GenPage 3.0 智能页面生成实战指南
人工智能
果霸大叔1 小时前
AI 网关和传统 API 网关,到底差在哪?—— 用一个"限流"场景讲透本质
人工智能
AliCloudROS1 小时前
OOS ChatOps 技能大爆发:一句话搞定云上运维的时代来了
人工智能
霍营_托尼1 小时前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
YonyouHRSaaS1 小时前
2026年10-11月AI面试选择指南:对国内主流AI面试系统进行对比,看看哪个更值得选!
人工智能·面试·职场和发展·hr·ai面试
会议咨询1 小时前
2026年智能计算、人工智能与制造技术国际会议(IAMT 2026)
人工智能·智能计算·制造技术
星云低代码开发平台2 小时前
AI 工作台点了取消,ERP 订单还会创建吗?从三层状态设计验收
人工智能
Axis tech2 小时前
通过MANUS手套推进由触觉驱动的机器人学习进程
人工智能·深度学习