Three.js系列: 造个海洋球池来学习物理引擎

2023-11-05

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,看起来黑乎乎的一片。

image.png

“上帝说要有光,于是就有了光”,黑乎乎是正常的,因为在我们场景中没有灯光,这个意思很简单,当夜晚的时候,关了灯当然是伸手不见五指。于是我们在场景中加入两盏灯,一个环境灯,一个直射灯,灯光在本篇文章中不是重点,所以就不会展开描述。只要记住,”天黑了,要开灯”

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

系列连载首发地址:

  1. Three.js系列: 造个海洋球池来学习物理引擎
  2. Three.js系列: 游戏中的第一、三人称视角
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Three.js系列: 造个海洋球池来学习物理引擎 的相关文章

  • JavaScript 支持逐字字符串吗?

    在 C 中 您可以像这样使用逐字字符串 server share file txt JavaScript中有类似的东西吗 模板字符串支持换行 so you can do this if you want https developer mo
  • 从 puppeteer PDF 中删除分页符?

    我目前正在尝试查看是否有一种方法可以删除我的 puppeteer PDF 中的分页符 因为我当前的 PDF 设置中的一些分页符正在以一种奇怪的方式切断文本 我正在谈论的内容的屏幕截图 我的傀儡代码 app get companyId pdf
  • ASP.NET 验证控件和 Javascript 确认框

    我有一个使用 NET 服务器端输入验证控件的页面 此页面还有一个 javascript 确认框 在提交表单时会触发该确认框 当前 当选择 提交 按钮时 会出现 javascript 确认框 一旦确认 就会触发 ASP NET 服务器端验证控
  • 指定 HTML5 输入类型 = 日期的值输出?

    我想将本机日期选择器添加到我的应用程序中 该应用程序当前使用遗留的本地系统 日期输入支持尚未广泛普及 但如果我可以基于兼容性提供这两种实现 那就太理想了 有没有办法指定 HTML 日期选择器给出的值的输出 歌剧的默认设置是yyyy mm d
  • 修复 Raphaël 路径节点上 Tipsy 工具提示的位置

    这是一个非常具体且有些复杂的问题 所以我设置了一个最小测试用例 http reveal dk 8080 revealit dk tipsytest 在阅读本文的其余部分之前 您可能应该先了解一下 我的页面显示悬停时突出显示区域的图像Raph
  • 如何在单击按钮时清除反应挂钩中的间隔

    我正在用反应钩子构建一个简单的计时器 我有两个按钮启动和重置 当我单击开始按钮时 handleStart 函数工作正常 计时器启动 但我不知道如何在单击重置按钮时重置计时器 这是我的代码 const App gt const timer s
  • 如何使用 github 托管外部 CSS 文件?

    我将 css 上传到 github 然后转到网站上的文件并单击 raw 选项 我尝试将其添加到网页中 但 chrome 给出以下错误 资源解释为样式表 但使用 MIME 类型 text plain 进行传输 https raw github
  • 如何将焦点设置在 BootStrap 中的第一个输入字段上? [复制]

    这个问题在这里已经有答案了 可能的重复 如何将焦点设置到独立于 id 的 HTML 表单中的第一个输入元素 https stackoverflow com questions 277544 how to set the focus to t
  • iPhone 上的锁定方向 UIWebView

    有没有办法锁定 UIWebView 的方向 使用 Obj C JS 还是 Html 我不想有按钮或任何东西 我只想在应用程序打开时将其锁定为纵向 好像这个堆栈溢出帖子 https stackoverflow com questions 43
  • 如何在数据表角度中基于 JSON 动态填充表值?

    我在用着Angular 数据表 https l lin github io angular datatables 我需要能够根据返回的数据动态创建表 换句话说 我不想指定列标题 Example json数据 id 2 city Baltim
  • JS:修改 JS 对象中的值/对

    我正在尝试找出修改对象的最佳方法 而无需三次写出类似的对象 所以我有这三个对象 var object1 start start end end type 1 var object2 start start end end type 2 va
  • 当 Chrome 中嵌套滚动中的数据更改时防止页面滚动

    我在页面中有一个固定大小的元素 带有 溢出 滚动 其内容经常更改 我预计该元素内部发生的更改会影响该元素的滚动 但不会影响页面滚动 但是当这个元素位于页面顶部时 页面本身开始滚动 我怎样才能防止这种情况发生 要重现此行为 我在 chrome
  • 将默认搜索文本添加到搜索框 html

    我正在努力将 搜索 文本添加到搜索框 我正在努力实现 onfocus 消失文本 And onblur 重新出现文本 到目前为止 我已经实现了这一点 但我必须将其硬编码为 html eg
  • JavaScript setTimeout 和更改系统时间会导致问题

    我注意到如果我设置setTimeout未来1分钟 然后将我的系统时间更改为过去5分钟 setTimeout功能将在 6 分钟后触发 我这样做是因为我想看看夏令时系统时钟更改期间会发生什么 我的 JavaScript 网页使用setTimeo
  • Firebase 身份验证和实时应用程序数据库如何保护自身安全?

    从一般开发的角度来看 我很好奇如何保护在线资源的访问 我们使用以下 Firebase 配置参数初始化 Web 应用程序 apikey authdomain projectid databaseurl messagesenderid 服务器如
  • jQuery:向左滑动和向右滑动

    我见过slideUp and slideDown在 jQuery 中 左右滑动的功能 方式怎么样 您可以使用 jQuery UI 中的附加效果来做到这一点 详情请参阅此处 http docs jquery com UI Effects Sl
  • jQuery UI 对话框 - 关闭后无法打开

    我有一个问题jquery ui dialog box https jqueryui com dialog 问题是 当我关闭对话框然后单击触发它的链接时 除非刷新页面 否则它不会再次弹出 如何在不刷新实际页面的情况下回调对话框 下面是我的代码
  • 测试 jQueryUI 是否已加载

    我正在尝试调试网站 并且我认为 jQueryUI 可能未正确加载 如何测试 jQueryUI 是否已加载 if jQuery ui UI loaded OR if typeof jQuery ui undefined UI loaded 应
  • YouTube 点击时禁用 HTML5

    有没有办法让我们通过javascript禁用HTML5视频的 播放 暂停 点击全屏 功能 然后在我们再次需要时将其放回去 我不知道你是否可以禁用它们 但你可以使用 css 删除它们 video webkit media controls f
  • 拉斐尔路径交叉点不起作用

    我对拉斐尔和 pathIntersection method JSFiddle 示例 http jsfiddle net t6gWt 2 您可以看到有两条线都与曲线相交 但当我使用 pathIntersection method 有一个未解

