把带贴图的 OBJ / glTF 转成 3D Tiles:我写了个 mesh-to-tiles

把带贴图的 OBJ / glTF 转成 3D Tiles:我写了个 mesh-to-tiles

做倾斜摄影或单体模型上 Cesium 时,经常卡在同一件事:手上是一份带贴图的 OBJ 或 glTF,场景里要的是 3D Tiles。现成工具能转,但 LOD、图集和纹理压缩往往要再串一圈。我把这条链路收成了一个 Node 命令行工具 mesh-to-tiles,输入网格,输出 3D Tiles 1.1 隐式八叉树。

仓库:github.com/Tao-tao520/...,协议 MIT。

它解决什么问题

目标很具体:

  • 输入:带贴图的 .obj(含 .mtl 和 map_Kd),或带底色贴图的 .glb / .gltf。GLB 里可以已经是 Draco。
  • 输出:tileset.json + 隐式八叉树瓦片。每个瓦片是一个 Draco 网格,外加一张重新烘焙的图集。
  • 加载端:Cesium 一类支持 3D Tiles 1.1 的引擎,材质按 unlit、双面来。

层级按模型包围盒自动切。refine 用 REPLACE:粗层被细层整块替换,避免两层叠在一起发虚。

图集最细一层是 4096,从原图采样;往上每一级边长减半,最低 128。默认编码成 KTX2(ETC1S / Basis Universal)。Cesium 加载时再转成当前显卡的块压缩格式,传输和显存都比一堆大 PNG 轻。

一条命令里做了什么

流水线可以记成四步:

  1. 读网格。OBJ 或 glTF / GLB 读进来,带上底色贴图。
  2. 按包围盒做隐式八叉树 。最细格子默认边长 40 米,层数上限默认 6,最细层 geometricError 默认 1 米。这些都能用参数改。
  3. 每个瓦片重烘焙一张图集。UV 岛按原贴图重排,不是简单把原图缩小塞进去。只有 4096 那一层按原图分辨率采样;更粗的层级先缩到 2048 再采样,避免每层都啃全分辨率。
  4. 压缩后写出 。几何用 draco3dgltf,符合 KHR_draco_mesh_compression。图集默认 KTX2 ETC1S(质量 128,不带 mipmap),符合 KHR_texture_basisu,编码走 worker_threads。加上 --no-ktx2 就保持 PNG。

坐标有两套,写错了模型会躺倒,这里单独说清楚:

  • 瓦片局部坐标是 Z 轴向上、单位米。
  • 瓦片里的 glTF 按规范写成 Y 轴向上。Cesium 加载时会转回 Z 轴向上。
  • --up 只说明输入模型 哪根轴朝上,不改输出方向。OBJ 默认 z,glTF / GLB 默认 y。
  • ODM 导出的 OBJ 和 GLB,顶点往往都是 Z 轴向上。OBJ 可以不写 --up;GLB 要写 --up z。
  • --longitude、--latitude、--height 是局部原点 (0,0,0) 的大地坐标,不会从网格里读 。--height 是 WGS84 椭球高,不是离地高。顶点原来的 Z 会保留。经纬度要么一起传,要么都不传。

环境与本地运行

需要 Node.js 18 或更高。

bash 复制代码
npm install
npm run build
node dist/cli.js --input model.obj --output ./tiles

带地理位置的例子:

bash 复制代码
mesh-to-tiles --input model.glb --output ./tiles --longitude 109.40388330 --latitude 34.03154500 --height 0

也可以 npm link 之后直接用全局命令 mesh-to-tiles。

不想在目标机器上装 Node

工具链依赖 sharp 和 Basis 的 WASM,拷一个 node_modules 过去很容易缺文件。仓库里按平台各打一份,把运行时一起带上:

bash 复制代码
npm run pack:win
npm run pack:linux

产物在 release/mesh-to-tiles-win-x64 和 release/mesh-to-tiles-linux-x64。目录里的 node、sharp 和 WASM 要一起拷贝,不要只拷脚本。Linux 包面向 glibc 系统,例如 Ubuntu。

Windows:

text 复制代码
mesh-to-tiles.cmd --input model.obj --output ./tiles --longitude 109.40 --latitude 34.03 --height 0

Linux:

bash 复制代码
chmod +x mesh-to-tiles
./mesh-to-tiles --input model.obj --output ./tiles --up z --longitude 109.40 --latitude 34.03 --height 0

参数一览

