ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Three.js 加载 3D Tiles 倾斜摄影全攻略:坐标转换与性能调优

Three.js 加载 3D Tiles 倾斜摄影全攻略:坐标转换与性能调优 倾斜摄影项目在 Web 端落地绕不开 3D Tiles 这道门槛。我这两年接触的 Three.js 可视化项目十个里至少有八个最后都会冒出同一个需求把倾斜摄影模型放到场景里。这类数据带着真实纹理、分块 LOD、动辄几十 GB浏览器要流畅跑起来必须靠 3D Tiles 这种流式调度格式。但 Three.js 本身不认识 3D Tiles很多 WebGL 开发者一上来就被卡在最难的一步到底怎么把数据加载进去。不少人的第一反应是换 Cesium。Cesium 确实是 3D Tiles 的黄金搭档可如果项目主体是 Three.js只为了加载一种数据就整个切到 Cesium代价非常大交互、相机、场景管理、UI 全部要重写。我在实际项目里最后用的是 3d-tiles-renderer 这个开源库把 3D Tiles 直接接进了 Three.js 工作流跑通了加载、坐标、单体化、性能优化一整套链路。这篇就按我走过的路把从数据格式、工具选型、坐标转换到实际调优的经验完完整整梳理出来给准备趟这条河的同行一份能直接抄作业的参考。1. 为什么要在 Three.js 里读倾斜摄影3D Tiles 到底解决了什么问题1.1 倾斜摄影数据长得和普通模型不一样倾斜摄影模型不是美术建模做出来的是无人机或者载人飞机挂五镜头相机从正射、前视、后视、左视、右视多个角度对地拍照然后通过空三解算、密集匹配、纹理映射生成的三维 mesh。这种模型的特点是几何规模巨大一个几平方公里的采集范围就可能有上亿三角形纹理是真实照片一个瓦片动辄几百 MB数据组织方式天生是分块的而不是单一文件。以前这类数据大多用 OSGB 格式存在本地桌面端用专用软件看网页端基本没办法直接跑。原因很简单OSGB 没有 Web 标准容器浏览器不认而且几 GB 的模型一次性加载进内存机器直接卡死。1.2 3D Tiles 把“大模型”拆成了“按需加载的瓦片树”3D Tiles 是 OGC 标准专门用来流式传输大规模三维地理空间数据。它把整个模型组织成一棵瓦片树tile tree每个瓦片有自己的包围盒、几何误差geometric error和子节点。渲染引擎根据相机位置和视角决定当前该加载哪些瓦片、该细化到哪一层。离得远就用粗模飞近了再加载建筑细节这个过程叫做 LODLevel of Detail调度。这套机制对倾斜摄影特别关键因为倾斜数据单帧就能产生几千万三角形没有 LOD 和瓦片级剔除任何一种前端方案都扛不住。3D Tiles 的核心思想跟地图金字塔很像但维度更高调度的是三维模型而不是图片。1.3 Three.js 为什么不原生支持以及有哪些办法Three.js 的定位是通用 WebGL 渲染引擎它管的是场景图、材质、光照、相机但不管“谁来告诉你该加载哪个文件、什么时候卸载哪个瓦片”。3D Tiles 的加载和调度逻辑非常复杂涉及 tileset.json 解析、包围盒层级遍历、屏幕空间误差计算、并发请求控制、缓存淘汰这些都不是渲染引擎的职责。所以想把 3D Tiles 弄进 Three.js有两条路要么自己实现整套调度逻辑要么用现成的桥接库。自己实现意味着要把 Cesium 花十几年做的瓦片调度机制重新造一遍项目周期直接爆炸。我最后选的是 3d-tiles-renderer一个专为 Three.js 设计的 3D Tiles 加载器后面第 3 章会详细展开为什么是它而不是别的方案。2. 动手前必须搞清楚的数据骨架瓦片格式与坐标系2.1 b3dm、i3dm、pnts、cmpt 分别是什么3D Tiles 的瓦片文件有几种不同格式虽然对外统一叫 3D Tiles内部差异很大。我用实际项目里遇到的场景给大家捋一遍b3dmBatched 3D Model倾斜摄影转换出来最常见的格式一个 b3dm 文件里打包了一批模型。文件分头、Feature Table、Batch Table 和 GLB 四段。Batch Table 存的是一批模型的属性比如每栋建筑的名称、高度、楼层数倾斜摄影单体化能不能在渲染端做靠的就是这里的 batchId 和属性表。i3dmInstanced 3D Model同一个模型重复出现多次适合批量放置树、路灯、电线杆倾斜摄影项目里很少直接用但在数字孪生城市项目里很常见。pntsPoint Cloud点云数据格式适合激光雷达或者其他散点数据。cmptComposite老版本里用来把多个瓦片打包成一个复合文件现在新生产的数据里已经很少见了。对于加载倾斜摄影绝大多数时候你只需要关心 b3dm 就够了。数据转换工具导出之后tileset.json 的 content 字段会指向一堆 b3dm 文件。2.2 ECEF 坐标系3D Tiles 的“真实底座”3D Tiles 对外公布的坐标是基于 WGS84 椭球的地心地固坐标系也就是 ECEFEarth-Centered, Earth-FixedEPSG 编号 4978。这个坐标系的原点在地心X 轴指向本初子午线与赤道交点Y 轴指向东经 90 度方向Z 轴指向北极。问题来了ECEF 坐标的值极大。比如北京某个点的 X、Y、Z 都是几百万米量级。Three.js 底层 WebGL 的顶点坐标普遍用 32 位浮点数在百万米量级下浮点精度只够区分到几米甚至几十米直接拿 ECEF 当 Three.js 场景坐标模型稍微大一点就会出现顶点抖动、建筑扭曲、地面裂缝。2.3 倾斜摄影里“RTC 中心”和瓦片变换的作用为了解决大坐标精度问题b3dm 内部实际上使用了一种 RTCRelative-to-Center相对中心机制。转换工具会为每个瓦片计算一个局部中心点几何体都相对这个中心点存储渲染时再把这个中心点坐标加回去。Feature Table 里的 RTC_CENTER 字段就是干这个的。每个瓦片还可能有自己的 transform 矩阵它把瓦片本地坐标转换到父节点坐标层层叠加最后落到 ECEF 全局坐标。理解这一点很关键你加载一个 b3dm 瓦片看到的模型坐标不是“相对零点的普通模型”而是经过一系列节点变换后得到的全局地心坐标。这也是很多人把瓦片直接丢进 Three.js 场景之后发现相机飞到了一个奇怪的位置甚至场景里什么都看不到的根本原因。3. 工具选型为什么 3d-tiles-renderer 是目前最靠谱的 Three.js 方案3.1 主流方案横向对比我在调研阶段整理过几个常见方案的对比直接放表格方案框架上手成本Three.js 友好度维护状态适合场景Cesium独立渲染引擎高差无法与 Three.js 共享场景非常活跃纯 GIS 平台有大量标准 GIS 功能需求iTowns独立渲染引擎高差API 风格偏向 Cesium一般法国开发主要在欧洲项目使用3d-tiles-rendererThree.js低原生 Three.js 类活跃以 Three.js 为核心需要加载 3D Tiles自研加载器Three.js极高完全可控自己维护有充足人力且需要深度定制调度逻辑Cesium 功能完整但它的渲染管线是独立的和 Three.js 完全两套想混用就得离屏渲染或者贴纹理性能损失很大。iTowns 同样主打 GIS 能力学习成本不低社区在国内也小众。自研一时爽后续调度、缓存、异常处理全都要自己扛一般项目不建议。3.2 3d-tiles-renderer 的核心工作链路这个库把 3D Tiles 的调度和渲染封装成了 Three.js 生态里的组件。它做的事情可以拆成四步解析 tileset.json构建瓦片树。每一帧根据相机位置计算每个瓦片的屏幕空间误差SSE决定要加载哪些瓦片、卸载哪些瓦片。对需要加载的瓦片发起网络请求解析 b3dm、i3dm、pnts把内部的 GLB 转成 Three.js 的 Object3D。把加载好的模型挂到一个统一的 group 下交给 Three.js 渲染。使用方只需要做三件事创建一个 TilesRenderer 实例、传入 tileset.json 的地址、每帧调用它的 update 方法。加载进来的瓦片树会自动处理 LOD 和显存释放。3.3 官方能力之外项目里还需要考虑什么官方库基础能力很扎实支持 Cesium Ion token 接入也支持 Draco 压缩和 KTX2 纹理跟 Three.js 的 GLTFLoader 生态可以打通。但有一点要注意它定位是“加载器”而不是“GIS 平台”。矢量数据叠加、坐标投影、图层管理这些能力需要自己在 Three.js 侧实现这也正是本文后面几章要讲的。选型结论只要项目是以 Three.js 为主体需要展示倾斜摄影 3D Tiles 数据3d-tiles-renderer 是当前维护最活跃、社区采用最广的解决方案。4. 从零跑通加载流程核心代码与常用调试手段4.1 环境准备Vite Three.js 3d-tiles-renderer我习惯用 Vite 搭一个最小项目几行命令就能搞定npm create vitelatest tiles-demo -- --template vanilla cd tiles-demo npm install three 3d-tiles-renderer装完之后把src/main.js清空从零写加载逻辑。如果你的项目是 Vue 或 React 也没关系核心逻辑是通用的网络请求和事件绑定不会因为框架改变。4.2 最小可运行代码加载 tileset.json下面这段代码就是我项目里加载 3D Tiles 的最小骨架逻辑上比官方 demo 更精简直接跑就能看到模型import * as THREE from three; import { OrbitControls } from three/examples/jsm/controls/OrbitControls.js; import { TilesRenderer } from 3d-tiles-renderer; const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 200000); camera.position.set(0, 500, 500); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.update(); const tiles new TilesRenderer(http://localhost:8080/tileset.json); tiles.setCamera(camera); tiles.setResolutionFromRenderer(camera, renderer); scene.add(tiles.group); function animate() { requestAnimationFrame(animate); controls.update(); tiles.setCamera(camera); tiles.setResolutionFromRenderer(camera, renderer); tiles.update(); renderer.render(scene, camera); } animate();这段代码有一个隐藏问题直接加载之后模型可能不在相机视野内因为 3D Tiles 的坐标是 ECEF 全局坐标模型可能在地球另一侧。这就要用到第 5 章的坐标变换。4.3 加载阶段的常见故障与排查顺序我踩过的加载失败问题九成出在下面几个点CORS 跨域angular 的 DevServer 或者本地静态服务器如果没开跨域头浏览器会直接拦截请求。本地调试可以给 Vite 或 nginx 加跨域配置生产环境最好把数据放到对象存储或者 CDN开好跨域。相对路径解析3D Tiles 的 tileset.json 内部会引用同目录下的 b3dm 文件如果通过构建工具打包瓦片文件的 URL 很容易被处理成 hash 路径导致 404。正确做法是把数据放在 public 目录或者独立静态服务器上用完整 URL 传给 TilesRenderer。版本不匹配3d-tiles-renderer 不同大版本之间 API 有变化比如某些版本要求传TilesRenderer时必须同时传一个 fetch 函数或者对 glTF 处理逻辑有区别。装好依赖后打开 README 确认一下当前版本的构造函数签名。4.4 监听瓦片加载事件做状态展示数据量大时用户需要知道“加载到哪一步了”这时可以用瓦片事件tiles.addEventListener(load-tile-set, () { console.log(tileset 元数据加载完成); }); tiles.addEventListener(load-model, (event) { console.log(一个瓦片模型加载完成, event.tile.content.uri); }); tiles.addEventListener(unload-model, (event) { console.log(一个瓦片模型卸载, event.tile.content.uri); });这套事件在做加载进度条、监控瓦片调度情况时很有用。生产环境我一般会在load-model里累加已加载瓦片数在unload-model里扣减从而获得当前驻留内存的瓦片总量。5. 坐标系偏移这个大坑把 ECEF 搬到本地原点5.1 现象直接加载后相机找不到模型第一次跑通代码的人打开页面大概率会看到一片空白控制台也没有报错。这不是没加载成功而是模型渲染在了 ECEF 坐标下的真实地球位置比如你要展示的是广州某片区域那模型就在广州对应的地心坐标系位置而相机在 (0, 500, 500) 这个“宇宙空间”里两者相隔几十万公里。理解这个现象后我就把所有精力放在了坐标变换上。核心目标把 ECEF 下的瓦片模型挪到以某个经纬高为原点、以米为单位的局部坐标系里这样 Three.js 的相机和场景管理器才能正常工作。5.2 原理ECEF 与 ENU 坐标系的转换在三维 GIS 里ENU 坐标系East-North-Up东-北-天是很常用的局部坐标系原点在地表某个点X 轴指向东Y 轴指向北Z 轴指向天。要从 ECEF 转到 ENU需要做两步先平移到原点再旋转到 ENU 的轴向。假设你指定的场景原点经纬高是(lon, lat, height)那么用 WGS84 椭球参数把原点经纬高换算成 ECEF 坐标originEcef。计算一个旋转矩阵R把 ECEF 的 XYZ 轴旋转到 ENU 的东、北、天方向。最终变换矩阵M R * T(-originEcef)。5.3 完整封装代码经纬高定向的 ENU 变换矩阵下面这段代码是项目里一直在用的工具函数输入原点经纬高返回一个 Three.js Matrix4import { Matrix4, Vector3, MathUtils } from three; const WGS84_A 6378137.0; const WGS84_F 1 / 298.257223563; const WGS84_E2 WGS84_F * (2 - WGS84_F); function geodeticToEcef(lonDeg, latDeg, height) { const lon MathUtils.degToRad(lonDeg); const lat MathUtils.degToRad(latDeg); const cosLat Math.cos(lat); const sinLat Math.sin(lat); const N WGS84_A / Math.sqrt(1 - WGS84_E2 * sinLat * sinLat); const x (N height) * cosLat * Math.cos(lon); const y (N height) * cosLat * Math.sin(lon); const z (N * (1 - WGS84_E2) height) * sinLat; return new Vector3(x, y, z); } export function ecefToEnuMatrix(lonDeg, latDeg, height) { const originEcef geodeticToEcef(lonDeg, latDeg, height); const lon MathUtils.degToRad(lonDeg); const lat MathUtils.degToRad(latDeg); const sinLon Math.sin(lon); const cosLon Math.cos(lon); const sinLat Math.sin(lat); const cosLat Math.cos(lat); // ECEF - ENU 旋转矩阵行依次为东、北、天方向在 ECEF 下的分量 const rotation new Matrix4().set( -sinLon, cosLon, 0, 0, -sinLat * cosLon, -sinLat * sinLon, cosLat, 0, cosLat * cosLon, cosLat * sinLon, sinLat, 0, 0, 0, 0, 1 ); const translation new Matrix4().makeTranslation( -originEcef.x, -originEcef.y, -originEcef.z ); return rotation.multiply(translation); }然后这样挂到场景里const tilesRoot new THREE.Object3D(); tilesRoot.matrixAutoUpdate false; tilesRoot.matrix.copy(ecefToEnuMatrix(113.3, 23.1, 0)); scene.add(tilesRoot); tilesRoot.add(tiles.group);这样一来tiles.group 内部的 ECEF 坐标经过 tilesRoot 的矩阵变换后就被映射到了以(113.3, 23.1, 0)为原点的 ENU 局部坐标系单位是米。相机放在(0, 500, 500)附近就能看到模型了。5.4 为什么局部坐标比“放大场景”更可靠有人可能觉得直接用相机飞到 ECEF 坐标点不就行了我一开始也是这么干的把相机设在模型旁边看起来没问题但当你把视角拉远再定位到另一个城市或者场景里同时叠加其他地理数据时浮点精度问题立刻爆发出来地面抖动、模型互相穿模。局部 ENU 坐标把数值控制在几千米到几十千米量级32 位浮点的精度损失完全可以接受稳定得多。另外要注意ENU 坐标适合城市级、园区级的范围。如果数据覆盖整个省或者全国距离原点几百公里外的地方ENU 坐标的横轴方向偏移会很严重这时就要考虑多原点分段加载或者直接用 Cesium 这类专门处理全球坐标的引擎。6. 性能调优与渲染细节把几十 GB 的倾斜数据跑得流畅6.1 LOD 调度参数控制加载粒度和内存驻留3d-tiles-renderer 提供了几个关键参数直接影响加载策略tiles.errorTarget 8; tiles.lruCacheSize 600; tiles.autoDisposeRendererResources true;errorTarget是屏幕空间误差阈值越小加载的瓦片越精细模型越清晰但请求和渲染压力越大。我一般从 8 起步根据机器性能上下调。lruCacheSize是 LRU 缓存上限超过这个数最久没用的瓦片会被卸载。内存吃紧时调小需要反复快速飞过同一区域时调大。autoDisposeRendererResources让瓦片卸载时自动释放 Geometry 和 Texture 资源这个一定要开否则长时间运行显存会被撑爆。6.2 纹理和压缩KTX2 与 Draco 是必选项倾斜摄影模型的纹理占数据量的绝大部分。原始 jpg/png 纹理解压后占用显存很大加载也慢。数据生产端最好像 Cesium 生态一样把纹理转成 KTX2Basis Universal 超压缩格式几何数据用 Draco 压缩。这样网络传输和显存占用都能大幅下降。需要注意3D Tiles 1.0 时代 b3dm 内嵌的是 GLB纹理压缩扩展支持程度和 3D Tiles 1.1 有差异。1.1 标准直接内嵌 glTF对 Draco、KTX2 这些扩展的支持更原生。如果拿到的数据是旧版转换工具导出的建议在数据端升级转换方式或者确认 Three.js/3d-tiles-renderer 能识别对应扩展。6.3 渲染侧参数相机、绘制距离与材质直接在 Three.js 里渲染大规模倾斜模型建议做几件事开启THREE.WebGLRenderer({ logarithmicDepthBuffer: true })可以缓解近距离和远距离同时出现时的深度冲突。far 值不要设置得太大能覆盖模型包围盒半径的两三倍就够Far 太大会导致 Depth 精度下降。倾斜摄影模型本身是带真实纹理的一般用默认的 MeshBasicMaterial 或 MeshLambertMaterial 就够不要上高光材质免得光照计算和纹理采样带来的开销白费。6.4 实际项目里的数据表现我处理过一个约 8 平方公里的倾斜摄影区块原始 OSGB 数据大概 60 GB用 ContextCapture 转成 3D Tiles 后约 18 GB纹理格式是 jpg。在普通 i7 16G 内存 GTX 1660 的机器上3d-tiles-renderer 默认参数下首屏加载大约 8 到 12 秒稳定飞行后 FPS 在 45 到 60 之间。如果换成 KTX2 纹理首屏时间能降到 4 秒左右。这个数据只能当参考你的网络环境、机器配置和数据切分粒度都会影响结果。7. 单体化怎么做射线拾取、批次 ID 与属性联动7.1 什么叫单体化倾斜摄影为什么要单体化倾斜摄影生成的模型是一整张连续的网格你点击一栋楼其实点到了一个大 mesh 的某个三角形无法知道这栋楼的名称、楼层、权属信息也没法单独高亮某一栋楼。这个“把连续模型拆分成可交互的单体对象”的过程就是 GIS 圈常说的单体化。在 Cesium 里做单体化常用方案是数据生产时给每栋建筑打上矢量面或者 batchId渲染时用 Pick 或 style 按属性高亮。Three.js 里没有 Cesium 那种内置的单体化体系但实现路径并不复杂关键在于数据端先准备好“每一个可交互对象都有唯一 ID”。7.2 数据侧单体化batchId 与属性表生产端如果能把建筑轮廓的矢量面shp 或 GeoJSON和倾斜模型叠加给每栋建筑生成一个 batchId并写入 Batch Table 属性表那么渲染端就能拿到“顶点 - 建筑 ID - 属性”的完整链路。3d-tiles-renderer 解析 b3dm 后会在瓦片对象上挂 Batch Table 实例你可以通过命中三角形所在对象向上找到瓦片再按 batchId 查属性。不过说实话这套链路对数据生产要求很高不是所有倾斜数据转换流程都会保留 batchId。我接触的很多项目数据源就是纯倾斜模型没有建筑矢量面这种就得用渲染侧方案。7.3 渲染侧“假单体化”覆盖体块是最实用的方案在没有 batchId 的情况下最稳的“假单体化”方案是用矢量数据生成轻量体块。简单说准备一份建筑轮廓面数据每个面按建筑高度拉伸成体块模型Three.js 里用 ExtrudeGeometry把这些体块叠加到倾斜模型上透明度设为 0只参与射线拾取不参与显示。点击体块时就能稳定拿到这栋楼的 ID再去后台查属性同时把对应体块高亮或者变色。这个方案的好处是不依赖倾斜模型的内部结构单体和交互完全解耦我在多个数字孪生项目里都用它维护成本极低。射线拾取的核心代码const raycaster new THREE.Raycaster(); const pointer new THREE.Vector2(); const buildingMeshes []; // 存放所有单体覆盖体块的数组 renderer.domElement.addEventListener(click, (event) { pointer.x (event.clientX / innerWidth) * 2 - 1; pointer.y -(event.clientY / innerHeight) * 2 1; raycaster.setFromCamera(pointer, camera); const hits raycaster.intersectObjects(buildingMeshes, false); if (hits.length 0) { const buildingId hits[0].object.userData.buildingId; highlightBuilding(buildingId); } });7.4 进阶方案离屏渲染 ID 通道如果覆盖体块和模型贴合度不好或者想直接在倾斜模型表面做拾取可以考虑 ID 通道方案每栋建筑在数据端赋一个唯一的颜色 ID把编号编码成 RGB渲染时把整个场景的材质临时替换成纯色 ID 材质渲染到 WebGLRenderTarget然后读取鼠标位置像素解码出建筑 ID。这个方案性能开销不大但对数据端要求高每一栋建筑的 ID 必须稳定写入而且做高亮效果时需要另外处理网络请求或动态样式。项目里如果单体数量大、需要点击到建筑真实表面这条路比覆盖体块更精确但开发成本也上一个台阶。8. 数据转换管线shp / fbx / 倾斜摄影成果怎么变 3D Tiles8.1 倾斜摄影成果转 3D Tiles生产工具怎么选如果你手里是无人机采集后的空三成果常见的输出是 ContextCaptureSmart3D的 OSGB 或 S3C。要变成 3D Tiles生产端有两条路线如果还在 ContextCapture 流程里直接在建模导出时选择 3D Tiles 格式这是最省事的导出的 tileset.json b3dm 就是标准瓦片结构。如果已经有现成的 OSGB 数据可以用 CesiumLab 这类桌面工具做格式转换。CesiumLab 对国内用户比较友好支持 osgb 转 3D Tiles也支持在转换时配置 LOD 层数和纹理压缩。转换时注意几个参数LOD 层数太少会导致近看模糊太多会生成大量小瓦片请求数暴涨纹理建议导出 jpg 或 webp避免一张瓦片里塞一张 8K 的 PNG。8.2 shp 面数据转 3D Tiles建筑拉伸与属性写入shp 数据通常是二维多边形没有高度不能直接变成真正意义上的倾斜摄影模型。它的用途更多是生成白模或单体化覆盖体块。如果确实需要把 shp 转成 3D Tiles 里的建筑体块标准流程是用 shpjs 或相关库解析 shp拿到建筑轮廓和属性字段楼层数、高度。在 Three.js 或其他建模工具里根据底面轮廓和高度拉伸成白模。导出成 GLB再用官方工具转成 b3dm。shp 属性里如果带楼层数白模高度按楼层数乘以层高比如 3 米计算会比纯手动设定准得多。不带属性的 shp 只能按统一高度生成效果会比较假。8.3 fbx 转 3D Tiles建模资产如何进入瓦片树很多项目有现成的 fbx 建筑模型想集成到 3D Tiles 场景里。fbx 不是 Web 标准格式直接转 b3dm 不现实需要先转成 glTF/GLB。我用 Blender 做中转比较多导入 fbx检查模型轴和单位3D Tiles 必须是 Z-up、单位米fbx 经常是 Y-up导出 GLB。出 GLB 之后用官方 3d-tiles-tools 的命令转 b3dmnpx 3d-tiles-tools glbToB3dm -i model.glb -o model.b3dm转换完成后还需要把 b3dm 组织进 tileset.json 的瓦片树结构。一个最简单的 tileset.json 是这样{ asset: { version: 1.0 }, geometricError: 500, root: { boundingVolume: { box: [0, 0, 0, 100, 0, 0, 0, 100, 0, 0, 0, 100] }, geometricError: 100, refine: ADD, content: { uri: model.b3dm } } }box 数组前三个是中心点坐标后九个数是三个半轴向量。如果你的模型不在原点附近需要按实际位置换算成 ECEF 或相对坐标这里也是新手最容易出错的地方。生产环境建议直接用 CesiumLab 或自研脚本切分多层级瓦片而不是手写这套 JSON因为数据量一大手工维护瓦片树根本不现实。8.4 转换后验证用第 4 章的加载器直接检验数据转换完我会先用一个最小页面快速验证把 tileset.json 放到静态服务器用第 4 章的代码加载重点是看三件事控制台有没有 404 和解析报错。模型是否出现在正确的地理位置。相机飞行过程中 LOD 变化是否顺畅有没有大量瓦片反复加载导致闪烁。验证通过之后再接入业务功能。这一步能提前发现大部分数据问题避免后面把问题归错到渲染代码上。我自己的体会是Three.js 加载 3D Tiles 这件事技术难点并不是加载本身而是坐标系、数据生产质量和 LOD 调优。每一步都有现成方案但把它们串成一条可靠的生产链路需要多次试错。如果你只是要快速验证一套数据建议先把第 4 章的代码跑通再逐步叠加坐标变换和单体化不要一开始就追求大而全的架构。这个链路跑顺后倾斜摄影在 Three.js 里的表现完全不输给传统 GIS 引擎。
返回列表