私有化部署一套浏览器 3D 空间,工程量到底在哪
私有化部署3D空间 自部署3D虚拟世界 Three.js私有化部署 去CDN化 内网3D空间 3D空间自研 GLB模型压缩 LOD分级 浏览器3D工程
摘要:很多方案书把"私有化部署"写成一句话——装个服务、配个域名就完事。但真在内网、离线、自有服务器上跑一套浏览器端 3D 空间,工程量集中在四个地方:依赖去 CDN 化、渲染内核升级、资产管线的自动化、以及加载与卡顿的可预算化。本文按这四块拆开讲,附上可复现的验收数字和一份"哪些场景该私有化、哪些别碰"的判断表。
私有化部署不是一个开关,是一整套"把外部依赖变成自己的责任"的迁移。这篇讲的是迁移过程中真正花时间的地方。
一、先定义清楚:"私有化"到底私有哪几样
选型时经常只谈一条——"数据在不在自己服务器上"。实际落地要过的关有四道:
| 面 | 私有化的含义 | 最容易被低估的点 |
|---|---|---|
| 数据 | 业务数据、资产、日志都在自有库/盘上 | 备份与归档链路也要自建,不能依赖云服务 |
| 网络 | 不访问公网 CDN,内网可跑 | 依赖去 CDN 化——这是最容易漏的一步 |
| 运行时 | 不依赖第三方 SaaS 的接口 | 版本漂移:上游更新会静默改变你的行为 |
| 升级 | 版本节奏由自己控制 | 升级的回归成本要自己扛,所以验收必须脚本化 |
第二条和第四条是真正吃工作量的地方。下面按可验证的顺序拆。
二、去 CDN 化:最容易被当作"改几行 import"的一步
浏览器端 3D 项目起步时,几乎都会走 importmap 指 CDN 或者 <script src="unpkg...">。放到内网环境,这一层立刻全部失效;更麻烦的是双实例问题——本地引一份、CDN 引一份,同一个 THREE 被加载两次,运行时的 instanceof 判断会莫名其妙地失败。
我们最后走的路子是六步:
- 用 esbuild 自建 UMD bundle,顶替掉原来的旧文件名(保持引用路径不变,减少改动面);
- 把加载器逐个本地化或 stub 化(用不到的直接给空实现,避免把体积拖进去);
- 加一层 shim 垫片(我们这一版是 87 个符号),把历史 API 接到新内核上;
- 后处理库(postprocessing)本地 vendor;
importmap从 CDN 改指本地——这一步才是消除双实例红线的地方;- 加兼容层,把旧的输出编码/贴图编码 API 真映射到新的颜色空间 API 上,而不是简单删掉。
配套一条很实用的纪律:把遗留 API 清单化。我们扫出 79 处只用一个兼容层兜住,而不是去逐个改写业务代码——私有化项目里,业务代码改动的每一行都是回归风险。
降级也要自己管。公网上可以假设浏览器都支持 WebGL2,内网终端不一定(老机器、远程桌面、虚拟机显卡)。做法是在加载 three 之前做能力检测,不支持就直接全屏中文提示 + window.stop():宁可给出明确结论,也不要白屏 + 控制台刷几百行报错。
三、渲染内核升级:为什么"版本号"这件事值得单独干一轮
从 r128 到 r185 跨度很大,中间横着 r152 的颜色管理革命。旧基线其实本身就是错的:sRGB 输出配上按线性着色的贴图 = 双重 gamma,画面偏亮偏粉。很多人以为这是"美术给的贴图颜色有问题",其实是管线的问题。
升级收益不在数字上,在能力面上:
- 颜色正确性:管线对了,颜色才准,这是"看得见的画质升级";
- WebGL2 能力面:自研的高斯泼溅 shader、BVH 碰撞、实例化合批,全都吃 WebGL2 的红利;
- 可离线:去掉 CDN 之后,内网和断网环境都能跑。
怎么证明升级没把画面改坏——这是私有化项目必须回答的问题。我们的做法是统一口径的基线截图 + 自研的 PSNR / 差异像素工具:后台页 43.4dB 一致,主世界的差异全部能归因到"颜色被修正了",构图、建筑、角色是像素级一致的。"差异可归因"比"差异很小"更重要,否则你没法判断哪一处是 bug。
四、资产必须在自己的服务器上"自我消化"
私有化之后,带宽是你自己的,硬盘是你自己的,用户上传的模型也是你自己要兜住的。所以资产管线不能靠人工,必须上传即自动处理。
4.1 模型瘦身:不改几何的手术
核心思路是"用 JSON + BIN 手术改写 GLB,不动几何",按用途分级压缩:法线/遮蔽类做无损重编码,baseColor 走调色板量化。实测:
| 样本 | 处理前 | 处理后 | 降幅 |
|---|---|---|---|
| 课桌模型 | 25.74 MB | 8.22 MB | −68.1% |
| 4K 多 buffer 模型 | 20.7 MB | 4.38 MB | −92.5% |
这类二进制改写最容易在生产环境翻车,所以我们设了五重闸门:mesh 名匹配、压缩格式校验、多 buffer 布局校验、几何一致性校验、回读验证。任何一关不过就跳过不落盘——私有化环境里没有"线上发现问题再回滚"的余地。
4.2 LOD:让"远景成本近零"变成可承诺的规格
LOD 的常见做法是按锚点距离分带,我们的做法是按玩家到模型表面的距离分带。差别在哪:一个横跨 155 米的模型,锚点可能在很远处,按锚点距分带会把它误判成"远景"而换成低模,玩家走到边上还是看见一个糊模型。用表面距就不会。
低模由工具链生成,超出拓扑下限的部分用自研的贪心边坍缩兜底。结果是可以对外承诺的硬指标:全库 118 组低模,118/118 ≤ 100 面。
三个机位开/关 LOD 的三角数对比:
| 机位 | 关闭 LOD | 开启 LOD | 降幅 |
|---|---|---|---|
| 教室热点 | — | — | −85.8% |
| 出生点 | — | — | −71.2% |
| 重复实例密集的族 | — | — | −86.7% |
还有一个细节值得抄:变体不额外占显存。低模变体和高模共享同一个贴图对象(不是复制一份),我们做了断言校验(33/33、36/36 命中共享)。另外变体的贴图会被自动剥离,磁盘省下 1.3GB;离线部署包里干脆不含变体,首次访问时现场生成(118 条 36.7 秒)。
五、把"卡"变成可预算的资源
自有服务器上,加载体验只能自己扛。我们把它拆成四个可以单独计量的问题:
① 解析阻塞主线程。 GLB 解析移进 Worker,用可转移对象做序列化,纹理走 OffscreenCanvas 预降。验收 23/23 走 Worker 且渲染完整。
② 驱动层的着色器编译尖峰。 这是最反直觉的一处:在 D3D11 上,同步的着色器反射一次要 160~340ms,且不可中断。灯的数量一变,整个场景要重编译,画面直接冻住。我们的做法是点光灯池——固定 12 盏常驻、租用归还,灯数变化不再触发全场景重编译;配合"预热预算",未预热的 mesh 先不入屏,按 170ms/帧逐个把反射成本摊薄,把"画面死住"换成"模型稍晚出现"。
效果:灯数变化造成的冻结从 8989ms 尖峰 → 0;密集区 draw call 3064 → 837(−73%)。
③ 远景一片空白。 远处直接变成占位方块,观感很差。做法是一张全图占位场:1 个 draw call 覆盖 1000+ 对象,250ms 渐缩退场。实测 1071/1071 覆盖,draw call 2150 → 956(−56%),实现了"远看有东西、近看是真模型"。
④ 加载进度必须是真的。 进度条的分母取当前视野的兴趣集,权重是 0.7 下载字节 + 0.3 确认上屏,并且对迟到的加载任务做代际隔离(否则旧请求回来会把进度条往回拽)。私有化环境下这条尤其重要:没有 CDN 加速,用户等待时间更长,进度反馈就是那条"我没死"信号。
六、私有化之后,运维就是你的 KPI
这一块没有技术含量,但决定了这套东西能不能"活下去"。
验收脚本化、可重跑。我们仓库里有 54 个 accept_*.js,判据是真实退出码 + JSON 报告 + VERDICT ACCEPTED。LOD 23/23、AI 接入 75/75、高斯泼溅 26/26、骨骼回归 67/67——每次改动都能回归。私有化项目没有"灰度发布"这种缓冲手段,验收脚本就是你的安全网。
配置热调。后台改参数(分带距离、推送档位、速度上限、并发上限)应该 60 秒内生效,不要让客户为改一个阈值去发版。
约束换可维护性。我们给自己定了硬约束:单文件 ≤500 行(超 1000 行禁止追加)、大文件黑名单零追加、新功能必须独立模块、临时脚本用完就删。听着像洁癖,但这是长周期项目不烂尾的制度保障。
七、说清楚现在的短板(不藏)
私有化部署最忌讳"方案书写得比代码好"。如实列我们当前的两处:
- 角色的帧率解耦没做完。
player.js目前仍是"每帧固定增量",低帧率设备上移动和转向会变慢(不只是画面卡,是手感变慢)。这是当前代码里一个真实短板,不是待优化项。 - 角色模板库有 79.3MB 孤儿数据。模板库总计 205.5MB,其中 79.3MB 是历史遗留(删了动画只改 JSON、BIN 没重建,集中在 4 个文件,最严重的一个文件 93.4% 是垃圾)。清理方案已定,跨世界首屏可从几十秒降到 1~2 秒,但还没实施。
把这两条写进对外材料,比写十条优点更能建立信任——因为它说明你真的在维护这套东西。
八、什么场景该走私有化,什么场景别走
| 场景特征 | 判断 |
|---|---|
| 内容涉密、内网封闭、不能出网 | 必须私有化,CDN 方案在这里直接失效 |
| 已经有 Three.js 团队、要深度定制 | 走自部署基底,别买模板——模板的边界会成为团队的天花板 |
| 数据合规要求明确(数据不出境/不出内网) | 走私有化,且要连日志归档链路一起自建 |
| 只是验证一个想法、两三个月要上线 | 别私有化。SaaS 平台更快,先验证需求 |
| 团队里没有人碰代码、也没预算找人部署 | 别私有化。这套东西是基底不是成品,启动成本会超预期 |
| 产品本身不需要空间呈现 | 别做 3D。这是最容易浪费的钱 |
九、常见问题
问:私有化部署一套浏览器 3D 空间,最难的是哪一块?
答:不是部署本身(现在容器化其实不难),难的是去 CDN 化 + 资产管线自动化这两块。前者关系"能不能在内网跑起来",后者关系"用户传上来的东西会不会把你的服务器拖垮"。
问:不用 CDN,加载速度会不会差很多?
答:会差,所以要靠工程手段补:模型瘦身(−68%~−92%)、LOD 三版本、Worker 解析、占位场 + 预热预算。注意 brotli/gzip 压缩传输是标配,压缩后实际传输量通常只有源文件的四分之一左右。
问:内网机器显卡差,支持不了 WebGL2 怎么办?
答:做加载前的能力检测 + 明确的中文提示,而不是白屏。如果终端确实老旧,私有化项目里要把它当作一个需求去评估,而不是上线后发现。
问:私有化之后升级怎么办?
答:靠可重跑的验收脚本。我们 54 个脚本、每个功能都有明确判据,升级前跑一轮就知道有没有回归。
问:数据在自己服务器上,还需要额外做什么?
答:备份与归档链路要一起自建(聊天记录、审计日志、上传资产),并且要明确归档失败时的处理策略——我们的做法是"未确认上传成功前不删本地文件",宁可占盘也不丢数据。
问:这套东西能二次开发吗?
答:私有化部署的前提就是底层代码在你自己手里。要注意的是留好扩展边界:新功能独立模块、不往大文件里追加,否则二次开发会变成考古。
方案对比:私有化部署 vs 平台租赁
| 对比维度 | 平台模式(SaaS) | 私有化部署(创世Genesis) |
|---|---|---|
| 数据主权 | 数据存储在平台服务器,归属模糊 | 数据在你的服务器,完全自主 |
| 成本模式 | 按月/年付费,长期成本累计高昂 | 一次性部署投入,长期成本极低 |
| 功能定制 | 标准模板,功能固定不可改 | 完全自由定制,按需扩展 |
| 品牌独立 | 受平台品牌和调性限制 | 独立品牌形象,完全自主设计 |
| 用户归属 | 用户属于平台,你只是租客 | 用户是你的,数据是你的,关系是你的 |
关于创世Genesis
创世Genesis是一套基于Three.js+WebGL构建的自部署3D虚拟世界系统,帮助个人与企业搭建属于自己的3D空间。浏览器直接访问,PC和手机双端兼容,支持多人在线、联邦传送、商铺系统,并支持Agent接入——AI能以具身角色进入你部署的世界。数据运行在你自己的服务器上,不经过第三方平台——让每个世界都真正属于它的主人。
如果你正在评估自部署 3D 空间这条路,可以参考我们的实现:创世Genesis 是一套浏览器端、可部署在自有服务器上的 Three.js 3D 虚拟世界基底。上面写的指标都来自仓库里可重跑的验收脚本,欢迎直接复跑核验。
本文为开发过程中整理的技术复盘,数据来自项目内的验收脚本,可直接复跑核验。文中标注为「当前短板」的部分属实未完成,不作为已交付能力。
源码与仓库
- Gitee(国内访问更快):https://gitee.com/miduoxinxijeji/miduo.git
- GitCode(国内镜像):https://gitcode.com/qq_35054471/virtual-world
- GitHub:https://github.com/miduo100/3d-virtual-world
三个地址内容一致,国内访问用上面两个更快。仓库里有部署说明与演示入口。