Vue3集成ThreeJS实现3D效果,threejs+Vite+Vue3+TypeScript 实战课程【一篇文章精通系列】

news2024/11/18 2:31:22

在这里插入图片描述

Vue3集成ThreeJS实现3D效果,threejs+Vite+Vue3+TypeScript 实战课程【一篇文章精通系列】

    • 项目简介
    • 一、项目初始化
      • 1、添加一些依赖项
    • 二、创建3D【基础搭建】
      • 1、绘制板子,立方体,球体
      • 2、材质和光照
      • 3、材质和光照和动画
      • 4、性能监控
      • 5、交互控制
      • 6、响应窗口变化
    • 三、基础场景搭建
      • 1、创建基础场景【实现添加几何体和删除几何体】
      • 2、实现雾化场景
      • 3、重写材质
      • 4、常见几何体
      • 5、修改几何体属性
      • 6、相机切换
      • 7、相机跟随
    • 四、光照
      • 1、环境光
      • 2、点光源
      • 3、聚光灯
      • 4、平行光
      • 5、半球光
    • 五、小车案例
      • 1、基础环境搭建
      • 2、载入模型,实现轨道控制器
      • 3、实现模型颜色材质调整,轮子转动
      • 4、源代码下载

项目简介

这是一个使用Vue3,TypeScript,Vite和Three.js的项目。Vue3是一个流行的JavaScript框架,用于构建用户界面。TypeScript是一种静态类型的编程语言,它是JavaScript的超集,可以编译成纯JavaScript。Vite是一个由Evan You开发的新的前端构建工具,能够提供快速的冷启动和即时热更新。

Three.js是一个轻量级的3D库,能够让我们在任何浏览器中创建和显示动画的3D计算机图形。在该项目中,我们将Three.js集成到了Vue3和TypeScript的环境中,使得我们可以在Vue组件中使用Three.js来创建3D图形。

此外,项目中还可能包含一些封装了Three.js的代码,以便于更方便的使用Three.js进行3D开发。

这样的技术组合可以让我们在前端环境中实现复杂的3D可视化效果,为项目增加更丰富的视觉体验。

🔸3D模型下载网站:https://sketchfab.com/feed
🔸3D人物动作绑定:www.mixamo.com
🔸3D角色生产工具:https://readyplayer.me/
🔸模型压缩网站:gltf.report
🔸查找天空背景:google key words: equirectangular sky / skybox background
🔸材质贴图素材:https://www.textures.com
🔸hdr素材库(环境贴图): https://polyhaven.com
🔸二次元风3D角色生产软件VRoid Studio: https://vroid.com/en/studio

🕹Sketchfab公用账号:
Login: lingo3dchina@gmail.com
PW: Lingo3dxoxo
Code:640841

一、项目初始化

npm install -g vite


npm init vite@latest threejs-vite-vue -- --template vue


cd threejs-vite-vue

npm install

npm run dev

项目创建成功
在这里插入图片描述注意threejs的版本

在这里插入图片描述 "@types/three": "^0.155.1",

项目创建成功在IDE当中导入项目

1、添加一些依赖项

npm install vue-router

npm install three

npm install @types/three -D

npm install ant-design-vue

在这里插入图片描述在这里插入图片描述创建一些路由相关
在这里插入图片描述在这里插入图片描述在这里插入图片描述

import {createRouter,createWebHistory,RouteRecordRaw} from "vue-router";
const routes: RouteRecordRaw[] = [
]
const router = createRouter({
    history:createWebHistory(),
    routes
})
router.beforeEach((to)=>{
    document.title = 'three+vite+vue3'+to.meta.title as string
})

export default router

在这里插入图片描述

import { createApp } from 'vue'
import './style.css';
import Antd from 'ant-design-vue';
import App from './App.vue';
import router from './router/index';
import 'ant-design-vue/dist/reset.css';

let app = createApp(App)
app.use(router)
app.use(Antd)
app.mount('#app')

在这里插入图片描述在这里插入图片描述

import {RouteRecordRaw} from "vue-router";
const chapter1 : RouteRecordRaw[] = [
]

export default chapter1;

在这里插入图片描述

import {createRouter,createWebHistory,RouteRecordRaw} from "vue-router";
import chapter1 from "./chapter1";
const routes: RouteRecordRaw[] = [
    ...chapter1
]
const router = createRouter({
    history:createWebHistory(),
    routes
})
export default router

在这里插入图片描述

<template>
  <router-view></router-view>
</template>
<script setup>
</script>
<style scoped>
</style>

在这里插入图片描述

<template>
  <div>
    第一个场景
  </div>
</template>
<script>
export default {
  name: "index"
}
</script>
<style scoped>
</style>

二、创建3D【基础搭建】

在这里插入图片描述

.container{
    width: 100vw;
    height: 100vh;
}

1、绘制板子,立方体,球体

Three.js来绘制一个简单的3D场景,包括一个平面(板子)、一个立方体和一个球体
在这里插入图片描述

<template>
  <div ref="containerRef" class="container">
  </div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from "vue";
import {
  AxesHelper, BoxGeometry,
  Color,
  Mesh,
  MeshBasicMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene, SphereGeometry,
  WebGLRenderer
} from "three";

const containerRef = ref<HTMLDivElement>()
//创建场景
const scene = new Scene();
//创建摄像机
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//设置摄像机位置
camera.position.set(-30,40,30)
//设置摄像机朝向
camera.lookAt(scene.position)

//重置webGL的颜色
const renderer =  new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)

//添加坐标系
const ases = new AxesHelper(20)
scene.add(ases)

//绘制板子,设置板子的宽度为60,设置板子的高度为20
const planeGeometry = new PlaneGeometry(60,20);
const meshBasicMaterial = new MeshBasicMaterial({color:0xcccccc});//设置材质颜色
const plane = new Mesh(planeGeometry,meshBasicMaterial)

plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15
plane.position.y = 0
plane.position.z = 0
scene.add(plane)

//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry = new BoxGeometry(4,4,4)
const cubeMaterial = new MeshBasicMaterial({color:0xff0000,wireframe:true})
const cube = new Mesh(cubeGeometry,cubeMaterial)
cube.position.set(2,2,2)
scene.add(cube)

//绘制球体,设置球体的半径为4
const sphereGeometry = new SphereGeometry(4)
const sphereMaterial = new MeshBasicMaterial({
  color: 0x7777ff,
  wireframe:true
})
const sphere = new Mesh(sphereGeometry,sphereMaterial)
sphere.position.x = 15
sphere.position.y = 4
sphere.position.z = 2
scene.add(sphere)

onMounted(()=>{
  //设置摄像头朝向
  containerRef.value?.appendChild(renderer.domElement)
  renderer.render(scene,camera)
})

</script>
<style scoped>

</style>

在这里插入图片描述

2、材质和光照

在Three.js中,材质和光照是让物体看起来更为真实的关键因素。材质定义了物体表面的外观,如颜色、纹理和光照效果。Three.js提供了多种类型的材质,适用于不同的光照效果。

物理基础渲染(Physically Based Rendering, PBR)是一种基于物理的渲染技术,使用物理基础渲染代码和材料处理技术来模拟光线和材料之间的物理相互作用,以创建逼真的材料外观和光照效果。这种渲染技术可以提供更真实的阴影,高光,反射和漫反射效果,使场景看起来更加真实。Three.js核心也包含了与Unreal、Unity、Disney和Pixar等巨头使用的相同的基于物理的渲染 (PBR) 算法。

