three.js使用3DTilesRendererJS加载3d tiles数据

原生的 three.js 目前不支持 3d tiles 数据的加载,不过开源社区已经给出了一些解决方案,其中最活跃的要属 3DTilesRendererJS。它为 three.js 提供了加载和调度 3d tiles 数据的基本能力,虽说和 Cesium.js 对 3d tiles 的支持相比还有很大的差距,但也比没有的好。毕竟 3d tiles 数据的加载和调度还是比较复杂的,要自己写也没那么容易,这一点在以前研究 Cesium.js 相关源码的时候就深有体会。

3DTilesRendererJS 最核心的类是 TilesRenderer,用来渲染和调度一份 3d tiles 数据,相当于Cesium.js 里的 Cesium3DTileset。使用起来非常简单,构造的时候传入 JSON 文件的 url 即可。

javascript 复制代码
const tileset = new TilesRenderer("http://localhost:8080/XXX/tileset.json");

构造 TilesRenderer 实例

接着,需要把 TilesRenderer 实例和 three.js 的 camera 以及 renderer 关联起来,根据 three.js 的相机和渲染器参数来设置切片显示的分辨率。

javascript 复制代码
tileset.setCamera(camera);
tileset.setResolutionFromRenderer(camera, renderer);

关联 three.js 的相机和渲染器参数

很多 3d tiles 数据都是做了顶点压缩或纹理压缩的,比如顶点的 DRACO 压缩、KTX2 和 DDS 等纹理压缩格式,对于这类数据需要在GLTF解析器(GLTFLoader)中添加解压缩的能力。下面以解压缩 DRACO 为例用代码加以说明。

javascript 复制代码
// 配置GLTF数据的解析器
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('./jsm/libs/draco/gltf/');

const loader = new GLTFLoader(tileset.manager);
loader.setDRACOLoader(dracoLoader);
tileset.manager.addHandler(/\.gltf$/, loader);

为GLTF解析器配置解压缩 DRACO 的能力

3d tiles数据以往基本上都是在 WGS84椭球上呈现的。现在要在 three.js 的局部场景下展示,则需要把它放在局部场景相机的视野范围内,并保证数据的上方向正确。这里封装了一个 adjustTilesPositionAndDirection 方法,将数据放置在局部场景的中心,并将Y轴正方向(Y+)作为数据的上方向。这样数据在 three.js 三维场景中就能正常摆放了。

javascript 复制代码
function rotationBetweenDirections(dir1, dir2) {
  const rotation = new THREE.Quaternion();
  const a = new THREE.Vector3().crossVectors(dir1, dir2);
  rotation.x = a.x;
  rotation.y = a.y;
  rotation.z = a.z;
  rotation.w = 1 + dir1.clone().dot(dir2);
  rotation.normalize();

  return rotation;
}

function adjustTilesPositionAndDirection(tiles) {
  if (!tiles) {
	return;
  }

  const sphere = new THREE.Sphere();
  tiles.getBoundingSphere(sphere);

  const position = sphere.center.clone();
  const distanceToEllipsoidCenter = position.length();

  const surfaceDirection = position.normalize();
  const up = new THREE.Vector3(0, 1, 0);
  const rotationToNorthPole = rotationBetweenDirections(surfaceDirection, up);

  tiles.group.quaternion.x = rotationToNorthPole.x;
  tiles.group.quaternion.y = rotationToNorthPole.y;
  tiles.group.quaternion.z = rotationToNorthPole.z;
  tiles.group.quaternion.w = rotationToNorthPole.w;

  tiles.group.position.y = - distanceToEllipsoidCenter;
}

调整数据在 three.js 场景中的位置和上方向

最后在每一帧渲染时都去执行一次 TilesRenderer 的更新。至此,一份3d tiles数据的基本加载就完成了。

javascript 复制代码
function renderLoop() {
  // 更新 TilesRenderer 之前需要更新 three.js 的相机参数
  camera.updateMatrixWorld();
  
  tileset.update(); // 更新 TilesRenderer

  renderer.render(scene, camera);
}

每一帧都更新 TilesRenderer

以上是使用 3DTilesRendererJS 的基本流程。还可以做一些辅助工作。

可以根据需要为数据注册一些插件,可选的插件在官方文档中查看。下面的示例以调试插件为例,简要说明插件的注册和使用方式。

javascript 复制代码
// 注册调试插件
tileset.registerPlugin(new DebugTilesPlugin());

// ...

// 获取调试插件,并显示包围盒的线框
tileset.getPluginByName('DEBUG_TILES_PLUGIN').displayBoxBounds = true;

插件的注册和使用

当场景中加载了多份 3d tiles 数据时,最好共享内存和下载队列,以减少性能开销。

javascript 复制代码
// 设置图层1的缓存大小
tileset.lruCache.minSize = 900;
tileset.lruCache.maxSize = 1300;

// 图层2和图层1共享内存和下载队列以减少性能开销
tileset2.lruCache = tileset.lruCache;
tileset2.downloadQueue = tileset.downloadQueue;
tileset2.parseQueue = tileset.parseQueue;

共享内存和下载队列

github上只提供了源码,没有提供打包好的库。有需要编译库的同学可以在这里下载。


个人觉得和 Cesium.js 相比,3DTilesRendererJS 加载和调度 3d tiles 的能力还是挺弱的。小场景、和数据之间交互(操作、修改)要求不那么高的情况下可以尝试。如果是做大场景下的 GIS 应用,也许 Cesium.js 和 Three.js 做深度融合(绘制在同一个 canvas 上,深度值做统一),GIS 功能交给 Cesium.js,Three.js 做一些效果上的补充,可能会是更好的方案。

相关推荐
糕冷小美n6 小时前
elementuivue2表格不覆盖整个表格添加固定属性
前端·javascript·elementui
小哥不太逍遥7 小时前
Technical Report 2024
java·服务器·前端
沐墨染7 小时前
黑词分析与可疑对话挖掘组件的设计与实现
前端·elementui·数据挖掘·数据分析·vue·visual studio code
anOnion7 小时前
构建无障碍组件之Disclosure Pattern
前端·html·交互设计
threerocks7 小时前
前端将死,Agent 永生
前端·人工智能·ai编程
问道飞鱼8 小时前
【前端知识】Vite用法从入门到实战
前端·vite·项目构建
爱上妖精的尾巴8 小时前
8-10 WPS JSA 正则表达式:贪婪匹配
服务器·前端·javascript·正则表达式·wps·jsa
Aliex_git9 小时前
浏览器 API 兼容性解决方案
前端·笔记·学习
独泪了无痕9 小时前
useStorage:本地数据持久化利器
前端·vue.js
程序员林北北9 小时前
【前端进阶之旅】JavaScript 一些常用的简写技巧
开发语言·前端·javascript