Array王锐大神力作:osg与PhysX结合系列内容——第2节 刚体物理表现

本节内容

建立物理世界

在我们建立了PhysX的基本对象(PxFoundation,PxPhysics等)之后,下一步首先要建立一个物理世界场景,它的类型是PxScene,可以通过PxPhysics::createScene()创建。但是前提是必须传递一个描述符对象作为参数,即PxSceneDesc。

新建描述符的时候需要传递一个公差参数,该参数必须和SDK管理器的公差一致。除此之外,PxSceneDesc还有几个常用的成员参数可以设置,如下所示(假设已初始化变量PxPhysics* sdk):

  • PxSceneDesc sceneDesc(sdk->getTolerancesScale());
    sceneDesc.gravity =PxVec3(0.0f, 0.0f, -9.8f); // 设置重力方向
    sceneDesc.filterShader =PxDefaultSimulationFilterShader; // 设置碰撞回调
    sceneDesc.flags =PxSceneFlags(); // 设置PxSceneFlag类型的标识集
    PxDefaultCpuDispatcher* cpu= PxDefaultCpuDispatcherCreate(1);
    sceneDesc.cpuDispatcher = cpu; // 设置使用CPU来管理物理仿真任务,使用1个线程

对于OSG来说,默认采用Z向上的右手坐标系,因此PhysX的物理世界也是以-Z为重力方向的。此外,如果希望用到GPU来加速物理仿真过程,这里也可以设置合适的gpuDispatcher参数。

建立刚体对象

PhysX中的物理对象都是一个个的PxActor对象,它们通过PxScene::addActor()加入到物理世界中,也可以通过PxScene::removeActor()从物理世界中去除。

每个PxActor都有一个对应的PxShape,表示这个物理对象的碰撞体形状信息;而PxShape也必然会关联一个PxMaterial对象,表示这个形状的物理材质属性。这里称之为“碰撞体”并不完全准确,因为PxShape实质上有三个主要的功能,即:

(1)判断PxShape之间是否互相碰撞,并改变对应的刚体属性;
(2)执行场景查询操作时使用,例如raycast(射线求交)或者sweep(形状扫掠);
(3)作为一个独立的触发器(trigger),当其它PxShape与它相交时触发回调。

假设对象PxActor* actor已经存在(PxActor的创建参见后文),那么将PxShape关联给该场景对象的标准流程应该是:

  • PxShape* shape = sdk->createShape(*geom, *mtl, true);
    actor->attachShape(*shape); // 如果需要取消关联,则为detachShape()
    shape->release(); // 释放这个形状,也就是说它是被当前actor独占的
  • 这里的PxGeometry* geom表示形状几何体信息,PxMaterial* mtl表示物理材质。

定义通用碰撞体形状

传递给PxShape的geom对象用来表示这个碰撞体的具体形状。通常来说,简单的形状意味着这个对象的仿真过程更加快速高效,仿真结果也更加准确;而复杂的形状意味着可以更好地匹配对象的实际外形,符合用户使用过程中的预期。PxGeometry定义了如下几种常用的类型:

  • PxSphereGeometry:球体;
  • PxBoxGeometry:立方体;
  • PxCapsuleGeometry:胶囊体,它经常被用来描述玩家角色运动时的碰撞体;
  • PxPlaneGeometry:平面;
  • PxConvexMeshGeometry:凸包,即能够正好囊括实际几何体顶点的最小凸多面体;
  • PxHeightFieldGeometry:高度图描述的地形;
  • PxTriangleMeshGeometry:用实际三角面网格来描述的几何形状,这种方法构建的碰撞体效率是最低的,并且有顶点总数的限制。

这其中,凸包(ConvexMesh),高度图地形(HeightField)和三角面网格(TriangleMesh)数据是需要预先处理的,即通过PxCooking的cook*()函数进行提前“烘焙”。

静态和动态刚体

  • 我们可以通过sdk的createRigidDynamic()和createRigidStatic()两个成员函数来创建动态或者静态类型的PxActor对象,然后将已经使用了具体PxGeometry(几何形状)和PxMaterial(物理材质)的PxShape(碰撞体)关联到这个对象,即可完成静态或者动态刚体对象的创建。
  • 动态(dynamic)刚体是最常见的一类刚体类型,它具备一切物理动力学的属性,例如质量/密度的影响,重力和作用力影响等。它可以与其它动态和静态刚体发生碰撞并实时计算和仿真碰撞效果。
  • 静态(static)刚体是一种常见的环境物体,它可以被视为具有无限大质量的一种物理对象,不受任何作用力或者重力影响,位置姿态也不会发生改变,静态刚体之间也不会计算碰撞。但是动态刚体撞到静态刚体是会计算其碰撞仿真结果的。
  • 运动学(kinematic)刚体是一种特殊的动态刚体,它可以受到用户控制去改变自身位置和姿态,可以与其它刚体产生碰撞并反馈结果,但是它不受到重力和其它作用力影响。因此这种刚体很适合用来做玩家控制的角色运动:不会因为碰撞而反弹或者歪倒,但是可以被其它物体阻挡,不会穿透。
  • Kinematic刚体并不是一种专门的类型,它本身必须是通过createRigidDynamic()创建的动态刚体,然后设置一个Flag标识来设定是否是Kinematic对象:
    actor->setRigidDynamicFlag(PxRigidDynamicFlag::eKINEMATIC, true);