对于纹理的应用,可以通过加载图片并设置其重复模式、采样模式以及重复次数来实现贴图效果。例如,创建一个地平面,并用下方展示的 2x2 像素的黑白格图片来作为纹理。首先加载这个纹理,设置重复模式(wrapS, wrapT),采样模式(magFilter)以及重复的次数。因为贴图是 2x2 大小,通过设置成平铺模式,并且重复次数是边长的一半,就可以让每个格子正好是1个单位的大小。

设置导航菜单组件
在这里插入图片描述

<template>
  <a-menu mode="horizontal" style="position: fixed">
    <a-sub-menu key="demo">
      <template #title>
        第一章
      </template>
      <a-menu-item key="1">
        <router-link to="/"> 第一个场景 </router-link>
      </a-menu-item>
      <a-menu-item key="2">
        <router-link to="/chapter1/2"> 第一个场景 </router-link>
      </a-menu-item>
    </a-sub-menu>
  </a-menu>
  <router-view></router-view>
</template>
<script setup>
</script>
<style scoped>
</style>

复制index,生成index2
在这里插入图片描述

<template>
  <div ref="containerRef" class="container">
  </div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from "vue";
import {
  AxesHelper, BoxGeometry,
  Color,
  Mesh,
  MeshBasicMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene, SphereGeometry,
  WebGLRenderer
} from "three";

const containerRef = ref<HTMLDivElement>()
//创建场景
const scene = new Scene();
//创建摄像机
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//设置摄像机位置
camera.position.set(-30,40,30)
//设置摄像机朝向
camera.lookAt(scene.position)

//重置webGL的颜色
const renderer =  new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)

//添加坐标系
const ases = new AxesHelper(20)
scene.add(ases)

//绘制板子,设置板子的宽度为60,设置板子的高度为20
const planeGeometry = new PlaneGeometry(60,20);
const meshBasicMaterial = new MeshBasicMaterial({color:0xcccccc});//设置材质颜色
const plane = new Mesh(planeGeometry,meshBasicMaterial)

plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15
plane.position.y = 0
plane.position.z = 0
scene.add(plane)

//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry = new BoxGeometry(4,4,4)
const cubeMaterial = new MeshBasicMaterial({color:0xff0000,wireframe:true})
const cube = new Mesh(cubeGeometry,cubeMaterial)
cube.position.set(2,2,2)
scene.add(cube)

//绘制球体,设置球体的半径为4
const sphereGeometry = new SphereGeometry(4)
const sphereMaterial = new MeshBasicMaterial({
  color: 0x7777ff,
  wireframe:true
})
const sphere = new Mesh(sphereGeometry,sphereMaterial)
sphere.position.x = 15
sphere.position.y = 4
sphere.position.z = 2
scene.add(sphere)

onMounted(()=>{
  //设置摄像头朝向
  containerRef.value?.appendChild(renderer.domElement)
  renderer.render(scene,camera)
})






</script>
<style scoped>

</style>

在这里插入图片描述

import {RouteRecordRaw} from "vue-router";
import Index from '../lesson/chapter1/index.vue'
import Index2 from '../lesson/chapter1/index2.vue'

const chapter1 : RouteRecordRaw[] = [
    {
        path:'/',
        component: Index,
        meta:{
            title:"第一个场景"
        }
    },
    {
        path:'/chapter1/2',
        component: Index2,
        meta:{
            title:"第二个场景"
        }
    }
]
export default chapter1;

实现第二个场景

<template>
  <div ref="containerRef" class="container">
  </div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from "vue";
import {
  AxesHelper, BoxGeometry,
  Color,
  Mesh,
  MeshBasicMaterial, MeshLambertMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene, SphereGeometry, SpotLight,
  WebGLRenderer
} from "three";

const containerRef = ref<HTMLDivElement>()
//创建场景
const scene = new Scene();
//创建摄像机
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//设置摄像机位置
camera.position.set(-30,40,30)
//设置摄像机朝向
camera.lookAt(scene.position)

//重置webGL的颜色
const renderer =  new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)
renderer.shadowMap.enabled = true

const spotLight = new SpotLight(0xffffff)
spotLight.castShadow = true
spotLight.position.set(-40,60,-10)
scene.add(spotLight)

//添加坐标系
const axes = new AxesHelper(20)
scene.add(axes)

//绘制板子,设置板子的宽度为60,设置板子的高度为20
const planeGeometry = new PlaneGeometry(60,20);
const meshBasicMaterial = new MeshLambertMaterial({color:0xcccccc});//设置材质颜色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //设置可以接收阴影
plane.rotation.x = -0.5 * Math.PI;
//plane.position.x = 15
//plane.position.y = 0
//plane.position.z = 0

scene.add(plane)


//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry = new BoxGeometry(4,4,4)
const cubeMaterial = new MeshLambertMaterial({color:0xff0000,wireframe:false})
const cube = new Mesh(cubeGeometry,cubeMaterial)
cube.castShadow = true
cube.position.set(2,2,2)
scene.add(cube)

//绘制球体,设置球体的半径为4
const sphereGeometry = new SphereGeometry(4)
const sphereMaterial = new MeshLambertMaterial({
  color: 0x7777ff,
  wireframe:false
})

const sphere = new Mesh(sphereGeometry,sphereMaterial)
sphere.castShadow = true
sphere.position.x = 15
sphere.position.y = 4
sphere.position.z = 2
scene.add(sphere)

onMounted(()=>{
  //设置摄像头朝向
  containerRef.value?.appendChild(renderer.domElement)
  renderer.render(scene,camera)
})

</script>
<style scoped>

</style>

在这里插入图片描述

3、材质和光照和动画

Three.js提供了一套强大的动画系统,可以应用于物体的位置、旋转、缩放、材质的颜色或不透明度等各个方面。这套系统中主要包括了KeyFrameTrack、AnimationClip、AnimationMixer和AnimationAction四个组件。

在制作动画时,我们通常会使用关键帧动画,即在不同时间点设置关键帧,然后由动画系统通过补间过程自动填补各关键帧之间的变化。例如,要为一个弹跳的球设置动画,只需要指定弹跳的顶部和底部的点,Three.js将在这两点之间的所有点上平滑地生成动画。此外,我们还可以通过合成和混合多个动画来创造出更复杂的效果。
复制index2创建index3
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<template>
  <div ref="containerRef" class="container">
  </div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from "vue";
import {
  AxesHelper, BoxGeometry,
  Color,
  Mesh,
  MeshBasicMaterial, MeshLambertMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene, SphereGeometry, SpotLight,
  WebGLRenderer
} from "three";

const containerRef = ref<HTMLDivElement>()
//创建场景
const scene = new Scene();
//创建摄像机
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//设置摄像机位置
camera.position.set(-30,40,30)
//设置摄像机朝向
camera.lookAt(scene.position)

//重置webGL的颜色
const renderer =  new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)
renderer.shadowMap.enabled = true

const spotLight = new SpotLight(0xffffff)
spotLight.castShadow = true
spotLight.position.set(-40,60,-10)
scene.add(spotLight)

//添加坐标系
const axes = new AxesHelper(20)
scene.add(axes)

//绘制板子,设置板子的宽度为60,设置板子的高度为20
const planeGeometry = new PlaneGeometry(100,50);
const meshBasicMaterial = new MeshLambertMaterial({color:0xcccccc});//设置材质颜色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //设置可以接收阴影
plane.rotation.x = -0.5 * Math.PI;
//plane.position.x = 15
//plane.position.y = 0
//plane.position.z = 0

