从 Three.js 到 JSAPI Three 开发入门
Three.js 到 JSAPI Three
目录
对比介绍
近年来,Three.js 越来越火,它让前端开发者能轻松打造各种炫酷的三维效果,从模型展示到特效动画应有尽有。不过作为一个通用三维引擎,Three.js 在地图场景下并没有开箱即用的地理封装——比如坐标投影、地图可视化组件等都需要开发者自行实现。
所以业界出现了像 Cesium.js 这样的三维地球引擎,专门面向孪生与地图可视化领域。但有没有一种方案让我既能享受 Three.js 的庞大生态与丰富的渲染能力,又能直接对接地图业务与数据服务呢?
这里就要介绍一下我最近接触到的 JSAPI Three。它在 Three.js 的基础上,提供了完整的地图三维渲染能力,让我们既能享受 Three.js 的通用能力和丰富社区,又能方便地构建数字孪生、城市可视化等真实地理场景。
下面我会介绍一下 JSAPI Three 以及它和通用三维引擎的差异。
一、环境初始化
1.1 安装
安装就不多说了,直接通过npm安装即可,里面有一些初始化配置需要设置,但在官方文档中也有说明
官方文档:lbsyun.baidu.com/jsapithree/…
1.2 初始化对比
Three.js 初始化
// 需要手动创建和配置各种对象
const container = document.getElementById('container');
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(5, 2, 8);
function animate() {
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
上面是使用three.js初始化添加了必要的Scene、Renderer、Camera等对象,什么都没有的空场景就需要这么多代码
JSAPI Three 初始化
// 一步到位,自动创建场景、相机、渲染器,engine对象统一管理
const engine = new mapvthree.Engine(document.getElementById('container'), {
map: {
center: [105.943271, 29.348709], // 地图中心位置
heading: 80, // 方位角 0-360
pitch: 80, // 俯仰角 0-90
}
});
在创建一个基础场景上,JSAPI Three 相比于 Three.js 会简单不少,提供 Engine 作为统一管理的引擎对象(包含场景、相机、渲染器、时间、地图状态)。
对比总结:使用 JSAPI Three,你不需要手动创建和管理 Scene、Camera、Renderer 等对象,通过Engine对象内部管理让用户可以更快上手搭建一个简单场景
1.3 光照和天空系统
JSAPI Three 预设了多种天空大气效果,能够提供比较不错的环境效果。
静态天空
const sky = engine.add(new mapvthree.StaticSky());
sky.time = 14 * 3600; // 设置时间为下午2点(单位:秒)
效果展示:
动态天空
const sky = engine.add(new mapvthree.DynamicSky());
sky.time = 14 * 3600; // 设置时间为下午2点(单位:秒)
效果展示:
1.4 添加物体
添加 Three.js 基础物体
JSAPI Three基于three.js实现,这意味着可以添加three.js内置的对象,使用它预设好的材质,几乎没有什么迁移成本
创建一个基础的立方体:
// 创建几何体和材质
const boxGeometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshStandardMaterial({
color: 'green',
});
const box = new THREE.Mesh(boxGeometry, material);
添加到 Three.js:
scene.add(box);
添加到 JSAPI Three:
engine.add(box);
通过上面的添加物体的对比可以直观感受到一个three.js效果想要放到JSAPI three引擎上是多么容易,这意味着three.js的丰富的社区(shader效果、几何效果、后处理效果)可以被JSAPI THREE共享
上图左边是我以前实现的一个噪声效果,我将其迁移到JSAPI Three中做的工作就只是复制粘贴,然后添加到engine中而已
添加 JSAPI Three 可视化组件
除了Three.js原生的API,JSAPI Three 提供了丰富的地图可视化组件
// 创建热力图
const heatmap = engine.add(
new Heatmap3D({
opacity: 0.8,
radius: 100, // 热力绘制半径
maxValue: 10, // 最大热力值
heightRatio: 200, // 高度系数
})
);
// 设置数据源
const dataSource = GeoJSONDataSource.fromGeoJSON(randomPoints(center, 0.02, 1500));
dataSource.defineAttribute('count');
heatmap.dataSource = dataSource;
这些API能够接入现有的业务数据,如GeoJSON,要自定义一个geometry,根据数据构建一个复杂的几何体还是有一定难度的,而JSAPI Three的自定义组件将顶点处理、geometry和material创建管理都放在内部,就算你没有用过three.js,使用JSAPI Three依然能够很快上手
二、事件交互
在三维场景中,事件交互让静态场景变得生动。在 Three.js 中需要手动实现射线检测,而 JSAPI Three 提供了开箱即用的事件系统。
2.1 Three.js 事件处理
在 Three.js 中,要实现"点击物体"需要手动编写射线检测:
// 创建射线投射器
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 监听点击事件
renderer.domElement.addEventListener('click', (event) => {
// 计算鼠标在归一化设备坐标中的位置
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 计算相交的物体
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
console.log('点击了:', clickedObject);
clickedObject.material.color.set(0xff0000);
}
});
问题:代码量大,需要手动管理射线检测,性能优化困难
2.2 JSAPI Three 事件系统
JSAPI Three 提供了简洁的事件系统 ,自动处理射线检测和坐标转换:
// 绑定点击事件(简洁!)
box.addEventListener('click', (e) => {
console.log('点击了立方体!');
console.log('地理坐标:', e.point); // 自动提供经纬度
console.log('投影坐标:', e.position); // 自动提供墨卡托坐标
box.material.color.set(0xff0000);
});
// 地图点击事件
engine.map.addEventListener('click', (e) => {
console.log('点击位置(经纬度):', e.point);
console.log('点击位置(投影坐标):', e.position);
console.log('点击的对象:', e.object); // 如果点击了物体则返回,否则null
});
2.3 实际应用:点击地图放置标记
一个常见的需求:点击地图,在点击位置放置标记
engine.map.addEventListener('click', (e) => {
// 创建标记
const marker = new THREE.Mesh(
new THREE.ConeGeometry(20, 60, 8),
new THREE.MeshStandardMaterial({color: 0xff0000})
);
// 直接使用事件中的投影坐标
marker.position.set(e.position[0], e.position[1], e.position[2]);
engine.add(marker);
console.log('标记已放置在:', e.point); // 经纬度
// 为标记添加点击删除功能
marker.addEventListener('click', (evt) => {
engine.remove(marker);
marker.geometry.dispose();
marker.material.dispose();
evt.stopPropagation(); // 阻止事件冒泡到地图
});
});
通过这个简单的例子可以看出,JSAPI Three 的事件系统不仅代码简洁,还自动提供了地理坐标信息,非常适合地图应用的开发。
2.4 核心差异
| 特性 | Three.js | JSAPI Three |
|---|---|---|
| 事件绑定 | 手动实现 | addEventListener() |
| 射线检测 | 手动 Raycaster | 自动处理 |
三、坐标系统对比
3.1 Three.js 标准坐标系
Three.js 使用的是右手笛卡尔坐标系:
- X轴:水平方向,指向右侧
- Y轴:垂直方向,指向上方
- Z轴:深度方向,指向观察者
坐标单位:
- 单位:无量纲的抽象单位
- 自定义比例:开发者可以自由定义单位含义(米、厘米等)
- 世界坐标:物体在场景中的绝对位置
- 局部坐标:物体相对于其父对象的位置
3.2 JSAPI Three 地理坐标系
在地图场景中,我们必须面对真实世界的地理坐标系。这意味着我们在代码中操作的每一个模型、点、线、面,实际上都对应着地球上的真实位置。JSAPI Three在内部 将地理坐标系映射到 Three.js 3D 空间
地理坐标 (经纬度)
↓ [投影]
投影坐标 (米)
↓ [相对于地图中心]
Three.js 世界坐标
坐标系统层次
层次1:地理坐标系 (Geographic Coordinate System)
- 单位:度 (°)
- 表示:经度 (longitude)、纬度 (latitude)、高程 (height/elevation)
- 范围:
- 经度:-180° ~ 180°
- 纬度:-90° ~ 90°
- 高程:米
这是我们最熟悉的坐标形式,比如:
const beijing = [116.404, 39.915, 50]; // 东经116.404°, 北纬39.915°, 高程50m
在地理信息系统 (GIS) 中,这个层级代表地球表面的真实位置,但它无法直接用于三维渲染,因为经纬度是球面坐标,Three.js 需要的是平面直角坐标。
层次2:投影坐标系 (Projected Coordinate System)
- 单位:米 (m)
- 作用:将球面坐标投影到平面
- 投影方式:如墨卡托投影
这种坐标系让地球表面看起来像一张展开的地图。
每一米的距离在计算上都有实际意义,适合进行平面几何和空间计算。
层次3:Three.js 世界坐标系
- 单位:米 (m)
- 坐标轴对应:
- X轴 = 投影东向 (Easting)
- Y轴 = 投影北向 (Northing)
- Z轴 = 高程 (Elevation)
JSAPI Three 会在底层自动进行中心平移,使得原点位于当前视图中心,从而避免大范围地图下的数值精度损失,因为投影的坐标数值过大,比如Web墨卡托投影坐标范围为:
X: -20037508.34 m ~ +20037508.34 m
Y: -20037508.34 m ~ +20037508.34 m
这个如果直接将其作为顶点传入gpu会损失精度,在渲染上表现出来就是闪烁、抖动等问题,所以一般会做中心偏移,这个偏移可以在CPU或GPU Shader上处理,JSAPI Three中是在CPU上进行的中心偏移运算,另外还有Shader上的中心偏移方案,如Cesium的就是这种方案解决损失问题
坐标转换示例
地理坐标这是平时我们经常能够接触到的,JSAPI Three自定义组件所需要的geojson中的坐标就是地理坐标,以一个点为例:
{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [110, 39, 0], // 地理坐标
}
}
]
}
有了这份数据你就可以传入点类型的组件,在坐标位置为110,39, 0的位置打点
但有时候也需要用到投影坐标,比如前面我们提到过怎么在three.js和JSAPI Three中添加一个box mesh, 这很简单,但是我们并没有设置这个mesh的中心位置,所以它的position默认在0,0,0位置,为世界中心,如果你想要将其放到你指定的一个位置,你需要通过position设置坐标
mesh.position.set(x, y, z);
这个时候能直接设置地理坐标,如上面的110,39, 0吗?
mesh.position.set(110, 39, 0); // 不可以,不能使用地理坐标
这段代码模型的位置完全不对,因为我们用到的是投影坐标
// 地理坐标转换为投影坐标
const position = engine.map.projectArrayCoordinate([110, 36, 0]);
// 设置位置
mesh.position.fromArray(position);
我们不需要关注投影坐标到Three.js坐标的转换,因为这个在内部做的自动偏移,对于我们这些使用者来说应该是无感知的, 所以我们需要关注的是地理坐标和投影坐标两种
模型上图
有了上面的理论储备,就可以上图three.js的内置对象和自定义模型等
// 模型上图
const center = [105.943667,29.349341];
const gltfLoader = new THREE.GLTFLoader();
// 加载模型
gltfLoader.load('assets/model/model.glb', function(gltf) {
const scene = gltf.scene;
// 设置位置
const position = engine.map.projectArrayCoordinate(center);
scene.position.fromArray(position);
// 添加到engine
engine.add(scene);
})
现在加载设置好正确位置就能看到模型了,不过JSAPI Three也有封装好了模型加载器,只用传入地理坐标也能正确加载定位
let model = engine.add(new mapvthree.SimpleModel({
name: 'model',
point: [105.943667,29.349341],
url: 'assets/models/building/5_tiyuzhongxin.glb',
}));
3.3 核心差异
| 特性 | Three.js | JSAPI Three |
|---|---|---|
| 坐标类型 | 世界坐标 | 地理坐标 + 世界坐标 |
| 坐标单位 | 自定义 | 米(投影单位) |
| 坐标原点 | 场景中心 | 地图投影中心 |
| 坐标范围 | 无限制 | 受地球大小限制 |
| Z轴含义 | 深度 | 高程(海拔) |
四、总结
JSAPI Three 在 Three.js 的基础上,提供了:
✅ 开箱即用的地图能力 - 自动处理地图瓦片、坐标投影
✅ 简化的初始化流程 - Engine 统一管理,代码量减少 50%
✅ 丰富的可视化组件 - 点、线、面等地图可视化组件
✅ 完整的地理坐标支持 - 原生支持经纬度,自动坐标转换
✅ Three.js 生态兼容 - 完全兼容 Three.js 所有特性
适用场景:数字孪生、智慧城市、地理可视化、虚拟旅游等需要结合地图的三维应用。