JSAPIThree 加载单体三维模型学习笔记:SimpleModel 简易加载方式
<blockquote><p>在三维场景中加载模型是最常见的需求之一。虽然可以直接使用 Three.js 的 GLTFLoader,但在不同投影方式下需要手动处理坐标转换,比较麻烦。今天就来学习 mapvthree 提供的 SimpleModel 类,看看它是如何简化这个过程的。</p>
</blockquote>
<h2 id="了解-simplemodel">了解 SimpleModel</h2>
<p>SimpleModel 是 mapvthree 对 Three.js 模型加载的封装,主要解决了以下问题:</p>
<h3 id="原生-threejs-加载方式的问题">原生 Three.js 加载方式的问题</h3>
<p>如果直接使用 Three.js 的 GLTFLoader 加载模型:</p>
<pre><code class="language-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);
});
</code></pre>
<p><strong>问题</strong>:这种方式只适用于平面投影(EPSG:3857),在 ECEF 等其他投影上,需要额外进行投影旋转,非常麻烦。</p>
<h3 id="simplemodel-的优势">SimpleModel 的优势</h3>
<p>SimpleModel 是对 Three.js 的封装,具有以下优势:</p>
<ul>
<li><strong>自动投影转换</strong>:自动根据当前投影进行坐标旋转,无需手动处理</li>
<li><strong>简化接口</strong>:统一的配置参数,更易使用</li>
<li><strong>多种加载方式</strong>:支持从 URL 加载,也支持传入已有的 Object3D 实例</li>
<li><strong>格式兼容</strong>:支持所有 Three.js 支持的模型格式(glb、gltf 等)</li>
<li><strong>坐标系转换</strong>:自动处理 Y-Up 到 Z-Up 的坐标系转换</li>
<li><strong>事件监听</strong>:提供加载完成事件,方便后续处理</li>
</ul>
<p><strong>我的理解</strong>:SimpleModel 本质上是对 Three.js 的 GLTFLoader 和 Object3D 的封装,让我们不需要关心底层的投影转换细节,专注于业务逻辑。</p>
<h2 id="第一步基本使用---从-url-加载模型">第一步:基本使用 - 从 URL 加载模型</h2>
<p>最简单的方式是从 URL 加载模型文件。</p>
<h3 id="基本示例">基本示例</h3>
<pre><code class="language-js">import * as mapvthree from '@baidumap/mapv-three';
const container = document.getElementById('container');
const engine = new mapvthree.Engine(container, {
map: {
center: ,
range: 1000,
pitch: 80,
projection: 'EPSG:3857',
provider: null,
},
});
// 加载单体模型
const model = engine.add(new mapvthree.SimpleModel({
name: '树木模型',
object: 'assets/models/tree/tree18.glb',
point: ,
scale: ,
rotation: ,
}));
</code></pre>
<p><strong>我的发现</strong>:只需要提供模型路径和位置,引擎会自动处理加载和投影转换。</p>
<p><strong>我的理解</strong>:</p>
<ul>
<li><code>object</code> 参数:模型文件的 URL 路径,支持 glb/gltf 格式(以及所有 Three.js 支持的格式)</li>
<li><code>point</code> 参数:模型在地图上的位置,格式为 <code>[经度, 纬度, 高度]</code></li>
<li><code>scale</code> 参数:模型缩放比例,格式为 <code></code></li>
<li><code>rotation</code> 参数:模型旋转角度,格式为 <code></code>,单位为弧度</li>
</ul>
<h2 id="第二步设置模型位置">第二步:设置模型位置</h2>
<p>模型位置使用地理坐标 <code>[经度, 纬度, 高度]</code> 来表示。</p>
<h3 id="构造时设置位置">构造时设置位置</h3>
<pre><code class="language-js">const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/tree/tree18.glb',
point: , // 经度、纬度、高度
}));
</code></pre>
<h3 id="动态修改位置">动态修改位置</h3>
<pre><code class="language-js">// 通过 point 属性修改
model.point = ;
// 或通过 setTransform 方法
model.setTransform({
point: ,
});
</code></pre>
<p><strong>我的发现</strong>:可以随时动态修改模型位置,引擎会自动更新模型的实际坐标。</p>
<p><strong>我的理解</strong>:</p>
<ul>
<li>高度(z 值)是相对于地面的高度</li>
<li>引擎会自动将地理坐标转换为场景坐标</li>
<li>不同投影方式下的转换逻辑由引擎自动处理</li>
</ul>
<h2 id="第三步设置模型旋转和缩放">第三步:设置模型旋转和缩放</h2>
<p>模型的旋转和缩放也非常简单。</p>
<h3 id="设置旋转">设置旋转</h3>
<pre><code class="language-js">const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/tree/tree18.glb',
point: ,
rotation: , //
}));
// 动态修改旋转
model.setTransform({
rotation: ,
});
</code></pre>
<p><strong>我的理解</strong>:</p>
<ul>
<li><code>rotation</code>(roll):绕 x 轴旋转</li>
<li><code>rotation</code>(pitch):绕 y 轴旋转</li>
<li><code>rotation</code>(heading):绕 z 轴旋转</li>
<li>单位是弧度,不是角度(角度需要转换:<code>角度 * Math.PI / 180</code>)</li>
</ul>
<h3 id="设置缩放">设置缩放</h3>
<pre><code class="language-js">const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/tree/tree18.glb',
point: ,
scale: , //
}));
// 动态修改缩放
model.setTransform({
scale: ,
});
</code></pre>
<p><strong>我的发现</strong>:可以对 x、y、z 三个方向分别设置缩放比例,实现非均匀缩放。</p>
<h3 id="使用-threejs-的-vector3">使用 Three.js 的 Vector3</h3>
<pre><code class="language-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),
});
</code></pre>
<p><strong>我的理解</strong>:SimpleModel 兼容数组和 Vector3 两种格式,可以根据习惯选择。</p>
<h2 id="第四步监听加载完成事件">第四步:监听加载完成事件</h2>
<p>模型加载是异步的,可以通过事件监听器获取加载完成的通知。</p>
<h3 id="监听-loaded-事件">监听 loaded 事件</h3>
<pre><code class="language-js">const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/tree/tree18.glb',
point: ,
}));
// 监听模型加载完成事件
model.addEventListener('loaded', e => {
console.log('模型加载完成:', e.value);
// 在加载完成后可以进行后续操作
// 例如:添加动画、修改材质等
});
</code></pre>
<p><strong>我的发现</strong>:在 <code>loaded</code> 事件中,可以通过 <code>e.value</code> 获取加载的模型对象,进行进一步的自定义操作。</p>
<p><strong>我的理解</strong>:</p>
<ul>
<li>如果需要在加载完成后进行操作(如修改材质、添加动画),一定要使用 <code>loaded</code> 事件</li>
<li>不要在构造函数返回后立即操作模型,因为此时模型可能还没加载完成</li>
</ul>
<h2 id="第五步动态更新模型变换">第五步:动态更新模型变换</h2>
<p>使用 <code>setTransform</code> 方法可以灵活地更新模型的位置、旋转和缩放。</p>
<h3 id="只更新部分参数">只更新部分参数</h3>
<pre><code class="language-js">// 只更新位置
model.setTransform({
point: ,
});
// 只更新旋转
model.setTransform({
rotation: ,
});
// 只更新缩放
model.setTransform({
scale: ,
});
</code></pre>
<p><strong>我的发现</strong>:<code>setTransform</code> 方法的参数都是可选的,可以只更新需要修改的参数。</p>
<h3 id="同时更新多个参数">同时更新多个参数</h3>
<pre><code class="language-js">// 同时更新位置、旋转和缩放
model.setTransform({
point: ,
rotation: ,
scale: ,
});
</code></pre>
<p><strong>我的理解</strong>:<code>setTransform</code> 是更新模型变换的推荐方式,比直接修改 <code>position</code>、<code>rotation</code>、<code>scale</code> 属性更安全。</p>
<h2 id="第六步理解-autoyuptozup-参数">第六步:理解 autoYUpToZUp 参数</h2>
<p>很多三维模型(如从建模软件导出的模型)使用 Y 轴向上的坐标系,而地理场景通常使用 Z 轴向上。</p>
<h3 id="autoyuptozup-的作用">autoYUpToZUp 的作用</h3>
<pre><code class="language-js">const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/tree/tree18.glb',
point: ,
autoYUpToZUp: true, // 默认为 true
}));
</code></pre>
<p><strong>我的理解</strong>:</p>
<ul>
<li><strong>autoYUpToZUp = true</strong>(默认):自动将 Y-Up 坐标系转换为 Z-Up,模型会自动旋转 90 度</li>
<li><strong>autoYUpToZUp = false</strong>:不进行坐标系转换,保持模型原始方向</li>
<li>这个参数<strong>仅对通过 URL 加载的模型有效</strong>,对直接传入的 Object3D 实例无效</li>
</ul>
<h3 id="什么时候需要关闭">什么时候需要关闭</h3>
<pre><code class="language-js">// 如果模型本身就是 Z-Up,或者已经做了正确的旋转
const model = engine.add(new mapvthree.SimpleModel({
object: 'assets/models/building.glb',
point: ,
autoYUpToZUp: false, // 不需要自动转换
}));
</code></pre>
<p><strong>我的发现</strong>:如果发现加载的模型方向不对(如倒着的、躺着的),可能是 <code>autoYUpToZUp</code> 的设置问题。</p>
<h2 id="第七步直接传入-object3d-实例">第七步:直接传入 Object3D 实例</h2>
<p>除了从 URL 加载,还可以直接传入已经加载好的 Three.js Object3D 实例。</p>
<h3 id="传入已有的-object3d">传入已有的 Object3D</h3>
<pre><code class="language-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: ,
scale: ,
}));
});
</code></pre>
<p><strong>我的理解</strong>:</p>
<ul>
<li>这种方式适合需要对模型进行预处理的场景</li>
<li>例如:修改材质、合并多个模型、自定义加载逻辑等</li>
<li>传入 Object3D 时,<code>autoYUpToZUp</code> 参数不生效</li>
</ul>
<h3 id="传入自定义创建的-mesh">传入自定义创建的 Mesh</h3>
<pre><code class="language-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: ,
}));
</code></pre>
<p><strong>我的发现</strong>:可以传入任何 Three.js 的 Object3D 对象,不仅限于从文件加载的模型。</p>
<p><strong>我的理解</strong>:这展示了 SimpleModel 的灵活性,它本质上是一个位置和变换管理器,可以管理任何 Three.js 对象。</p>
<h2 id="第八步完整示例">第八步:完整示例</h2>
<p>我想写一个完整的示例,把学到的都用上:</p>
<pre><code class="language-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: ,
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: ,
scale: ,
rotation: ,
autoYUpToZUp: true, // 自动坐标系转换
}));
// 监听加载完成事件
model.addEventListener('loaded', e => {
console.log('模型加载完成:', e.value);
// 可以在这里进行后续操作
// 例如:修改材质、添加动画等
});
// 动态更新模型位置(例如:5秒后移动模型)
setTimeout(() => {
model.setTransform({
point: ,
rotation: ,
scale: ,
});
}, 5000);
</code></pre>
<p><strong>我的感受</strong>:掌握了 SimpleModel,加载单体模型变得非常简单,不需要关心投影转换的细节!</p>
<h2 id="第九步踩过的坑">第九步:踩过的坑</h2>
<p>作为一个初学者,我踩了不少坑,记录下来避免再犯:</p>
<h3 id="坑-1模型不显示">坑 1:模型不显示</h3>
<p><strong>原因</strong>:模型路径错误,或者模型文件格式不支持。</p>
<p><strong>解决</strong>:</p>
<ol>
<li>检查模型文件路径是否正确</li>
<li>确认模型文件格式是否为 glb/gltf(或其他 Three.js 支持的格式)</li>
<li>打开浏览器控制台查看是否有加载错误</li>
<li>检查模型位置是否在视野范围内</li>
</ol>
<h3 id="坑-2模型方向不对">坑 2:模型方向不对</h3>
<p><strong>原因</strong>:坐标系转换问题,或者旋转设置不正确。</p>
<p><strong>解决</strong>:</p>
<ol>
<li>尝试修改 <code>autoYUpToZUp</code> 参数(true/false)</li>
<li>调整 <code>rotation</code> 参数,尝试不同的旋转角度</li>
<li>在建模软件中检查模型的坐标系和方向</li>
</ol>
<h3 id="坑-3模型太大或太小">坑 3:模型太大或太小</h3>
<p><strong>原因</strong>:模型原始尺寸与场景比例不匹配。</p>
<p><strong>解决</strong>:</p>
<ol>
<li>调整 <code>scale</code> 参数,尝试不同的缩放比例</li>
<li>如果模型太小看不见,尝试放大 10 倍、100 倍</li>
<li>如果模型太大,尝试缩小到 0.1、0.01</li>
</ol>
<h3 id="坑-4动态修改不生效">坑 4:动态修改不生效</h3>
<p><strong>原因</strong>:在模型加载完成前就进行了修改操作。</p>
<p><strong>解决</strong>:</p>
<ol>
<li>使用 <code>loaded</code> 事件,确保在模型加载完成后再操作</li>
<li>使用 <code>setTransform</code> 方法而不是直接修改属性</li>
<li>检查是否启用了 <code>enableAnimationLoop</code>(某些操作需要渲染循环)</li>
</ol>
<h3 id="坑-5不同投影下模型位置不对">坑 5:不同投影下模型位置不对</h3>
<p><strong>原因</strong>:没有正确理解 SimpleModel 的自动投影转换。</p>
<p><strong>解决</strong>:</p>
<ol>
<li>确认传入的是地理坐标 <code>[经度, 纬度, 高度]</code>,而不是场景坐标</li>
<li>不要手动计算投影转换,SimpleModel 会自动处理</li>
<li>如果需要场景坐标,使用 <code>engine.map.projectArrayCoordinate()</code> 转换</li>
</ol>
<h3 id="坑-6传入-object3d-时-autoyuptozup-不生效">坑 6:传入 Object3D 时 autoYUpToZUp 不生效</h3>
<p><strong>原因</strong>:<code>autoYUpToZUp</code> 只对通过 URL 加载的模型有效。</p>
<p><strong>解决</strong>:</p>
<ol>
<li>如果传入 Object3D 实例,需要手动处理坐标系转换</li>
<li>或者使用 <code>rotation</code> 参数手动旋转</li>
<li>或者在加载时就处理好坐标系</li>
</ol>
<h2 id="我的学习总结">我的学习总结</h2>
<p>经过这一天的学习,我掌握了:</p>
<ol>
<li><strong>SimpleModel 的本质</strong>:对 Three.js 加载方式的封装,自动处理投影转换</li>
<li><strong>支持的格式</strong>:所有 Three.js 支持的模型格式(glb、gltf 等)</li>
<li><strong>两种加载方式</strong>:从 URL 加载,或传入 Object3D 实例</li>
<li><strong>位置设置</strong>:使用地理坐标 <code>[经度, 纬度, 高度]</code></li>
<li><strong>旋转和缩放</strong>:使用数组或 Vector3 格式</li>
<li><strong>动态更新</strong>:使用 <code>setTransform</code> 方法</li>
<li><strong>坐标系转换</strong>:理解 <code>autoYUpToZUp</code> 的作用</li>
<li><strong>事件监听</strong>:使用 <code>loaded</code> 事件处理加载完成后的逻辑</li>
</ol>
<p><strong>我的感受</strong>:SimpleModel 让加载单体模型变得非常简单,不需要关心底层的投影转换细节。它本质上是对 Three.js 的封装,所以如果熟悉 Three.js,上手会非常快!</p>
<p><strong>下一步计划</strong>:</p>
<ol>
<li>学习如何加载和管理多个模型</li>
<li>学习 LODModel 实现性能优化</li>
<li>学习如何给模型添加动画</li>
</ol>
<hr>
<blockquote>
<p>学习笔记就到这里啦!作为一个初学者,我觉得 SimpleModel 是一个非常实用的工具类,它简化了三维模型的加载和管理。关键是要理解它是对 Three.js 的封装,支持所有 Three.js 支持的模型格式,并能自动处理不同投影方式下的坐标转换。希望我的笔记能帮到其他初学者!大家一起加油!</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/map-3d-vis/p/19362522
頁:
[1]