scene.add(plane)


//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry = new BoxGeometry(4,4,4)
const cubeMaterial = new MeshLambertMaterial({color:0xff0000,wireframe:false})
const cube = new Mesh(cubeGeometry,cubeMaterial)
cube.castShadow = true
cube.position.set(2,2,2)
scene.add(cube)


//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry1 = new BoxGeometry(4,4,4)
const cubeMaterial1 = new MeshLambertMaterial({color:0xff0000,wireframe:false})
const cube1 = new Mesh(cubeGeometry1,cubeMaterial1)
cube1.castShadow = true
cube1.position.set(-10,2,2)
scene.add(cube1)



//绘制球体,设置球体的半径为4
const sphereGeometry = new SphereGeometry(4)
const sphereMaterial = new MeshLambertMaterial({
  color: 0x7777ff,
  wireframe:false
})

const sphere = new Mesh(sphereGeometry,sphereMaterial)
sphere.castShadow = true
sphere.position.x = 15
sphere.position.y = 4
sphere.position.z = 2
scene.add(sphere)


//控制物体运动
let step = 0;

function renderScene() {
  step += 0.04;

  cube.rotation.x += 0.02;
  cube.rotation.y += 0.02;
  cube.rotation.z += 0.02;

  cube1.rotation.x += -0.02;
  cube1.rotation.y += -0.02;
  cube1.rotation.z += -0.02;
  cube1.scale.set((2 + 1 * Math.cos(step)), (2 + 1 * Math.cos(step)), (2 + 1 * Math.cos(step)));

  //控制物体
  sphere.position.x = 20 + 10 * Math.cos(step); //cos为数据当中的函数 余弦函数
  sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));  //abs为绝对值  sin为正弦函数

  requestAnimationFrame(renderScene)
  renderer.render(scene,camera)
}
renderScene()

onMounted(()=>{
  //设置摄像头朝向
  containerRef.value?.appendChild(renderer.domElement)
  renderer.render(scene,camera)
})

</script>
<style scoped>

</style>

在这里插入图片描述

4、性能监控

Three.js的性能监控工具Stats.js是一个强大的插件,它能够监测帧率、内存等数据的变化。在动画或网页开发中,帧率是衡量和描述动画是否流畅的一个重要单位。Stats.js可以帮助开发者实时了解Three.js的渲染性能,尤其是渲染帧率(FPS),即每秒钟完成的渲染次数。理想状态下,渲染帧率应该达到每秒60次。

在使用Stats.js时,首先需要引入相关的脚本文件。然后,实例化一个Stats对象,并将该对象生成的DOM元素添加到页面中。通过这种方式,我们可以在开发过程中实时监控Three.js的性能,及时发现并解决可能存在的问题,从而提升用户体验。

安装stats.js插件

npm install stats.js

复制index3.vue创建index4.vue
在这里插入图片描述

    import index4 from '../lesson/chapter1/index4.vue'

    ,
    {
        path:'/chapter1/4',
        component: index4,
        meta:{
            title:"性能监控"
        }
    }

在这里插入图片描述
在这里插入图片描述

 <div ref="statsRef"></div>

const statsRef = ref<HTMLDivElement>()

const stats = new Stats()
stats.showPanel(0)

在这里插入图片描述

  stats.update()

  //创建场景
  const scene = new Scene();
  stats.dom.style.top = "50px"
  statsRef.value?.append(stats.dom)

访问第四个场景
http://127.0.0.1:5173/chapter1/4

在这里插入图片描述

5、交互控制

dat.gui@0.7.9是一个轻量级的JavaScript库,它的主要功能是帮助用户添加交互式控制面板,以便在3D场景中调整对象参数并实时预览结果。

复制一下index4.vue 为index5.vue
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

 {
        path:'/chapter1/5',
        component: index5,
        meta:{
            title:"交互控制"
        }
    }

在这里插入图片描述

     <a-menu-item key="5">
        <router-link to="/chapter1/5"> 第五个场景 </router-link>
      </a-menu-item>

安装dat.gui

npm install dat.gui@0.7.9

在这里插入图片描述

npm install @types/dat.gui@0.7.9 -D

在这里插入图片描述
在这里插入图片描述

import * as dat from "dat.gui"

在这里插入图片描述

const controlRef = ref({
  rotationSpeed:0.02,
  bouncingSpeed:0.03,
})
const gui = new dat.GUI();
gui.add(controlRef.value,"rotationSpeed",0,0.5)
gui.add(controlRef.value,"bouncingSpeed",0,0.5)



  step += 0.04;

  cube.rotation.x += controlRef.value.rotationSpeed;
  cube.rotation.y += controlRef.value.rotationSpeed;
  cube.rotation.z += controlRef.value.rotationSpeed;

  cube1.rotation.x += -controlRef.value.rotationSpeed;
  cube1.rotation.y += -controlRef.value.rotationSpeed;
  cube1.rotation.z += -controlRef.value.rotationSpeed;

  step += controlRef.value.bouncingSpeed;


在这里插入图片描述
放置重复初始化

if(document.querySelectorAll(".dg.ac>.dg.main.a").length === 0){
  const gui = new dat.GUI()
  gui.add(controlRef.value,"rotationSpeed",0,0.5)
  gui.add(controlRef.value,"bouncingSpeed",0,0.5)
}

index5.vue全部代码

<template>
  <div ref="statsRef"></div>
  <div ref="containerRef" class="container">
  </div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from "vue";
import {
  AxesHelper, BoxGeometry,
  Color,
  Mesh,
  MeshBasicMaterial, MeshLambertMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene, SphereGeometry, SpotLight,
  WebGLRenderer
} from "three";
import Stats from "stats.js"
import * as dat from "dat.gui"

const containerRef = ref<HTMLDivElement>()
const statsRef = ref<HTMLDivElement>()

const stats = new Stats()
stats.showPanel(0)

//创建场景
const scene = new Scene();
//创建摄像机
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//设置摄像机位置
camera.position.set(-30,40,30)
//设置摄像机朝向
camera.lookAt(scene.position)

//重置webGL的颜色
const renderer =  new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)
renderer.shadowMap.enabled = true

const spotLight = new SpotLight(0xffffff)
spotLight.castShadow = true
spotLight.position.set(-40,60,-10)
scene.add(spotLight)

//添加坐标系
const axes = new AxesHelper(20)
scene.add(axes)

//绘制板子,设置板子的宽度为60,设置板子的高度为20
const planeGeometry = new PlaneGeometry(100,50);
const meshBasicMaterial = new MeshLambertMaterial({color:0xcccccc});//设置材质颜色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //设置可以接收阴影
plane.rotation.x = -0.5 * Math.PI;
//plane.position.x = 15
//plane.position.y = 0
//plane.position.z = 0

scene.add(plane)


//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry = new BoxGeometry(4,4,4)
const cubeMaterial = new MeshLambertMaterial({color:0xff0000,wireframe:false})
const cube = new Mesh(cubeGeometry,cubeMaterial)
cube.castShadow = true
cube.position.set(2,2,2)
scene.add(cube)


//绘制立方体,设置板子的长宽高分别是4,4,4
const cubeGeometry1 = new BoxGeometry(4,4,4)
const cubeMaterial1 = new MeshLambertMaterial({color:0xff0000,wireframe:false})
const cube1 = new Mesh(cubeGeometry1,cubeMaterial1)
cube1.castShadow = true
cube1.position.set(-10,2,2)
scene.add(cube1)



