手里有个GLB模型,想让它在网页里像三维地图那样加载,还能和真实经纬度对得上。这个问题我断断续续折腾了小半个月,期间翻过不少资料,踩过不少坑。今天这篇,我就把从零开始摸出来的完整流程捋清楚:GLB模型怎么进GISBox,怎么转成3DTiles,服务怎么发出去让浏览器真正能打开,以及最容易翻车的几个细节。如果你是第一次接触GLB、GISBox、3DTiles这三个词,这篇文章就是给你写的;如果你已经在项目里转过一轮,也可以直接跳去后面的踩坑章节。
1. 为什么GLB不能直接上Web:从单模型到3DTiles的形态跨越
1.1 GLB和3DTiles到底差在哪
很多刚接触三维GIS的朋友容易混淆一件事:GLB不是也能在网页里显示吗?为什么非要多一步转成3DTiles?
GLB是glTF格式的二进制打包形态,一个文件里把网格、材质、纹理、动画全塞进去,体积紧凑,加载快,特别适合单个模型的展示。你可以把它理解成一件“独立包装的商品”,商家发货给你,你打开就能用。但这件商品上没有写清楚“它应该放在哪个货架、哪个位置”。
3DTiles不一样,它是面向海量地理空间数据的三维瓦片规范,核心是空间索引、LOD、批量渲染和属性信息。它就像一套完整的仓储货架系统,每件商品放哪个货架、哪个格子、离得远怎么简化展示、离得近怎么精细展示,全部安排得明明白白。
| 对比项 | GLB | 3DTiles |
|---|---|---|
| 数据形态 | 单文件二进制 | 瓦片集,tileset.json入口+若干瓦片文件 |
| 空间索引 | 无 | 有树状空间索引 |
| LOD层级 | 无 | 多级LOD |
| 地理参考 | 默认无 | 可有明确地理坐标 |
| 适用场景 | 单模型展示、AR/VR、工业模型 | 三维GIS、大场景、海量模型加载 |
1.2 直接拿GLB当地图数据用,问题出在哪
先说清楚:Cesium、MapBox这类三维地球引擎不是不能加载GLB,而是加载后的效率和可用性撑不起真正的GIS需求。
我一开始图省事,直接把几十个GLB文件挨个加载到Cesium场景里。前几个还挺流畅,到十几个就开始掉帧,上百个基本卡成PPT。原因很简单:每个GLB都是独立模型,引擎要给每个模型单独做渲染调度,没有空间裁剪优化,也没有动态简化。更麻烦的是,GLB本身不携带地理坐标信息,每个模型要手动设置经度、纬度、高度、朝向,位置全靠手工摆,数据一多就根本维护不过来。
3DTiles解决的就是这个问题。数据发布方在服务端把模型切分成带LOD的瓦片,浏览器端只加载当前视角范围内的瓦片,远处的模型自动用低精度版本,近处才加载高精度版本。这样一来,大场景、海量模型才能跑得动。
1.3 转换这一下,背后到底做了什么
把GLB转成3DTiles,不是简单改个文件后缀,而是做了一次比较完整的空间数据加工:
- 坐标基准处理:把模型的局部坐标系归一到地理坐标系下,或者换算到指定的投影坐标。
- 单位统一:GLB默认单位是米,但很多建模软件导出的模型实际比例可能是毫米、厘米,这一步必须校准。
- 纹理重采样:把PNG、JPG或TGA贴图重新编码成更适合GPU实时加载的格式,并控制单张纹理尺寸。
- LOD生成:对模型网格做抽稀简化,生成多级精度,近处用精模,远处用粗模。
- 瓦片切分:按空间范围把模型分割成多块,生成tileset.json作为入口索引。
可以用一个生活类比:GLB是一张超高分辨率的景区全景照片,你想在手机上流畅查看,就得把它切成很多小块,并且根据你放大缩小的程度决定加载哪些块。3DTiles就是这套切块和加载规则,GISBox就是替你完成切块的加工工具。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开工前的工具箱:GISBox版本选择与GLB模型源盘点
2.1 GISBox是什么,版本怎么选
GISBox是一款面向三维GIS数据集成、转换和发布的一体化工具,支持GLB、SHP、倾斜摄影等多源数据,最常用的能力就是把GLB处理成3DTiles并发布HTTP服务。实际操作中,我发现它最大的价值是省掉了手动拼接“数据转换工具+瓦片切分工具+Web服务工具”的麻烦,一条链路全包了。
版本选择上,我的建议是优先用官方发布的稳定版,不要一上来就追新版。原因很简单:新版功能虽然多,但插件、示例工程和网上教程往往还没跟上,遇到问题排查起来费劲。我之前用过一次测试版,界面和稳定版差异很大,照着教程找不到按钮,严重影响效率。
安装时有两个细节:
- 安装路径不要带中文和空格,比如
D:\GISBox,不要放在D:\软件\GIS Box。3DTiles生成过程中涉及大量文件路径拼接,中文路径很容易在后续发布环节出现404或者纹理丢失。 - 杀毒软件如果拦截,先把目录加入白名单再运行。这类工具经常要批量读写文件和起本地服务,容易被误报。
2.2 GLB模型从哪来:下载源和机巢模型
模型源是很多新手动手前卡住的地方。结合我自己的实践,GLB模型通常来自这几个渠道:
- SketchUp官方3D Warehouse:上面有海量的建筑、机巢、城市部件模型,大部分是SKP格式,需要先转成GLB再进入GISBox。
- 厂商项目交付包:很多项目里提到的“机巢的glb模型”,一般会随设备资料或项目维保包一起提供。如果交付包里只有SKP、OBJ、FBX,没有GLB,就需要自己转。这种模型往往精度高、部件多,转换前记得先核对单位。
- 免费在线模型库:OpenGameArt、Poly Haven、CGTrader的免费区都有不少GLB/glTF模型,适合练习和小场景演示。
- 自己建模导出:Blender导出的GLB质量最可控,推荐优先掌握这条路。
需要提醒一句:从网站下载的模型,使用前一定看清版权协议。商用项目用错了授权来源,后面会惹麻烦。
2.3 SKP转GLB的前置处理
很多机巢、建筑精模都是SKP格式,因为SketchUp建模效率高,非专业建模人员也能上手。但SKP在Web端兼容性差,必须转成GLB。
如果你打算用Blender中转,转换前先做三件事:
- 在SketchUp里删除隐藏图层和无关组件。很多模型文件里藏着辅助线、地面网格、参考面,体积大还没用,不删的话转出来又大又乱。
- 统一单位。SketchUp默认模板可能是毫米,也可能是米,导出前在模型信息里确认单位。单位错了,后面在GISBox里模型会大1000倍或小1000倍,非常隐蔽。
- 检查贴图路径。SKP里的贴图如果指向外部文件,导出GLB前最好先把贴图全部嵌入,否则Blender导入后纹理全丢。
做好这些预处理,后面每一步都会顺很多。
3. 手把手GLB导入GISBox:从拖拽到图层预览
3.1 新建工程和数据源
打开GISBox,第一步不是急着拖模型,而是先新建一个工程。工程是数据组织的根目录,后面生成的瓦片、发布的服务都会挂在这个工程下,统一管理。如果跳过工程直接处理数据,后期项目文件一多,很容易出现“找不到数据在哪”的情况。
新建工程后,找到数据源管理面板,添加GLB模型文件。不同版本的按钮位置可能略有差异,但逻辑一致:在数据源或图层区域点击添加,选择GLB文件,然后等它解析。
这里有一个容易被忽略的点:添加GLB之前,先去Blender或其他工具里看一眼这个模型的三角面数和纹理总大小。经验阈值可以参考:
- 三角面数在20万以内:直接处理没问题。
- 三角面数在20万到100万:还能跑,但尽量开启纹理压缩。
- 三角面数超过100万:建议先做减面处理,或者拆分成多个模型再进管线。
别指望GISBox是全能的,建模阶段把面数控制好,后面生成3DTiles的成功率和流畅度都会好很多。
3.2 坐标参考和单位设置
这是整个导入过程中最容易出错、也最关键的一步。
GLB本身没有地理坐标,导入GISBox时你必须告诉它“这个模型在地球上哪个位置”。有两种常见情况:
情况一:模型有真实地理坐标。比如项目里给了建筑物中心点的经纬度,或者模型是依据测绘数据建的。这时在坐标设置里填入对应经度、纬度、高度,系统会把模型移到真实位置。方向也要注意:建筑正北方向如果在建模时没摆正,导入后需要设置旋转角。
情况二:模型只是展示用途,不需要真实地理坐标。这时可以选局部坐标系,手动把模型放在原点附近,后续自己调整视角。这种方法适合演示项目,不适合后面叠加其他GIS数据。
单位问题更隐蔽。GLB规范默认单位是米,但有些导出工具会写错单位。如果导入后模型位置不对、大小不符,优先检查单位和坐标值。
我的习惯是:导入前先在模型工具里把模型移动到世界原点附近,记录中心点坐标,再导入GISBox时填这个坐标。这样即使发生偏移,也容易推算规律。
3.3 图层设置:纹理、法线和高度模式
模型导入成功后,在图层属性里主要有几个设置项:
- 纹理:必须勾选。如果模型是白模可以关掉,但绝大多数场景要保留。
- 法线:建议勾选。法线贴图能保留模型表面细节,尤其对建筑立面和机巢这类有棱线细节的模型,影响非常明显。
- 高度模式:这是3DTiles加载时模型如何贴地的规则。clampToGround表示模型底部贴到地表,适合路灯、岗亭、普通建筑;absolute表示保持原始绝对高度,适合桥梁、地形起伏较大的区域。
我的建议是:除非你确定模型底部就在地面上且地形平坦,否则优先用absolute,手动调整高度。clampToGround在某些地形数据不精确时,会把模型“吸”进地里或者抬到空中,画面很诡异。
预览窗口里还可以旋转观察模型方向。导入完成后先确认模型正北朝向,不对的话在属性里直接设置旋转角度,比如偏了90度就填90。这一步比到发布后再调整省事得多。
4. 3DTiles服务发布与浏览器验证
4.1 为什么要先“构建瓦片”,而不是直接导出文件
很多人把“转3DTiles”理解成“导出3dtiles文件”,这是个误区。
GISBox里的操作通常是“构建瓦片”或“生成3DTiles”,它会生成一个包含tileset.json和一堆瓦片文件的目录。这个过程做了切片、LOD生成、纹理重采样,需要一定时间。
构建参数方面,我常用的推荐配置是:
- 纹理压缩:开启。单张纹理建议上限2048或4096,如果模型很大,2048更稳妥。
- LOD层级:让系统自动生成,或者选2-3级。层级太多构建时间长,层级太少远处的模型显示模糊。
- 输出目录:单独建一个文件夹,别放在工程根目录,方便后期清理和发布。
构建完成后,检查输出目录里有没有tileset.json。如果没有,说明构建失败,回到模型预处理重新排查。
4.2 服务发布:端口、目录和跨域
发布服务本质上是把刚才生成的3DTiles目录变成一个HTTP静态服务,浏览器通过URL就能访问。
在GISBox服务发布面板里,选择瓦片输出目录,设置端口和发布名称。端口我一般用8090或者8088,避开常用的8080,减少和其他开发服务的冲突。
发布成功后会得到一个类似这样的地址:
code复制http://127.0.0.1:8090/你的工程名/tileset.json
先用浏览器直接访问这个URL,看能否正常返回JSON内容。这一步能最快判断服务是否真的起来了。
如果你想让局域网里的其他电脑也访问,把地址里的127.0.0.1换成电脑的局域网IP,比如 http://192.168.1.100:8090/...。同时需要在防火墙里放行这个端口,否则别人访问会超时。
跨域问题也值得注意:如果Cesium页面是从另一个域名加载tileset,服务端需要启用CORS。GISBox一般默认支持,但如果自己写服务,别忘了加 Access-Control-Allow-Origin 响应头,否则浏览器会直接拦截。
4.3 用Cesium和浏览器双端验证:怎么看瓦片是否生效
服务发布完,最后一步是验证。我习惯分两层验证:
第一层:浏览器直接访问tileset.json,确认服务可用、JSON结构完整。
第二层:用CesiumJS写一个最简单的页面加载这个tileset,确认模型真实渲染出来。
Cesium加载本地3DTiles的示例代码大概是这样的:
javascript复制const viewer = new Cesium.Viewer("cesiumContainer");
const tileset = await Cesium.Cesium3DTileset.fromUrl(
"http://127.0.0.1:8090/yourProject/tileset.json"
);
viewer.scene.primitives.add(tileset);
viewer.zoomTo(tileset);
如果你的Cesium页面使用了有地形的全球数据,默认视角会先飞到地球某处。加载完本地瓦片后,用 viewer.zoomTo(tileset) 把视角定位到模型附近。如果模型没有出现,可能不是数据问题,而是视角被地形挡住了,把地形关掉再试一次。
如果使用在线Cesium需要token,但加载本地3DTiles瓦片一般不需要token。如果你只是验证本地模型,可以创建一个最简Viewer,不走Cesium ion。
5. 踩坑实录:模型错位、纹理丢失与服务不显示的排查链路
5.1 模型错位、悬浮和埋地:从单位到坐标的排查
我在不少项目里遇到过同一个现象:模型加载出来了,但位置不在预期地点,要么悬浮在半空,要么半截插在地底下。
遇到这种情况,按下面这条链路一步步查:
- 确认GLB本身没问题。先用Blender或网页GLB查看器打开原始模型,看模型是否完整、坐标系是否正确。很多时候问题根本不是转换造成的,是原始模型就没放在原点。
- 检查单位。SketchUp如果以毫米为单位建模,导出GLB时如果单位设置错,模型尺寸会放大或缩小1000倍。最简单的方法:在Blender里导入GLB后看长宽高尺寸是否合理。
- 检查导入坐标。如果填的经纬度是模型中心点的,但模型本身建模时原点在角落,系统按原点坐标放置,就会出现偏移。解决办法是建模时把模型中心放在世界原点,或者用系统提供的坐标平移功能。
- 检查高度模式。如果地形有起伏,而模型用的是absolute,且高度值为0,模型可能埋在地下;如果用的是clampToGround,模型又可能被强行贴到地面导致悬浮感。根据实际情况反复切换测试。
- 检查旋转角度。建筑正北方向没有校准的话,模型会在场景里歪着。GISBox属性里设置旋转角,通常90度一档测试,很快能找到正确值。
我自己的经验是:把“模型中心放原点”“单位统一为米”“正北朝上”这三件套在建模阶段做好,能规避至少八成的位置问题。
5.2 纹理丢失或变白:贴图打包和格式问题
模型转成3DTiles后变白模,是新手最容易遇到、也最容易懵的问题。明明GISBox里预览是好的,发布出来就白了一片。
常见原因有四类:
第一,GLB没打包纹理。很多工具导出GLB时,默认不嵌入贴图,只保存贴图外部路径。如果之后模型文件被移动,或者GISBox读取不到原路径,纹理就会丢。解决办法是在导出工具里勾选“Embed Textures”或“打包纹理”,把贴图嵌进GLB文件。
第二,贴图格式不兼容。TGA、BMP这类格式,浏览器和GPU管线支持很差,最好在Blender里统一改成PNG或JPEG,重新导出。
第三,贴图路径或文件名包含中文、空格、特殊字符。这一点在Windows上尤其明显。把贴图文件名改成纯英文,路径也不要带中文目录。
第四,单张贴图过大。8192像素级别的贴图,在构建3DTiles时可能因为内存或格式问题被丢弃。用2048或4096更稳妥。
处理纹理问题的标准动作,我一般这样操作:在Blender里重新导入原始模型,检查材质节点的Base Color和贴图节点是不是都正确连接,然后把贴图重存为PNG并勾选打包,再导出GLB。经过这一步,纹理丢失问题基本就解决了。
5.3 服务不显示或404:从入口JSON到b3dm请求的排查
A:Cesium场景加载了,但连瓦片路径都请求不到。
先明确一个概念:3DTiles的入口是tileset.json,里面记录着各个瓦片的相对路径。浏览器请求模型时,会先访问tileset.json,再根据里面的路径去加载真正的瓦片数据(比如b3dm)。所以排查顺序应该是:
- 浏览器直接访问服务根地址下的tileset.json,看是否返回正常JSON。如果404,先看发布目录是不是选错了,是否定位到了tileset.json的父目录。
- 如果JSON能打开,看F12开发者工具的Network面板,搜索b3dm或相关瓦片请求。如果请求404,多半是瓦片目录里包含了中文或特殊字符,路径解析出问题。最简单的解决办法是:输出瓦片时用纯英文目录名,重来一遍。
- 如果跨域问题导致加载失败,F12控制台会提示CORS错误。本地调试可以用
http://localhost而非file://,或者临时用带跨域头的静态服务。 - 如果本机访问正常,但局域网内其他电脑打不开,先ping IP,再检查防火墙是否放行端口,最后确认服务监听的不是只绑定127.0.0.1。
还有一个小坑:浏览器缓存。服务发出去之后,改了瓦片数据再重新发布,浏览器可能还在用旧缓存。刷新时强制刷新或者开无痕窗口,能少走不少弯路。
6. 周边工具链:SKP转GLB、SHP转3DTiles、网页GLB查看
6.1 SKP转GLB的三条实操路线
第一条路线:Blender + SketchUp导入插件。这也是我最推荐的方式。SketchUp模型导入Blender后,可以检查单位、修正坐标、重绑材质,导出GLB时可控性最强。插件版本和Blender版本一定要匹配,装的时候留意。
第二条路线:DAE中转。SketchUp导出Collada(DAE)格式,Blender导入DAE,再导出GLB。优点是无需额外插件,缺点是材质贴图经常丢,需要在Blender里重连贴图节点。适合单个模型、纹理简单的情形。
第三条路线:在线转换工具。把SKP文件上传到在线转换平台,选目标格式GLB,下载结果。这个方法对文件大小限制严,模型复杂容易失败,而且模型数据上传到第三方服务有隐私风险,敏感项目不能用。
无论哪条路线,转换前都做这个动作:删除隐藏图层、炸开不必要的组件、确认单位。做完这些,转换成功率会大幅提升。
6.2 SHP转3DTiles:别指望直接一键出精细模型
很多做GIS的朋友问SHP是不是也能转3DTiles。答案是能,但要先理解一个现实:SHP是二维矢量数据,只有边界线、属性表,没有高度、没有纹理、没有屋顶结构。直接转出来的3DTiles是扁平的面片,不是立体建筑。
真正有价值的做法是“拉伸”:
- 在QGIS里加载SHP,设置正确的坐标参考系(比如WGS84或CGCS2000)。
- 给属性表增加height字段,填入每个地块的建筑高度。
- 用支持拉伸的工具把二维面拉升成三维体。
- 再把拉伸后的三维模型转成3DTiles。
最简单的踩坑提醒:SHP的坐标系和3DTiles目标坐标系不一致时,拉伸出来的模型会漂移到海里或者位置偏出几百米。转换前务必统一坐标系。如果只是为了在Cesium里临时看效果,也可以把SHP转成GeoJSON后用GeoJsonDataSource加载显示,但那不是真正的3DTiles,性能和处理规模会受限。
6.3 网页GLB查看的轻量方案
有时候你只是想快速看一眼GLB模型,不想打开GISBox和Cesium。这时有两个轻量方案。
方案一:model-viewer组件。这是Google开源的最简单方案,一行标签就能在网页里展示GLB。
html复制<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<model-viewer
src="model.glb"
camera-controls
auto-rotate
shadow-intensity="1"
style="width: 100%; height: 600px;">
</model-viewer>
方案二:three.js GLTFLoader。适合想自定义交互逻辑的场景,比如加按钮、换动画、点选模型。
html复制<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
</script>
需要注意:不管用哪种方案,尽量不要直接双击HTML文件通过file://打开,浏览器会拦截本地资源加载。在模型所在目录打开命令行执行 python -m http.server 8080,然后访问 http://localhost:8080,这是最省事的本地查看方式。
最后说点实在的。整套流程跑通之后,我最大的体会是:转换工具层出不穷,但最核心的还是规矩的建模和准确的坐标。先保证GLB模型单位统一、贴图打包、放在原点附近,转换和服务发布基本就是顺水推舟。刚开始别急着处理大场景,拿一个简单模型跑通全链路,再逐步往上加。这篇里分享的坑,多半是你早晚会遇到的,建议收藏。如果后面还有模型压平、属性查询、服务联动这类需求,咱们再继续聊。
