从Three.js到JSAPI THREE

82 阅读11分钟

从 Three.js 到 JSAPI Three 开发入门

Three.js 到 JSAPI Three

目录


对比介绍

近年来,Three.js 越来越火,它让前端开发者能轻松打造各种炫酷的三维效果,从模型展示到特效动画应有尽有。不过作为一个通用三维引擎,Three.js 在地图场景下并没有开箱即用的地理封装——比如坐标投影、地图可视化组件等都需要开发者自行实现。

所以业界出现了像 Cesium.js 这样的三维地球引擎,专门面向孪生与地图可视化领域。但有没有一种方案让我既能享受 Three.js 的庞大生态与丰富的渲染能力,又能直接对接地图业务与数据服务呢?

这里就要介绍一下我最近接触到的 JSAPI Three。它在 Three.js 的基础上,提供了完整的地图三维渲染能力,让我们既能享受 Three.js 的通用能力和丰富社区,又能方便地构建数字孪生、城市可视化等真实地理场景。

JSAPI Three 效果展示

下面我会介绍一下 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共享

image.png

上图左边是我以前实现的一个噪声效果,我将其迁移到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依然能够很快上手

image.png


二、事件交互

在三维场景中,事件交互让静态场景变得生动。在 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.jsJSAPI Three
事件绑定手动实现addEventListener()
射线检测手动 Raycaster自动处理

三、坐标系统对比

3.1 Three.js 标准坐标系

Three.js 使用的是右手笛卡尔坐标系

  • X轴:水平方向,指向右侧
  • Y轴:垂直方向,指向上方
  • Z轴:深度方向,指向观察者

image.png

坐标单位

  • 单位:无量纲的抽象单位
  • 自定义比例:开发者可以自由定义单位含义(米、厘米等)
  • 世界坐标:物体在场景中的绝对位置
  • 局部坐标:物体相对于其父对象的位置

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);
})

image.png 现在加载设置好正确位置就能看到模型了,不过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.jsJSAPI Three
坐标类型世界坐标地理坐标 + 世界坐标
坐标单位自定义米(投影单位)
坐标原点场景中心地图投影中心
坐标范围无限制受地球大小限制
Z轴含义深度高程(海拔)

四、总结

JSAPI Three 在 Three.js 的基础上,提供了:

开箱即用的地图能力 - 自动处理地图瓦片、坐标投影
简化的初始化流程 - Engine 统一管理,代码量减少 50%
丰富的可视化组件 - 点、线、面等地图可视化组件
完整的地理坐标支持 - 原生支持经纬度,自动坐标转换
Three.js 生态兼容 - 完全兼容 Three.js 所有特性

适用场景:数字孪生、智慧城市、地理可视化、虚拟旅游等需要结合地图的三维应用。