参数 说明 默认
--input .obj、.glb 或 .gltf 必填
--output 输出目录,每次运行会先清空 必填
`--up z y` 输入模型的向上轴
--leaf-size 最细格子边长,米 40
--leaf-error 最细层级的 geometricError,米 1
--max-levels 层级数上限 6
--longitude 局部原点经度,度 不写
--latitude 局部原点纬度,度 不写
--height 局部原点椭球高,米 0
--no-ktx2 图集保持 PNG 默认编码 KTX2

输出目录每次都会先清空,路径别指到还有别的文件的文件夹。

产物长什么样

text 复制代码
tiles/
  tileset.json
  tiles/{level}/{x}/{y}/{z}.glb
  subtrees/0/0/0/0.subtree

tileset.json 是 3D Tiles 1.1,subdivisionScheme 为 OCTREE,refine 为 REPLACE。一个 subtree 覆盖全部层级。glTF 为 Y 轴向上、unlit、Draco。底色贴图默认 image/ktx2,并声明 KHR_texture_basisu。

层级 L 的图集边长是:

text 复制代码
max(128, 4096 / 2^(maxLevel - L))

模型不大、层级少的时候,最细层不一定真到 4096,按这个公式算即可。

使用时注意的几点

  1. GLB 的向上轴别照搬 glTF 默认值。 规范里 glTF 是 Y-up,但不少生产管线(比如 ODM)导出的顶点是 Z-up。模型进 Cesium 后横躺,先查 --up。
  2. 高度是椭球高。 和地形的离地高不是同一个数。贴地效果不对时,先核对原点高程,再查地形。
  3. KTX2 编码在 CPU 上。 大模型、高层级时,这一步会占时间。调试阶段可以先加 --no-ktx2,确认几何和 LOD,再打开 KTX2 出正式包。
  4. 图集有上限。 单瓦片一张图集,UV 岛会重排。原模型贴图又多又碎时,4096 也不一定装得下所有细节,必要时把 --leaf-size 调小,让单个瓦片盖的范围变小。
  5. 发布包要整目录拷贝。 缺了同目录的 node、sharp 或 WASM,命令行能启动也会在读图或压纹理时报错。

技术栈(方便对照版本)

部分 使用
语言与运行时 TypeScript,Node.js 18+
glTF 与 Draco @gltf-transform/core、extensions、functions,draco3dgltf
贴图缩放与采样 sharp
KTX2 @loaders.gl/textures 里的 Basis Universal WASM,worker_threads 并行
瓦片格式 3D Tiles 1.1 implicit octree,KHR_draco_mesh_compression,KHR_materials_unlit,可选 KHR_texture_basisu

小结

mesh-to-tiles 把「带贴图网格 → 隐式八叉树 3D Tiles」收成一条命令:LOD 按包围盒生成,每层一张重烘焙图集,几何 Draco,纹理默认 KTX2,并用经纬度和椭球高把局部原点放到地球上。

代码和用法见 GitHub:Tao-tao520/mesh-to-tiles。参数和输出格式以仓库 README 为准。


关键词: 3D Tiles,Cesium,glTF,OBJ,Draco,KTX2,隐式八叉树,LOD

相关推荐
nicksxs21 分钟前
把 LLM 判断拆成 Choice / Score / Noul:Jev SDK 六条实测
人工智能
AICoder码农王23 分钟前
AgentScope Java v2 Lab-1,审批只走有门的腿
人工智能
AI前端工程狮24 分钟前
1 秒超时,3 秒才写完:MCP 写操作的重复提交实测
人工智能·程序员
dyxal42 分钟前
FireRedVAD 深度解析:小红书开源的工业级语音活动检测“小钢炮”
人工智能·开源·语音识别
加宝的起点雷达1 小时前
40 条候选里 38 条来自同一个源,主卡整屏重样
人工智能·算法·创业
紧跟先前的步伐1 小时前
Go AI Agent 实战 28|Agent 的预算、超时与停止条件
开发语言·人工智能·后端·ai·golang
蓝星科学1 小时前
AI Agent 说「已完成」,凭什么信——ChipCode 的证据链与产物门禁设计
人工智能
田里的水稻1 小时前
FA_CNN卷积神经网络二十六---从全连接到卷积PyTorch
人工智能·pytorch·cnn
艺杯羹1 小时前
MCP本地服务集成避坑:从Stdio管道僵死到双向JSON-RPC稳健通信
人工智能·python·ai·系统架构·大语言模型·ai编程·mcp