最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Threejs實現(xiàn)物理運動模擬

 更新時間:2026年03月23日 08:39:53   作者:誰在黃金彼岸  
本文主要介紹了Threejs實現(xiàn)物理運動模擬,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、物理運動原理

在 Three.js 中實現(xiàn)物理運動的核心思想是:

  1. Three.js 負(fù)責(zé)渲染:Three.js 是一個圖形庫,用于創(chuàng)建和渲染 3D 場景。
  2. 物理引擎負(fù)責(zé)計算:物理引擎(如 Cannon.js或 Ammo.js, 本文用的是前一種)模擬現(xiàn)實世界的物理規(guī)則,例如重力、碰撞、反彈等。
  3. 數(shù)據(jù)同步:在每一幀中,將物理引擎計算的結(jié)果(如物體的位置、速度、旋轉(zhuǎn)等)同步到 Three.js 的對象上。

二、實現(xiàn)步驟詳解

1. 初始化 Three.js 場景

在 initScene 方法中,創(chuàng)建 Three.js 的基本組件(場景、相機、渲染器等)。

function initScene() {
  if (!canvas.value) return
  makeRenderer(canvas.value.clientWidth, canvas.value.clientHeight)
  makeScene()
  makeCamera(canvas.value.clientWidth, canvas.value.clientHeight)
  orbitControls()
  addSky()
  addGround()
}
  • 初始化 WebGL 渲染器,并設(shè)置畫布大小和設(shè)備像素比。 makeRenderer
  • 創(chuàng)建 Three.js 場景,并添加光源。 makeScene
  • 創(chuàng)建透視相機,并設(shè)置初始位置。 makeCamera
  • 添加軌道控制器,用于控制相機視角。 orbitControls
  • 和 :分別添加天空和地面。 addSky``addGround

2. 創(chuàng)建物理世界

使用 Cannon.js 創(chuàng)建物理世界,并設(shè)置重力。

world = new Cannon.World()
world.gravity.set(0, -9.82, 0) // 設(shè)置重力 (m/s2)
  • world.gravity.set(0, -9.82, 0):設(shè)置重力方向為向下(Y 軸負(fù)方向),模擬地球重力。

3. 創(chuàng)建物體并同步到物理引擎

在 Three.js 和物理引擎中分別創(chuàng)建物體,并保持它們之間的同步。

創(chuàng)建球體(Three.js 對象)

sphere = new THREE.Mesh(
  new THREE.SphereGeometry(2, 32, 32),
  new THREE.MeshBasicMaterial({ color: 0xff11ff }),
)
sphere.position.set(0, 10, 0)
scene.add(sphere)
  • 使用 THREE.SphereGeometry 創(chuàng)建一個半徑為 2 的球體。
  • 將球體添加到 Three.js 場景中。

創(chuàng)建對應(yīng)的物理剛體(Cannon.js 對象)

const sphereBody = new Cannon.Body({
  mass: 1,
  shape: new Cannon.Sphere(2),
  position: new Cannon.Vec3(sphere.position.x, sphere.position.y, sphere.position.z),
})
world.addBody(sphereBody)
  • 使用 Cannon.Sphere 創(chuàng)建一個物理剛體,并設(shè)置質(zhì)量為 1。
  • 將物理剛體添加到物理世界中。

添加地面

const groundBody = new Cannon.Body({
  mass: 0,
  shape: new Cannon.Plane(),
})
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
world.addBody(groundBody)
  • 創(chuàng)建一個靜態(tài)的平面作為地面(mass: 0 表示靜態(tài)物體)。
  • 使用 setFromEuler 旋轉(zhuǎn)平面使其水平。

4. 動畫循環(huán)

在每一幀中更新物理引擎的狀態(tài),并同步到 Three.js 場景。

function animate() {
  animationFrameId = requestAnimationFrame(animate)

  // 更新物理引擎
  updatePhysic()

  // 渲染場景
  renderer.render(scene, camera)
}

