查看原文
其他

Cocos Creator 3.0 3D 物理讲解

Jayce Lai COCOS 2022-06-10

为更好地帮助大家丝滑升级,减少开发阻碍,我们带来了Cocos Creator 3.0 技术专栏第三期,为大家详细讲解 Cocos Creator 3.0 3D 物理。

在此也统一回复下大家比较关心的问题:

本专栏将不定期保持更新,包括 2.X 升级攻略等,尽力帮大家降低升级成本

关于文档的问题我们一直在持续更新中,有任何问题也可以随时反馈给我们。

今天我们将通过一些概念和案例,来为大家介绍如何使用 Creator 的 3D 物理框架,以便未进行过物理开发或者不熟悉该框架的开发者们可以轻松上手

Creator 的 3D 物理是一套全新的开发框架,它的目标是使物理开发在兼具性能的同时变得更加简单

以下内容主要讲解其中使用到的功能和技术原理,篇幅有限无法涉及到所有的细枝末节,但大部分内容在引擎文档均有介绍,可以配合阅读。




01

选择引擎



在 Creator 中进行 3D 物理类应用的开发,第一步就是选择物理引擎,目前提供了三种选择,分别为 builtin\cannon.js\bullet(ammo.js),它们之间主要的区别是包体、性能和功能特性。

物理引擎特点
builtin极其轻量的碰撞检测系统,仅支持盒、球、胶囊体形状和触发事件
cannon.js纯 js 开发的物理引擎,支持大部分特性,易于扩展,但性能不够好,包体约为 141 KB
ammo.js

由 bullet 物理引擎编译而来,支持所有特性,性能最优,但包体较大且不易扩展,其中 js 版本的包体约为 1.32 MB,wasm 版本的包体约为 690 KB

其中主要的功能特性支持情况如下:

功能特性builtincannon.jsammo.js
质心
盒、球
胶囊可以用基础形状拼凑
凸包

静态地形、静态平面
静态网格
极其有限的支持
圆锥、圆柱
✔(凸包实现)
单纯形
有限的支持
复合形状
射线检测、掩码过滤
多步模拟、碰撞矩阵
触发事件
自动休眠
碰撞事件、碰撞数据
物理材质
静态、运动学
动力学
点对点、铰链约束(实验)
wasm



02

概念讲解



以下向大家介绍了物理模块比较重要的组件和功能。

  • Static / Kinematic / Dynamic

    刚体类型
    Static静态刚体,可用于描述静止的建筑物,只与 Dynamic 和 Kinematic 类型的物体产生事件
    Dynamic动力学刚体,能够受到力的作用,物理引擎会接管该物体的运动,通过物理层的数值来操控
    Kinematic运动学刚体,通常用于表达电梯这类平台运动的物体,通过修改变换信息来操控

    下图中白色为静态,蓝色为运动学,黄色为动力学。其中白色和蓝色都是操控的变换信息,很明显的看出几个表现

  1. 白色和蓝色之间会出现穿透现象
  2. 白色的静态物体也可以运动
  3. 两个黄色方块表现不同,白色上方的静止不动,蓝色上方的会跟随着运动