//绘制球体,设置球体的半径为4
const sphereGeometry = new SphereGeometry(4)
const sphereMaterial = new MeshLambertMaterial({
  color: 0x7777ff,
  wireframe:false
})

const sphere = new Mesh(sphereGeometry,sphereMaterial)
sphere.castShadow = true
sphere.position.x = 15
sphere.position.y = 4
sphere.position.z = 2
scene.add(sphere)

const controlRef = ref({
  rotationSpeed:0.02,
  bouncingSpeed:0.03,
})

if(document.querySelectorAll(".dg.ac>.dg.main.a").length === 0){
  const gui = new dat.GUI()
  gui.add(controlRef.value,"rotationSpeed",0,0.5)
  gui.add(controlRef.value,"bouncingSpeed",0,0.5)
}

//控制物体运动
let step = 0;

function renderScene() {

  stats.update()

  step += 0.04;

  cube.rotation.x += controlRef.value.rotationSpeed;
  cube.rotation.y += controlRef.value.rotationSpeed;
  cube.rotation.z += controlRef.value.rotationSpeed;

  cube1.rotation.x += -controlRef.value.rotationSpeed;
  cube1.rotation.y += -controlRef.value.rotationSpeed;
  cube1.rotation.z += -controlRef.value.rotationSpeed;

  step += controlRef.value.bouncingSpeed;

  cube1.scale.set((2 + 1 * Math.cos(step)), (2 + 1 * Math.cos(step)), (2 + 1 * Math.cos(step)));
  //控制物体
  sphere.position.x = 20 + 10 * Math.cos(step); //cos为数据当中的函数 余弦函数
  sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));  //abs为绝对值  sin为正弦函数

  requestAnimationFrame(renderScene)
  renderer.render(scene,camera)
}
renderScene()

onMounted(()=>{

  //创建场景
  const scene = new Scene();
  stats.dom.style.top = "50px"
  statsRef.value?.append(stats.dom)

  //设置摄像头朝向
  containerRef.value?.appendChild(renderer.domElement)
  renderer.render(scene,camera)
})

</script>
<style scoped>

</style>

6、响应窗口变化

和之前一样创建index6.vue
在这里插入图片描述

/*
监听在窗口变化的时候重新设置大小
* */
window.addEventListener('resize',()=>{
  camera.aspect = window.innerWidth / window.innerHeight;
  //更新相机投影矩阵
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth,window.innerHeight)
})

在这里插入图片描述

三、基础场景搭建

1、创建基础场景【实现添加几何体和删除几何体】

在这里插入图片描述
将index6复制到chapter2下的index

在这里插入图片描述


const controlRef = ref({
  rotationSpeed:0.02,
  bouncingSpeed:0.03,
  numberOfObjects:0,
  addCube:function (){
    //绘制立方体,设置板子的长宽高分别是4,4,4
    const cubeGeometry = new BoxGeometry(4,4,4)
    const cubeMaterial = new MeshLambertMaterial({color:0xff0000,wireframe:false})
    const cube = new Mesh(cubeGeometry,cubeMaterial)
    cube.name = "cube-"+scene.children.length
    cube.castShadow = true
    cube.position.x = -30 + Math.round((Math.random() * 60))
    cube.position.y =  Math.round((Math.random() * 5))
    cube.position.z = -20 + Math.round((Math.random() * 40))
    scene.add(cube)
    this.numberOfObjects = scene.children.length
  },
  removeCube:function (){
    const allChildren = scene.children;
    const lastObject = allChildren[allChildren.length - 1];
    if(lastObject instanceof Mesh && lastObject.name.startsWith('cube')){
      scene.remove(lastObject);
    }
    this.numberOfObjects = scene.children.length
  }
})

在这里插入图片描述


if(document.querySelectorAll(".dg.ac>.dg.main.a").length === 0){
  const gui = new dat.GUI()
  gui.add(controlRef.value,"addCube")
  gui.add(controlRef.value,"removeCube")
  gui.add(controlRef.value,"numberOfObjects").listen()
  gui.add(controlRef.value,"rotationSpeed",0,0.5)
  gui.add(controlRef.value,"bouncingSpeed",0,0.5)

}

在这里插入图片描述


  stats.update()

  //遍历场景当中的所有内容
  scene.traverse((e) =>{
    /*
    * 
    if ( e instanceof Mesh && e != plane){
      e.rotation.x += controlRef.value.rotationSpeed;
      e.rotation.y += controlRef.value.rotationSpeed;
      e.rotation.z += controlRef.value.rotationSpeed;
    }
    */

    if ( e.name.startsWith('cube')){
      e.rotation.x += controlRef.value.rotationSpeed;
      e.rotation.y += controlRef.value.rotationSpeed;
      e.rotation.z += controlRef.value.rotationSpeed;
    }
  })

实现点击添加cube和删除cube
在这里插入图片描述

2、实现雾化场景

雾化效果是一种常见的视觉效果,它可以使场景中的物体看起来更加模糊和透明。在Three.js中,可以通过设置材质的透明度和混合模式来实现雾化效果
在这里插入图片描述

addFog:function (){
    scene.fog = new Fog(0xffffff,0.015,100)
    this.numberOfObjects = scene.children.length
  }
gui.add(controlRef.value,"addFog")

http://127.0.0.1:5173/chapter2/2
在这里插入图片描述
移除雾化
在这里插入图片描述

removeFog:function (){
    scene.fog = null
  }
  gui.add(controlRef.value,"removeFog")

在这里插入图片描述
在这里插入图片描述

3、重写材质

在Three.js中,材质是定义物体外观的关键。通过创建自定义材质,可以对物体的外观进行更精细的控制,包括如何设置材质的颜色、纹理和透明度等属性。
在这里插入图片描述

toggleMaterial:function (){
    if(!scene.overrideMaterial){
      scene.overrideMaterial = new MeshLambertMaterial({
        color:0xffffff,
      })
    }else{
      scene.overrideMaterial =null
    }
  }

 gui.add(controlRef.value,"toggleMaterial")

在这里插入图片描述

4、常见几何体

在Three.js中,几何体是一个数据结构,它包含了用于描述三维物体的基本信息,如顶点(vertices)、线(lines)和面(faces)。几何体可以被用来定义物体的形状和大小。

常见的几何体类型有以下几种:

BoxGeometry(立方体几何体):通过指定宽度、高度和深度来创建一个立方体。
SphereGeometry(球体几何体):通过指定半径来创建一个球体。
CylinderGeometry(圆柱体几何体):通过指定高度、半径和圆周上的段数来创建一个圆柱体。
PlaneGeometry(平面几何体):一种基础的二维几何体,可以用来绘制平面。
ConeGeometry(圆锥体几何体):通过指定高度、底部半径和顶部半径,以及圆周上的段数来创建一个圆锥体。
TubularGeometry(管状几何体):这是一种具有圆形截面的管道形状,需要指定管道的中心轴线、直径、高度和圆周上的段数。
在这里插入图片描述


const geoms:BufferGeometry[] = []
geoms.push(new CylinderGeometry(1,4,8))
geoms.push(new BoxGeometry(2,2,2))
geoms.push(new OctahedronGeometry(3))
geoms.push(new TetrahedronGeometry(3))
geoms.push(new TorusGeometry(3,1,10,10))
//材质
const materials = [
  new MeshLambertMaterial({
    color:Math.random() * 0xffffff,
    flatShading:true
  }),
  new MeshBasicMaterial({
    color: 0x000000,
    wireframe:true
  })
]
geoms.forEach((g,i) =>{
  const mesh = createMultiMaterialObject(g,materials)
  mesh.castShadow = true
  mesh.position.x = -24 + i * 10,
  mesh.position.y = 4,
  scene.add(mesh)
})

