目錄
項目搭建
初始化three.js基礎代碼
設置環(huán)境紋理加載模型
使用Cannon-es實現(xiàn)物理世界
今天簡單實現(xiàn)一個three.js的小Demo,加強自己對three知識的掌握與學習,只有在項目中才能靈活將所學知識運用起來,話不多說直接開始。
項目搭建
本案例還是借助框架書寫three項目,借用vite構建工具搭建vue項目,vite這個構建工具如果有不了解的朋友,可以參考我之前對其講解的文章:vite腳手架的搭建與使用 。搭建完成之后,用編輯器打開該項目,在終端執(zhí)行 npm i 安裝一下依賴,安裝完成之后終端在安裝 npm i three 即可。
<template>
<!-- 踢球游戲 -->
<kickballGame></kickballGame>
</template>
<script setup>
import kickballGame from './components/kickballGame.vue';
</script>
<style lang="less">
*{
margin: 0;
padding: 0;
}
</style>
初始化three.js基礎代碼
three.js開啟必須用到的基礎代碼如下:
導入three庫:
import * as THREE from 'three'
初始化場景:
const scene = new THREE.Scene()
初始化相機:
// 初始化相機
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.set(4,2,0)
camera.updateProjectionMatrix()
初始化渲染器:
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 設置抗鋸齒
logarithmicDepthBuffer: true, // 使用對數(shù)緩存
})
renderer.setSize(window.innerWidth,window.innerHeight)
// 設置色調映射
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
// 設置陰影
renderer.shadowMap.enabled = true
// 設置陰影類型,實現(xiàn)更加平滑和真實的陰影效果
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)
監(jiān)聽屏幕大小的改變并導入控制器:
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 初始化控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true
// 監(jiān)聽屏幕大小變化
window.addEventListener("resize",()=>{
renderer.setSize(window.innerWidth,window.innerHeight)
camera.aspect = window.innerWidth/window.innerHeight
camera.updateProjectionMatrix()
})
設置渲染函數(shù):
const render = () =>{
requestAnimationFrame(render);
renderer.render(scene, camera);
}
ok,寫完基礎代碼之后,接下來開始具體的Demo實操。
設置環(huán)境紋理加載模型
使用TextureLoader用于加載圖片紋理的工具,將圖片文件加載為 Three.js 中的紋理對象,并且可以將紋理應用到場景中的任何物體上,從而實現(xiàn)更加生動的渲染效果。
const texture = new THREE.TextureLoader()
texture.load("./textures/outdoor.jpg", (texture)=>{
texture.mapping = THREE.EquirectangularReflectionMapping
// 設置環(huán)境紋理
scene.background = texture
scene.environment = texture
scene.backgroundBlurriness = 0.3 // 設置模糊度
})
環(huán)境紋理設置完成之后通過GLTFLoader將GLTF格式的3D模型加載到網(wǎng)頁上,以及使用DRACOLoader能夠有效的壓縮3D模型文件的大小,提高加載速度和用戶體驗。
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
// 解壓模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/')
// 加載模型
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('./model/playground02.glb',(glb) => {
const model = glb.scene
scene.add(model)
})
設置聚光燈讓球的陰影變得更明顯:
// 添加聚光燈
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(10,50,0)
spotLight.castShadow = true
spotLight.shadow.mapSize.width = 2048
spotLight.shadow.mapSize.height = 2048
spotLight.shadow.camera.near = 0.5
spotLight.shadow.camera.far = 500
spotLight.shadow.camera.fov = 30
spotLight.shadow.bias = -0.00008
spotLight.intensity = 0.1
scene.add(spotLight)
使用Cannon-es實現(xiàn)物理世界
Cannon-es通常被用作物理引擎庫,用于模擬三維場景中的物理效果。它可以實現(xiàn)碰撞檢測、物體受力作用、模擬重力等功能,從而讓3D場景更加真實和有趣。
這里終端先進行下載該相關庫,然后進行實例化創(chuàng)建物理世界:
import * as CANNON from 'cannon-es'
// 初始化物理世界
const world = new CANNON.World()
world.gravity.set(0,-9.82,0)
let clock = new THREE.Clock()
// 設置渲染函數(shù)
const render = () =>{
let delta = clock.getDelta()
world.step(delta) // 更新物理世界
if(ball && ballBody) {
ball.position.copy(ballBody.position)
ball.quaternion.copy(ballBody.quaternion)
}
controls.update()
requestAnimationFrame(render)
renderer.render(scene,camera)
}
render()
這里通過traverse遍歷3D場景圖,遍歷場景中所有的對象(Object3D)以及它們的子對象,并對它們進行操作或者判斷。
model.traverse((child)=>{
if(child.isMesh && child.name.search(/Solid/ == -1)) {
child.castShadow = true
child.receiveShadow = true
// 創(chuàng)建trimesh類型
const trimesh = new CANNON.Trimesh(
child.geometry.attributes.position.array,
child.geometry.index.array
)
// 創(chuàng)建剛體
const trimeshBody = new CANNON.Body({
mass: 0,
shape: trimesh
})
// 獲取世界位置和旋轉給到物理世界
trimeshBody.position.copy(child.getWorldPosition(new THREE.Vector3()))
trimeshBody.quaternion.copy(
child.getWorldQuaternion(new THREE.Quaternion())
)
// 添加剛體到物理世界
world.addBody(trimeshBody)
if(child.name === "door"){
child.material = new THREE.MeshBasicMaterial({
color: 0x000000,
opacity:0,
transparent:true
})
}
}
if(child.name === 'Soccer_Ball'){
ball = child
// 創(chuàng)建球體
const ballShape = new CANNON.Sphere(0.15)
// 創(chuàng)建剛體
ballBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0,5,0),
shape: ballShape
})
// 添加剛體到物理世界
world.addBody(ballBody)
}
setTimeout(()=>{
ballBody.position.set(0,15,0)
ballBody.velocity.set(0,0,0)
ballBody.angularVelocity.set(0,0,0)
},2000)
})
通過設置點擊事件動態(tài)讓 Vec3(表示三維空間中的位置、方向、速度等值) ,為了顯示改變數(shù)值大小,接下來定義一個變量來去動態(tài)改變其值,這里借用gsap動畫庫去讓其一直重復的在某個范圍來回的跳動:
let percentage = ref(30)
gsap.to(percentage, {
duration: 1,
value: 100,
ease: "linear",
repeat: -1,
onUpdate: () =>{
percentage.value = Math.floor(percentage.value)
}
})
將動態(tài)變量設置在 Vec3 參數(shù)里面,如下:
let isClick = false
// 設置點擊事件
window.addEventListener('click',()=>{
if(isClick) return
isClick = true
ballBody.applyForce(
new CANNON.Vec3(
-1*percentage.value,
6*percentage.value,
(Math.random() - 0.5)*percentage.value
),ballBody.position)
setTimeout(()=>{
isClick = false
ballBody.position.set(0,15,0)
ballBody.velocity.set(0,0,0)
ballBody.angularVelocity.set(0,0,0)
},4000)
})
接下來通過html和css樣式來動態(tài)顯示數(shù)值的變化:
<template>
<div class="">
<h1>{{ percentage }}</h1>
</div>
</template>
<style lang="less">
canvas {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
}
h1{
position: fixed;
left: 0;
top: 0;
z-index: 10;
color: #fff;
}
</style>
文章來源:http://www.zghlxwxcb.cn/news/detail-501110.html
demo做完,給出本案例的完整代碼:(獲取素材也可以私信博主)?文章來源地址http://www.zghlxwxcb.cn/news/detail-501110.html
<template>
<div class="">
<h1>{{ percentage }}</h1>
</div>
</template>
<script setup>
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { ref } from 'vue'
import gsap from 'gsap'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
let percentage = ref(30)
gsap.to(percentage, {
duration: 1,
value: 100,
ease: "linear",
repeat: -1,
onUpdate: () =>{
percentage.value = Math.floor(percentage.value)
}
})
// 初始化場景
const scene = new THREE.Scene()
// 初始化相機
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.set(4,2,0)
camera.updateProjectionMatrix()
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 設置抗鋸齒
logarithmicDepthBuffer: true, // 使用對數(shù)緩存
})
renderer.setSize(window.innerWidth,window.innerHeight)
// 設置色調映射
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
// 設置陰影
renderer.shadowMap.enabled = true
// 設置陰影類型,實現(xiàn)更加平滑和真實的陰影效果
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)
// 初始化控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true
// 監(jiān)聽屏幕大小變化
window.addEventListener("resize",()=>{
renderer.setSize(window.innerWidth,window.innerHeight)
camera.aspect = window.innerWidth/window.innerHeight
camera.updateProjectionMatrix()
})
// 設置環(huán)境紋理
const texture = new THREE.TextureLoader()
texture.load("./textures/outdoor.jpg", (texture)=>{
texture.mapping = THREE.EquirectangularReflectionMapping
// 設置環(huán)境紋理
scene.background = texture
scene.environment = texture
scene.backgroundBlurriness = 0.3 // 設置模糊度
})
let ball,ballBody
// 解壓模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/')
// 加載模型
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('./model/playground02.glb',(glb) => {
const model = glb.scene
model.traverse((child)=>{
if(child.isMesh && child.name.search(/Solid/ == -1)) {
child.castShadow = true
child.receiveShadow = true
// 創(chuàng)建trimesh類型
const trimesh = new CANNON.Trimesh(
child.geometry.attributes.position.array,
child.geometry.index.array
)
// 創(chuàng)建剛體
const trimeshBody = new CANNON.Body({
mass: 0,
shape: trimesh
})
// 獲取世界位置和旋轉給到物理世界
trimeshBody.position.copy(child.getWorldPosition(new THREE.Vector3()))
trimeshBody.quaternion.copy(
child.getWorldQuaternion(new THREE.Quaternion())
)
// 添加剛體到物理世界
world.addBody(trimeshBody)
if(child.name === "door"){
child.material = new THREE.MeshBasicMaterial({
color: 0x000000,
opacity:0,
transparent:true
})
}
}
if(child.name === 'Soccer_Ball'){
ball = child
// 創(chuàng)建球體
const ballShape = new CANNON.Sphere(0.15)
// 創(chuàng)建剛體
ballBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0,5,0),
shape: ballShape
})
// 添加剛體到物理世界
world.addBody(ballBody)
}
setTimeout(()=>{
ballBody.position.set(0,15,0)
ballBody.velocity.set(0,0,0)
ballBody.angularVelocity.set(0,0,0)
},2000)
})
scene.add(model)
})
// 添加聚光燈
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(10,50,0)
spotLight.castShadow = true
spotLight.shadow.mapSize.width = 2048
spotLight.shadow.mapSize.height = 2048
spotLight.shadow.camera.near = 0.5
spotLight.shadow.camera.far = 500
spotLight.shadow.camera.fov = 30
spotLight.shadow.bias = -0.00008
spotLight.intensity = 0.1
scene.add(spotLight)
// 初始化物理世界
const world = new CANNON.World()
world.gravity.set(0,-9.82,0)
let clock = new THREE.Clock()
// 設置渲染函數(shù)
const render = () =>{
let delta = clock.getDelta()
world.step(delta) // 更新物理世界
if(ball && ballBody) {
ball.position.copy(ballBody.position)
ball.quaternion.copy(ballBody.quaternion)
}
controls.update()
requestAnimationFrame(render)
renderer.render(scene,camera)
}
render()
let isClick = false
// 設置點擊事件
window.addEventListener('click',()=>{
if(isClick) return
isClick = true
ballBody.applyForce(
new CANNON.Vec3(
-1*percentage.value,
6*percentage.value,
(Math.random() - 0.5)*percentage.value
),ballBody.position)
setTimeout(()=>{
isClick = false
ballBody.position.set(0,15,0)
ballBody.velocity.set(0,0,0)
ballBody.angularVelocity.set(0,0,0)
},4000)
})
</script>
<style lang="less">
canvas {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
}
h1{
position: fixed;
left: 0;
top: 0;
z-index: 10;
color: #fff;
}
</style>
到了這里,關于Three.js--》實現(xiàn)3d踢球模型展示的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!