以上现象的原因是:

    1. 静态和运动学都不会受到力的作用,所以产生了穿透,这是正常现象
    2. 静态物体的确是可以运动的,静态是指在时空中,每一个时刻都是静态,不会考虑其它时刻的状态
    3. 与静态物体不同,运动学物体会根据附近时刻估算出运动状态(比如速度),又由于摩擦力的作用,因此带动了黄色方块
  • RigidBody / Collider / Trigger

    组件类型
    刚体组件(RigidBody)它负责操控物理对象与运动相关的属性,以及配置分组,一个节点最多一个
    碰撞器组件(Collider)它负责操控物理对象上形状相关的属性,一个节点可以有多个
    触发器(Trigger)它是勾选上了IsTrigger的碰撞器组件,它会像幽灵一样穿透其它物体,并提供穿透开始、保持,以及结束的事件

    是否一定要加刚体组件是提问最多的,这里介绍一个小技巧:

    1. 需要配置分组,加
    2. 需要设置为运动学或者动力学类型(不加意味着是静态类型),加
  • Raycast / Mask Filter / Collision Matrix

    功能模块
    射线检测(Raycast)利用射线与其它碰撞体进行碰撞检测,并记录相应的碰撞数据
    掩码过滤(Mask Filter)利用位运算实现两个物体间是否相互过滤,重要的碰撞检测优化技术
    碰撞矩阵(Collision Matrix)它用于初始化物体的分组和掩码,是掩码过滤的上层封装
  • 这套框架还有很多需要打磨的地方,例如有些开发者认为在静态对象上配置分组不够便捷、分组不应该和节点的层剥离开、射线不应该有组。

    这些我们都记录下来了,同时也在寻找更友好的方式。在这里感谢开发者们宝贵的反馈建议。



    03

    使用讲解



    以下将会结合一个射箭范例中的几个问题场景,介绍对应使用到的功能特性和解题思路,以及涉及到的易误点。

    • 用基础形状组合十字架——复合形状

    下图中用两个盒形状组合一个十字架,节点上所有的碰撞体组合成了一个十字形状,这是实现带有凹面形状最基础的方法:

    容易误用的地方是在多个节点中添加碰撞体拼凑出十字架后,希望它碰撞后可以保持整体结构进行运动,这在目前的结构中是无法做到的,只能往单个节点上添加碰撞体来实现。

    • 射箭与回收箭——运动学、动力学、事件

    射箭的第一步是拉弓,箭需要完全跟随弹性绳骨骼一起运动,不希望箭受到物理规则的影响,此时应将箭的刚体设置为 Kinematic 类型;第二步是松开弹性绳发射箭,这时希望给箭设置初速度后,可以按照物理规则进行运动,因此将箭的刚体设置为 Dynamic 类型

    回收箭的大致过程是在箭射出去后,一旦触碰到触发区域就将其还原到弓上。这可以通过制作监听区域来实现,首先利用碰撞体组件拼凑出区域,同时将碰撞体组件的 IsTrigger 勾选上。(下图中的蓝色地板为监听区域)

    易误点:

      1. 利用修改变换信息来操作动力学(Dynamic)类型的刚体,应当通过速度、力或冲量等物理层的数值
      2. 利用静态(Static)类型的刚体来监听事件,静态刚体只会和带有类型为运动学或动力学的刚体产生事件,可以更改为运动学(Kinematic)类型,或者事件通过另一方进行注册
      3. 监听事件时仅监听了触发开始(OnTriggerEnter),但是误以为包括了触发保持(OnTriggerStay)和结束(OnTriggerExit)

      • 瞄准——碰撞矩阵(过滤检测)、射线检测、静态平面

      瞄准是射箭前的步骤,准心处于箭头指向所在的射线上,在十字架前面加一个静态平面碰撞体,然后利用射线检测就可以得到准心的位置;

      静态平面只是用来做射线检测,给它专门建立一个分组,并且不与箭、苹果等等物体进行检测,这是最通用的性能优化方法

      调用射线检测方法时,设置传入掩码为仅和静态平面检测,即 0b10(二进制表示法)。

      易误点:

        1. 分不清刚体组件上的分组和节点上的层。这两者的概念类似,但是使用者不同,分组的使用者是物理模块,层的使用者是渲染模块
        2. 对掩码的理解不到位,不知传何值。这里提供一个小技巧,以一个能够筛选出Others的掩码举例,首先Othersindex值为2,哪么只要让二进制掩码从右往左的顺序第2位为1,就能让Others通过筛选,也就是ob100(这里强烈建议不要随便更改分组的索引)
        3. 误认为射线检测接口的返回值是击中的数据。获取结果有专门的接口,此处设计是为了强调这是个复用对象。为了减少垃圾内存,每次调用接口只会更新它们的数据,而不是重新生成新的(若需要持久记录,哪么可以克隆一份)

        • 射击苹果——静态网格、凸包、多步模拟(步长调整)

        一般的苹果都带有凹面,处理好凹类或带连续平滑不规则曲面的模型都非常棘手,这是因为目前成熟的理论和技术都建立在离散、凸包的世界之上(微积分中用差分近似表示微分就是最典型的范例)。

        实时物理引擎中,对于这类物体只能支持到静态或运动学类型的刚体层级,对于动力学就束手无策了。然而不幸的是,真实的苹果运动表现强烈依赖动力学,这种情况只能给苹果填加凸包形式的网格碰撞体(需将 convex 勾选上),再加上一个动力学刚体,用近似物体去参与模拟

        运动表现与模拟参数有非常大的关系,穿透是最具有代表性的现象,这可以通过缩减步长和增加步数来实现,调整步长有个小技巧:输入分式,即 1/Frame,其中 Frame 表示帧率

        易误点:

        1. 在带有未勾选 convex 的网格碰撞器上添加了的动力学刚体,与其它物体产生了穿透现象,或者说完全没有反应,这是典型的错误使用,只有勾上 convex 的才能支持动力学刚体

        2. 对一个顶点数极多的模型开启了 convex,过多的顶点数会使凸包的面数增多,这对性能有很大的影响,而且实际上并不需要面数特别多的凸包,一般建议模型的顶点数应小于 255

        3. 开启凸包后,模型的凹面处的接触不贴近,这是正常现象,现在的实时技术是将模型用多个凸包组合来解决,如下图所示

        4. 只调整了步长,但未调整步数,这两者需要相互配合才有效果。小技巧是,步数可以随意设置较大的值,步长根据最大的速度值进行调整,值越大,步长应当越小




        04

        运动控制



        以下将介绍如何利用碰撞数据实现一个动力学类型刚体的运动控制。

        • 碰撞数据

        如下图所示,红色的球表示的是碰撞点的位置,蓝色箭头指的是碰撞点所处面的法线。碰撞数据需要通过碰撞事件来获取(事件提供的碰撞数据是复用对象)。

        • 地面判断

        角色是否在地面上,可以通过法线的指向来得知,对应的条件为normal.y > 0。有时候还需要知道地面的倾斜情况,这里通过normal.y的大小就能知道,越接近于1,哪么地面就越平(法线垂直向上);越接近于0,哪么地面就越陡峭。


        • 跳跃

        一旦能够确定是否在地面后,就可以加上跳跃行为了。首先需要明确的一点是,这里使用的是动力学类型的刚体(原因是我希望通过物理引擎接管刚体后得到更加真实的物理反馈),所以需要通过改变物理数值来达到目的,例如直接将角色线性速度的y分量设置为5,如下图(图中碰撞体用的是胶囊体,这与实际场景相关,可自行更改)。


        直接y轴速度,可以良好的工作在完全水平的地面上。但有时候跳跃还需要根据地面斜度来做出不同的行为,这里提供一个思路:提供一个配置因子,范围为0到1,根据因子将当前法线和垂直法线插值,得出目标法线,再乘以跳跃的速率,进而得出期望的跳跃速度,将其加入到角色的线性速度之中。

        • 行走和站立

        站立行为,如果希望角色不会摔倒,哪么直接将AngularFactor全设置为0即可。行走行为,可根据角色的运动状态来实现,假设需要将角色往x轴运动,哪么就修改角色x轴的线性速度。因此控制行走,只需要两个量,一个是运动方向,另一个是运动速率。

        做出好的运动控制的唯一窍门就是控制好速度,但这也是最容易错误使用的点:

        • 随意设置速度,这是运动不真实最根本的原因
        • 设置过大的速度,这将很容易出现穿透现象,可以尝试上文介绍的多步模拟技术

        • 没有掌握控制速度的方式,有些开发者直接修改为某个固定值大小,这当然不是错误的做法,但修改速度之前应当考虑清楚这是不是想要的结果。

          有个小技巧是,如果是跳跃行为,可以考虑直接设置为固定值;

          如果是行走行为,更好的方式是根据上个时刻的状态进行修改

        • 使用了Kinematic类型的刚体,期望物理引擎会帮助你处理好碰撞行为,通过上文可以知道这是不可能的。

          但实际上有很多好的角色控制都基于Kinematic行实现,这里面的技术细节远比本文介绍的复杂。




        05

        结语




        以上大部分的内容案例均可在案例仓库中找到,比如上面的截图中用来辅助显示碰撞点信息的组,其他内容比如弓箭、运动控制的案例也会持续合并进去,请感兴趣的同学持续关注。


        相信大家看到上面糖豆人类的物理实现展示也很激动,3.0 引擎目前已经完全可以支撑同类型游戏的开发,大家可以按照这篇文章中的指引和参考我们的范例多多尝试。


        未来在物理引擎上还有很广阔的探索空间,目前在计划中的包括


        • 生平台 PhysX 物理引擎接入,将大大加强原生平台的物理性能
        • 更多物理约束类型支持
        • 布娃娃系统
        • 物理驱动的动画系统
        • 持续碰撞检测(CCD)
        • 节点链组合

        后续本专栏将不定期更新,为大家带来包括但不限于以下内容:


        • 《Cocos Creator 3.0 里如何玩转 npm 海量资源》by 放空

        • 《Cocos Creator 3.0 的资源系统》by Santy Wang

        ......


        留言告诉我们其他你想看的内容,我们也会持续加强物理引擎的能力,帮助开发者做出超好玩的物理反馈游戏。


        最后,欢迎对物理引擎开发有兴趣的同学将简历甩进我们 HR 的邮箱中!


        邮箱地址:hr@cocos.com。



        往期精彩


        您可能也对以下帖子感兴趣

        文章有问题?点此查看未经处理的缓存