在这里插入图片描述

5、修改几何体属性

在Three.js中,几何体的属性可以通过修改其顶点、线和面的数据来改变物体的形状和大小。以下是一些常见的修改几何体属性的方法:

修改顶点数据:通过修改几何体的vertices属性,可以改变物体的形状。例如,可以将一个立方体的顶点数据修改为一个球体的顶点数据,从而创建一个球形物体。

修改线数据:通过修改几何体的lines属性,可以改变物体的边界线。例如,可以将一个立方体的边线修改为一个圆柱体的边线,从而创建一个圆柱形物体。

修改面数据:通过修改几何体的faces属性,可以改变物体的表面。例如,可以将一个立方体的面修改为一个球体的面,从而创建一个球形物体。

修改材质属性:通过修改几何体的material属性,可以改变物体的颜色、纹理和透明度等视觉效果。例如,可以将一个立方体的材质修改为一个半透明的红色材料,从而创建一个半透明的红色立方体。

需要注意的是,修改几何体属性需要对Three.js的底层实现有一定的了解,并且需要注意性能问题。如果频繁地修改几何体属性,可能会导致性能下降。因此,在实际应用中,应该根据需求选择合适的方法来修改几何体属性。
在这里插入图片描述

scaleX:1,
  scaleY:1,
  scaleZ:1,
  positionX:1,
  positionY:1,
  positionZ:1,
  translateZ:1,
  translateY:1,
  translateX:1,
  obj:{
    x:0,
    y:0,
    z:0,
  },
  translate:function (){
    this.obj.x = this.translateX
    this.obj.y = this.translateY
    this.obj.z = this.translateZ
  }

在这里插入图片描述

/*设置大小*/
  const scaleFolder = gui.addFolder("scale")
  scaleFolder.add(controlRef.value,"scaleX",0,5);
  scaleFolder.add(controlRef.value,"scaleY",0,5);
  scaleFolder.add(controlRef.value,"scaleZ",0,5);
  /*设置位置*/
  const positionFolder = gui.addFolder("position")
  positionFolder.add(controlRef.value,"positionX",-5,5);
  positionFolder.add(controlRef.value,"positionY",-5,5);
  positionFolder.add(controlRef.value,"positionZ",-5,5);

  /*设置斜角位置*/
  const translateFolder = gui.addFolder("translate")
  translateFolder.add(controlRef.value,"translateX",-5,5);
  translateFolder.add(controlRef.value,"translateY",-5,5);
  translateFolder.add(controlRef.value,"translateZ",-5,5);
  translateFolder.add(controlRef.value,"translate");

在这里插入图片描述

watch(()=>controlRef.value.obj,(n)=>{
  cube.translateX(n.x)
  cube.translateY(n.y)
  cube.translateZ(n.z)
},{deep:true})

在这里插入图片描述

6、相机切换

在Three.js中,相机是用于渲染场景的工具。主要包括透视相机(PerspectiveCamera)和正交相机(OrthographicCamera)。

透视相机可以创建具有深度感的三维效果,而正交相机则可以在二维平面上进行投影。

实现相机视角的切换,主要有两种方法。一种是使用Tween.js库来实现平滑过渡的效果。Tween.js库可以很容易实现两个值之间的过度,中间值都会自动计算出来。

另一种是通过鼠标拉拽来改变相机的位置、旋转角度等,比如使用OrbitControls类。

OrbitControls类是Three.js提供的鼠标、方向键与场景交互的控件,通过鼠标的操作可以改变相机的视角,从而改变视觉,使得视觉效果更具有真实感。

此外,如果想要切换不同的场景,可以通过创建多个场景对象,并在每个场景中添加不同的模型、灯光等元素。

使用renderer.render(scene, camera)方法在渲染循环中渲染当前场景,使用scene.dispose()方法清除当前场景中的元素,释放内存。当需要切换到下一个场景时,重复上述步骤,并将下一个场景设置为当前场景。

设置场景和多个物体
在这里插入图片描述

//绘制板子,设置板子的宽度为60,设置板子的高度为20
const planeGeometry = new PlaneGeometry(100,50,1);
const meshBasicMaterial = new MeshLambertMaterial({color:0xffffff});//设置材质颜色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //设置可以接收阴影
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15
plane.position.y = 0
plane.position.z = 0

scene.add(plane)

const cubeGeometryC = new BoxGeometry(4,4,4)
const cubeMaterialC = new MeshLambertMaterial({color:0xff0000,wireframe:false})
const cube = new Mesh(cubeGeometryC,cubeMaterialC)
cube.castShadow = true
cube.position.set(2,2,2)
scene.add(cube)

for (let j = 0;j < planeGeometry.parameters.height / 2;j++){
  for (let i = 0;i < planeGeometry.parameters.width / 2;i++){
    const cube = new Mesh(cubeGeometryC,cubeMaterialC)
    cube.position.z = -(planeGeometry.parameters.height / 2) + 2 + j * 5;
    cube.position.x = -(planeGeometry.parameters.height / 2) + 2 + i * 5;
    cube.position.y = 0;
    scene.add(cube)
  }
}

在这里插入图片描述

设置切换相机
在这里插入图片描述

 gui.add(controlRef.value,"camera").listen()
  gui.add(controlRef.value,"switchCamera")

在这里插入图片描述

watch(()=>controlRef.value.camera,(n)=>{
  if(n === 'Orthographic'){
    cameraRef.value = new OrthographicCamera(window.innerWidth / -16 ,window.innerWidth / 16 ,window.innerHeight / 16 ,window.innerHeight / -16,-200,500 )
    cameraRef.value.position.set(-120,60,180)
    cameraRef.value.lookAt(scene.position)
  }else{
    cameraRef.value = new PerspectiveCamera(45,window.innerWidth / window.innerHeight ,0.1,1000 )
    cameraRef.value.position.set(-120,60,180)
    cameraRef.value.lookAt(scene.position)
  }
})

在这里插入图片描述
在这里插入图片描述

7、相机跟随

在Three.js中,相机跟随物体的技术广泛应用于实现如游戏中的摄像机跟随角色、VR中的视点跟踪等效果。

要实现这一功能,首先需要获取到目标物体(例如一个游戏角色或者一个3D模型)的位置信息,然后将相机的位置设置为该物体的对应位置,从而实现视角的跟随。

此外,关于具体的实现方式,有多种可选的策略。

如果你想要创建一个第一人称视角的效果,可以使用键盘的WASD键控制相机的移动方向;而如果你希望实现第三人称视角的效果,则可以通过鼠标来控制相机的视角朝向。

另外,对于复杂的场景,比如管道内的视野展示或者物体在三维空间中任意方向移动的情况,你可能需要结合使用一些额外的工具和方法。

例如,你可以创建一个管道模型来帮助你观察物体的运动方向,并通过调整相机的位置和朝向,使得镜头能够紧密地跟随物体的移动。
在这里插入图片描述


const lookAtGeom = new SphereGeometry(20)
const lookAtMesh = new Mesh(
    lookAtGeom,
    new MeshLambertMaterial({
      color:0xff0000
    })
)
scene.add(lookAtMesh)

//控制物体运动
let step = 0;

function renderScene() {

  stats.update()

  if (cameraRef.value){
    step += 0.01
    const x = 10 + 100 * Math.sin(step)
    cameraRef.value.lookAt(new Vector3(x,10,0))  // Vector3是三维的坐标

    lookAtMesh.position.copy(new Vector3(x,10,0))
    cameraRef.value.lookAt(new Vector3(x,10,0))

    renderer.render(scene,cameraRef.value)
  }
}  

