JSAPIThree 加载单体三维模型学习笔记:SimpleModel 简易加载方式

在三维场景中加载模型是最常见的需求之一。虽然可以直接使用 Three.js 的 GLTFLoader,但在不同投影方式下需要手动处理坐标转换,比较麻烦。今天就来学习 mapvthree 提供的 SimpleModel 类,看看它是如何简化这个过程的。

了解 SimpleModel

SimpleModel 是 mapvthree 对 Three.js 模型加载的封装,主要解决了以下问题:

原生 Three.js 加载方式的问题

如果直接使用 Three.js 的 GLTFLoader 加载模型:

js 复制代码
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

const loader = new GLTFLoader();
loader.load('assets/models/tree/tree18.glb', gltf => {
    const model = gltf.scene;
    
    model.position.set(x, y, z);
    model.rotateX(Math.PI / 2);
    model.scale.setScalar(10);
    
    engine.add(model);
});

问题:这种方式只适用于平面投影(EPSG:3857),在 ECEF 等其他投影上,需要额外进行投影旋转,非常麻烦。

SimpleModel 的优势

SimpleModel 是对 Three.js 的封装,具有以下优势:

  • 自动投影转换:自动根据当前投影进行坐标旋转,无需手动处理
  • 简化接口:统一的配置参数,更易使用
  • 多种加载方式:支持从 URL 加载,也支持传入已有的 Object3D 实例
  • 格式兼容:支持所有 Three.js 支持的模型格式(glb、gltf 等)
  • 坐标系转换:自动处理 Y-Up 到 Z-Up 的坐标系转换
  • 事件监听:提供加载完成事件,方便后续处理

我的理解:SimpleModel 本质上是对 Three.js 的 GLTFLoader 和 Object3D 的封装,让我们不需要关心底层的投影转换细节,专注于业务逻辑。

第一步:基本使用 - 从 URL 加载模型

最简单的方式是从 URL 加载模型文件。

基本示例

js 复制代码
import * as mapvthree from '@baidumap/mapv-three';

const container = document.getElementById('container');

const engine = new mapvthree.Engine(container, {
    map: {
        center: [120.628, 27.786, 0],
        range: 1000,
        pitch: 80,
        projection: 'EPSG:3857',
        provider: null,
    },
});

// 加载单体模型
const model = engine.add(new mapvthree.SimpleModel({
    name: '树木模型',
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0],
    scale: [10, 10, 10],
    rotation: [Math.PI / 2, 0, 0],
}));

我的发现:只需要提供模型路径和位置,引擎会自动处理加载和投影转换。

我的理解

  • object 参数:模型文件的 URL 路径,支持 glb/gltf 格式(以及所有 Three.js 支持的格式)
  • point 参数:模型在地图上的位置,格式为 [经度, 纬度, 高度]
  • scale 参数:模型缩放比例,格式为 [x缩放, y缩放, z缩放]
  • rotation 参数:模型旋转角度,格式为 [roll, pitch, heading],单位为弧度

第二步:设置模型位置

模型位置使用地理坐标 [经度, 纬度, 高度] 来表示。

构造时设置位置

js 复制代码
const model = engine.add(new mapvthree.SimpleModel({
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0], // 经度、纬度、高度
}));

动态修改位置

js 复制代码
// 通过 point 属性修改
model.point = [120.629, 27.787, 50];

// 或通过 setTransform 方法
model.setTransform({
    point: [120.629, 27.787, 50],
});

我的发现:可以随时动态修改模型位置,引擎会自动更新模型的实际坐标。

我的理解

  • 高度(z 值)是相对于地面的高度
  • 引擎会自动将地理坐标转换为场景坐标
  • 不同投影方式下的转换逻辑由引擎自动处理

第三步:设置模型旋转和缩放

模型的旋转和缩放也非常简单。

设置旋转

js 复制代码
const model = engine.add(new mapvthree.SimpleModel({
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0],
    rotation: [Math.PI / 2, 0, 0], // [roll, pitch, heading]
}));