function updatePhysic() {
  world.step(1 / 60) // 模擬 60 幀每秒
  sphere.position.copy(sphereBody.position) // 同步位置
}
  • world.step(1 / 60):更新物理引擎的世界狀態(tài),模擬 60 幀每秒。
  • sphere.position.copy(sphereBody.position):將物理引擎計算的結(jié)果同步到 Three.js 的球體對象上。

5. 處理窗口大小調(diào)整

當(dāng)窗口大小發(fā)生變化時,重新調(diào)整渲染器和相機的參數(shù)。

function onWindowResize() {
  if (!canvas.value) return
  const width = canvas.value.clientWidth
  const height = canvas.value.clientHeight

  camera.aspect = width / height
  camera.updateProjectionMatrix()
  renderer.setSize(width, height)
}
  • 調(diào)整相機的寬高比,并更新投影矩陣。
  • 重新設(shè)置渲染器的尺寸。

三、完整流程總結(jié)

  1. 初始化 Three.js 場景
    • 創(chuàng)建場景、相機、渲染器。
    • 添加光源、天空、地面等元素。
  2. 初始化物理引擎
    • 創(chuàng)建物理世界,并設(shè)置重力。
    • 添加地面和物體的物理剛體。
  3. 動畫循環(huán)
    • 在每一幀中更新物理引擎的狀態(tài)。
    • 將物理引擎的結(jié)果同步到 Three.js 對象上。
    • 渲染場景。
  4. 處理窗口大小調(diào)整
    • 監(jiān)聽窗口大小變化事件,動態(tài)調(diào)整相機和渲染器的參數(shù)。

四、關(guān)鍵點解析

1. 數(shù)據(jù)同步

  • 物理引擎中的物體(如 sphereBody)和 Three.js 中的物體(如 sphere)需要保持同步。
  • 在每一幀中,通過 sphere.position.copy(sphereBody.position) 實現(xiàn)位置同步。

2. 性能優(yōu)化

  • 使用 requestAnimationFrame 創(chuàng)建動畫循環(huán),確保流暢的動畫效果。
  • 設(shè)置物理引擎的更新頻率(如 world.step(1 / 60)),避免過度計算。

3. 物理材質(zhì)

  • 在代碼中,定義了兩種物理材質(zhì)(groundMaterial 和 sphereMaterial),并通過 設(shè)置摩擦系數(shù)和彈性系數(shù)。 ContactMaterial
  • 這些參數(shù)影響物體的碰撞行為,例如反彈高度和滑動距離。

五、源碼

<template>
  <el-container style="width: 100%; height: 100%">
    <!-- 頭部標(biāo)題 -->
    <el-header class="header"> 物理運動</el-header>
    <!-- 主要內(nèi)容區(qū)域,包含 Three.js 畫布 -->
    <el-main class="canvas-container">
      <canvas ref="canvas" class="canvas"></canvas>
    </el-main>
  </el-container>
