04 - 弹性与接触材质详解

发布于 2025-07-24 00:22 775 字 4 min read

做产品前,先别急着写代码:我是怎么判断一个点子值不值得做的想偷卷?但微信不支持md文档?这个软件助你!新手小白如何开发练手项目?前端常见安全问题 + 防御方法 + 面试回答吉比特(雷霆游戏)前端二面问题总结吉比特27前端实习面试纷享销客前端实习一面08-刚体约束 Constraint07-监听碰撞事件和获取碰撞信息06 - 物体休眠与休眠事件详解(Cannon.js)05-碰撞与碰撞组04 - 弹性与接触材质详解03 - Cannon 材质与摩擦系数设置02 - Cannon 引擎基础碰撞讲解01 - Cannon.js 引擎的用途和基本使用一款减轻前端图片命名工作量的图片转换器总结:一个双非大学生的两年,是怎么过来的?数学公式Input-输入框组件实现详解用HTML5实现实时ASCII艺术摄像头object-fit: contain - CSS 属性详解GSAP (GreenSock Animation Platform) 动画库学习指南Webview通信系统学习指南实现手势操控3D粒子动画?太好玩了!东田数码科技前端面试前端性能优化面试回答技巧(一)React与Vue表单的对比差异融山科技前端面经·React的入门学习(一)九方前端面试JavaScript 核心:数组/字符串 API 精要指南JavaScript的常用数组API原理解析前端练习小网站-新手突破之路前端の骚操作代码合集 (二)| 让你的网页变得有趣前端Threejs入门(五)Vue 3 响应式原理:computed的模板解包机制Vue 监听器 watch 深度解析Axios 深入解析从0到1配置vue项目跟git仓库连接!前端Three.js入门(四)你懂keep-alive吗?见过Vue的小区保安吗?前端Three.js入门(三)前端Three.js入门(二)前端Three.js入门(一)-Three的第一个简单的页面 Git 大冒险:解锁代码管理的秘密武器如何将网站加载速度提升50%?前端性能优化全攻略前端の骚操作代码合集 | 让你的网页充满恶趣味进击蓝桥杯!2025web前端HTML5uniapp-页面生命周期
04 弹性与接触材质详解 作者: 前端AC | 原文: https://juejin.cn/post/7530141922515779594 04 弹性与接触材质详解 本节目标 理解 restitution(弹性系数)的意义 设置两种材质之间的弹跳效果 对比高弹性与低弹性...

04 - 弹性与接触材质详解

作者: 前端AC | 原文: https://juejin.cn/post/7530141922515779594

04 - 弹性与接触材质详解

本节目标

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

什么是弹性系数(restitution)?

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

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

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

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

new CANNON.ContactMaterial(matA, matB, {
  restitution: 0.9
})

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

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


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

<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>

观察结果

2025-07-23T13_02_28.319Z-592165.gif
2025-07-23T13_02_28.319Z-592165.gif
  • 红色小球(restitution = 0):一落地即停止
  • 绿色小球(restitution = 0.9):反复弹跳好几次后才慢慢停止

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


小结

  • restitution 决定了刚体之间碰撞的反弹程度
  • 设置在 ContactMaterial 中控制材质对之间的表现
  • 弹跳模拟可以用于球类、橡胶类、弹簧类等物理对象
  • 实际模拟中弹性和摩擦常常需要搭配调试