从零开始,搭建属于自己的数字孪生项目(第一期)

74 阅读5分钟

书接上文,我新建了一个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>

以上代码运行起来后的效果如下图

image.png

大家可以试着弄一下