Vue3中使用Three.js实现实时展示3D姿态

初始渲染

首先在模板中创建一个div​:

1
2
3
4
// src\components\ShowPosture\ShowPosture.vue
<template>
<div class="content" id="3d" />
</template>

然后在js中引入需要的Three.js组件

1
2
3
4
// src\components\ShowPosture\ShowPosture.js
import { WebGLRenderer, PerspectiveCamera, Scene, TextureLoader, BoxGeometry, MeshBasicMaterial, Mesh, AxesHelper } from 'three';
// 立方体的材质,需要用require引入
var mesh = require("../../assets/1.png");

setup函数中创建渲染需要的变量和函数:

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
// src\components\ShowPosture\ShowPosture.js
const renderer = new WebGLRenderer();
const camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
var renderCube = function () {
// 创建场景
const scene = new Scene();
// 设定渲染器大小
renderer.setSize(window.innerWidth / 2 - 20, window.innerHeight / 3);
// 设定场景空白处的颜色
renderer.setClearColor("#ffffff");
document.getElementById("3d").appendChild(renderer.domElement);
// 使用TextureLoader创建立方体的材质
const texture = new TextureLoader().load(mesh);
// 设定立方体的大小
const geometry = new BoxGeometry(10, 20, 5);
// 给立方体添加材质
const material = new MeshBasicMaterial({ map: texture });
const cube = new Mesh(geometry, material);
// 在场景中添加立方体
scene.add(cube);
// 添加坐标轴
const axesHelper = new AxesHelper(13);
scene.add(axesHelper);
camera.position.x = 0;
camera.position.y = -30;
camera.position.z = 0;
camera.lookAt(scene.position);
// 动画函数,实时修改角度的值,实现立方体的旋转
function animate() {
requestAnimationFrame(animate);
cube.rotation.x = angle[0] / 50;
cube.rotation.y = angle[1] / 50;
cube.rotation.z = angle[2] / 50;
// cube.rotation.x = 0;
// cube.rotation.y = 0;
// cube.rotation.z += 0.01;
renderer.render(scene, camera);
};
animate();
}

onMounted函数中调用renderCube函数:

1
onMounted(renderCube);

现在运行页面,页面上已经出现了场景,上面会有一个立方体,但是不会动:

我们可以把renderCube函数中的

1
2
3
cube.rotation.x = angle[0] / 50;
cube.rotation.y = angle[1] / 50;
cube.rotation.z = angle[2] / 50;

改成

1
2
3
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
cube.rotation.z += 0.01;

这时可以看到立方体开始自己旋转了。

实现实时渲染

为了可以实时接收立方体的旋转数据,我们需要连接WebSocket,并实时更新立方体的角度数据;因为页面上不仅仅有这一个组件,为了方便地实现跨组件的数据共享,这里使用了Pinia。

首先先创建一个store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src\store\data.js
import { defineStore } from 'pinia';

export const useStore = defineStore('data', {
state: () => {
return {
// angle就是立方体xyz三个角度旋转的角度
angle: [0, 0, 0],
}
},
actions: {
// 更新store中的数据
update(data) {
this.angle = data.Angle;
}
},
})

然后连接WebSocket,接受数据并更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useStore } from '@/store/data';
import { onMounted } from 'vue';

export default {
setup() {
var store = useStore();
var ws = null;
var connect = function () {
ws = new WebSocket("ws://**********");
ws.onmessage = function (evt) {
try {
// 使用store中的action更新数据
store.update(JSON.parse(evt.data));
} catch (e) { }
};
};
onMounted(connect);
},
}

ShowPosture.js中引入store,并监听数据变更:

1
2
import { onMounted, watch } from 'vue';
import { storeToRefs } from 'pinia';
1
2
3
4
5
var angleRef = storeToRefs(store).angle;
var angle = angleRef.value;
watch(angleRef, () => {
angle = angleRef.value;
});

在上面使用了Pinia的storeToRefs函数,是用于将store中的基础类型数据转换为响应式变量的,而angle是一个数组,是引用类型,它已经是响应性变量了,因此是可以直接被监听到的。但是这里使用了storeToRefs,因此我们需要监听angleRef.value。监听后,刷新页面连接WebSocket后,已经可以实现立方体的实时姿态展示了。

修改页面大小时改变场景的大小

渲染完成后,修改页面大小会发现场景的大小不会改变,很影响效果,因此需要加上修改页面大小时改变场景的大小的功能。

Index.vue中添加监听页面大小改变的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { onMounted, ref } from 'vue';
import ShowPosture from '@/components/ShowPosture/ShowPosture.vue';

export default {
setup() {
// 用于监听窗口大小,改变画板大小
var right3DWidth = ref(0);
var right3Dheight = ref(0);
var getWidthAndWatch = function () {
right3DWidth.value = document.getElementById("3d").clientWidth;
right3Dheight.value = document.getElementById("3d").clientHeight;
window.onresize = () => (() => {
right3DWidth.value = document.getElementById("3d").clientWidth;
right3Dheight.value = document.getElementById("3d").clientHeight;
})();
}
onMounted(getWidthAndWatch);

return { right3DWidth, right3Dheight }
},
components: {
ShowPosture,
}
}

注意获取元素高/宽度时有三种选择:

  • clientHeight:元素的像素高度,包含元素的高度+内边距,不包含水平滚动条,边框和外边距。
  • offsetHeight:元素的像素高度 包含元素的垂直内边距和边框,水平滚动条的高度,且是一个整数。
  • scrollHeight:元素内容的高度,包括溢出的不可见内容。

在这里选择的是clientHeight

Index.vue中的模板中引入ShowPosture组件时,添加两个props

1
<ShowPosture :right3DWidth="right3DWidth" :right3Dheight="right3Dheight" />

然后在ShowPosture.js中接受这两个props,并监听它们:

1
2
3
4
props: {
right3DWidth: Number,
right3Dheight: Number
}
1
2
3
4
5
6
var { right3Dheight, right3DWidth } = toRefs(props);
watch(right3DWidth, () => {
camera.aspect = right3DWidth.value / right3Dheight.value;
camera.updateProjectionMatrix();
renderer.setSize(right3DWidth.value, right3Dheight.value);
});

注意这里接收到的props已经失去响应性了,需要使用toRefs使其变成响应性变量。监听到高/宽度改变后,使用camera.updateProjectionMatrix更新相机的视角,再使用renderer.setSize更新场景大小,就完成了修改页面大小时改变场景的大小。