〖Array王锐大神力作〗osg与PhysX结合系列内容——地形碰撞体

  • “烘焙”物理碰撞体
  • HeightField与TriangleMesh
  • 物理材质的概念与使用
  • 直接读取高度图数据
  • 与osg::HeightField结合使用
  • Pvd调试环境
  • 构建测试场景并运行

“烘焙”物理碰撞体

在上一篇文章中,我们介绍了多种不同的PxGeometry,它们被用来描述物理碰撞体的具体形状。简单的几何体类型不需要再做赘述;本节内容我们将着重讨论一下HeightField和TriangleMesh这两种几何体类型,基于它们分别构建三维地形的物理碰撞体,并且运行一些简单的刚体运动测试。

这两种几何体类型,再加上常用于运动对象构建的“凸包”类型(ConvexMesh),都是需要预先烘焙(cook)之后才可以使用的。这一点从对应的PxGeometry类的构造函数声明就可以看出:

  • PxHeightFieldGeometry(PxHeightField* hf, …);
  • PxTriangleMeshGeometry(PxTriangleMesh* mesh, …);
  • PxConvexMeshGeometry(PxConvexMesh* mesh, …);

对应的hf或者mesh参数都需要通过PxCooking烘焙对象生成,并且需要提前准备一个名称后缀为Desc的类,来定义被烘焙几何体的原始信息。

相比之下,其它碰撞体的构建过程就简单许多了:

  • PxBoxGeometry(float hx, float hy, float hz); // 设置立方体三条边的长度值
  • PxSphereGeometry(float ir); // 设置半径值

HeightField与TriangleMesh

PxCooking烘焙对象是全局存在的,一个程序只需要一个就可以了。它的创建过程为:
physx::PxCookingParams defParams(physx::PxTolerancesScale());
_cooking = PxCreateCooking(PX_PHYSICS_VERSION, _foundation, defParams);

这里的_foundation也就是我们一开始就创建了的PxFoundation对象。除此之外还有用于创建各种物理对象的PxPhysics对象_sdk。这三者都应当直接全局记录下来,并随时使用。在osgPhysX中它们被共同安置在osgPhysics::Engine类中。

PxHeightField的创建需要调用_cooking->createHeightField()来完成,其中需要提供一个PxHeightFieldDesc作为输入参数。定义PxHeightFieldDesc并填充数据的代码片段为:
PxHeightFieldDesc heightFieldDesc;
heightFieldDesc.nbColumns = numColumns; // 设置地形网格的列数
heightFieldDesc.nbRows = numRows; // 设置地形网格的行数
heightFieldDesc.format = PxHeightFieldFormat::eS16_TM; // 地形数据格式,只可选S16
heightFieldDesc.thickness = thickness; // 设置地面的厚度,它会影响到求交判断的结果
void* sData = (void*)malloc(sizeof(PxHeightFieldSample) * numColumns * numRows);
heightFieldDesc.samples.data = sData; // 保存具体的地形网格数据内容
heightFieldDesc.samples.stride = sizeof(PxHeightFieldSample);
……
PxU8* ptr = (PxU8*)heightFieldDesc.samples.data;
{ // 此处开始遍历所有的地形网格数据,并进行赋值; // 每个采样点可以被认为是一块四边形区域,它被分成2个三角形,并分别设置对应的地形材质
PxHeightFieldSample* sample = (PxHeightFieldSample*)ptr;
sample->height = <Int16类型的高度值>;
sample->materialIndex0 = <三角形区域0的材质ID>;
sample->materialIndex1 = <三角形区域1的材质ID>;
ptr += heightFieldDesc.samples.stride;
}

这个流程虽然比较繁琐,但是其中的数据定义与我们通常定义高度图/DEM数据的方式并没有很大区别,因此不难理解。完成PxHeightFieldDesc对象的录入之后,需要用下面的代码将它烘焙为一个PxHeightField对象,并及时释放不必要的内存数据:

PxHeightField* heightField = _cook->createHeightField(
heightFieldDesc, _sdk->getPhysicsInsertionCallback());
free(sData); // 非常重要!Desc对象在烘焙完成后已经没用了,因此要释放掉其中额外分配的内存

