前言
本文不讲述建模部分,这个我自己也不是很熟练,等以后有机会了再统一出个建模的文章。
初始化项目
这块已经讲述过很多了,这里就不多赘述了
设置全局样式
1 | *{ |
基础目录结构和实现逻辑
这个和我之前那篇智慧城市的文章一样,就使用那个拆分就可以了
导入组件
首先在App.vue中1
2
3
4
5
6
7
8
9<template>
<div class="home">
<Scene></Scene>
</div>
</template>
<script setup>
import Scene from "@/components/Scene.vue";
</script>
添加事件总览
新建utils文件夹,新建eventHub.js1
2
3
4
5import Mitt from "mitt";
const eventHub = new Mitt();
export default eventHub;
组件导入目录
Scene组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48<template>
<div class="scene" ref="sceneDiv"></div>
</template>
<script setup>
import { onMounted, ref} from "vue";
// 导入场景
import scene from "@/three/scene";
import camera from "@/three/camera";
// 导入辅助坐标轴
import axesHelper from "@/three/axesHelper";
// 导入渲染器
import renderer from "@/three/renderer";
// 初始化调整屏幕
import "@/three/init";
// 导入添加物体函数
import createMesh from "@/three/createMesh";
// 导入每一帧的执行函数
import animate from "@/three/animate";
// 场景元素div
let sceneDiv = ref(null);
// 添加相机
scene.add(camera);
// 添加辅助坐标轴
scene.add(axesHelper);
// 创建物体
createMesh();
onMounted(() => {
sceneDiv.value.appendChild(renderer.domElement);
animate();
});
</script>
<style>
.scene {
width: 100vw;
height: 100vh;
position: fixed;
z-index: 100;
left: 0;
top: 0;
}
</style>
新建/src/three文件夹
在这个文件夹下面初始化我们的基础文件,初始化的代码就不解释了,之前的文章都有讲过
camera.js
1 | import * as THREE from "three"; |
renderer.js
1 | import * as THREE from "three"; |
init.js
1 | import camera from "./camera"; |
axesHelper.js
1 | import * as THREE from "three"; |
controls.js
1 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; |
animate.js
1 | import camera from "./camera"; |
scene.js
这里我们导入一个纹理图片1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import * as THREE from "three";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
// 初始化场景
const scene = new THREE.Scene();
// 导入hdr纹理
const hdrLoader = new RGBELoader();
hdrLoader.loadAsync("./textures/023.hdr").then((texture) => {
scene.background = texture;
scene.environment = texture;
scene.environment.mapping = THREE.EquirectangularReflectionMapping;
});
// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 100, 10);
scene.add(light);
export default scene;
createMesh.js
1 | import scene from "./scene"; |
导入模型
City.js
注意:DRACOLoader是用来解析glb文件的,然后这个文件是需要从node_modules\three\examples\js\libs文件夹下面拷贝出来的,然后放到public文件夹下面,这样才能正常解析1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
export default class City {
constructor(scene) {
// 载入模型
this.scene = scene;
this.loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
this.loader.setDRACOLoader(dracoLoader);
this.loader.load("./model/city4.glb", (gltf) => {
console.log(gltf);
scene.add(gltf.scene);
});
}
}
添加热气球动画
模型里面已经设置了动画,在这里只需要调用即可,不用自己绘制动画
animate.js
调用updateMesh,传入time1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { updateMesh } from "@/three/createMesh";
import * as THREE from "three";
import camera from "./camera";
import renderer from "./renderer";
import controls from "./controls";
import scene from "./scene";
const clock = new THREE.Clock();
function animate() {
const time = clock.getDelta();
controls.update(time);
updateMesh(time);
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
export default animate;
City.js
导入glb中的动画1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import * as THREE from "three";
export default class City {
constructor(scene) {
// 载入模型
this.scene = scene;
this.loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
this.loader.setDRACOLoader(dracoLoader);
this.loader.load("./model/city4.glb", (gltf) => {
console.log(gltf);
scene.add(gltf.scene);
// 场景子元素遍历
this.gltf = gltf;
gltf.scene.traverse((child) => {
if (child.name === "热气球") {
// console.log(child);
this.mixer = new THREE.AnimationMixer(child);
this.clip = gltf.animations[1];
this.action = this.mixer.clipAction(this.clip);
this.action.play();
}
});
});
}
update(time) {
if (this.mixer) {
this.mixer.update(time);
}
}
}
动画效果
仔细看,可以看到热气球是在飘动的
添加汽车动画
City.js
在模型里面,已经设置了汽车的轨迹,我们只需要根据轨迹来设置汽车的位置即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import * as THREE from "three";
import gsap from "gsap";
export default class City {
constructor(scene) {
// 载入模型
this.scene = scene;
this.loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
this.loader.setDRACOLoader(dracoLoader);
this.loader.load("./model/city4.glb", (gltf) => {
console.log(gltf);
scene.add(gltf.scene);
// 场景子元素遍历
this.gltf = gltf;
gltf.scene.traverse((child) => {
if (child.name === "热气球") {
// console.log(child);
this.mixer = new THREE.AnimationMixer(child);
this.clip = gltf.animations[1];
this.action = this.mixer.clipAction(this.clip);
this.action.play();
}
if (child.name === "汽车园区轨迹") {
// console.log(child);
const line = child;
line.visible = false;
// 根据点创建曲线
const points = [];
for (
let i = line.geometry.attributes.position.count - 1;
i >= 0;
i--
) {
points.push(
new THREE.Vector3(
line.geometry.attributes.position.getX(i),
line.geometry.attributes.position.getY(i),
line.geometry.attributes.position.getZ(i)
)
);
}
this.curve = new THREE.CatmullRomCurve3(points);
this.curveProgress = 0;
this.carAnimation();
}
if (child.name === "redcar") {
console.log(child);
this.redcar = child;
}
});
});
}
update(time) {
if (this.mixer) {
this.mixer.update(time);
}
}
carAnimation() {
gsap.to(this, {
curveProgress: 0.999,
duration: 10,
repeat: -1,
onUpdate: () => {
const point = this.curve.getPoint(this.curveProgress);
this.redcar.position.set(point.x, point.y, point.z);
if (this.curveProgress + 0.001 < 1) {
const point = this.curve.getPoint(this.curveProgress + 0.001);
this.redcar.lookAt(point);
}
},
});
}
}
动画效果
仔细看,可以看到汽车是在运动的
设置不同视角观看以及热气球动画
模型视角
在建模的时候已经给了我们多个视角,我们只需要对应切换即可
依旧使用之前智慧城市的控制屏幕代码
并添加事件
BigScreen.vue
1 | <template> |
App.vue
在App.vue中导入并使用1
2
3
4
5
6
7
8
9
10
11<template>
<div class="home">
<Scene></Scene>
<BigScreen></BigScreen>
</div>
</template>
<script setup>
import Scene from "@/components/Scene.vue";
import BigScreen from "@/components/BigScreen.vue";
</script>
Camera.js
根据不同的事件切换不同的相机视角1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import * as THREE from "three";
import eventHub from "@/utils/eventHub";
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerHeight / window.innerHeight,
1,
100000
);
// 设置相机位置
// object3d具有position,属性是1个3维的向量
camera.position.set(1000, 1000, 1000);
class CameraModule {
constructor() {
this.activeCamera = camera;
this.collection = {
default: camera,
};
eventHub.on("toggleCamera", (name) => {
this.setActive(name);
});
}
add(name, camera) {
this.collection[name] = camera;
}
setActive(name) {
this.activeCamera = this.collection[name];
}
}
export default new CameraModule();
City.js
在City.js中添加相机视角1
2
3
4import cameraModule from "../camera";
gltf.cameras.forEach((camera) => {
cameraModule.add(camera.name, camera);
});
在City.js中切换热气球动画1
2
3
4
5
6
7eventHub.on("actionClick", (i) => {
console.log(i);
this.action.reset();
this.clip = this.gltf.animations[i];
this.action = this.mixer.clipAction(this.clip);
this.action.play();
});
其他文件
在其他使用到camera的地方全部切换
- animate.js
1
2import cameraModule from "./camera";
renderer.render(scene, cameraModule.activeCamera); controls.js
1
2
3
4
5import cameraModule from "./camera";
const controls = new OrbitControls(
cameraModule.activeCamera,
renderer.domElement
);init.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import cameraModule from "./camera";
import renderer from "./renderer";
// 更新摄像头
cameraModule.activeCamera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
cameraModule.activeCamera.updateProjectionMatrix();
// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
// console.log("resize");
// 更新摄像头
cameraModule.activeCamera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
cameraModule.activeCamera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});Scene.vue
1
2
3import cameraModule from "@/three/camera";
// 添加相机
scene.add(cameraModule.activeCamera);
切换视角效果
切换观览模式
BigScreen.vue
1 | <div class="right"> |
1 | const toggleControls = (name) => { |
controls.js
切换控制器,具体的控制器代码可以去threejs官网查看,这里使用了FlyControls和FirstPersonControls1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { FlyControls } from "three/examples/jsm/controls/FlyControls";
import { FirstPersonControls } from "three/examples/jsm/controls/FirstPersonControls";
import cameraModule from "./camera";
import renderer from "./renderer";
import eventHub from "@/utils/eventHub";
class ControlsModule {
constructor() {
this.setOrbitControls();
eventHub.on("toggleControls", (name) => {
this[`set${name}Controls`]();
});
}
setOrbitControls() {
// 初始化控制器
this.controls = new OrbitControls(
cameraModule.activeCamera,
renderer.domElement
);
// 设置控制器阻尼
this.controls.enableDamping = true;
// 设置自动旋转
// controls.autoRotate = true;
this.controls.maxPolarAngle = Math.PI / 2;
this.controls.minPolarAngle = 0;
}
setFlyControls() {
this.controls = new FlyControls(
cameraModule.activeCamera,
renderer.domElement
);
this.controls.movementSpeed = 100;
this.controls.rollSpeed = Math.PI / 60;
}
setFirstPersonControls() {
this.controls = new FirstPersonControls(
cameraModule.activeCamera,
renderer.domElement
);
this.controls.movementSpeed = 100;
this.controls.rollSpeed = Math.PI / 60;
}
}
export default new ControlsModule();
animate.js
切换控制器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import * as THREE from "three";
import cameraModule from "./camera";
import renderer from "./renderer";
import controlsModule from "./controls";
import scene from "./scene";
import { updateMesh } from "@/three/createMesh";
const clock = new THREE.Clock();
function animate(t) {
const time = clock.getDelta();
controlsModule.controls.update(time);
updateMesh(time);
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, cameraModule.activeCamera);
}
export default animate;
切换控制器效果
结语
本篇文章就简单阐述到这里了,每天学习一点点,做大做强!