</template>
<script lang="ts">
export default {
  name: 'PhysicalMotion', // 使用多詞名稱
}
</script>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { Sky } from 'three/examples/jsm/objects/Sky'
import { ShaderMaterial, MathUtils } from 'three'
import * as Cannon from 'cannon'
// Three.js 相關(guān)變量聲明
// 場景
let scene: THREE.Scene
// 透視相機
let camera: THREE.PerspectiveCamera
// WebGL渲染器
let renderer: THREE.WebGLRenderer
// 天空
let sky: THREE.Mesh
// 動畫幀ID
let animationFrameId: number
let controls: InstanceType<typeof OrbitControls>
let world: InstanceType<typeof Cannon.World>
let sphere: THREE.Mesh
let sphereBody: InstanceType<typeof Cannon.Body>
// 畫布引用
const canvas = ref<HTMLCanvasElement>()
// 組件掛載時初始化場景并開始動畫
onMounted(() => {
  initScene()
  animate()
  // 添加窗口大小改變事件監(jiān)聽
  window.addEventListener('resize', onWindowResize)
})
// 初始化場景
function initScene() {
  if (!canvas.value) return
  makeRenderer(canvas.value.clientWidth, canvas.value.clientHeight)
  makeScene()
  // 創(chuàng)建圓形
  sphere = new THREE.Mesh(
    new THREE.SphereGeometry(2, 32, 32),
    new THREE.MeshBasicMaterial({ color: 0xff11ff }),
  )
  sphere.position.set(0, 10, 0)
  scene.add(sphere)
  world = new Cannon.World()
  world.gravity.set(0, -9.82, 0)
  //創(chuàng)建物理材料
  const groundMaterial = new Cannon.Material("groundMaterial")
  const sphereMaterial = new Cannon.Material("sphereMaterial")
  const contactMaterial = new Cannon.ContactMaterial(groundMaterial, sphereMaterial, {
    friction: 0.3,
    restitution: 0.5,
  })
  world.addContactMaterial(contactMaterial)
  sphereBody = new Cannon.Body({
    mass: 1,
    shape: new Cannon.Sphere(2),
    position: new Cannon.Vec3(sphere.position.x, sphere.position.y, sphere.position.z),
    material: sphereMaterial,
  })
  world.addBody(sphereBody)
  const groundBody = new Cannon.Body({
    mass: 0,
    shape: new Cannon.Plane(),
    material: groundMaterial,
  })
  groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
  world.addBody(groundBody)
  makeCamera(canvas.value.clientWidth, canvas.value.clientHeight)
  orbitControls()
  addSky()
  addGround()
}
function updatePhysic(){
  world.step(1 / 60)
  sphere.position.copy(sphereBody.position)
}
function makeScene() {
  // 創(chuàng)建場景
  scene = new THREE.Scene()
  //添加光源
  const light = new THREE.DirectionalLight(0xffffff, 1)
  scene.add(light)
}
function makeRenderer(width: number, height: number) {
  // 創(chuàng)建WebGL渲染器
  renderer = new THREE.WebGLRenderer({ canvas: canvas.value, antialias: true })
  renderer.setSize(width, height)
  // 設(shè)置設(shè)備像素比,確保在高分辨率屏幕上清晰顯示
  renderer.setPixelRatio(window.devicePixelRatio)
}
function makeCamera(width: number, height: number) {
  // 創(chuàng)建透視相機
  camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
  // 設(shè)置相機位置
  camera.position.set(0, 10, 20)
  camera.lookAt(0, 0, 0)
}
function orbitControls() {
  controls = new OrbitControls(camera, canvas.value)
  // 限制攝像機的極角范圍(單位是弧度)
  controls.minPolarAngle = 0 // 最小角度,0 表示水平視角
  controls.maxPolarAngle = MathUtils.degToRad(85) // 最大角度,例如 85 度
  // 可選:限制攝像機的縮放范圍
  controls.minDistance = 5 // 最小距離
  controls.maxDistance = 100 // 最大距離
}
function addSky() {
  sky = new Sky()
  sky.scale.setScalar(450000) // 設(shè)置天空的縮放比例
  scene.add(sky)
  const sun = new THREE.Vector3()
  // 配置天空參數(shù)
  const effectController = {
    turbidity: 10,
    rayleigh: 2,
    mieCoefficient: 0.005,
    mieDirectionalG: 0.8,
    elevation: 2,
    azimuth: 180,
    exposure: renderer.toneMappingExposure,
  }
  const uniforms = (sky.material as ShaderMaterial).uniforms
  uniforms['turbidity'].value = effectController.turbidity
  uniforms['rayleigh'].value = effectController.rayleigh
  uniforms['mieCoefficient'].value = effectController.mieCoefficient
  uniforms['mieDirectionalG'].value = effectController.mieDirectionalG
  const phi = THREE.MathUtils.degToRad(90 - effectController.elevation)
  const theta = THREE.MathUtils.degToRad(effectController.azimuth)
  sun.setFromSphericalCoords(1, phi, theta)
  uniforms['sunPosition'].value.copy(sun)
}
// 添加大地
function addGround() {
  const groundGeometry = new THREE.PlaneGeometry(100, 100)
  const groundMaterial = new THREE.MeshStandardMaterial({
    color: 0x7ec850, // 綠色草地顏色
    side: THREE.DoubleSide,
  })
  const ground = new THREE.Mesh(groundGeometry, groundMaterial)
  ground.rotation.x = Math.PI / 2 // 旋轉(zhuǎn)平面使其水平
  scene.add(ground)
}
// 動畫循環(huán)函數(shù)
function animate() {
  animationFrameId = requestAnimationFrame(animate)
  controls.update()
  updatePhysic()
  // 渲染場景
  renderer.render(scene, camera)
}
// 清理函數(shù)
function cleanup() {
  // 取消動畫幀`
  if (animationFrameId) {
    cancelAnimationFrame(animationFrameId)
  }
  // 清理渲染器
  if (renderer) {
    // 移除所有事件監(jiān)聽器
    renderer.domElement.removeEventListener('resize', onWindowResize)
    // 釋放渲染器資源
    renderer.dispose()
    // 清空渲染器
    renderer.forceContextLoss()
    // 移除畫布
    renderer.domElement.remove()
  }
  // 清理場景
  if (scene) {
    // 遍歷場景中的所有對象
    scene.traverse((object) => {
      if (object instanceof THREE.Mesh) {
        // 釋放幾何體資源
        object.geometry.dispose()
        // 釋放材質(zhì)資源
        if (Array.isArray(object.material)) {
          object.material.forEach((material) => material.dispose())
        } else {
          object.material.dispose()
        }
      }
    })
    // 清空場景
    scene.clear()
  }
  // 清理相機
  if (camera) {
    camera.clear()
  }
}
// 窗口大小改變時的處理函數(shù)
function onWindowResize() {
  if (!canvas.value) return
  const width = canvas.value.clientWidth
  const height = canvas.value.clientHeight
  console.log('onWindowResize', width, height)
  // 更新相機
  camera.aspect = width / height
  camera.updateProjectionMatrix()
  // 更新渲染器
  renderer.setSize(width, height)
  // 確保渲染器的像素比與設(shè)備匹配
  renderer.setPixelRatio(window.devicePixelRatio)
}
// 組件卸載時清理資源
onUnmounted(() => {
  cleanup()
  // 移除窗口大小改變事件監(jiān)聽
  window.removeEventListener('resize', onWindowResize)
})
</script>
<style scoped>
/* 頭部樣式 */
.header {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  color: #333;
}
/* 畫布容器樣式 */
.canvas-container {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
/* 畫布樣式   // 保持畫布比例*/
.canvas {
  width: 100%;
  height: 100%;
}
</style>

到此這篇關(guān)于Threejs實現(xiàn)物理運動模擬的文章就介紹到這了,更多相關(guān)Threejs 物理運動模擬內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

手游| 页游| 咸丰县| 五大连池市| 怀化市| 桃园市| 道真| 平遥县| 扎赉特旗| 鄂托克旗| 宜丰县| 内黄县| 鄱阳县| 海伦市| 黑龙江省| 大丰市| 增城市| 沈丘县| 砚山县| 新田县| 利辛县| 昌平区| 靖西县| 宝山区| 三原县| 潼南县| 拜泉县| 新龙县| 全南县| 左权县| 澄迈县| 呼伦贝尔市| 皮山县| 吉安市| 象州县| 焉耆| 武宁县| 无极县| 日照市| 乌拉特后旗| 万山特区|