// 动态修改旋转
model.setTransform({
    rotation: [0, Math.PI / 4, Math.PI / 2],
});

我的理解

  • rotation[0](roll):绕 x 轴旋转
  • rotation[1](pitch):绕 y 轴旋转
  • rotation[2](heading):绕 z 轴旋转
  • 单位是弧度,不是角度(角度需要转换:角度 * Math.PI / 180

设置缩放

js 复制代码
const model = engine.add(new mapvthree.SimpleModel({
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0],
    scale: [10, 10, 10], // [x缩放, y缩放, z缩放]
}));

// 动态修改缩放
model.setTransform({
    scale: [20, 20, 20],
});

我的发现:可以对 x、y、z 三个方向分别设置缩放比例,实现非均匀缩放。

使用 Three.js 的 Vector3

js 复制代码
import * as THREE from 'three';

// 也可以使用 Three.js 的 Vector3
model.setTransform({
    point: new THREE.Vector3(120.628, 27.786, 50),
    scale: new THREE.Vector3(15, 15, 15),
    rotation: new THREE.Vector3(Math.PI / 2, 0, 0),
});

我的理解:SimpleModel 兼容数组和 Vector3 两种格式,可以根据习惯选择。

第四步:监听加载完成事件

模型加载是异步的,可以通过事件监听器获取加载完成的通知。

监听 loaded 事件

js 复制代码
const model = engine.add(new mapvthree.SimpleModel({
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0],
}));

// 监听模型加载完成事件
model.addEventListener('loaded', e => {
    console.log('模型加载完成:', e.value);
    
    // 在加载完成后可以进行后续操作
    // 例如:添加动画、修改材质等
});

我的发现 :在 loaded 事件中,可以通过 e.value 获取加载的模型对象,进行进一步的自定义操作。

我的理解

  • 如果需要在加载完成后进行操作(如修改材质、添加动画),一定要使用 loaded 事件
  • 不要在构造函数返回后立即操作模型,因为此时模型可能还没加载完成

第五步:动态更新模型变换

使用 setTransform 方法可以灵活地更新模型的位置、旋转和缩放。

只更新部分参数

js 复制代码
// 只更新位置
model.setTransform({
    point: [120.629, 27.787, 50],
});

// 只更新旋转
model.setTransform({
    rotation: [0, Math.PI / 4, 0],
});

// 只更新缩放
model.setTransform({
    scale: [15, 15, 15],
});

我的发现setTransform 方法的参数都是可选的,可以只更新需要修改的参数。

同时更新多个参数

js 复制代码
// 同时更新位置、旋转和缩放
model.setTransform({
    point: [120.629, 27.787, 50],
    rotation: [Math.PI / 2, Math.PI / 4, 0],
    scale: [20, 20, 20],
});

我的理解setTransform 是更新模型变换的推荐方式,比直接修改 positionrotationscale 属性更安全。

第六步:理解 autoYUpToZUp 参数

很多三维模型(如从建模软件导出的模型)使用 Y 轴向上的坐标系,而地理场景通常使用 Z 轴向上。

autoYUpToZUp 的作用

js 复制代码
const model = engine.add(new mapvthree.SimpleModel({
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0],
    autoYUpToZUp: true, // 默认为 true
}));

我的理解

  • autoYUpToZUp = true(默认):自动将 Y-Up 坐标系转换为 Z-Up,模型会自动旋转 90 度
  • autoYUpToZUp = false:不进行坐标系转换,保持模型原始方向
  • 这个参数仅对通过 URL 加载的模型有效,对直接传入的 Object3D 实例无效

什么时候需要关闭

js 复制代码
// 如果模型本身就是 Z-Up,或者已经做了正确的旋转
const model = engine.add(new mapvthree.SimpleModel({
    object: 'assets/models/building.glb',
    point: [120.628, 27.786, 0],
    autoYUpToZUp: false, // 不需要自动转换
}));