在这里插入图片描述

四、光照

1、环境光

Three.js中环境光(AmbientLight)是一种全局光照,它能够均匀地照亮场景中的物体。

与点光源和平行光源不同,环境光不会直接照亮物体,而是与场景中的颜色相乘,从而使得物体的颜色变暗或变亮。

环境光通常用于模拟全局的光照效果,例如在室外场景中模拟太阳的光线、室内场景中模拟灯光的反射等。

通过调整环境光的颜色和强度,可以改变整个场景的亮度和色调,从而增强渲染的真实感。
在这里插入图片描述

ambientColor:"#0c0c0c"



const ambientLight = new AmbientLight(controlRef.value.ambientColor)
scene.add(ambientLight)

watch(()=>controlRef.value.ambientColor,(n)=>{
  ambientLight.color = new Color(n)
})

gui.addColor(controlRef.value,"ambientColor")

在这里插入图片描述

2、点光源

Three.js库中的THREE.PointLight(点光源)是一种单点发光、照射所有方向的光源,比如夜空中的照明弹。

在这里插入图片描述

 ambientColor:"#0c0c0c",
  pointColor:"#ccffcc",
  distance: 100,
const pointLight = new PointLight(controlRef.value.pointColor)
pointLight.distance = 100
pointLight.position.copy(lookAtMesh.position)
scene.add(pointLight)

watch(
    () => controlRef.value.pointColor,
    () => {
      pointLight.color = new Color(controlRef.value.pointColor)
    }
)
watch(
    () => controlRef.value.distance,
    () => {
      pointLight.distance = controlRef.value.distance
    }
)
 gui.addColor(controlRef.value,"pointColor")
  gui.add(controlRef.value,"distance",-1000,1000)

3、聚光灯

Three.js中的聚光灯(SpotLight)是一种光源类型,用于在场景中创建聚焦光照。

它有一个锥形的照射范围,可以模拟手电筒或灯塔等发出的光线。

聚光灯具有方向和角度,可以通过调整其属性来控制照射范围和强度
在这里插入图片描述
在这里插入图片描述

target:'plane',

在这里插入图片描述


watch(
    () => controlRef.value.target,
    (t) => {
      if(t === 'cube'){
        spotLight.target = cube
      }else if( t === 'sphere'){
        spotLight.target = sphere
      }else {
        spotLight.target = plane
      }
    }
)

在这里插入图片描述

4、平行光

Three.js中的平行光(DirectionalLight)是一种光源类型,它发出的光线是平行的并且沿特定方向传播。

这种光源模拟太阳光等效果,因为它的表现像是无限远,从它发出的光线都是平行的。平行光通常用于模拟太阳光、月光等远离物体的光源。

你可以通过调整平行光的颜色、强度以及方向属性来控制照射效果。

在着色器中计算时,平行光的方向向量会直接与模型顶点的法线方向进行点乘操作,从而确定该点的亮度。
在这里插入图片描述

//添加平行光
const directionalColor = "#ff5808"
const directionalLight = new DirectionalLight(directionalColor)
directionalLight.position.set(-40,60,-10)
directionalLight.castShadow = true
directionalLight.intensity = 0.5
scene.add(directionalLight)

5、半球光

Three.js中的半球光(HemisphereLight)是一种光源类型,它模拟了天空和地面的反射效果。这种光源的特性在于,其发出的光线颜色从天空光线颜色渐变到地面光线颜色。

具体来说,半球光的原理由两部分组成,一部分是从下往上的平行光,另一部分是从上半球往中心点的光。

这样,实现了模拟模型法线向上的部分天空光线照射到物体上,法线向下的部分接收来自于地面的反射环境光。

然而需要注意的是,半球光无法投射阴影。

在创建半球光时,可以分别指定天空和地面的颜色。
在这里插入图片描述

//添加半球光
// 创建球体几何体和材质
const sphereGeometry1 = new SphereGeometry(2, 32, 32);
const sphereMaterial1 = new MeshLambertMaterial({
  color: 0x7777ff,
  wireframe:false
})
// 创建网格对象并添加到场景中
const spherea = new  Mesh(sphereGeometry1, sphereMaterial1);
scene.add(spherea);
// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  // 更新球体材质的emissive属性以实现半球光效果
  const time = Date.now() * 0.001;
  sphereMaterial.emissive.setRGB(Math.sin(time) * 0.5 + 0.5, Math.cos(time) * 0.5 + 0.5, Math.sin(time * 2) * 0.5 + 0.5);
  renderer.render(scene, camera);
}
animate();


在这里插入图片描述

五、小车案例

1、基础环境搭建

在这里插入图片描述

<template>
  <div ref="statsRef"></div>
  <div ref="containerRef" class="container">
  </div>
</template>

<script lang="ts" setup>

import {
  ACESFilmicToneMapping, AxesHelper,
  Color,
  EquirectangularReflectionMapping, Fog, GridHelper, Material,
  PerspectiveCamera,
  Scene,
  sRGBEncoding,
  WebGLRenderer
} from "three";
import Stats from "stats.js"
import * as dat from "dat.gui"
import {onMounted, ref, watchEffect} from "vue";
//
import venice_sunset_1k from '../../assets/venice_sunset_1k.hdr?url'
import car from '../../assets/car.glb?url'
import {RGBELoader} from "three/examples/jsm/loaders/RGBELoader";

const scene = new Scene();

const grid = new GridHelper(20,40,0xfffff,0xffff);

const containerRef = ref<HTMLDivElement>()
const statsRef = ref<HTMLDivElement>()

const stats = new Stats();
const controlRef = ref({
  bodyColor:"#0c0c0c",
  glassColor:"#0c0c0c",
  detailColor:"#0c0c0c",
})
const cameraRef = ref<PerspectiveCamera>()
const rendererRef = ref<WebGLRenderer>()

//它会检查当前网页中是否存在具有特定类名(即".dg.ac>.dg.main.a")的元素。
// 如果不存在,它将创建一个新的dat.GUI对象,
// 并在该对象中添加三个颜色控件:bodyColor、glassColor和detailColor。
// 这些颜色控件的值都是从名为controlRef的引用所指向的对象中获取的
function initGUI() {
  if(document.querySelectorAll(".dg.ac>.dg.main.a").length === 0){
    const gui = new dat.GUI()
    gui.addColor(controlRef.value,"bodyColor")
    gui.addColor(controlRef.value,"glassColor")
    gui.addColor(controlRef.value,"detailColor")
  }
}
/*
它使用PerspectiveCamera构造函数创建了一个新的透视相机对象。
这个构造函数需要四个参数:视角角度(在这里为45度)、纵横比(在这里为窗口宽度除以窗口高度)、近裁剪平面距离(在这里为0.1)
以及远裁剪平面距离(在这里为1000)。然后,它设置了摄像机的位置坐标为(-30,40,30),使摄像机面向场景的位置
* */
function initCamera() {
  cameraRef.value =  new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
  cameraRef.value.position.set(-30,40,30)
  cameraRef.value.lookAt(scene.position)
}

