項(xiàng)目簡介
這是一個(gè)使用Vue3,TypeScript,Vite和Three.js的項(xiàng)目。Vue3是一個(gè)流行的JavaScript框架,用于構(gòu)建用戶界面。TypeScript是一種靜態(tài)類型的編程語言,它是JavaScript的超集,可以編譯成純JavaScript。Vite是一個(gè)由Evan You開發(fā)的新的前端構(gòu)建工具,能夠提供快速的冷啟動和即時(shí)熱更新。
Three.js是一個(gè)輕量級的3D庫,能夠讓我們在任何瀏覽器中創(chuàng)建和顯示動畫的3D計(jì)算機(jī)圖形。在該項(xiàng)目中,我們將Three.js集成到了Vue3和TypeScript的環(huán)境中,使得我們可以在Vue組件中使用Three.js來創(chuàng)建3D圖形。
此外,項(xiàng)目中還可能包含一些封裝了Three.js的代碼,以便于更方便的使用Three.js進(jìn)行3D開發(fā)。
這樣的技術(shù)組合可以讓我們在前端環(huán)境中實(shí)現(xiàn)復(fù)雜的3D可視化效果,為項(xiàng)目增加更豐富的視覺體驗(yàn)。
??3D模型下載網(wǎng)站:https://sketchfab.com/feed
??3D人物動作綁定:www.mixamo.com
??3D角色生產(chǎn)工具:https://readyplayer.me/
??模型壓縮網(wǎng)站:gltf.report
??查找天空背景:google key words: equirectangular sky / skybox background
??材質(zhì)貼圖素材:https://www.textures.com
??hdr素材庫(環(huán)境貼圖): https://polyhaven.com
??二次元風(fēng)3D角色生產(chǎn)軟件VRoid Studio: https://vroid.com/en/studio
??Sketchfab公用賬號:
Login: lingo3dchina@gmail.com
PW: Lingo3dxoxo
Code:640841
一、項(xiàng)目初始化
npm install -g vite
npm init vite@latest threejs-vite-vue -- --template vue
cd threejs-vite-vue
npm install
npm run dev
項(xiàng)目創(chuàng)建成功注意threejs的版本
"@types/three": "^0.155.1",
項(xiàng)目創(chuàng)建成功在IDE當(dāng)中導(dǎo)入項(xiàng)目
1、添加一些依賴項(xiàng)
npm install vue-router
npm install three
npm install @types/three -D
npm install ant-design-vue
創(chuàng)建一些路由相關(guān)
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>
第一個(gè)場景
</div>
</template>
<script>
export default {
name: "index"
}
</script>
<style scoped>
</style>
二、創(chuàng)建3D【基礎(chǔ)搭建】
.container{
width: 100vw;
height: 100vh;
}
1、繪制板子,立方體,球體
Three.js來繪制一個(gè)簡單的3D場景,包括一個(gè)平面(板子)、一個(gè)立方體和一個(gè)球體
<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>()
//創(chuàng)建場景
const scene = new Scene();
//創(chuàng)建攝像機(jī)
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//設(shè)置攝像機(jī)位置
camera.position.set(-30,40,30)
//設(shè)置攝像機(jī)朝向
camera.lookAt(scene.position)
//重置webGL的顏色
const renderer = new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)
//添加坐標(biāo)系
const ases = new AxesHelper(20)
scene.add(ases)
//繪制板子,設(shè)置板子的寬度為60,設(shè)置板子的高度為20
const planeGeometry = new PlaneGeometry(60,20);
const meshBasicMaterial = new MeshBasicMaterial({color:0xcccccc});//設(shè)置材質(zhì)顏色
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)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制球體,設(shè)置球體的半徑為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(()=>{
//設(shè)置攝像頭朝向
containerRef.value?.appendChild(renderer.domElement)
renderer.render(scene,camera)
})
</script>
<style scoped>
</style>
2、材質(zhì)和光照
在Three.js中,材質(zhì)和光照是讓物體看起來更為真實(shí)的關(guān)鍵因素。材質(zhì)定義了物體表面的外觀,如顏色、紋理和光照效果。Three.js提供了多種類型的材質(zhì),適用于不同的光照效果。
物理基礎(chǔ)渲染(Physically Based Rendering, PBR)是一種基于物理的渲染技術(shù),使用物理基礎(chǔ)渲染代碼和材料處理技術(shù)來模擬光線和材料之間的物理相互作用,以創(chuàng)建逼真的材料外觀和光照效果。這種渲染技術(shù)可以提供更真實(shí)的陰影,高光,反射和漫反射效果,使場景看起來更加真實(shí)。Three.js核心也包含了與Unreal、Unity、Disney和Pixar等巨頭使用的相同的基于物理的渲染 (PBR) 算法。
對于紋理的應(yīng)用,可以通過加載圖片并設(shè)置其重復(fù)模式、采樣模式以及重復(fù)次數(shù)來實(shí)現(xiàn)貼圖效果。例如,創(chuàng)建一個(gè)地平面,并用下方展示的 2x2 像素的黑白格圖片來作為紋理。首先加載這個(gè)紋理,設(shè)置重復(fù)模式(wrapS, wrapT),采樣模式(magFilter)以及重復(fù)的次數(shù)。因?yàn)橘N圖是 2x2 大小,通過設(shè)置成平鋪模式,并且重復(fù)次數(shù)是邊長的一半,就可以讓每個(gè)格子正好是1個(gè)單位的大小。
設(shè)置導(dǎo)航菜單組件
<template>
<a-menu mode="horizontal" style="position: fixed">
<a-sub-menu key="demo">
<template #title>
第一章
</template>
<a-menu-item key="1">
<router-link to="/"> 第一個(gè)場景 </router-link>
</a-menu-item>
<a-menu-item key="2">
<router-link to="/chapter1/2"> 第一個(gè)場景 </router-link>
</a-menu-item>
</a-sub-menu>
</a-menu>
<router-view></router-view>
</template>
<script setup>
</script>
<style scoped>
</style>
復(fù)制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>()
//創(chuàng)建場景
const scene = new Scene();
//創(chuàng)建攝像機(jī)
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//設(shè)置攝像機(jī)位置
camera.position.set(-30,40,30)
//設(shè)置攝像機(jī)朝向
camera.lookAt(scene.position)
//重置webGL的顏色
const renderer = new WebGLRenderer();
renderer.setClearColor(new Color(0xeeeeee))
renderer.setSize(window.innerWidth,window.innerHeight)
//添加坐標(biāo)系
const ases = new AxesHelper(20)
scene.add(ases)
//繪制板子,設(shè)置板子的寬度為60,設(shè)置板子的高度為20
const planeGeometry = new PlaneGeometry(60,20);
const meshBasicMaterial = new MeshBasicMaterial({color:0xcccccc});//設(shè)置材質(zhì)顏色
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)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制球體,設(shè)置球體的半徑為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(()=>{
//設(shè)置攝像頭朝向
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:"第一個(gè)場景"
}
},
{
path:'/chapter1/2',
component: Index2,
meta:{
title:"第二個(gè)場景"
}
}
]
export default chapter1;
實(shí)現(xiàn)第二個(gè)場景
<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>()
//創(chuàng)建場景
const scene = new Scene();
//創(chuàng)建攝像機(jī)
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//設(shè)置攝像機(jī)位置
camera.position.set(-30,40,30)
//設(shè)置攝像機(jī)朝向
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)
//添加坐標(biāo)系
const axes = new AxesHelper(20)
scene.add(axes)
//繪制板子,設(shè)置板子的寬度為60,設(shè)置板子的高度為20
const planeGeometry = new PlaneGeometry(60,20);
const meshBasicMaterial = new MeshLambertMaterial({color:0xcccccc});//設(shè)置材質(zhì)顏色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //設(shè)置可以接收陰影
plane.rotation.x = -0.5 * Math.PI;
//plane.position.x = 15
//plane.position.y = 0
//plane.position.z = 0
scene.add(plane)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制球體,設(shè)置球體的半徑為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(()=>{
//設(shè)置攝像頭朝向
containerRef.value?.appendChild(renderer.domElement)
renderer.render(scene,camera)
})
</script>
<style scoped>
</style>
3、材質(zhì)和光照和動畫
Three.js提供了一套強(qiáng)大的動畫系統(tǒng),可以應(yīng)用于物體的位置、旋轉(zhuǎn)、縮放、材質(zhì)的顏色或不透明度等各個(gè)方面。這套系統(tǒng)中主要包括了KeyFrameTrack、AnimationClip、AnimationMixer和AnimationAction四個(gè)組件。
在制作動畫時(shí),我們通常會使用關(guān)鍵幀動畫,即在不同時(shí)間點(diǎn)設(shè)置關(guān)鍵幀,然后由動畫系統(tǒng)通過補(bǔ)間過程自動填補(bǔ)各關(guān)鍵幀之間的變化。例如,要為一個(gè)彈跳的球設(shè)置動畫,只需要指定彈跳的頂部和底部的點(diǎn),Three.js將在這兩點(diǎn)之間的所有點(diǎn)上平滑地生成動畫。此外,我們還可以通過合成和混合多個(gè)動畫來創(chuàng)造出更復(fù)雜的效果。
復(fù)制index2創(chuàng)建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>()
//創(chuàng)建場景
const scene = new Scene();
//創(chuàng)建攝像機(jī)
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//設(shè)置攝像機(jī)位置
camera.position.set(-30,40,30)
//設(shè)置攝像機(jī)朝向
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)
//添加坐標(biāo)系
const axes = new AxesHelper(20)
scene.add(axes)
//繪制板子,設(shè)置板子的寬度為60,設(shè)置板子的高度為20
const planeGeometry = new PlaneGeometry(100,50);
const meshBasicMaterial = new MeshLambertMaterial({color:0xcccccc});//設(shè)置材質(zhì)顏色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //設(shè)置可以接收陰影
plane.rotation.x = -0.5 * Math.PI;
//plane.position.x = 15
//plane.position.y = 0
//plane.position.z = 0
scene.add(plane)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制球體,設(shè)置球體的半徑為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)
//控制物體運(yùn)動
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為數(shù)據(jù)當(dāng)中的函數(shù) 余弦函數(shù)
sphere.position.y = 2 + 10 * Math.abs(Math.sin(step)); //abs為絕對值 sin為正弦函數(shù)
requestAnimationFrame(renderScene)
renderer.render(scene,camera)
}
renderScene()
onMounted(()=>{
//設(shè)置攝像頭朝向
containerRef.value?.appendChild(renderer.domElement)
renderer.render(scene,camera)
})
</script>
<style scoped>
</style>
4、性能監(jiān)控
Three.js的性能監(jiān)控工具Stats.js是一個(gè)強(qiáng)大的插件,它能夠監(jiān)測幀率、內(nèi)存等數(shù)據(jù)的變化。在動畫或網(wǎng)頁開發(fā)中,幀率是衡量和描述動畫是否流暢的一個(gè)重要單位。Stats.js可以幫助開發(fā)者實(shí)時(shí)了解Three.js的渲染性能,尤其是渲染幀率(FPS),即每秒鐘完成的渲染次數(shù)。理想狀態(tài)下,渲染幀率應(yīng)該達(dá)到每秒60次。
在使用Stats.js時(shí),首先需要引入相關(guān)的腳本文件。然后,實(shí)例化一個(gè)Stats對象,并將該對象生成的DOM元素添加到頁面中。通過這種方式,我們可以在開發(fā)過程中實(shí)時(shí)監(jiān)控Three.js的性能,及時(shí)發(fā)現(xiàn)并解決可能存在的問題,從而提升用戶體驗(yàn)。
安裝stats.js插件
npm install stats.js
復(fù)制index3.vue創(chuàng)建index4.vue
import index4 from '../lesson/chapter1/index4.vue'
,
{
path:'/chapter1/4',
component: index4,
meta:{
title:"性能監(jiān)控"
}
}
<div ref="statsRef"></div>
const statsRef = ref<HTMLDivElement>()
const stats = new Stats()
stats.showPanel(0)
stats.update()
//創(chuàng)建場景
const scene = new Scene();
stats.dom.style.top = "50px"
statsRef.value?.append(stats.dom)
訪問第四個(gè)場景
http://127.0.0.1:5173/chapter1/4
5、交互控制
dat.gui@0.7.9是一個(gè)輕量級的JavaScript庫,它的主要功能是幫助用戶添加交互式控制面板,以便在3D場景中調(diào)整對象參數(shù)并實(shí)時(shí)預(yù)覽結(jié)果。
復(fù)制一下index4.vue 為index5.vue
{
path:'/chapter1/5',
component: index5,
meta:{
title:"交互控制"
}
}
<a-menu-item key="5">
<router-link to="/chapter1/5"> 第五個(gè)場景 </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;
放置重復(fù)初始化
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)
//創(chuàng)建場景
const scene = new Scene();
//創(chuàng)建攝像機(jī)
const camera = new PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000)
//設(shè)置攝像機(jī)位置
camera.position.set(-30,40,30)
//設(shè)置攝像機(jī)朝向
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)
//添加坐標(biāo)系
const axes = new AxesHelper(20)
scene.add(axes)
//繪制板子,設(shè)置板子的寬度為60,設(shè)置板子的高度為20
const planeGeometry = new PlaneGeometry(100,50);
const meshBasicMaterial = new MeshLambertMaterial({color:0xcccccc});//設(shè)置材質(zhì)顏色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //設(shè)置可以接收陰影
plane.rotation.x = -0.5 * Math.PI;
//plane.position.x = 15
//plane.position.y = 0
//plane.position.z = 0
scene.add(plane)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制立方體,設(shè)置板子的長寬高分別是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)
//繪制球體,設(shè)置球體的半徑為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)
}
//控制物體運(yùn)動
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為數(shù)據(jù)當(dāng)中的函數(shù) 余弦函數(shù)
sphere.position.y = 2 + 10 * Math.abs(Math.sin(step)); //abs為絕對值 sin為正弦函數(shù)
requestAnimationFrame(renderScene)
renderer.render(scene,camera)
}
renderScene()
onMounted(()=>{
//創(chuàng)建場景
const scene = new Scene();
stats.dom.style.top = "50px"
statsRef.value?.append(stats.dom)
//設(shè)置攝像頭朝向
containerRef.value?.appendChild(renderer.domElement)
renderer.render(scene,camera)
})
</script>
<style scoped>
</style>
6、響應(yīng)窗口變化
和之前一樣創(chuàng)建index6.vue
/*
監(jiān)聽在窗口變化的時(shí)候重新設(shè)置大小
* */
window.addEventListener('resize',()=>{
camera.aspect = window.innerWidth / window.innerHeight;
//更新相機(jī)投影矩陣
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight)
})
三、基礎(chǔ)場景搭建
1、創(chuàng)建基礎(chǔ)場景【實(shí)現(xiàn)添加幾何體和刪除幾何體】
將index6復(fù)制到chapter2下的index
const controlRef = ref({
rotationSpeed:0.02,
bouncingSpeed:0.03,
numberOfObjects:0,
addCube:function (){
//繪制立方體,設(shè)置板子的長寬高分別是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()
//遍歷場景當(dāng)中的所有內(nèi)容
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;
}
})
實(shí)現(xiàn)點(diǎn)擊添加cube和刪除cube
2、實(shí)現(xiàn)霧化場景
霧化效果是一種常見的視覺效果,它可以使場景中的物體看起來更加模糊和透明。在Three.js中,可以通過設(shè)置材質(zhì)的透明度和混合模式來實(shí)現(xiàn)霧化效果
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、重寫材質(zhì)
在Three.js中,材質(zhì)是定義物體外觀的關(guān)鍵。通過創(chuàng)建自定義材質(zhì),可以對物體的外觀進(jìn)行更精細(xì)的控制,包括如何設(shè)置材質(zhì)的顏色、紋理和透明度等屬性。
toggleMaterial:function (){
if(!scene.overrideMaterial){
scene.overrideMaterial = new MeshLambertMaterial({
color:0xffffff,
})
}else{
scene.overrideMaterial =null
}
}
gui.add(controlRef.value,"toggleMaterial")
4、常見幾何體
在Three.js中,幾何體是一個(gè)數(shù)據(jù)結(jié)構(gòu),它包含了用于描述三維物體的基本信息,如頂點(diǎn)(vertices)、線(lines)和面(faces)。幾何體可以被用來定義物體的形狀和大小。
常見的幾何體類型有以下幾種:
BoxGeometry(立方體幾何體):通過指定寬度、高度和深度來創(chuàng)建一個(gè)立方體。
SphereGeometry(球體幾何體):通過指定半徑來創(chuàng)建一個(gè)球體。
CylinderGeometry(圓柱體幾何體):通過指定高度、半徑和圓周上的段數(shù)來創(chuàng)建一個(gè)圓柱體。
PlaneGeometry(平面幾何體):一種基礎(chǔ)的二維幾何體,可以用來繪制平面。
ConeGeometry(圓錐體幾何體):通過指定高度、底部半徑和頂部半徑,以及圓周上的段數(shù)來創(chuàng)建一個(gè)圓錐體。
TubularGeometry(管狀幾何體):這是一種具有圓形截面的管道形狀,需要指定管道的中心軸線、直徑、高度和圓周上的段數(shù)。
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))
//材質(zhì)
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中,幾何體的屬性可以通過修改其頂點(diǎn)、線和面的數(shù)據(jù)來改變物體的形狀和大小。以下是一些常見的修改幾何體屬性的方法:
修改頂點(diǎn)數(shù)據(jù):通過修改幾何體的vertices屬性,可以改變物體的形狀。例如,可以將一個(gè)立方體的頂點(diǎn)數(shù)據(jù)修改為一個(gè)球體的頂點(diǎn)數(shù)據(jù),從而創(chuàng)建一個(gè)球形物體。
修改線數(shù)據(jù):通過修改幾何體的lines屬性,可以改變物體的邊界線。例如,可以將一個(gè)立方體的邊線修改為一個(gè)圓柱體的邊線,從而創(chuàng)建一個(gè)圓柱形物體。
修改面數(shù)據(jù):通過修改幾何體的faces屬性,可以改變物體的表面。例如,可以將一個(gè)立方體的面修改為一個(gè)球體的面,從而創(chuàng)建一個(gè)球形物體。
修改材質(zhì)屬性:通過修改幾何體的material屬性,可以改變物體的顏色、紋理和透明度等視覺效果。例如,可以將一個(gè)立方體的材質(zhì)修改為一個(gè)半透明的紅色材料,從而創(chuàng)建一個(gè)半透明的紅色立方體。
需要注意的是,修改幾何體屬性需要對Three.js的底層實(shí)現(xiàn)有一定的了解,并且需要注意性能問題。如果頻繁地修改幾何體屬性,可能會導(dǎo)致性能下降。因此,在實(shí)際應(yīng)用中,應(yīng)該根據(jù)需求選擇合適的方法來修改幾何體屬性。
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
}
/*設(shè)置大小*/
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);
/*設(shè)置位置*/
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);
/*設(shè)置斜角位置*/
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、相機(jī)切換
在Three.js中,相機(jī)是用于渲染場景的工具。主要包括透視相機(jī)(PerspectiveCamera)和正交相機(jī)(OrthographicCamera)。
透視相機(jī)可以創(chuàng)建具有深度感的三維效果,而正交相機(jī)則可以在二維平面上進(jìn)行投影。
實(shí)現(xiàn)相機(jī)視角的切換,主要有兩種方法。一種是使用Tween.js庫來實(shí)現(xiàn)平滑過渡的效果。Tween.js庫可以很容易實(shí)現(xiàn)兩個(gè)值之間的過度,中間值都會自動計(jì)算出來。
另一種是通過鼠標(biāo)拉拽來改變相機(jī)的位置、旋轉(zhuǎn)角度等,比如使用OrbitControls類。
OrbitControls類是Three.js提供的鼠標(biāo)、方向鍵與場景交互的控件,通過鼠標(biāo)的操作可以改變相機(jī)的視角,從而改變視覺,使得視覺效果更具有真實(shí)感。
此外,如果想要切換不同的場景,可以通過創(chuàng)建多個(gè)場景對象,并在每個(gè)場景中添加不同的模型、燈光等元素。
使用renderer.render(scene, camera)
方法在渲染循環(huán)中渲染當(dāng)前場景,使用scene.dispose()
方法清除當(dāng)前場景中的元素,釋放內(nèi)存。當(dāng)需要切換到下一個(gè)場景時(shí),重復(fù)上述步驟,并將下一個(gè)場景設(shè)置為當(dāng)前場景。
設(shè)置場景和多個(gè)物體
//繪制板子,設(shè)置板子的寬度為60,設(shè)置板子的高度為20
const planeGeometry = new PlaneGeometry(100,50,1);
const meshBasicMaterial = new MeshLambertMaterial({color:0xffffff});//設(shè)置材質(zhì)顏色
const plane = new Mesh(planeGeometry,meshBasicMaterial)
plane.receiveShadow = true //設(shè)置可以接收陰影
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)
}
}
設(shè)置切換相機(jī)
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、相機(jī)跟隨
在Three.js中,相機(jī)跟隨物體的技術(shù)廣泛應(yīng)用于實(shí)現(xiàn)如游戲中的攝像機(jī)跟隨角色、VR中的視點(diǎn)跟蹤等效果。
要實(shí)現(xiàn)這一功能,首先需要獲取到目標(biāo)物體(例如一個(gè)游戲角色或者一個(gè)3D模型)的位置信息,然后將相機(jī)的位置設(shè)置為該物體的對應(yīng)位置,從而實(shí)現(xiàn)視角的跟隨。
此外,關(guān)于具體的實(shí)現(xiàn)方式,有多種可選的策略。
如果你想要?jiǎng)?chuàng)建一個(gè)第一人稱視角的效果,可以使用鍵盤的WASD鍵控制相機(jī)的移動方向;而如果你希望實(shí)現(xiàn)第三人稱視角的效果,則可以通過鼠標(biāo)來控制相機(jī)的視角朝向。
另外,對于復(fù)雜的場景,比如管道內(nèi)的視野展示或者物體在三維空間中任意方向移動的情況,你可能需要結(jié)合使用一些額外的工具和方法。
例如,你可以創(chuàng)建一個(gè)管道模型來幫助你觀察物體的運(yùn)動方向,并通過調(diào)整相機(jī)的位置和朝向,使得鏡頭能夠緊密地跟隨物體的移動。
const lookAtGeom = new SphereGeometry(20)
const lookAtMesh = new Mesh(
lookAtGeom,
new MeshLambertMaterial({
color:0xff0000
})
)
scene.add(lookAtMesh)
//控制物體運(yùn)動
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是三維的坐標(biāo)
lookAtMesh.position.copy(new Vector3(x,10,0))
cameraRef.value.lookAt(new Vector3(x,10,0))
renderer.render(scene,cameraRef.value)
}
}
四、光照
1、環(huán)境光
Three.js中環(huán)境光(AmbientLight)是一種全局光照,它能夠均勻地照亮場景中的物體。
與點(diǎn)光源和平行光源不同,環(huán)境光不會直接照亮物體,而是與場景中的顏色相乘,從而使得物體的顏色變暗或變亮。
環(huán)境光通常用于模擬全局的光照效果,例如在室外場景中模擬太陽的光線、室內(nèi)場景中模擬燈光的反射等。
通過調(diào)整環(huán)境光的顏色和強(qiáng)度,可以改變整個(gè)場景的亮度和色調(diào),從而增強(qiáng)渲染的真實(shí)感。
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、點(diǎn)光源
Three.js庫中的THREE.PointLight(點(diǎn)光源)是一種單點(diǎn)發(fā)光、照射所有方向的光源,比如夜空中的照明彈。
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)是一種光源類型,用于在場景中創(chuàng)建聚焦光照。
它有一個(gè)錐形的照射范圍,可以模擬手電筒或燈塔等發(fā)出的光線。
聚光燈具有方向和角度,可以通過調(diào)整其屬性來控制照射范圍和強(qiáng)度
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)是一種光源類型,它發(fā)出的光線是平行的并且沿特定方向傳播。
這種光源模擬太陽光等效果,因?yàn)樗谋憩F(xiàn)像是無限遠(yuǎn),從它發(fā)出的光線都是平行的。平行光通常用于模擬太陽光、月光等遠(yuǎn)離物體的光源。
你可以通過調(diào)整平行光的顏色、強(qiáng)度以及方向?qū)傩詠砜刂普丈湫Ч?/p>
在著色器中計(jì)算時(shí),平行光的方向向量會直接與模型頂點(diǎn)的法線方向進(jìn)行點(diǎn)乘操作,從而確定該點(diǎn)的亮度。
//添加平行光
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)是一種光源類型,它模擬了天空和地面的反射效果。這種光源的特性在于,其發(fā)出的光線顏色從天空光線顏色漸變到地面光線顏色。
具體來說,半球光的原理由兩部分組成,一部分是從下往上的平行光,另一部分是從上半球往中心點(diǎn)的光。
這樣,實(shí)現(xiàn)了模擬模型法線向上的部分天空光線照射到物體上,法線向下的部分接收來自于地面的反射環(huán)境光。
然而需要注意的是,半球光無法投射陰影。
在創(chuàng)建半球光時(shí),可以分別指定天空和地面的顏色。
//添加半球光
// 創(chuàng)建球體幾何體和材質(zhì)
const sphereGeometry1 = new SphereGeometry(2, 32, 32);
const sphereMaterial1 = new MeshLambertMaterial({
color: 0x7777ff,
wireframe:false
})
// 創(chuàng)建網(wǎng)格對象并添加到場景中
const spherea = new Mesh(sphereGeometry1, sphereMaterial1);
scene.add(spherea);
// 渲染循環(huán)
function animate() {
requestAnimationFrame(animate);
// 更新球體材質(zhì)的emissive屬性以實(shí)現(xiàn)半球光效果
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、基礎(chǔ)環(huán)境搭建
<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>()
//它會檢查當(dāng)前網(wǎng)頁中是否存在具有特定類名(即".dg.ac>.dg.main.a")的元素。
// 如果不存在,它將創(chuàng)建一個(gè)新的dat.GUI對象,
// 并在該對象中添加三個(gè)顏色控件: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構(gòu)造函數(shù)創(chuàng)建了一個(gè)新的透視相機(jī)對象。
這個(gè)構(gòu)造函數(shù)需要四個(gè)參數(shù):視角角度(在這里為45度)、縱橫比(在這里為窗口寬度除以窗口高度)、近裁剪平面距離(在這里為0.1)
以及遠(yuǎn)裁剪平面距離(在這里為1000)。然后,它設(shè)置了攝像機(jī)的位置坐標(biāo)為(-30,40,30),使攝像機(jī)面向場景的位置
* */
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)
}
/*
創(chuàng)建了一個(gè)新的WebGL渲染器并將其賦值給rendererRef。這個(gè)渲染器啟用了抗鋸齒功能,
設(shè)置了像素比例為窗口的設(shè)備像素比,
并將寬度和高度設(shè)置為窗口的內(nèi)寬度和內(nèi)高度。它還設(shè)置了輸出編碼為sRGBEncoding,
色調(diào)映射為ACESFilmicToneMapping,色調(diào)映射曝光為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
}
/*
將背景顏色設(shè)為深灰色(#333),然后加載名為"venice_sunset_1k"的環(huán)境貼圖,并將其映射方式設(shè)為EquirectangularReflectionMapping。這將會使場景具有反射光照的效果。
接下來的幾行代碼修改了grid組件所用材質(zhì)的透明度、深度寫入以及透明度屬性,使其呈現(xiàn)出半透明效果。
最后,它向場景中添加了一個(gè)長度為20的新坐標(biāo)軸助手。
*
*/
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(()=>{
//創(chuàng)建場景
stats.dom.style.top = "50px"
statsRef.value?.append(stats.dom)
initScene()
initCamera()
initRenderer()
})
/*
持續(xù)運(yùn)行的循環(huán)渲染函數(shù),用于不斷更新和重新繪制3D場景。
每一幀開始時(shí),它會先調(diào)用stats對象的update方法,用于統(tǒng)計(jì)當(dāng)前性能信息。
接著,它會調(diào)用requestAnimationFrame方法再次請求下一幀的動畫。這個(gè)方法會在瀏覽器認(rèn)為適合的時(shí)候安排一次重繪,通常是在下一次刷新周期之前。
如果存在cameraRef引用,則使用該相機(jī)進(jìn)行當(dāng)前幀的渲染。rendererRef指針表示的是已經(jīng)初始化好的WebGL渲染器,而scene則是需要渲染的三維場景。
最后,根據(jù)性能計(jì)數(shù)器的時(shí)間戳,對網(wǎng)格物體的位置做了一次平移操作,使得網(wǎng)格能夠以一定的速度沿Z軸方向移動。這里使用了取模運(yùn)算符%來讓網(wǎng)格的位置在其運(yùn)動過程中始終保持在一個(gè)范圍內(nèi)。
注意requestAnimationFrame(renderScene)這一句的作用。這是一個(gè)JavaScript API,它可以在瀏覽器下次重繪之前,要求瀏覽器執(zhí)行指定的函數(shù)(在這個(gè)例子中就是renderScene())。這樣做的好處是可以減少不必要的CPU和GPU工作,從而提高頁面性能。
每個(gè)requestAnimationFrame()調(diào)用都會返回一個(gè)定時(shí)ID,你可以用這個(gè)ID取消未執(zhí)行的動畫。如果要停止動畫,只需清除對應(yīng)的定時(shí)ID即可。
在這個(gè)例子中,requestAnimationFrame(renderScene)會在每次渲染完成后立即調(diào)用自己,從而形成一個(gè)無限循環(huán),不斷地重復(fù)執(zhí)行渲染過程。只要頁面沒有關(guān)閉,這個(gè)函數(shù)就會一直被調(diào)用下去。
* */
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()
/*
這段代碼是一個(gè)Vue watchEffect鉤子函數(shù),當(dāng)某些數(shù)據(jù)發(fā)生變化時(shí),會觸發(fā)此函數(shù)執(zhí)行。
函數(shù)內(nèi)部主要做了兩件事:
將rendererRef的domElement屬性(即渲染器的DOM元素)添加到containerRef指定的容器中。這意味著該渲染器將會在對應(yīng)的HTML元素中顯示。
在窗口大小發(fā)生改變時(shí),監(jiān)聽并響應(yīng)事件。當(dāng)窗口尺寸發(fā)生改變時(shí),會更新相機(jī)的寬高比,計(jì)算新的投影矩陣,并且重新設(shè)置渲染器的尺寸,使其與窗口尺寸保持一致。
因此,這段代碼的作用是將渲染結(jié)果正確地顯示出來,并確保在窗口尺寸改變時(shí)能夠及時(shí)更新視口大小和視角。
* */
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、載入模型,實(shí)現(xiàn)軌道控制器
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(()=>{
//創(chuàng)建場景
stats.dom.style.top = "50px"
statsRef.value?.append(stats.dom)
initScene()
initCamera()
initRenderer()
initControl()
})
3、實(shí)現(xiàn)模型顏色材質(zhì)調(diào)整,輪子轉(zhuǎn)動
//軌道控制器
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
}
實(shí)現(xiàn)效果文章來源:http://www.zghlxwxcb.cn/news/detail-770614.html
4、源代碼下載
https://download.csdn.net/download/qq_44757034/88582419文章來源地址http://www.zghlxwxcb.cn/news/detail-770614.html
到了這里,關(guān)于Vue3集成ThreeJS實(shí)現(xiàn)3D效果,threejs+Vite+Vue3+TypeScript 實(shí)戰(zhàn)課程【一篇文章精通系列】的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!