我的发现 :如果发现加载的模型方向不对(如倒着的、躺着的),可能是 autoYUpToZUp 的设置问题。

第七步:直接传入 Object3D 实例

除了从 URL 加载,还可以直接传入已经加载好的 Three.js Object3D 实例。

传入已有的 Object3D

js 复制代码
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

// 先用 Three.js 原生方式加载
const loader = new GLTFLoader();
loader.load('assets/models/tree/tree18.glb', gltf => {
    const mesh = gltf.scene;
    
    // 将加载好的模型传给 SimpleModel
    const model = engine.add(new mapvthree.SimpleModel({
        name: '树木模型',
        object: mesh, // 传入 Object3D 实例
        point: [120.628, 27.786, 0],
        scale: [10, 10, 10],
    }));
});

我的理解

  • 这种方式适合需要对模型进行预处理的场景
  • 例如:修改材质、合并多个模型、自定义加载逻辑等
  • 传入 Object3D 时,autoYUpToZUp 参数不生效

传入自定义创建的 Mesh

js 复制代码
import * as THREE from 'three';

// 创建一个自定义的立方体
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);

// 将自定义 Mesh 传给 SimpleModel
const model = engine.add(new mapvthree.SimpleModel({
    name: '立方体',
    object: cube,
    point: [120.628, 27.786, 0],
}));

我的发现:可以传入任何 Three.js 的 Object3D 对象,不仅限于从文件加载的模型。

我的理解:这展示了 SimpleModel 的灵活性,它本质上是一个位置和变换管理器,可以管理任何 Three.js 对象。

第八步:完整示例

我想写一个完整的示例,把学到的都用上:

js 复制代码
import * as mapvthree from '@baidumap/mapv-three';
import * as THREE from 'three';

const container = document.getElementById('container');

const engine = new mapvthree.Engine(container, {
    map: {
        center: [120.628, 27.786, 0],
        range: 1000,
        pitch: 80,
        projection: 'EPSG:3857',
        provider: null,
    },
    rendering: {
        enableAnimationLoop: true,
    },
});

// 加载单体模型
const model = engine.add(new mapvthree.SimpleModel({
    name: '树木模型',
    object: 'assets/models/tree/tree18.glb',
    point: [120.628, 27.786, 0],
    scale: [10, 10, 10],
    rotation: [Math.PI / 2, 0, 0],
    autoYUpToZUp: true, // 自动坐标系转换
}));

// 监听加载完成事件
model.addEventListener('loaded', e => {
    console.log('模型加载完成:', e.value);
    
    // 可以在这里进行后续操作
    // 例如:修改材质、添加动画等
});

// 动态更新模型位置(例如:5秒后移动模型)
setTimeout(() => {
    model.setTransform({
        point: [120.629, 27.787, 50],
        rotation: [Math.PI / 2, Math.PI / 4, 0],
        scale: [15, 15, 15],
    });
}, 5000);

我的感受:掌握了 SimpleModel,加载单体模型变得非常简单,不需要关心投影转换的细节!

第九步:踩过的坑

作为一个初学者,我踩了不少坑,记录下来避免再犯:

坑 1:模型不显示

原因:模型路径错误,或者模型文件格式不支持。

解决

  1. 检查模型文件路径是否正确
  2. 确认模型文件格式是否为 glb/gltf(或其他 Three.js 支持的格式)
  3. 打开浏览器控制台查看是否有加载错误
  4. 检查模型位置是否在视野范围内

坑 2:模型方向不对

原因:坐标系转换问题,或者旋转设置不正确。

解决

  1. 尝试修改 autoYUpToZUp 参数(true/false)
  2. 调整 rotation 参数,尝试不同的旋转角度
  3. 在建模软件中检查模型的坐标系和方向

坑 3:模型太大或太小

原因:模型原始尺寸与场景比例不匹配。

解决

  1. 调整 scale 参数,尝试不同的缩放比例
  2. 如果模型太小看不见,尝试放大 10 倍、100 倍
  3. 如果模型太大,尝试缩小到 0.1、0.01