/*

创建了一个新的WebGL渲染器并将其赋值给rendererRef。这个渲染器启用了抗锯齿功能,
设置了像素比例为窗口的设备像素比,
并将宽度和高度设置为窗口的内宽度和内高度。它还设置了输出编码为sRGBEncoding,
色调映射为ACESFilmicToneMapping,色调映射曝光为0.85。

* */
function initRenderer(){
  rendererRef.value = new WebGLRenderer({antialias:true})

  rendererRef.value.setPixelRatio(window.devicePixelRatio)
  rendererRef.value.setSize(window.innerWidth,window.innerHeight)
  rendererRef.value.outputEncoding = sRGBEncoding
  rendererRef.value.toneMapping = ACESFilmicToneMapping
  rendererRef.value.toneMappingExposure = 0.85
}

/*
将背景颜色设为深灰色(#333),然后加载名为"venice_sunset_1k"的环境贴图,并将其映射方式设为EquirectangularReflectionMapping。这将会使场景具有反射光照的效果。

接下来的几行代码修改了grid组件所用材质的透明度、深度写入以及透明度属性,使其呈现出半透明效果。

最后,它向场景中添加了一个长度为20的新坐标轴助手。
*
*/
function initScene(){
  scene.background = new Color(0x333333)
  scene.environment = new RGBELoader().load(venice_sunset_1k)
  scene.environment.mapping = EquirectangularReflectionMapping;
 // scene.fog = new Fog(0x333333,10,15)

  const material = grid.material as Material
  material.opacity = 0.2
  material.depthWrite = false
  material.transparent = true
  scene.add(grid)

  const axes = new AxesHelper(20)
  scene.add(axes)

}

initGUI()
onMounted(()=>{
  //创建场景
  stats.dom.style.top = "50px"
  statsRef.value?.append(stats.dom)
  initScene()
  initCamera()
  initRenderer()
})

/*
持续运行的循环渲染函数,用于不断更新和重新绘制3D场景。

每一帧开始时,它会先调用stats对象的update方法,用于统计当前性能信息。

接着,它会调用requestAnimationFrame方法再次请求下一帧的动画。这个方法会在浏览器认为适合的时候安排一次重绘,通常是在下一次刷新周期之前。

如果存在cameraRef引用,则使用该相机进行当前帧的渲染。rendererRef指针表示的是已经初始化好的WebGL渲染器,而scene则是需要渲染的三维场景。

最后,根据性能计数器的时间戳,对网格物体的位置做了一次平移操作,使得网格能够以一定的速度沿Z轴方向移动。这里使用了取模运算符%来让网格的位置在其运动过程中始终保持在一个范围内。

注意requestAnimationFrame(renderScene)这一句的作用。这是一个JavaScript API,它可以在浏览器下次重绘之前,要求浏览器执行指定的函数(在这个例子中就是renderScene())。这样做的好处是可以减少不必要的CPU和GPU工作,从而提高页面性能。

每个requestAnimationFrame()调用都会返回一个定时ID,你可以用这个ID取消未执行的动画。如果要停止动画,只需清除对应的定时ID即可。

在这个例子中,requestAnimationFrame(renderScene)会在每次渲染完成后立即调用自己,从而形成一个无限循环,不断地重复执行渲染过程。只要页面没有关闭,这个函数就会一直被调用下去。
* */
function renderScene() {
  stats.update()
  requestAnimationFrame(renderScene)
  if(cameraRef.value){
    rendererRef.value!.render(scene,cameraRef.value)
  }
  const time = -performance.now() / 1000
  grid.position.z = -(time) % 1
}

renderScene()

/*
这段代码是一个Vue watchEffect钩子函数,当某些数据发生变化时,会触发此函数执行。

函数内部主要做了两件事:

将rendererRef的domElement属性(即渲染器的DOM元素)添加到containerRef指定的容器中。这意味着该渲染器将会在对应的HTML元素中显示。
在窗口大小发生改变时,监听并响应事件。当窗口尺寸发生改变时,会更新相机的宽高比,计算新的投影矩阵,并且重新设置渲染器的尺寸,使其与窗口尺寸保持一致。
因此,这段代码的作用是将渲染结果正确地显示出来,并确保在窗口尺寸改变时能够及时更新视口大小和视角。
* */
watchEffect(()=>{
  containerRef.value?.appendChild(rendererRef.value!.domElement)
  window.addEventListener('resize',()=>{
    cameraRef.value!.aspect = window.innerWidth / window.innerHeight
    //更新相投影矩阵
    cameraRef.value!.updateProjectionMatrix();
    rendererRef.value!.setSize(window.innerWidth,window.innerHeight)
  },false)
})

</script>

<style scoped>

</style>

在这里插入图片描述

2、载入模型,实现轨道控制器

在这里插入图片描述

import venice_sunset_1k from '../../assets/venice_sunset_1k.hdr?url'
import Car from '../../assets/car.glb?url'

在这里插入图片描述

//轨道控制器
const controlsRef = ref<OrbitControls>()

在这里插入图片描述


function initGLTF() {
  const dracoLoader = new DRACOLoader();
  dracoLoader.setDecoderPath('/gltf/');
  const  loader = new GLTFLoader();
  loader.setDRACOLoader (dracoLoader) ;
  loader.load(Car,(gltf: GLTF) => {
    console.log(gltf)
    const carModel = gltf.scene.children[0];
    scene.add(carModel)
  })
}

function initControl() {
  if(cameraRef.value) {
    controlsRef.value = new OrbitControls(cameraRef.value,containerRef.value);
    controlsRef.value.enableDamping = true
    controlsRef.value.maxDistance = 9
    controlsRef.value.target.set(0,0.5,0)
    controlsRef.value.update()
  }
}
initGUI()
initGLTF()
onMounted(()=>{
  //创建场景
  stats.dom.style.top = "50px"
  statsRef.value?.append(stats.dom)
  initScene()
  initCamera()
  initRenderer()
  initControl()
})

3、实现模型颜色材质调整,轮子转动

在这里插入图片描述

//轨道控制器
const controlsRef = ref<OrbitControls>()
const bodyMaterial = new MeshPhysicalMaterial({
  color:0xff0000,metalness:1.0,roughness:0.5,clearcoat:1.0,clearcoatRoughness:0.03,sheen:0.5
})
const glassMaterial = new MeshPhysicalMaterial({
  color:0xffffff,metalness:0.25,roughness:0,transmission:1.0
})
const detailMaterial = new MeshPhysicalMaterial({
  color:0xff0000,metalness:1.0,roughness:0.5
})

在这里插入图片描述



watch(()=> controlRef.value.bodyColor,(c)=>{
  bodyMaterial.color.set(c);
})
watch(()=> controlRef.value.glassColor,(c)=>{
  glassMaterial.color.set(c);
})
watch(()=> controlRef.value.detailColor,(c)=>{
  detailMaterial.color.set(c);
})

在这里插入图片描述

  const carModel = gltf.scene.children[0];
    (carModel.getObjectByName('body') as Mesh).material = bodyMaterial;
    (carModel.getObjectByName('glass') as Mesh).material = glassMaterial;
    (carModel.getObjectByName('rim_fl') as Mesh).material = detailMaterial;
    (carModel.getObjectByName('rim_fr') as Mesh).material = detailMaterial;
    (carModel.getObjectByName('rim_rr') as Mesh).material = detailMaterial;
    (carModel.getObjectByName('rim_rl') as Mesh).material = detailMaterial;
    (carModel.getObjectByName('trim') as Mesh).material = detailMaterial;
    wheels.push(
        carModel.getObjectByName('wheel_fl'),
        carModel.getObjectByName('wheel_fr'),
        carModel.getObjectByName('wheel_rl'),
        carModel.getObjectByName('wheel_rr'),
    )