createHeightField()中还有一个额外的PxPhysicsInsertionCallback参数,现阶段我们还不需要用到它,直接使用默认回调即可。以后如果有机会尝试做运行时的实时地形创建的话,也许会自定义这个参数。

同理,PxTriangleMesh的创建需要调用_cooking->createTriangleMesh()来完成,其中需要提供一个PxTriangleMeshDesc作为输入参数。定义PxTriangleMeshDesc并填充数据的代码片段为:
std::vector verts; // 预设的三角网格顶点数组
std::vector indices; // 预设的三角网格索引数组
……
PxTriangleMeshDesc meshDesc;
meshDesc.points.count = verts.size(); // 顶点的总数
meshDesc.points.stride = sizeof(PxVec3);
meshDesc.points.data = &(verts[0]); // 顶点数组的起始指针
meshDesc.triangles.count = indices.size() / 3; // 三角形的总数(不是索引值的总数)
meshDesc.triangles.stride = 3 * sizeof(PxU32);
meshDesc.triangles.data = &(indices[0]); // 索引数组的起始指针
PxTriangleMesh* mesh= _cook->createTriangleMesh(
meshDesc, _sdk->getPhysicsInsertionCallback());

注意,如果是创建ConvexMesh,那么整个过程几乎和上面的代码相同,主要的区别就是不需要再设置triangles(不需要考虑索引),以及TriangleMesh替换成ConvexMesh而已。但是如此烘焙得到的结果将是凸包类型的碰撞体几何形状。
此外,PxTriangleMeshDesc还有一个materialIndices属性,可以设置每个三角面的具体材质ID。

物理材质的概念与使用

在之前的章节中,我们一直在使用默认的物理材质。不过对于实际应用来说,让不同的物体或者地面区域具有不同的物理属性,这无疑可以实现更为准确的物理效果和更为丰富的表现力。PhysX中使用PxMaterial类来表示物理材质。用户需要使用_sdk->createMaterial()来创建新的材质;然后在必要的时候,通过PxMaterial::release()来释放之前分配的材质。

定义和创建物理材质的基本过程如下:
float staticFriction = 0.5f, dynamicFriction = 0.5f, restitution = 0.5f;
PxMaterial* mtl = _sdk->createMaterial(staticFriction, dynamicFriction, restitution);
这里的staticFriction,dynamicFriction,restitution分别表示静摩擦系数,动摩擦系数和恢复系数。下面的简单表格演示了一些常见的理想物理材质对应的参数设置:

在创建HeightField和TriangleMesh的时候,我们可以详细设置逐三角面的材质ID,从而精确地定义地面或者三角网格表面的材质类型和特点(例如,平原/山地/山顶雪原)。如果设置了材质ID,那么传递给对应PxShape对象的材质对象也需要是一个数组的形式,并且数组中元素的数量与材质ID相互对应。

从前文中我们已知,PxShape对象通过_sdk->createShape()从具体的几何体类型创建,然后通过PxActor::attachShape()关联到角色对象。在关联到角色之前,我们可以通过setMaterials()给这个PxShape设置一个材质数组。如下所示:
std::vector<PxMaterial*> materials; // 设置材质数组
……
shape->setMaterials(&materials[0], materials.size()); // 关联一个材质数组
actor->attach(shape);

如果希望获取某个材质ID对应的材质对象,也可以在角色创建之后,重新获取它的碰撞体形状,并通过PxShape::getMaterialFromInternalFaceIndex()获取ID对应的材质对象。这一过程简述如下:
PxShape* shape = NULL; actor->getShapes(&shape, 1); // 注意这里假设只有1个形状
PxMaterial* mtl = shape->getMaterialFromInternalFaceIndex(0); // ID=0的材质

直接读取高度图数据

OSG的examples目录下有一组历史悠久的高度图数据,在OSG源代码目录中,找到:examples/osghangglide/terrain_coords.h这个文件,打开它之后发现是一组38 * 39个XYZ数据(变量float vertex[][][3]),其中Z数据保存的就是具体的高度值。

我们将这组数据传递给physx::PxHeightFieldDesc,从而构建一个高度图碰撞体,并进而构建一个PxRigidStatic对象(静态对象,显然它不太可能是动态的)。

