造个海洋球池来学习物理引擎【Three.js系列】
github地址:https://github.com/hua1995116/Fly-Three.js
大家好,我是秋风。继上一篇《Three.js系列: 游戏中的第一/三人称视角》今天想要和大家分享的呢,是做一个海洋球池。
海洋球大家都见过吧?就是商场里非常受小孩子们青睐的小球,自己看了也想往里蹦跶的那种。
就想着做一个海洋球池,然后顺便带大家来学习学习 Three.js 中的物理引擎。
那么让我们开始吧,要实现一个海洋球池,那么首先肯定得有“球”吧。
因此先带大家来实现一个小球,而恰恰在 Three.js 中定义一个小球非常的简单。因为 Three.js 给我们提供非常丰富几何形状 API ,大概有十几种吧。
提供的几何形状恰巧有我们需要的球形, 球形的 API 叫 SphereGeometry。
SphereGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)
这个API 一共有 7 个参数,但是呢,我们需要用到就只有前3个参数,后面的暂时不需要管。
Radius 的意思很简单,就是半径,说白了就是设置小球的大小,首先我们设置小球的大小,设置为 0.5,然后其次就是 widthSegments 和 heightSegments ,这俩值越大,球的棱角就越少,看起来就越细腻,但是精细带来的后果就是性能消耗越大,widthSegments 默认值为32,heightSegments默认值为 16 ,我们可以设置 20, 20
const sphereGeometry = new THREE.SphereGeometry(0.5, 20, 20);
这非常的简单,虽然小球有了形状,我们还得给小球设置上材质,材质就是类似我们现实生活中的材料,不是是只要是球形的就叫一个东西,比如有玻璃材质的弹珠,有橡胶材质的网球等等,不同的材质会与光的反射不一样,看起来的样子也不一样。在 Three.js 中我们就设置一个标准物理材质 MeshStandardMaterial
,它可以设置金属度和粗糙度,会对光照形成反射,然后把球的颜色设置成红色,
const sphereMaterial = new THREE.MeshStandardMaterial({
color: '#ff0000'
});
const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(mesh);
然后我们将它添加到我们的场景中,emmm,看起来黑乎乎的一片。
“上帝说要有光,于是就有了光”,黑乎乎是正常的,因为在我们场景中没有灯光,这个意思很简单,当夜晚的时候,关了灯当然是伸手不见五指。于是我们在场景中加入两盏灯,一个环境灯,一个直射灯,灯光在本篇文章中不是重点,所以就不会展开描述。只要记住,”天黑了,要开灯”
// Ambient light
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
// Directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight.position.set(2, 2, -1)
scene.add(directionalLight)
嗯!现在这个球终于展现出它的样子了。
一个静态的还海洋球肯定没有什么意思,我们需要让它动起来,因此我们需要给它添加物理引擎。有了物理引擎之后小球就会像现实生活中的样子,有重力,在高空的时候它会做自由落地运动,不同材质的物体落地的时候会有不同的反应,网球落地会弹起再下落,铅球落地则是静止的。
常用的 3d 物理引擎有Physijs 、Ammo.js 、Cannon.js 和 Oimo.js 等等。这里我们用到的则是 Cannon.js
在 Cannon.js 官网有很多关于 3d 物理的效果,详细可以看他的官网 https://pmndrs.github.io/cannon-es/
引入 Cannon.js
import * as CANNON from 'https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.js';
首先先创建一个物理的世界,并且设置重力系数 9.8
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
在物理世界中创建一个和我们 Three.js 中一一对应的小球,唯一不一样的就是需要设置 mass,就是小球的重量。
const shape = new CANNON.Sphere(0.5);
const body = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: shape,
});
world.addBody(body);
然后我们再修改一下我们的渲染逻辑,我们需要让每一帧的渲染和物理世界对应。
+ const clock = new THREE.Clock();
+ let oldElapsedTime = 0;
const tick = () => {
+ const elapsedTime = clock.getElapsedTime()
+ const deltaTime = elapsedTime - oldElapsedTime;
+ oldElapsedTime = elapsedTime;
+ world.step(1 / 60, deltaTime, 3);
controls.update();
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick();
但是发现我们的小球并没有动静,原因是我们没有绑定物理世界中和 Three.js 小球的关系。
const tick = () => {
...
+ mesh.position.copy(body.position);
...
}
来看看现在的样子。
小球已经有了物理的特性,在做自由落体了~ 但是由于没有地面,小球落向了无尽的深渊,我们需要设置一个地板来让小球落在一个平面上。
创建 Three.js 中的地面, 这里主要用到的是 PlaneGeometry
它有4个参数
PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer)
和之前类似我们只需要关注前 2 个参数,就是平面的宽和高,由于平面默认是 x-y 轴的平面,由于Three.js 默认用的是右手坐标系,对应的旋转也是右手法则,所以逆时针为正值,顺时针为负值,而我们的平面需要向顺时针旋转 90°,所以是 -PI/2
const planeGeometry = new THREE.PlaneGeometry(20, 20);
const planeMaterial = new THREE.MeshStandardMaterial({
color: '#777777',
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI * 0.5;
scene.add(plane);
然后继续绑定平面的物理引擎,写法基本和 Three.js 差不多,只是 API 名字不一样
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
floorBody.mass = 0;
floorBody.addShape(floorShape);
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1, 0, 0), Math.PI * 0.5);
world.addBody(floorBody);
来看看效果:
但是这个效果仿佛是一个铅球落地的效果,没有任何回弹以及其他的效果。为了让小球不像铅球一样直接落在地面上,我们需要给小球增加弹性系数。
const defaultMaterial = new CANNON.Material("default");
const defaultContactMaterial = new CANNON.ContactMaterial(
defaultMaterial,
defaultMaterial,
{
restitution: 0.4,
}
);
world.addContactMaterial(defaultContactMaterial);
world.defaultContactMaterial = defaultContactMaterial;
...
const body = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: shape,
+ material: defaultMaterial,
});
...
查看效果:
海洋球池当然不能只有一个球,我们需要有很多很多球,接下来我们再来实现多个小球的情况,为了生成多个小球,我们需要写一个随机小球生成器。
const objectsToUpdate = [];
const createSphere = (radius, position) => {
const sphereMaterial = new THREE.MeshStandardMaterial({
metalness: 0.3,
roughness: 0.4,
color: Math.random() * 0xffffff
});
const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
mesh.scale.set(radius, radius, radius);
mesh.castShadow = true;
mesh.position.copy(position);
scene.add(mesh);
const shape = new CANNON.Sphere(radius * 0.5);
const body = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: shape,
material: defaultMaterial,
});
body.position.copy(position);
world.addBody(body);
objectsToUpdate.push({
mesh,
body,
});
};
以上只是对我们之前写的代码做了一个函数封装,并且让小球的颜色随机,我们暴露出小球的位置以及小球的大小两个参数。
最后我们需要修改一下更新的逻辑,因为我们需要在每一帧修改每个小球的位置信息。
const tick = () => {
...
for (const object of objectsToUpdate) {
object.mesh.position.copy(object.body.position);
object.mesh.quaternion.copy(object.body.quaternion);
}
...
}
紧接着我们再来写一个点击事件,点击屏幕的时候能生成 100 个海洋球。
window.addEventListener('click', () => {
for (let i = 0; i < 100; i++) {
createSphere(1, {
x: (Math.random() - 0.5) * 10,
y: 10,
z: (Math.random() - 0.5) * 10,
});
}
}, false);
查看下效果:
初步的效果已经实现了,由于我们的池子只有底部一个平面,没有设置任何墙,所以小球就四处散开了。所以大家很容易地想到,我们需要建设4面墙,由于墙和底部平面有的区别就是有厚度,它不是一个单纯的面,因此我们需要用到新的形状 —— BoxGeometry
, 它一共也有7个参数,但是我们也只需要关注前3个,对应的就是长宽高。
BoxGeometry(width : Float, height : Float, depth : Float, widthSegments : Integer, heightSegments : Integer, depthSegments : Integer)
现在我们来建立一堵 长20, 宽 5, 厚度为 0.1 墙。
const box = new THREE.BoxGeometry(20, 5, 0.1);
const boxMaterial = new THREE.MeshStandardMaterial({
color: '#777777',
metalness: 0.3,
roughness: 0.4,
});
const box = new THREE.Mesh(box, boxMaterial);
box.position.set(0, 2.5, -10);
scene.add(box)
现在它长成了这个样子:
接着我们”依葫芦画瓢“完成剩下3面墙:
Untitled
然后我们也给我们的墙添加上物理引擎,让小球触摸到的时候,仿佛是真的碰到了墙,而不是穿透墙。
const halfExtents = new CANNON.Vec3(20, 5, 0.1)
const boxShape = new CANNON.Box(halfExtents)
const boxBody1 = new CANNON.Body({
mass: 0,
material: defaultMaterial,
shape: boxShape,
})
boxBody1.position.set(0, 2.5, -10);
world.addBody(boxBody1);
...
boxBody2
boxBody3
boxBody4
查看效果
收获满满一盆海洋球
大功告成!
来总结一下我们本期学习的内容,一共用到 SphereGeometry、PlaneGeometry、 BoxGeometry,然后学习了 Three.js 几何体 与 物理引擎 cannon.js 绑定,让小球拥有物理的特性。
主要得步骤为
- 定义小球
- 引入物理引擎
- 将 Three.js 和 物理引擎结合
- 生成随机球
- 定义墙
好了,以上就是本章的全部内容了,下一个篇章再见。
github地址:https://github.com/hua1995116/Fly-Three.js
系列连载首发地址:
造个海洋球池来学习物理引擎【Three.js系列】的更多相关文章
- Cocos2d-x3.2 使用物理引擎进行碰撞检测[转]
通常在游戏简单逻辑判断和模拟真实的物理世界时,我们只需要在定时器中判断游戏中各个精灵的条件是否满足判断条件就可以了.例如,在飞机大战中,判断我方子弹和敌机是否发生碰撞一般在定时器中通过敌机所在位置的矩 ...
- Cocos2d-x3.2总结---使用物理引擎进行碰撞检测
[转自]: http://blog.csdn.net/cbbbc/article/details/38541099 通常在游戏简单逻辑判断和模拟真实的物理世界时,我们只需要在定时器中判断游戏中各个精灵 ...
- Cocos2d-x 使用物理引擎进行碰撞检测
[转自]: http://blog.csdn.net/cbbbc/article/details/38541099 通常在游戏简单逻辑判断和模拟真实的物理世界时,我们只需要在定时器中判断游戏中各个精灵 ...
- 基于Babylon.js编写宇宙飞船模拟程序1——程序基础结构、物理引擎使用、三维罗盘
计划做一个宇宙飞船模拟程序,首先做一些技术准备. 可以访问https://ljzc002.github.io/test/Spacetest/HTML/PAGE/spacetestwp2.html查看测 ...
- p2.js物理引擎学习
P2简介 P2是一款基于Javascript编写的HTML5 2D物理引擎,和Box2D.Nape等2D物理引擎一样,P2集成了各种复杂的物理公式和算法,可以帮助我们轻松的实现碰撞.反弹等物理现象的模 ...
- 【AwayPhysics学习笔记】:Away3D物理引擎的简介与使用
首先我们要了解的是AwayPhysics这个物理引擎并不是重头开始写的新物理引擎,而是使用Flascc技术把一个已经很成熟的Bullet物理引擎引入到了Flash中,同时为了让as3可以使用这个C++ ...
- 物理引擎简介——Cocos2d-x学习历程(十三)
Box2D引擎简介 Box2D是与Cocos2d-x一起发布的一套开源物理引擎,也是Cocos2d-x游戏需要使用物理引擎时的首选.二者同样提供C++开发接口,所使用的坐标系也一致,因此Box2D与C ...
- libgdx学习记录18——Box2d物理引擎
libgdx封装了Box2D物理引擎,通过这个引擎能够模拟物理现实,使设计出的游戏更具有真实感. libgdx中,Box2d程序的大概过程: 1. 创建物理世界world,并设置重力加速度. 2. 创 ...
- 【英宝通Unity4.0公开课学习 】(四)GUI到物理引擎
今天老妈打电话来说和老爸吵架了... 真的是家家都有本难念的经啊.前后帮她分析了个半小时才帮她解开心结...现在想想老爸还是蛮可怜的,连分享的人都木有 讲的GUI都看睡着了...因为想着可以用NGUI ...
随机推荐
- DOM 小总结
DOM 是什么 文档对象模型,是针对 HTML 和 XML 文档的一个 API (应用程序编程接口), 描绘了一个层次化的节点树. D: document 当 web 浏览器浏览一个页面的时候,DOM ...
- js 中的submit 回调函数
1.submit.php <?php $arr = $_POST; $arr['msg']=1; //echo $_POST['uname']; echo json_encode($arr); ...
- 分享一个react 图片上传组件 支持OSS 七牛云
react-uplod-img 是一个基于 React antd组件的图片上传组件 支持oss qiniu等服务端自定义获取签名,批量上传, 预览, 删除, 排序等功能 需要 react 版本大于 v ...
- java重载时自动转换咋回事?举例说明
当一个重载的方法被调用时,Java在调用方法的参数和方法的自变量之间寻找匹配. 但是,这种匹配并不总是精确的.只有在找不到精确匹配时,Java的自动转换才会起作用. (如果定义了test(int ...
- 使用 IDEA 创建 SpringBoot 项目(详细介绍)+ 源码案例实现
使用 IDEA 创建 SpringBoot 项目 一.SpringBoot 案例实现源码 二.SpringBoot 相关配置 1. 快速创建 SpringBoot 项目 1.1 新建项目 1.2 填写 ...
- Input的校验表达式
1.只是不能输入空格 <input type="text" onkeyup="this.value=this.value.replace(/^ +| +$/g,'' ...
- FastAPI(七十)实战开发《在线课程学习系统》接口开发--留言功能开发
在之前的文章:FastAPI(六十九)实战开发<在线课程学习系统>接口开发--修改密码,这次分享留言功能开发 我们能梳理下对应的逻辑 1.校验用户是否登录 2.校验留言的用户是否存在 3. ...
- Java-GUI编程之事件处理
事件处理 前面介绍了如何放置各种组件,从而得到了丰富多彩的图形界面,但这些界面还不能响应用户的任何操作.比如单击前面所有窗口右上角的"X"按钮,但窗口依然不会关闭.因为在 AWT ...
- Intel主板芯片组
写这个的初衷还是由于linux内核本身就是硬件的抽象,如果你对硬件的相关发展,机制以及架构不了解,实际你也是看不懂linux内核代码以及看不懂linux很多命令输出的结果的,如果你看内核代码就会发现内 ...
- VScode链接服务器并配置公钥-SSH Keys
VScode链接服务器并配置公钥-SSH Keys 一直在用Xshell做SSH连接服务器与虚拟机,但是中文乱码的问题一直找不到解决方案,干脆使用编辑器自带的插件,集成之后用起来也方便 1.概述 做法 ...