在三維場景中加載模型是最常見的需求之一。雖然可以直接使用 Three.js 的 GLTFLoader,但在不同投影方式下需要手動處理座標轉換,比較麻煩。今天就來學習 mapvthree 提供的 SimpleModel 類,看看它是如何簡化這個過程的。
瞭解 SimpleModel
SimpleModel 是 mapvthree 對 Three.js 模型加載的封裝,主要解決了以下問題:
原生 Three.js 加載方式的問題
如果直接使用 Three.js 的 GLTFLoader 加載模型:
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 加載模型文件。
基本示例
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],單位為弧度
第二步:設置模型位置
模型位置使用地理座標 [經度, 緯度, 高度] 來表示。
構造時設置位置
const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/tree/tree18.glb',
point: [120.628, 27.786, 0], // 經度、緯度、高度
}));
動態修改位置
// 通過 point 屬性修改
model.point = [120.629, 27.787, 50];
// 或通過 setTransform 方法
model.setTransform({
point: [120.629, 27.787, 50],
});
我的發現:可以隨時動態修改模型位置,引擎會自動更新模型的實際座標。
我的理解:
- 高度(z 值)是相對於地面的高度
-
- 引擎會自動將地理座標轉換為場景座標
-
- 不同投影方式下的轉換邏輯由引擎自動處理
第三步:設置模型旋轉和縮放
模型的旋轉和縮放也非常簡單。
設置旋轉
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)
- 單位是弧度,不是角度(角度需要轉換:
設置縮放
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
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 事件
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 方法可以靈活地更新模型的位置、旋轉和縮放。
只更新部分參數
// 只更新位置
model.setTransform({
point: [120.629, 27.787, 50],
});
// 只更新旋轉
model.setTransform({
rotation: [0, Math.PI / 4, 0],
});
// 只更新縮放
model.setTransform({
scale: [15, 15, 15],
});
我的發現:setTransform 方法的參數都是可選的,可以只更新需要修改的參數。
同時更新多個參數
// 同時更新位置、旋轉和縮放
model.setTransform({
point: [120.629, 27.787, 50],
rotation: [Math.PI / 2, Math.PI / 4, 0],
scale: [20, 20, 20],
});
我的理解:setTransform 是更新模型變換的推薦方式,比直接修改 position、rotation、scale 屬性更安全。
第六步:理解 autoYUpToZUp 參數
很多三維模型(如從建模軟件導出的模型)使用 Y 軸向上的座標系,而地理場景通常使用 Z 軸向上。
autoYUpToZUp 的作用
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 實例無效
什麼時候需要關閉
// 如果模型本身就是 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
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參數不生效
- 傳入 Object3D 時,
傳入自定義創建的 Mesh
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 對象。
第八步:完整示例
我想寫一個完整的示例,把學到的都用上:
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:模型不顯示
原因:模型路徑錯誤,或者模型文件格式不支持。
解決:
- 檢查模型文件路徑是否正確
-
- 確認模型文件格式是否為 glb/gltf(或其他 Three.js 支持的格式)
-
- 打開瀏覽器控制枱查看是否有加載錯誤
-
- 檢查模型位置是否在視野範圍內
坑 2:模型方向不對
原因:座標系轉換問題,或者旋轉設置不正確。
解決:
- 嘗試修改
autoYUpToZUp參數(true/false) -
- 調整
rotation參數,嘗試不同的旋轉角度
- 調整
-
- 在建模軟件中檢查模型的座標系和方向
坑 3:模型太大或太小
原因:模型原始尺寸與場景比例不匹配。
解決:
- 調整
scale參數,嘗試不同的縮放比例 -
- 如果模型太小看不見,嘗試放大 10 倍、100 倍
-
- 如果模型太大,嘗試縮小到 0.1、0.01
坑 4:動態修改不生效
原因:在模型加載完成前就進行了修改操作。
解決:
- 使用
loaded事件,確保在模型加載完成後再操作 -
- 使用
setTransform方法而不是直接修改屬性
- 使用
-
- 檢查是否啓用了
enableAnimationLoop(某些操作需要渲染循環)
- 檢查是否啓用了
坑 5:不同投影下模型位置不對
原因:沒有正確理解 SimpleModel 的自動投影轉換。
解決:
- 確認傳入的是地理座標
[經度, 緯度, 高度],而不是場景座標 -
- 不要手動計算投影轉換,SimpleModel 會自動處理
-
- 如果需要場景座標,使用
engine.map.projectArrayCoordinate()轉換
- 如果需要場景座標,使用
坑 6:傳入 Object3D 時 autoYUpToZUp 不生效
原因:autoYUpToZUp 只對通過 URL 加載的模型有效。
解決:
- 如果傳入 Object3D 實例,需要手動處理座標系轉換
-
- 或者使用
rotation參數手動旋轉
- 或者使用
-
- 或者在加載時就處理好座標系
我的學習總結
經過這一天的學習,我掌握了:
- SimpleModel 的本質:對 Three.js 加載方式的封裝,自動處理投影轉換
-
- 支持的格式:所有 Three.js 支持的模型格式(glb、gltf 等)
-
- 兩種加載方式:從 URL 加載,或傳入 Object3D 實例
-
- 位置設置:使用地理座標
[經度, 緯度, 高度]
- 位置設置:使用地理座標
-
- 旋轉和縮放:使用數組或 Vector3 格式
-
- 動態更新:使用
setTransform方法
- 動態更新:使用
-
- 座標系轉換:理解
autoYUpToZUp的作用
- 座標系轉換:理解
-
- 事件監聽:使用
loaded事件處理加載完成後的邏輯 我的感受:SimpleModel 讓加載單體模型變得非常簡單,不需要關心底層的投影轉換細節。它本質上是對 Three.js 的封裝,所以如果熟悉 Three.js,上手會非常快!
- 事件監聽:使用
下一步計劃:
- 學習如何加載和管理多個模型
-
- 學習 LODModel 實現性能優化
-
- 學習如何給模型添加動畫
學習筆記就到這裏啦!作為一個初學者,我覺得 SimpleModel 是一個非常實用的工具類,它簡化了三維模型的加載和管理。關鍵是要理解它是對 Three.js 的封裝,支持所有 Three.js 支持的模型格式,並能自動處理不同投影方式下的座標轉換。希望我的筆記能幫到其他初學者!大家一起加油!