这里要特别注意的是PhysX中的高度图坐标系问题。在之前的章节中我们没有特别关心PhysX的坐标系设定,因为它与OSG一样采用了右手坐标系,并且无论是重力方向还是具体的碰撞体坐标值,都可以根据自己的需要设置,不用考虑Y-up还是Z-up的问题。

但是对于高度图对象,PhysX默认认为它的“行”方向为X+,“列”方向为Z+,高度方向为Y+;这就和OSG高度图对象的默认方向(X+,Y+,高度Z+)有区别了,如图所示。

与osg::HeightField结合使用

为了确保PhysX构建的高度图可以在OSG中正确渲染显示,首先我们需要将生成的PxRigidStatic对象沿着X方向旋转90度,即:
PxRigidStatic* actor = _sdk->createRigidStatic(
PxTransform(PxQuat(osg::PI_2, PxVec3(1.0f, 0.0f, 0.0f))));

这样可以确保生成的物理碰撞体也是Z-up的状态,同时,如果我们要构建这个物理碰撞体对应的渲染模型的话,可以直接使用osg::HeightField和osg::ShapeDrawable,并且正确调整上层Transform节点:
PxHeightFieldGeometry hfGeom;
PxHeightField* hf = hfGeom.heightField;
……
osg::HeightField* grid = new osg::HeightField;
grid->allocate(hf->getNbColumns(), hf->getNbRows());
grid->setXInterval(hfGeom.columnScale);
grid->setYInterval(hfGeom.rowScale);

for (unsigned int r = 0; r < hf->getNbRows(); ++r)
for (unsigned int c = 0; c < hf->getNbColumns(); ++c)
{
const PxHeightFieldSample& s = hf->getSample(r, c);
grid->setHeight(c, r, (float)s.height * hfGeom.heightScale);
}
geode->addDrawable(new osg::ShapeDrawable(grid));
transform->addChild(geode); // 假设geode和transform都已经定义好了
transform->setMatrix(osg::Matrix::rotate(-osg::PI_2, osg::Y_AXIS)
* osg::Matrix::rotate(-osg::PI_2, osg::Z_AXIS)
* osgPhysics::toMatrix(PxMat44(actor->getGlobalPose())));
也可以考虑在输入DEM信息到PhysX的时候就进行变换来确保数据坐标系的一致,这里的设置仅作参考。

Pvd调试环境

这种高度图坐标系匹配的问题,如果单纯依靠OSG的渲染画面来做判断和对齐,那必然是一个非常痛苦的过程。并且随着物理场景的构建愈发复杂,能够在“渲染流程”之外看到物理世界自己的仿真运行结果,并据此进行判断和程序调试——这无疑成了一个不可或缺的需求。PhysX考虑到了这一点,因此它为开发者提供了一个相当强大的“利器”,即PhysX Visual Debugger(简称PVD)。

这个工具目前需要注册NVIDIA开发者之后才能够下载:https://developer.nvidia.com/physx-visual-debugger

PVD只有在DEBUG,CHECKED或者PROFILE配置下才能够使用,RELEASE配置下是无法启用的。它可以被理解为是一个在线的调试器/仿真器,和用户程序通过TCP/IP协议进行通讯,并显示当前物理场景的动态更新和变化结果。

创建一个PVD连接的过程如下(一个用户程序中只需要创建一次,销毁则调用PVD对象的release()函数):
physx::PxPvd* pvd = PxCreatePvd(_foundation);
physx::PxPvdTransport
pvdTransport = PxDefaultPvdSocketTransportCreate(“127.0.0.1”, 5425, 10); // 默认为localhost:5425
pvd->connect(*pvdTransport, PxPvdInstrumentationFlag::eALL);

构建测试场景并运行

osgPhysics中提供了一个专门的例子physics_heightfield,在DEBUG模式下运行这个例子(按“1”键发射动态物理对象)以及PVD的效果如下图所示:

