初始渲染
首先在模板中创建一个div
:
1 2 3 4
| <template> <div class="content" id="3d" /> </template>
|
然后在js中引入需要的Three.js组件
1 2 3 4
| import { WebGLRenderer, PerspectiveCamera, Scene, TextureLoader, BoxGeometry, MeshBasicMaterial, Mesh, AxesHelper } from 'three';
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
| 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); 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; renderer.render(scene, camera); }; animate(); }
|
在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
| import { defineStore } from 'pinia';
export const useStore = defineStore('data', { state: () => { return { angle: [0, 0, 0], } }, actions: { 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.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
更新场景大小,就完成了修改页面大小时改变场景的大小。