定义刚体的动力学属性

刚体,尤其是动态刚体,提供了一些必要的成员方法来完成各种动力学属性的设置。这其中主要包括:

  • setGlobalPose():设置刚体当前的位置姿态,这个方法对所有刚体都适用。对于动态刚体而言,如果设置的新位置正好和别的刚体重叠,那么它可能被巨大而突然的反作用力“弹飞”;静态刚体不会有任何反应,Kinematic刚体会自动偏移到正好不产生碰撞的位置上。
  • setMass():针对动态刚体,设置它的质量属性。
  • setLinearVelocity():针对动态刚体,设置它当前运动的线速度值,从而改变刚体的运动方向和速度。
  • setAngularVelocity():针对动态刚体,设置它当前运动的角速度值,从而改变旋转方向和速度。
  • addForce():针对动态刚体,给它的质心设置一个三维的作用力(或冲量),从而改变它的线性运动。
  • addTorque(),针对动态刚体,给它设置一个三维的扭矩,从而改变它的旋转运动。

注意1:速度或者力的设置,输入参数的类型为PxVec3,它和osg::Vec3可以视为直接转换的关系,前提是重力方向为-Z方向。
注意2:GlobalPose是一个PxTransform对象,它的输入量是一个PxMat44,它与osg::Matrix也可以直接进行转换,因为双方都是行主序的矩阵格式。

碰撞回调

  • 在建立物理世界的小节中,我们曾经写下了这么一行很不经意的代码:
    sceneDesc.filterShader = PxDefaultSimulationFilterShader; // 设置碰撞回调
  • 虽然它看起来只是使用了默认的某个对象,但是这里还是需要抛砖引玉一下,因为后续的教程中我们有可能要大刀阔斧地在这里改革一番。
  • 这里设置的回调,在PhysX被称为“shader”,也就是着色器。听起来很古怪,但是这是有一定历史渊源的。PhysX作为一个成熟而开放的物理引擎,是允许用户自己定义两个物体碰撞之后的具体行为的,例如:你可能希望A物体像子弹一样直接穿过B物体而不产生反作用力;或者希望查询后台数据库来判断某两个对象是否应该产生碰撞。因此,从理论上来说,PhysX需要对场景中的每两个对象都做一次回调,并允许用户自定义回调的结果。
  • 但是直接这样做想必会带来巨大的性能损失,甚至因为用户代码的不当处置,直接中断整个仿真流程。因此,PhysX
    3.0之后推出了一个基于shader的回调系统,它允许用户直接在GPU向量处理器上执行自定义的代码,并且不会因此降低系统仿真的效率。
  • 这个神奇的系统在真正的游戏逻辑开发过程中会大有用武之地,不过目前我们先按兵不动,使用系统默认的回调“着色器”即可。并且,在后续教程和自学代码的过程中,如果又看到shader字样,那么做好心理准备:这个shader并非和渲染效果相关,而是来自PhysX所定义的杰作而已。

构建测试场景并运行

osgPhysX中使用Engine单态类来封装PxPhysics和PxCooking相关的所有操作,第一次使用Engine::instance()的时候即会自动建立整个物理引擎系统。

所有和物理世界构建,刚体对象构建,碰撞体形状构建有关的函数,均可以参看osgPhysX工程中的physics/PhysicsUtil.h文件的内容,其中主要包括:

  • createScene():创建物理世界。
  • createSphereActor(),createBoxActor(),……,createTriangleMeshActor():快速创建所有常见碰撞体类型的物理对象。density参数为0的话,创建静态刚体;否则创建动态刚体对象。
  • createConvexMesh(),createTriangleMesh(),createHeightField():烘焙生成凸包、三角面网格、高度图这三种特殊的碰撞体形状。对于前两者,可以直接读取一个osg::Node来识别和生成对应的物理碰撞体形状。

一个初步的案例可以参看osgPhysX工程中的tests/hello_physics_world_test,它演示了最基本的物理场景创建以及动态刚体的操作,按下键盘的“1”键可以发射一个新的立方体刚体,来冲击已有的立方体墙。

构建测试环境并运行

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片