Array王锐大神力作:osg与PhysX结合系列内容——第3节 地形碰撞体相关推荐

  1. Array王锐大神力作:osg与PhysX结合系列内容——第5节 角色动画效果(上)

    [Array王锐大神力作]osg与PhysX结合系列内容--角色动画效果(上) 物理引擎先放一边 动画库ozz-animation 动画资源管理 载入和预处理动画 合并到OSG显示 物理引擎先放一边 ...

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

    [Array王锐大神力作]osg与PhysX结合系列内容--刚体物理表现 本节内容 建立物理世界 建立刚体对象 定义通用碰撞体形状 静态和动态刚体 定义刚体的动力学属性 碰撞回调 构建测试场景并运行 ...

  3. Array王锐大神力作:osg与PhysX结合系列内容——第1节 PhysX核心功能及基本程序结构介绍

    [Array王锐大神力作]osg与PhysX结合系列内容--PhysX核心功能及基本程序结构介绍 本节内容 下载和编译PhysX PhysX的核心功能 PhysX的基本程序结构 PhysX与OSG结合 ...

  4. Array王锐大神力作:osg与PhysX结合系列内容——第0节 前言篇

    [Array王锐大神力作]osg与PhysX结合系列内容--前言篇 导语 系列内容预告 导语 OpenSceneGraph(OSG)是一个历史悠久的三维渲染引擎,至今仍然有广泛的用户群体,以及大量成功 ...

  5. Array王锐大神力作:osg与PhysX结合系列内容——第4节 角色运动控制

    [Array王锐大神力作]osg与PhysX结合系列内容--角色运动控制 运动学角色体(CCT) 角色的创建和参数设置 角色的交互控制 上下楼梯 设置空气墙 构建测试场景并运行 运动学角色体(CCT) ...

  6. Array王锐力作:osg与PhysX结合系列内容——第5节 角色动画效果(下)

    [Array王锐大神力作]osg与PhysX结合系列内容--角色动画效果(下) 角色动作的过渡切换 角色动画高级技巧 构建测试场景并运行 数据驱动的角色运动 角色动作的过渡切换 我们在研究下一步的高级 ...

  7. 请大神指导EGS5中DOSRZnrc的相关内容

    本人现在做蒙卡模拟方面的课题,其中涉及到EGS5中DOSRZnrc code模拟CT数据,我想了解一下这是代码的内容以及相关介绍,求相关大神帮忙解答

  8. 那些在Youtube上呼风唤雨,收入数十万美元的大神们

    据说在起点写书的大神们年收入能达到数十万甚至百万人民币,在Youtube上也有一群大神能赚到一大笔.Business Insider最近报道了在Youtube上最红.收入最高的十位大神,他/她们的收入 ...

  9. 今日重磅!恺明大神又一力作!重新思考万能的ImageNet预训练模型

    译者 | 刘畅 林椿眄 整理 | Jane 出品 | AI科技大本营 Google 最新的研究成果 BERT 的热度还没褪去,大家都还在讨论是否 ImageNet 带来的预训练模型之风真的要进入 NL ...

最新文章

  1. 【C++】C++11 STL算法(三):分隔操作(Partitioning operations)、排序操作(Sorting operations)
  2. HTTP FTP 返回状态代码
  3. vue前端上传文件夹的插件_基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件...
  4. C语言-结构体内存对齐
  5. rda分析怎么做_RDA分析
  6. Houdini使用Python给点连线
  7. mysql计算增长率
  8. latex中页眉怎么去掉_LaTeX页面布局专题——页眉和页脚
  9. 授人以鱼:教你找电影
  10. fixed在ios失效解决方案
  11. 楚狂人--驱动开发基础
  12. c语言小蜜蜂编程题,C语言经典题目“小蜜蜂“代码.docx
  13. 开源MySQL数据仓库解决方案:Infobright
  14. 每日一支TED——Ethan Nadelmann:为什么我们应该终止禁毒战争
  15. recvfrom的addrlen参数
  16. JS生成 UUID的四种方法
  17. Linux里面 grep的用法之根据关键词查询进程PID
  18. gnuradio android手机,常用的gnuradio 模块
  19. 企业律师事务官方介绍网站源码
  20. 我公务员工作七年后的肺腑之言(好文)

热门文章

  1. Unity制作“见缝插针”小游戏
  2. L298N双路电机驱动模块使用指南
  3. 饭桶:しち: 遗传算法
  4. 计算机软考下午考试,2020年计算机软考下午考题解答技巧方法
  5. Blender安装最新版本
  6. 细说——命令执行_代码执行
  7. 【CISSP备考】第12章:安全通信与网络攻击
  8. · python的线程等待
  9. oracle ons offline 服务启动失败
  10. 三次样条插值(Cubic Spline Interpolation)及代码实现(C语言)