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
中控制材质对之间的表现 - 弹跳模拟可以用于球类、橡胶类、弹簧类等物理对象
- 实际模拟中弹性和摩擦常常需要搭配调试