随机推荐

  • Origin6.0使用的一个坑

    今天在使用Origin6在画图时 遇到的一个软件问题 数据量大概在几万个 这些数据中仅有一个数为1 其他皆为0 但是绘制出的图片全是0 只有在放大后 这个数据1才绘制出来 不放大的话全是0 Origin这个绘制逻辑很坑 会误导使用者 一定要
  • Vue+高德地图api

    文章目录 前言 一 准备工作 1 申请应用 2 引入 3 创建地图 二 使用高德地图 1 覆盖物 1 点标记 2 海量点标记 3 轨迹回放 2 矢量图形 1 多边形 3 图层 1 热力图 2 Canvas图层 4 坐标转换 前言 项目使用了
  • GD32F103基础教程—按键输入实验(七)

    一 教程简介 本章主要是讲解GPIO输入实验 及相关GPIO输 出配置方法 通过使用按键控制LED的亮灭 二 实验流程 1 工程配置 按键实验工程配置方法与第五章的配置方法一致 具体请查看第五章教程 本章不再赘述 本章增加一个KEY c与K
  • 监狱智能管理平台 ——监室点名(人脸识别)

    从事人脸识别行业渐进2年 主要开发监狱的人脸识别服务 内心一直有一个梦想 打造出一款智能化的现代化的监狱管理系统 不过要智能化到什么程度 心里却没有普尼 恐怕是从事IT行业后 总想设计出一款满意的产品 对自己从事该行业有一个交代做的怪吧 自
  • 基于深度学习的高精度烟雾检测识别系统(PyTorch+Pyside6+YOLOv5模型)

    摘要 基于深度学习的高精度烟雾检测识别系统可用于日常生活中或野外来检测与定位烟雾目标 利用深度学习算法可实现图片 视频 摄像头等方式的烟雾目标检测识别 另外支持结果可视化与图片或视频检测结果的导出 本系统采用YOLOv5目标检测模型训练数据
  • import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt from torch imp...

    这是一段 Python 代码 主要是在导入一些库和定义一个神经网络模型类 Net 代码中导入了 torch torch nn numpy 和 matplotlib pyplot 库 并在 Net 类中进行了一些初始化 代码还提到了一个微分方
  • mybatis中使用map批量更新

    最近项目中会用到批量更新功能 数据是存在map中的 key作为更新的id 而value作为更新的值 纠结了很久最后算是解决了 特此记录 希望对有需要的有一定帮助
  • 数据库数据库连接:1、短连接 2、长连接  3、连接池介绍和区别

    一 数据库连接 1 短连接 2 长连接 3 连接池 二 短连接 短连接是指程序与数据库通信时建立的连接 执行操作后 马上就关闭 短连接简单地来说 就是每次操作数据库 都要打开和关闭数据连接 基本操作是 连接 数据传输 关闭连接 三 长连接
  • Android TextView 属性 textsize 的单位是什么?

    首选我们找到 源码中的TextView 找到 textsize 属性 一个 int 类型默认值为 15 初使化自定义属性 我们看一个 getDeimensionPixelSize 方法的解释可以看出 获取 是 15 单位是什么 是px 那我
  • 蓝桥真题-网站扩张

    代码 package com hualing lanqiao import java util Scanner public class Expansion 网站扩张 规则 第一个人使用7天后 在第八天可以邀请另一个人使用 后每隔两天可以邀
  • 应急响应 - Windows启动项分析,Windows计划任务分析,Windows服务分析

    作者简介 CSDN top100 阿里云博客专家 华为云享专家 网络安全领域优质创作者 推荐专栏 对网络安全感兴趣的小伙伴可以关注专栏 网络安全入门到精通 Windows应急响应 一 启动项分析 1 msconfig 2 gpedit ms
  • Mybatis

    第1章 框架概述 1 1 软件开发常用结构 1 1 1 三层架构 三层架构包含的三层 界面层 User Interface layer 业务逻辑层 Business Logic Layer 数据访问层 Data access layer 三
  • 小熊带你学Python(六)——字符串

    上一节提到序列的应用 序列就是指代的字符串 列表 元组 集合 字典等数据结构的一个集合 我们先从字符串开始讲起 字符串 是一串字母的集合 我们在编程实现各种功能时 很多时候其实都是在操作这些字符串 字符串的变化中实现了各种功能 1 字符串的
  • Springboot项目优化日志logback-spring.xml详解

    Springboot项目优化日志logback spring xml详解 一 描述 二 配置文件 三 效果 一 描述 几种常见的日志 Log4j 是最早的日志框架 是apach旗下的 可以单独使用 也可配合日志框架JCL使用 Log4j2
  • 仅提供信息存储空间服务器,Docker本身的存储空间管理

    原标题 Docker本身的存储空间管理 目标 两台host主机透过一个网络接口共享磁盘设备 iSCSI 共享设备的主机和名字 target dev loop8 gt initiator dev sdb gt initiator docker
  • 摄影毁一生单反穷三代顺口溜_什么?这点预算你竟买了一套摄影设备!

    图片 来自网络 文字 小松鼠 看了文章标题而点进来的朋友们 都是有这方面想法的 本文适合于家境一般的业余摄影爱好者 如果家里有矿或是立志成为专业摄影师的 就没必要往下看了 注 文末有福利 以下为正文 先来一组图 光照的光 花光的光 俗话说
  • 简单的线性单向链表

    数组的不足 我们之前用的数组也是一种数据结构 数组是顺序存储的 数组逻辑关系上相邻的两个元素在物理位置上也相邻 这就导致了在对数组进行插入或删除操作时 需移动大量数组元素 并且数组的长度是固定的 而且必须预先定义 数组的长度难以缩放 对长度
  • glut实现雪花动态效果

    glut实现雪花动态效果 实验题目 总体思路 3 2主要函数说明 按键操作 实验结果 实验题目 1 绘制雪花 2 在屏幕的多个随机位置绘制雪花 3 使每朵雪花绕自己的中心旋转 4 使每朵雪花下降 5 翻页键控制相机视野 按UP键增加物体与观
  • Vue中使用element-plus中的el-dialog定义弹窗-内部样式修改-v-model实现-demo

    效果图 实现代码
  • Three.js系列: 造个海洋球池来学习物理引擎

    github地址 https github com hua1995116 Fly Three js 大家好 我是秋风 继上一篇 Three js系列 游戏中的第一 三人称视角 今天想要和大家分享的呢 是做一个海洋球池 海洋球大家都见过吧 就