Three.js 结合 cannon-es 实现物理引擎基础示例
本文详解如何在Three.js项目中集成cannon-es物理引擎。内容涵盖安装引入、创建物理世界与刚体、设置重力加速度及约束系统,并通过球体自由落体与地面碰撞的完整代码示例,展示如何在渲染循环中同步物理数据与3D视觉表现,实现浏览器端真实的3D物理模拟效果。
Cannon.js 是一款轻量级且高性能的物理引擎,其代码结构简洁,易于理解和二次开发。
它具备轻量级与高性能特性,专为快速运算和轻量级应用设计,源码清晰直观。
该引擎提供完整的3D物理模拟能力,涵盖刚体碰撞检测、力学计算及约束求解,能够还原真实世界的物理交互效果。
其约束系统具有高度灵活性,支持距离约束、弹簧约束、旋转约束等多种类型,开发者可根据具体需求定制复杂的物理连接。
Cannon.js 基于 WebGL 技术构建,能够无缝集成到各类 WebGL 应用中,轻松在浏览器环境中呈现复杂的3D物理交互效果。
该引擎具备良好的跨平台兼容性,支持主流桌面浏览器及移动设备,确保物理模拟应用在不同终端上稳定运行。
cannon-es 的安装与引入流程
首先,通过 npm 命令行工具安装 cannon-es 模块,将其添加至项目依赖中。
npm install --save cannon-es在 HTML 和 JavaScript 文件中分别引入 cannon-es、three 以及 three/jsm 模块,其中 three/jsm 用于引入 Three.js 的特定实例组件。
<script type="importmap">
{
"imports": {
"three": "../js/three.module.js",
"three/jsm/": "../js/jsm/",
"cannon-es": "../node_modules/cannon-es/dist/cannon-es.js"
}
}
</script>在 JavaScript 代码中,通过全局引入方式加载 cannon-es 模块,以便在后续逻辑中调用其物理引擎功能。
import * as Cannon from 'cannon-es'初始工作:3D环境搭建与物体准备
首先构建3D场景并引入必要的3D实例,为后续物理模拟搭建基础环境。
import * as THREE from 'three'
import * as Cannon from 'cannon-es'
import { OrbitControls } from '../../js/jsm/controls/OrbitControls.js'
// 目标:物理引擎 common-es 使用
// 1. 下载引入 cannon-es 包
// 2. 准备球体,地面,灯光和阴影
// 3. 基于 cannon 创建物理世界,创建物理世界的小球(图形+材质)
// 4. 在渲染循环中更新物理世界内的球体运动轨迹,并影响到 3D 物体(核心思想)
let scene, camera, renderer, controls, ball, floor, world, sphereBody
function init() {
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 0, 30)
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.querySelector('#app').appendChild(renderer.domElement)
renderer.shadowMap.enabled = true
controls = new OrbitControls(camera, renderer.domElement)
const axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
}第一步是准备球体模型。在3D场景中,网格模型(Mesh)需通过几何体(Geometry)定义外形;同理,在物理世界中,你需要设置物体Body的几何形状。CannonJS 提供了多种定义几何体形状的API,例如长方体Box、球体Sphere等,开发者可根据需求选择合适的形状。
由于场景中已存在视觉球体,接下来需在 Cannon.js 的 world 中创建对应的物理球体。这需要使用 Body 类,它支持自由落体运动及与其他 Body 的碰撞检测。在创建 Body 之前,必须先定义一个 Shape(形状对象)。
//创建屋里小球
const sphereShape = new Cannon.Sphere(1)
// 创建物理小球材质
const sphereMaterial = new Cannon.Material();第二步,通过 Cannon.World 类创建物理世界,并定义其物理属性,例如设置重力加速度。重力加速度的属性gravity类似于 Body 的位置,是一个三维向量Vec3。该向量的 x、y、z 分量值需根据项目坐标系设定;假设场景中 y 轴竖直向上,则重力加速度应设置为 y 轴负方向。
world = new Cannon.World({
// 自由落体加速度
gravity: new Cannon.Vec3(0, -9.8, 0)
})第三步,使用方法.addBody()将物体添加到物理世界。将物理球体 Body 加入 World 后,该 Body 将直接受物理世界加速度(如重力)的影响。
// 创建物理世界
sphereBody = new Cannon.Body({
shape: sphereShape, // 物体形状
material: sphereMaterial, // 物位材质
position: new Cannon.Vec3(0,0,0,),
mass: 1
})
// 把物理小球加入到物理世界
world.addBody(sphereBody)第四步,创建一个地面模型,作为球体自由落体的参照物,用于接收碰撞效果。
floor = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial({color: 0xffff00,}));
floor.position.set(0, -10, 0);
floor.rotation.set(-Math.PI/2, 0, 0);
floor.receiveShadow = true;第五步,所有3D实例和物体都需要光照支持,否则无法在视口中显示。因此,需要添加环境光和定向灯光以照亮场景。
//环境光
const ambientLingth = new THREE.AmbientLight(0xffffff, 1)
//灯光
const directionLight = new THREE.DirectionalLight(0xffffff, 1)
directionLight.position.set(0, 15, 0)
directionLight.castShadow = true第六步,将球体、地面、环境光及定向灯光全部添加到场景中,完成视觉资源的整合。
scene.add(ball)
scene.add(floor)
scene.add(ambientLingth)
scene.add(directionLight)第七步,在渲染循环中持续更新物理世界内球体的运动轨迹,并将物理数据同步给3D物体,这是实现物理与视觉同步的核心思想。
const clock = new THREE.Clock()
function renderLoop() {
// 4. 在渲染循环中更新物理世界内的球体运动轨迹,并影响到 3D 物体(核心思想)
const time = clock.getDelta()
// 物理世界不断渲染
// 参数1:固定步长,间隔多久重绘物理世界
// 参数2:自上次调用后要经过多长时间
world.step(1 / 160, time)
// 把物理世界小球最新位置更新给 3D 物体小球位置
ball.position.copy(sphereBody.position)
renderer.render(scene, camera)
requestAnimationFrame(renderLoop)
}完整代码和效果图展示

