书接上文,我新建了一个vite框架,引用了vue3与threejs,我新建了一个名字为ThreeJs的组件并在App.vue里引入,App.vue的代码如下:
<template>
<div>
<ThreeJs />
</div>
</template>
<script setup>
import ThreeJs from './components/ThreeJs.vue';
</script>
<style scoped></style>
ThreeJs.vue的代码如下: 首先写一个dom容器,用来装模型用的
<template>
<div ref="sceneContainer" class="scene-container"></div>
</template>
再给这个div写点样式
<style scoped>
.scene-container {
/* position: relative; */
width: 100vw;
height: 100vh;
}
</style>
接下来就是重点了 先在script里引入相应的vue方法
import { ref, onMounted, onUnmounted } from 'vue';
再引入three.js
import * as THREE from 'three';
再引入轨道控制器,有的小伙伴问了,控制器是干嘛的?个人理解,就是有了它你就可以放大、缩小、旋转里面的3D模型了
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; // 导入控制器
然后获取到刚刚的dom容器sceneContainer并初始化一些参数
const sceneContainer = ref(null);
let scene, camera, renderer, cube, controls;
接下来就是初始化的事情了,我们可以创建一个初始化函数initScene,将3D建模需要初始化的东西都放在里面
让我们看看这个初始化函数里面都做了哪些事情?
首先创建了场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff); // 给场景的背景添加了颜色
在这里,我给大家细说一下场景:
场景内可以放置物体、布置光源等,物体可以是各种形状的、各种颜色的,材质也可以不一样,光源可以是不同类型的、不同颜色的,也可以布置在不同的位置
场景可以想象成你要构建的 3D 画面的舞台,你在舞台上放置各种不同种类的道具(物体),布置不同的光源达到不同的效果
接下来我们要创建一个相机
camera = new THREE.PerspectiveCamera(75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
PerspectiveCamera是一种透视相机,模拟人眼的效果所看到的景象,是最常用的一种摄像机,它有四个参数PerspectiveCamera(fov, aspect, near, far);
fov — 摄像机视锥体垂直视野角度
aspect — 摄像机视锥体长宽比
near — 摄像机视锥体近端面
far — 摄像机视锥体远端面
不知道这四个参数具体传多少的时候就照着我的写就行,慢慢就懂了
创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
sceneContainer.value.appendChild(renderer.domElement);
WebGLRenderer就是一个渲染器的名称,里面的参数antialias: true表示抗锯齿,最后sceneContainer.value.appendChild(renderer.domElement);表示将这个渲染器放在sceneContainer这个dom里。
在Three.js中,渲染器(Renderer)是用来将场景中的物体和光源渲染到浏览器的画布上的组件。它负责计算出3D场景中物体的位置、颜色和光照效果,并将最终结果显示在屏幕上
可以简单理解为相机的洗相片功能,在你布置好舞台(场景),并在满意的位置对舞台(相机)进行拍摄录像后,你想将其导出为文件等呈现在你的电脑上,完成这个过程的就是渲染器
添加物体/模型
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
这里我们以正方体为例子,先创建了个几何体,再创建个材质,然后将集合体和材质合起来THREE.Mesh(geometry, material)变成物体,最后scene.add(cube)添加到场景中
添加坐标轴与控制器
// 5. 添加坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 6. 添加控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果
controls.dampingFactor = 0.25; // 阻尼因子
神说要有光
// 7. 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
到目前为止initScene函数里的所有内容都已经完成了,整体代码如下:
// 初始化场景
const initScene = () => {
// 1. 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff);
// 2. 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
// 3. 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
sceneContainer.value.appendChild(renderer.domElement);
// 4. 添加物体(立方体)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 5. 添加坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 6. 添加控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果
controls.dampingFactor = 0.25; // 阻尼因子
// 7. 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
};
再写个循环函数
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
controls.update(); // 更新控制器
renderer.render(scene, camera);
};
再写个窗口大小自适应函数
// 窗口大小自适应
const onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
在生命周期onMounted里初始化这些函数,并且监听窗口变化
// 生命周期
onMounted(() => {
initScene();
animate();
window.addEventListener('resize', onWindowResize);
});
在注销这个组件里也要取消监听,达到释放内存空间的效果
onUnmounted(() => {
window.removeEventListener('resize', onWindowResize);
sceneContainer.value.removeChild(renderer.domElement);
renderer.dispose();
controls.dispose();
});
下面是ThreeJs.vue组件的整体代码,大家可以参考一下
<template>
<div ref="sceneContainer" class="scene-container"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; // 导入控制器
// Three.js 核心对象
const sceneContainer = ref(null);
let scene, camera, renderer, cube, controls;
// 初始化场景
const initScene = () => {
// 1. 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff);
// 2. 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
// 3. 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
sceneContainer.value.appendChild(renderer.domElement);
// 4. 添加物体(立方体)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 5. 添加坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 6. 添加控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果
controls.dampingFactor = 0.25; // 阻尼因子
// 7. 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);
};
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
controls.update(); // 更新控制器
renderer.render(scene, camera);
};
// 窗口大小自适应
const onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
// 生命周期
onMounted(() => {
initScene();
animate();
window.addEventListener('resize', onWindowResize);
});
onUnmounted(() => {
window.removeEventListener('resize', onWindowResize);
sceneContainer.value.removeChild(renderer.domElement);
renderer.dispose();
controls.dispose();
});
</script>
<style scoped>
.scene-container {
/* position: relative; */
width: 100vw;
height: 100vh;
}
</style>
以上代码运行起来后的效果如下图
大家可以试着弄一下