在这里插入图片描述


  for (let i = 0;i < wheels.length;i++){
    wheels[i]!.rotation.x = time * Math.PI * 2
  }

实现效果
在这里插入图片描述

4、源代码下载

https://download.csdn.net/download/qq_44757034/88582419

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1283501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Liunx系统使用超详细(三)

本篇内容开始逐渐描述有关liunx的各种命令的使用方法&#xff01; 目录 一、目录和文件区别 1.1目录&#xff1a; 1.2文件&#xff1a; 1.3总结&#xff1a; 二、Linux命令的写法 三、linux命令清屏 四、pwd命令 五、ls命令 5.1 ls&#xff1a; 5.2 ls -l&#xff1a…

【C++】异常处理 ⑧ ( 标准异常类 | 标准异常类继承结构 | 常用的标准异常类 | 自定义异常类继承 std::exception 基类 )

文章目录 一、抛出 / 捕获 多个类型异常对象1、标准异常类2、标准异常类继承结构3、常用的标准异常类 二、自定义异常类继承 std::exception 基类1、自定义异常类继承 std::exception 基类2、完整代码示例 - 自定义异常类继承 std::exception 基类 一、抛出 / 捕获 多个类型异常…

茄子科技张韶全:跨多云大数据平台DataCake在OceanBase的实践

11 月 16 日&#xff0c;OceanBase 在北京顺利举办 2023 年度发布会&#xff0c;正式宣布&#xff1a;将持续践行“一体化”产品战略&#xff0c;为关键业务负载打造一体化数据库。其中&#xff0c;在“数字化转型升级实践专场”&#xff0c;我们有幸邀请到了茄子科技大数据技术…

经验分享|MySQL分区实战(RANGE)

概述 分区概述 在 MySQL 中&#xff0c; InnoDB存储引擎长期以来一直支持表空间的概念。在 MySQL 8.0 中&#xff0c;同一个分区表的所有分区必须使用相同的存储引擎。但是&#xff0c;也可以为同一 MySQL 服务器甚至同一数据库中的不同分区表使用不同的存储引擎。 通俗地讲…

锂电池包膜机通过设备管理系统做好预测性维护的作用

在现代工业生产中&#xff0c;包膜机在锂电产业链中处于电池制造环节&#xff0c;是锂电池生产线上的关键设备之一。然而&#xff0c;随着生产规模的扩大和工作环境的复杂化&#xff0c;锂电池包膜机也面临着常见故障和维护需求。为了更好地管理和维护锂电池包膜机&#xff0c;…

Unity 关于SpriteRenderer 和正交相机缩放

float oldWidth 750f;float oldHeight 1334f;float newWidth Screen.width;float newHeight Screen.height;float oldAspect oldWidth / oldHeight;float newAspect newWidth / newHeight;//水平方向缩放float horizontalCompressionRatio newAspect / oldAspect;//垂直…

刷题系列——排序算法

参考&#xff1a;README - 十大经典排序算法 1&#xff09;排序算法分为内部外部排序两种&#xff0c;这个之前并不了解&#xff0c;外部排序需要访问外存的这个就是指需要额外内存比如另一个list或者dict存储中间结果。 2&#xff09;稳定性&#xff1a;排序后 2 个相等键值…

C#基础与进阶扩展合集-进阶篇(持续更新)

目录 本文分两篇&#xff0c;基础篇点击&#xff1a;C#基础与进阶扩展合集-基础篇 一、进阶 1、Predicate 2、设置C#语言版本 3、ListCollectionView过滤集合 4、值类型与引用类型 5、程序设置当前项目工作目录 6、获取App.config配置文件中的值 7、Linq常用语句 8、…

三种定时器的实现方式

一、Scheduled Schedule是Spring框架提供的一种简单的定时任务调度方法&#xff0c;通过注解的方式即可实现定时任务的调度。它适用于简单的定时任务需求&#xff0c;例如每隔一段时间执行一次任务或者在特定时间执行任务。Scheduled可以轻松地集成到Spring应用中&#xff0c;…

DeepVoice AI - Text To Voice

No sign-up, No API Keys, no recurr

LabVIEW开发工业设备远程在线状态监测

LabVIEW开发工业设备远程在线状态监测 项目需要减少意外停机和维护费用、提供更完整的机器操作和状态图、改进设备使用情况跟踪。 该解决方案是一个多节点&#xff08;即多站点&#xff09;远程监控系统&#xff0c;它利用了基于NI cRIO的控制器和定制的LabVIEW监测软件。 方…

Idea 导入Mysql8.0驱动jar包

库是模块可以依赖的已编译代码的集合。在IntelliJ IDEA中&#xff0c;可以在三个级别上定义库&#xff1a; 全局 &#xff08;可用于许多项目&#xff09;&#xff0c; 项目&#xff08;可用于项目中的所有模块&#xff09;和模块 &#xff08;可用于一个模块&#xff09; 简单…

Leetcode 80 删除排序数组中的重复项 II

class Solution {// 双指针// slow代表已完成需要的数组的后一位&#xff0c;即要插入的位置// fast代表待检查的第一个元素public int removeDuplicates(int[] nums) {return f(nums, 2);}public int f(int[] nums, int k){int n nums.length;int slow k;int fast k;while(…

开源CDN软件GoEdge —— 筑梦之路

官方网站&#xff1a;GoEdge CDN - 制作自己的CDN - GoEdge CDN | 自建CDN GoEdge是一款管理分布式CDN边缘节点的开源工具软件&#xff0c;目的是让用户轻松地、低成本地创建CDN/WAF等应用。 特性 免费 - 开源、免费、自由、开放 简单 - 架构简单清晰&#xff0c;安装简单&a…

vue2使用ElementUI

elementui官网&#xff1a;组件 | Element 1、全部引入 下载&#xff1a;npm i element-ui 在 main.js 中写入以下内容&#xff1a;import Vue from vue; import ElementUI from element-ui; import element-ui/lib/theme-chalk/index.css; import App from ./App.vue;Vue.use(…

12月2号作业

#include <iostream>using namespace std; class Sofa{ private:string setting;string *lying new string;public:Sofa(){cout << "Sofa::无参构造函数" << endl;}Sofa(string setting,string lying):setting(setting),lying(new string (lying)…

封装带插槽的表格

子组件 <template><div><table><thead><tr><th v-for"col,colIndex in columns" :key"colIndex">{{ col.title }}</th></tr></thead><tbody v-if"instList.length >0"><tr …

IDEA 保存自动ESLint格式化

作为后端人员&#xff0c;偶尔修改一下前端代码&#xff0c;ESLint总提示格式不正确。有没有什么办法实现自动格式化呢&#xff1f; 安装插件Save Actions Tool 设置中搜索eslint 勾选 Run eslint --fix on save 这样以后&#xff0c;只要保存文件就会自动格式化了。 参考 …

Python 流程控制

目录 程序流程 顺序结构 分支结构 单分支 双分支 多分支 if 嵌套 循环结构 while循环 for 循环 退出循环 循环与分支嵌套 附录 程序流程 程序是由语句构成&#xff0c;而流程控制语句 是用来控制程序中每条语句执行顺序的语句。可以通过控制语句实现更丰富的逻辑…

vue+electron问题汇总

1. Vue_Bug Failed to fetch extension, trying 4 more times 描述&#xff1a;项目启动时报错 解决&#xff1a;注释图片中内容 2. Module not found: Error: Can’t resolve ‘fs’ in 描述&#xff1a;项目启动报错 解决&#xff1a;vue.config.js中添加图中数据 3.导入…