import * as THREE from 'three'
import * as Cannon from 'cannon-es'
import { OrbitControls } from '../../js/jsm/controls/OrbitControls.js'
// 目标:物理引擎 common-es 使用
// 1. 下载引入 cannon-es 包
// 2. 准备球体,地面,灯光和阴影
// 3. 基于 cannon 创建物理世界,创建物理世界的小球(图形+材质)
// 4. 在渲染循环中更新物理世界内的球体运动轨迹,并影响到 3D 物体(核心思想)
let scene, camera, renderer, controls, ball, floor, world, sphereBody
function init() {
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 0, 30)
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.querySelector('#app').appendChild(renderer.domElement)
renderer.shadowMap.enabled = true
controls = new OrbitControls(camera, renderer.domElement)
const axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
}
const clock = new THREE.Clock()
function renderLoop() {
// 4. 在渲染循环中更新物理世界内的球体运动轨迹,并影响到 3D 物体(核心思想)
const time = clock.getDelta()
// 物理世界不断渲染
// 参数1:固定步长,间隔多久重绘物理世界
// 参数2:自上次调用后要经过多长时间
world.step(1 / 160, time)
// 把物理世界小球最新位置更新给 3D 物体小球位置
ball.position.copy(sphereBody.position)
renderer.render(scene, camera)
requestAnimationFrame(renderLoop)
}
// 2. 准备球体,地面,灯光和阴影
function createBase () {
// 球体
ball = new THREE.Mesh(new THREE.SphereGeometry(1,32, 16), new THREE.MeshStandardMaterial({color: 0xffff00,}));
ball.castShadow = true
// 地面
floor = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial({color: 0xffff00,}));
floor.position.set(0, -10, 0);
floor.rotation.set(-Math.PI/2, 0, 0);
floor.receiveShadow = true;
//环境光
const ambientLingth = new THREE.AmbientLight(0xffffff, 1)
//灯光
const directionLight = new THREE.DirectionalLight(0xffffff, 1)
directionLight.position.set(0, 15, 0)
directionLight.castShadow = true
scene.add(ball)
scene.add(floor)
scene.add(ambientLingth)
scene.add(directionLight)
}
// 3. 基于 cannon 创建物理世界
function createWorld () {
world = new Cannon.World({
// 自由落体加速度
gravity: new Cannon.Vec3(0, -9.8, 0)
})
//创建屋里小球
const sphereShape = new Cannon.Sphere(1)
// 创建物理小球材质
const sphereMaterial = new Cannon.Material();
// 创建物理世界
sphereBody = new Cannon.Body({
shape: sphereShape, // 物体形状
material: sphereMaterial, // 物位材质
position: new Cannon.Vec3(0,0,0,),
mass: 1
})
// 把物理小球加入到物理世界
world.addBody(sphereBody)
}
init()
createBase()
createWorld()
setTimeout(() => {
renderLoop()
},1000)至此,关于 Three.js 结合 cannon-es 基础使用的教程介绍完毕。如需了解更多相关使用技巧,请查阅本站往期文章或继续浏览后续内容,感谢支持!
JS中使用位运算(~~)和一元加号(+)快速转换字符串为整数
2026-11-03 22:41:12
怎么使用命令行批量格式化项目中的JS文件
2026-11-03 19:04:42
JS代码美化后报错怎么办?常见格式化冲突解决方法
2026-11-03 18:47:01
不用moment.js,怎么用原生JS Intl API格式化时间戳
2026-11-03 18:10:30
原生JS与dayjs获取当前日期的代码对比
2026-11-03 17:57:26
