04 - 弹性与接触材质详解

04 - 弹性与接触材质详解

本节目标

  • 理解 restitution(弹性系数)的意义
  • 设置两种材质之间的弹跳效果
  • 对比高弹性与低弹性物体的物理行为差异

什么是弹性系数(restitution)?

restitution 表示两个物体碰撞时的"反弹程度":

restitution 效果
0 完全不弹跳
1 完全弹回原高度
0 ~ 1 部分弹跳

它控制的是动能的保留程度。数值越高,动能损耗越少,弹跳越多。

在 Cannon.js 中,它在 ContactMaterial 中设置:

js 复制代码
new CANNON.ContactMaterial(matA, matB, {
  restitution: 0.9
})

示例:高弹与低弹两个球的落地行为

我们将创建两个球,一个弹性系数为 0(完全不弹),另一个为 1(高度弹跳),观察它们的表现差异。


完整 Vue3 示例(使用 cannon-es)

vue 复制代码
<script setup>
import { ref, onMounted } from 'vue'
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

const canvasRef = ref()

onMounted(() => {
  // 1. Three.js 场景、相机、渲染器
  const scene = new THREE.Scene()
  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000)
  camera.position.set(0, 5, 15)
  camera.lookAt(0, 0, 0)

  const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true })
  renderer.setSize(window.innerWidth, window.innerHeight)

  // 2. 轨道控制器
  const controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true // 阻尼,惯性效果
  controls.dampingFactor = 0.05

  // 3. 光照
  scene.add(new THREE.AmbientLight(0xffffff, 0.7))
  const dirLight = new THREE.DirectionalLight(0xffffff, 1)
  dirLight.position.set(10, 10, 10)
  scene.add(dirLight)

  // 4. 地面网格
  const groundGeo = new THREE.BoxGeometry(20, 1, 20)
  const groundMat = new THREE.MeshStandardMaterial({ color: 0x888888 })
  const groundMesh = new THREE.Mesh(groundGeo, groundMat)
  groundMesh.position.set(0, -0.5, 0)
  scene.add(groundMesh)

  // 5. 物理世界初始化
  const world = new CANNON.World()
  world.gravity.set(0, -9.82, 0)

  // 6. 地面物理体
  const groundBody = new CANNON.Body({
    mass: 0,
    shape: new CANNON.Box(new CANNON.Vec3(10, 0.5, 10)),
  })
  groundBody.position.set(0, -0.5, 0)
  world.addBody(groundBody)

  // 7. 创建材质
  const groundMaterial = new CANNON.Material('ground')
  const softMaterial = new CANNON.Material('soft')
  const bouncyMaterial = new CANNON.Material('bouncy')

  groundBody.material = groundMaterial

  // 8. 接触材质
  world.addContactMaterial(new CANNON.ContactMaterial(groundMaterial, softMaterial, {
    friction: 0.4,
    restitution: 0.0,
  }))

  world.addContactMaterial(new CANNON.ContactMaterial(groundMaterial, bouncyMaterial, {
    friction: 0.4,
    restitution: 0.9,
  }))

  // 9. 两个球体(视觉+物理)
  const sphereGeo = new THREE.SphereGeometry(1, 32, 32)

  const softMesh = new THREE.Mesh(
    sphereGeo,
    new THREE.MeshStandardMaterial({ color: 0xff5555 })
  )
  softMesh.position.set(-3, 8, 0)
  scene.add(softMesh)

  const softBody = new CANNON.Body({
    mass: 1,
    shape: new CANNON.Sphere(1),
    position: new CANNON.Vec3(-3, 8, 0),
    material: softMaterial,
  })
  world.addBody(softBody)

  const bouncyMesh = new THREE.Mesh(
    sphereGeo,
    new THREE.MeshStandardMaterial({ color: 0x55ff55 })
  )
  bouncyMesh.position.set(3, 8, 0)
  scene.add(bouncyMesh)

  const bouncyBody = new CANNON.Body({
    mass: 1,
    shape: new CANNON.Sphere(1),
    position: new CANNON.Vec3(3, 8, 0),
    material: bouncyMaterial,
  })
  world.addBody(bouncyBody)

  // 10. 动画循环
  const fixedTimeStep = 1 / 60

  function animate() {
    requestAnimationFrame(animate)

    world.step(fixedTimeStep)

    softMesh.position.copy(softBody.position)
    softMesh.quaternion.copy(softBody.quaternion)

    bouncyMesh.position.copy(bouncyBody.position)
    bouncyMesh.quaternion.copy(bouncyBody.quaternion)

    controls.update()

    renderer.render(scene, camera)
  }
  animate()

  // 11. 窗口尺寸变化处理
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
  })
})
</script>

<template>
  <canvas ref="canvasRef" style="display: block; width: 100vw; height: 100vh;"></canvas>
</template>

观察结果

  • 红色小球(restitution = 0):一落地即停止
  • 绿色小球(restitution = 0.9):反复弹跳好几次后才慢慢停止

你也可以试试修改 restitution = 1.0,观察是否可以无限弹跳(理论上动能完全保留,不会停下)。


小结

  • restitution 决定了刚体之间碰撞的反弹程度
  • 设置在 ContactMaterial 中控制材质对之间的表现
  • 弹跳模拟可以用于球类、橡胶类、弹簧类等物理对象
  • 实际模拟中弹性和摩擦常常需要搭配调试
相关推荐
恋猫de小郭8 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅15 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅17 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端