坑 4:动态修改不生效

原因:在模型加载完成前就进行了修改操作。

解决

  1. 使用 loaded 事件,确保在模型加载完成后再操作
  2. 使用 setTransform 方法而不是直接修改属性
  3. 检查是否启用了 enableAnimationLoop(某些操作需要渲染循环)

坑 5:不同投影下模型位置不对

原因:没有正确理解 SimpleModel 的自动投影转换。

解决

  1. 确认传入的是地理坐标 [经度, 纬度, 高度],而不是场景坐标
  2. 不要手动计算投影转换,SimpleModel 会自动处理
  3. 如果需要场景坐标,使用 engine.map.projectArrayCoordinate() 转换

坑 6:传入 Object3D 时 autoYUpToZUp 不生效

原因autoYUpToZUp 只对通过 URL 加载的模型有效。

解决

  1. 如果传入 Object3D 实例,需要手动处理坐标系转换
  2. 或者使用 rotation 参数手动旋转
  3. 或者在加载时就处理好坐标系

我的学习总结

经过这一天的学习,我掌握了:

  1. SimpleModel 的本质:对 Three.js 加载方式的封装,自动处理投影转换
  2. 支持的格式:所有 Three.js 支持的模型格式(glb、gltf 等)
  3. 两种加载方式:从 URL 加载,或传入 Object3D 实例
  4. 位置设置 :使用地理坐标 [经度, 纬度, 高度]
  5. 旋转和缩放:使用数组或 Vector3 格式
  6. 动态更新 :使用 setTransform 方法
  7. 坐标系转换 :理解 autoYUpToZUp 的作用
  8. 事件监听 :使用 loaded 事件处理加载完成后的逻辑

我的感受:SimpleModel 让加载单体模型变得非常简单,不需要关心底层的投影转换细节。它本质上是对 Three.js 的封装,所以如果熟悉 Three.js,上手会非常快!

下一步计划

  1. 学习如何加载和管理多个模型
  2. 学习 LODModel 实现性能优化
  3. 学习如何给模型添加动画

学习笔记就到这里啦!作为一个初学者,我觉得 SimpleModel 是一个非常实用的工具类,它简化了三维模型的加载和管理。关键是要理解它是对 Three.js 的封装,支持所有 Three.js 支持的模型格式,并能自动处理不同投影方式下的坐标转换。希望我的笔记能帮到其他初学者!大家一起加油!

相关推荐
青春pig头少年5 小时前
决战408:OS大题我拿拿拿(非PV)
操作系统·学习笔记·408
map_3d_vis1 天前
JSAPIThree 加载 3D Tiles 学习笔记:大规模三维场景渲染
初学者·3d tiles·mapvthree·三维场景
Logic1011 天前
《Mysql数据库应用》 第2版 郭文明 实验5 存储过程与函数的构建与使用核心操作与思路解析
数据库·sql·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
Logic1011 天前
《Mysql数据库应用》 第2版 郭文明 实验1 在MySQL中创建数据库和表核心操作与思路解析
数据库·sql·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
一马平川的大草原2 天前
AI Agent常见问题和核心术语
人工智能·学习笔记·agent
Logic1013 天前
《Mysql数据库应用》 第2版 郭文明 实验6 数据库系统维护核心操作与思路解析
数据库·sql·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
Logic1013 天前
《数据库运维》 郭文明 实验4 数据库备份与恢复实验核心操作与思路解析
运维·数据库·sql·mysql·学习笔记·形考作业·国家开放大学
Addisonx3 天前
深度复盘 III: 核心逻辑篇:构建 WebGL 数字孪生的“业务中枢”与“安全防线”
webgl·three.js
爱看书的小沐3 天前
【小沐学WebGIS】基于Three.JS绘制二三维地图地球晨昏效果(WebGL / vue / react )
javascript·vue.js·gis·webgl·three.js·opengl·晨昏线