在现代 Web 开发中,3D 地图成为了展示数据和地理信息的一种新颖方式。借助 Three.js 和 Vue3,我们可以快速搭建一个 3D 地图应用。本教程将带你逐步实现 Vue3 与 Three.js 的集成,并构建一个简单的 3D 地图场景。
一、项目准备
1. 初始化 Vue3 项目
首先,创建一个 Vue3 项目。使用 Vue CLI 来快速初始化项目:
vue create vue-3d-map
cd vue-3d-map
2. 安装 Three.js 依赖
在项目中安装 Three.js:
npm install three
3. 项目结构概览
我们的项目结构大致如下:
vue-3d-map
│
├── public
│ ├── index.html
│
├── src
│ ├── components
│ │ └── Map3D.vue
│ ├── App.vue
│ └── main.js
│
└── package.json
二、创建 Map3D 组件
在 src/components/
目录下创建 Map3D.vue
组件,作为我们 3D 地图的核心组件。
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script>
import * as THREE from 'three';
export default {
name: "Map3D",
mounted() {
this.initScene();
},
methods: {
initScene() {
// 创建场景
this.scene = new THREE.Scene();
// 设置相机
const width = this.$refs.mapContainer.clientWidth;
const height = this.$refs.mapContainer.clientHeight;
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
this.camera.position.set(0, 10, 30);
// 创建渲染器
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
this.$refs.mapContainer.appendChild(this.renderer.domElement);
// 创建光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(10, 20, 10);
this.scene.add(pointLight);
// 创建地图基础立方体
this.createBaseCube();
// 渲染场景
this.renderScene();
},
createBaseCube() {
const geometry = new THREE.BoxGeometry(10, 1, 10);
const material = new THREE.MeshStandardMaterial({ color: 0x4CAF50 });
const cube = new THREE.Mesh(geometry, material);
this.scene.add(cube);
},
renderScene() {
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderScene);
}
}
};
</script>
<style>
.map-container {
width: 100%;
height: 100vh;
overflow: hidden;
}
</style>
代码解析
- 初始化场景:我们使用
THREE.Scene()
创建一个 Three.js 场景,并配置相机PerspectiveCamera
。 - 创建光源:添加环境光
AmbientLight
和点光源PointLight
,使得物体更加清晰。 - 创建基础立方体:用
BoxGeometry
创建一个立方体来模拟地图的底层。 - 渲染循环:通过
requestAnimationFrame
实现持续渲染。
三、在主页面中引入 3D 地图
在 App.vue
中引入 Map3D
组件:
<template>
<div id="app">
<Map3D />
</div>
</template>
<script>
import Map3D from './components/Map3D.vue';
export default {
name: 'App',
components: {
Map3D
}
};
</script>
运行项目
启动项目,应该可以看到一个简单的 3D 地图场景:
npm run serve
四、丰富地图元素
现在,我们可以添加更多的地图元素,如山峰、水体等,以模拟真实地理环境。
1. 添加地形(平面网格)
在 createBaseCube
函数中,加入一个简单的平面网格:
createBaseCube() {
const geometry = new THREE.PlaneGeometry(50, 50);
const material = new THREE.MeshStandardMaterial({
color: 0x8FBC8F });
const plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -Math.PI / 2; // 平面朝上
this.scene.add(plane);
}
2. 添加山峰
使用随机生成的锥体来模拟山峰:
addMountain() {
for (let i = 0; i < 5; i++) {
const geometry = new THREE.ConeGeometry(1 + Math.random() * 3, 5 + Math.random() * 10, 32);
const material = new THREE.MeshStandardMaterial({
color: 0x8B4513 });
const mountain = new THREE.Mesh(geometry, material);
mountain.position.set(
Math.random() * 30 - 15,
5,
Math.random() * 30 - 15
);
this.scene.add(mountain);
}
}
调用 addMountain
来生成随机的山峰:
this.addMountain();
3. 添加水体(透明平面)
addWaterBody() {
const geometry = new THREE.PlaneGeometry(15, 15);
const material = new THREE.MeshStandardMaterial({
color: 0x1E90FF,
transparent: true,
opacity: 0.6
});
const water = new THREE.Mesh(geometry, material);
water.position.y = 0.1;
water.rotation.x = -Math.PI / 2;
this.scene.add(water);
}
在初始化中调用 addWaterBody
:
this.addWaterBody();
五、相机控制和交互
为实现相机控制,我们可以引入 OrbitControls
,让用户自由旋转和缩放视角。
npm install three/examples/jsm/controls/OrbitControls
然后在 Map3D.vue
中添加 OrbitControls
:
import {
OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
initScene() {
// 创建相机和渲染器
...
// 添加相机控制
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
}
在 renderScene
中更新控制器:
renderScene() {
this.controls.update();
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderScene);
}
六、完整代码
以下是包含相机控制的完整代码:
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
export default {
name: "Map3D",
mounted() {
this.initScene();
},
methods: {
initScene() {
this.scene = new THREE.Scene();
const width = this.$refs.mapContainer.clientWidth;
const height = this.$refs.mapContainer.clientHeight;
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
this.camera.position.set(0, 10, 30);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
this.$refs.mapContainer.appendChild(this.renderer.domElement);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(10, 20, 10);
this.scene.add(pointLight);
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.createBaseCube();
this.addMountain();
this.addWaterBody();
this.renderScene();
},
createBaseCube() {
const geometry = new THREE.PlaneGeometry(50, 50);
const material = new THREE.MeshStandardMaterial({ color: 0x8FBC8F });
const plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -Math.PI / 2;
this.scene.add(plane);
},
addMountain() {
for (let i = 0; i < 5; i++) {
const geometry = new THREE.ConeGeometry(1 + Math.random() * 3, 5 + Math.random() * 10, 32);
const material = new THREE.Mesh
StandardMaterial({ color: 0x8B4513 });
const mountain = new THREE.Mesh(geometry, material);
mountain.position.set(
Math.random() * 30 - 15,
5,
Math.random() * 30 - 15
);
this.scene.add(mountain);
}
},
addWaterBody() {
const geometry = new THREE.PlaneGeometry(15, 15);
const material = new THREE.MeshStandardMaterial({
color: 0x1E90FF,
transparent: true,
opacity: 0.6
});
const water = new THREE.Mesh(geometry, material);
water.position.y = 0.1;
water.rotation.x = -Math.PI / 2;
this.scene.add(water);
},
renderScene() {
this.controls.update();
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderScene);
}
}
};
</script>
<style>
.map-container {
width: 100%;
height: 100vh;
overflow: hidden;
}
</style>
总结
通过本教程,我们成功实现了在 Vue3 中使用 Three.js 构建 3D 地图的基本步骤。在此基础上,可以继续扩展地图的细节,如标记点、3D 模型导入等。希望这篇文章帮助你入门 Vue 和 Three.js 的组合开发。