前言:最近在接触OpenGl和DX11的时候,顺便学习了Bullet这个3D物理引擎的基本使用,记录一下。

|BulletPhysics介绍

BulletPhysics是一个跨平台的开源物理引擎,也是三大主流3D物理引擎之一,支持三维碰撞检测、柔体动力学和刚体动力学,多用于游戏开发和电影制作中。(GTA5,荒野大嫖客也使用了这个物理引擎)

为了更容易使用物理引擎,我们必须掌握它里面的几个基本概念。

物理世界:

用来模拟各种刚体的运动。

物理世界有个重要的函数——stepSimulation模拟步长函数,它通过传入的时间大小(float deltaTime),

来给世界里所有刚体进行一段时间(deltaTime长的时间)流逝的模拟。

刚体:

参与物理模拟的物体,例如一个球体,一个长方体,或者由多个复杂形状组合成的物体。

包含形状,摩擦系数,阻尼系数,弹性系数等属性。

基本使用原理:

每帧调用物理世界的模拟步长函数,来使物理世界中模拟时间流逝。

每次模拟之后,每个刚体都会更新自己的位置及旋转角度。

然后在模拟之后根据每个刚体更新后的相应位置及旋转角度,来用图形表现方法来绘制表现。

|1、下载Bullet库,编译,配置项目

可参考该篇博客: http://www.cnblogs.com/liangliangh/p/3575590.html

|2、初始化物理世界

Broadphase(粗测阶段):

我们需要提前设置好世界大小和最大刚体数等参数传递用于构造BroadPhase。

BroadPhase的作用是在碰撞检测的初测阶段,通过基于重叠包围盒的加速结构的三维扫描和裁剪,快速并粗略筛选掉许多不会发生碰撞的对象对。

tip:另外还有NarrowPhase(细测阶段),只不过它不需要参数初始化,它负责碰撞检测的最后一步测试,也是详细的碰撞测试,比较耗费性能,所以才需要一个粗测阶段粗略过滤掉大部分不会碰撞的形状对。

CollisionConfiguration(碰撞配置):

则是规定哪些物体能和哪些物体碰撞的设置(例如一些多人射击游戏中,队友之间不会发生碰撞,但是和其他物体都能发生碰撞)

默认值是均能互相发生碰撞,本文使用了默认值。

创建并初始化物理世界代码:

    //设置世界的空间大小,限定刚体运动的空间范围
btVector3 worldAabbMin(-, -, -);
btVector3 worldAabbMax(, , );
//设置最大刚体数
int maxProxies = ;
//利用以上配置创建粗测阶段所需参数
btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin, worldAabbMax, maxProxies); //创建好碰撞配置
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration); //创建求解器
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver(); //使用以上创建的设置来创建物理世界
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
//设置物理世界重力(这里在y轴上的重力设为10N/kg)
dynamicsWorld->setGravity(btVector3(, -, ));

这样我们就成功创建了一个带有重力的物理世界dynamicsWorld

(注意:new的东西要在不需要物理世界的时候delete掉回收内存,而且delete顺序不妥则可能会出错,下面提供一个释放代码参考)

#define SAFE_DELETE_PTR(ptr) do{if(ptr){delete ptr;ptr = nullptr;}}while(0);

PhysicsWorld::~PhysicsWorld() {
//必须先delete DynamicWorld
SAFE_DELETE_PTR(mDynamicsWorld);
//再delete其他相关资源
SAFE_DELETE_PTR(mBroadphase);
SAFE_DELETE_PTR(mCollisionConfiguration);
SAFE_DELETE_PTR(mDispatcher);
SAFE_DELETE_PTR(mSolver);
}

|3、创建刚体

静态刚体

静态刚体意思是固定不会动的物体,例如地面,或者坚硬的墙之类的。

动态刚体

动态刚体意思则是可以运动的物体,例如子弹,车, 足球之类的。

因为世界一般都有地面,所以第一个要生成的刚体往往是地面,以地面刚体的生成举例:

地面一般是固定不变的,所以它是静态刚体,我们设置mass时要设置为0

(密度为0时会被Bullet认为是静态刚体,非0时则认为是动态刚体)

地面是平面形状的,所以形状要设置成 btStaticPlaneShape(即静态平面形状)。

创建一个平面状的静态刚体(作为地面)的代码例子:

    //创建 物体的初始位置旋转角度信息:旋转角度0,位置在Y轴-1距离
btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(, , , ), btVector3(, -, )));
//创建 静态平面形状
btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(, , ), );
//生成设置信息
btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(, groundMotionState, groundShape, btVector3(, , ));
//根据设置信息 创建刚体
btRigidBody* groundbody = new btRigidBody(groundRigidBodyCI);
//设置摩擦系数0.5
groundbody->setFriction(0.5f);
//将地面刚体添加到 物理世界
dynamicWorld->addRigidBody(groundbody);

创建一个球状的动态刚体的代码例子:

    //创建 物体的初始位置旋转角度信息:旋转角度0,位置在Y轴10距离的高空
btDefaultMotionState* ballMotionState = new btDefaultMotionState(btTransform(btQuaternion(, , , ), btVector3(, , ))); //创建 半径0.5的球体形状
btCollisionShape* ballShape = new btSphereShape(0.5); //设置密度(特殊地,密度为0时会被认为静态刚体,非0时则作为动态刚体)
int mass = ;
//惯性    
btVector3 inertia;
//根据密度自动计算并设置惯性
ballShape->calculateLocalInertia(mass, inertia); //生成设置信息
btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(mass, ballMotionState, ballShape, inertia); //根据设置信息 创建刚体
btRigidBody* ballBody = new btRigidBody(groundRigidBodyCI);
//设置摩擦系数0.5
ballBody->setFriction(0.5f);
//将该刚体添加到物理世界里
dynamicsWorld->addRigidBody(ballBody);

(Bullet还有其它很多基本三维形状类,不同的形状需要的构造参数也不一样,了解更多可查阅官方文档)

其它部分基本跟上面的代码一样。

|4、开始模拟

为了让物理世界的模拟和画面显示的同步,

需要在程序的主循环函数(也就是每帧都会调用的一个主函数)里某个位置使用(一般是在渲染之前的位置)。

物理世界的模拟步长函数:

int btDiscreteDynamicsWorld::stepSimulationint stepSimulation(btScalar timeStep,int maxSubSteps=);

timeStep也就是要模拟的时间段大小,maxSubSteps是指模拟的子步骤的数量,

简单来说就是将时间段拆成maxSubSteps个子时间段,然后对每个子时间段依次进行模拟。

如果子步骤数量比较小,有些速度比较快的物体可能因为模拟的时间段比较大,容易穿透过其他物体模型。

将时间段拆成若干个更小的子时间段来依次模拟能够更容易避免穿模现象,当然求解多若干次是会付出性能代价的

(比较适中恰当的子步骤数量是10,最好根据自己程序性能和正确性的平衡来修改)

模拟完,还要更新各物体的渲染逻辑位置角度信息

(物理引擎的位置角度信息和渲染逻辑的位置角度信息是分别独立的,物理模拟后须将物理引擎的位置角度信息赋给渲染逻辑的位置角度信息)

本文假设主循环函数为void updateScene(float deltaTime);

void updateScene(float deltaTime) {
//主循环函数的其它内容(一般是逻辑处理)
//balabala..... //物理世界模拟
//通过10次子步骤求解,模拟出deltaTime后的物理世界变化。
dynamicsWorld->stepSimulation(deltaTime, 1); //更新物理世界每一个物体
auto & objectArray = dynamicsWorld->getCollisionObjectArray();
for (int i = ; i < objectArray.size(); ++i)
{
//处于不活动状态或者是静态刚体的话,则不处理
if (!objectArray[i]->isActive() || objectArray[i]->isStaticObject())continue;
Transform* object = reinterpret_cast<Transform*>(objectArray[i]->getUserPointer());
//没有用户指针的话,则不处理
if (!object)continue;
//更新目标物体的位置
const auto & pos = objectArray[i]->getWorldTransform().getOrigin();
object->setPosition(pos.x(), pos.y(), pos.z());
//更新目标物体的旋转角度
const auto & rotationM = objectArray[i]->getWorldTransform().getRotation();
object->setRotation(rotationM.getX(), rotationM.getY(), rotationM.getZ(), rotationM.getW());
}
//主循环函数的其他内容(一般是渲染)
//bala......
}

如果成功的话我们就能模拟出一个带重力的物理世界,

生成好地板刚体,球刚体,并把球设置在高空,那么我们将通过图形渲染方法会看到球受重力影响下落的物理效果。

|5、删除刚体

此外,在游戏过程中,也存在可能中途删除物体的情况。

由于物理引擎和渲染逻辑是分别独立的,要删除一个物体,则不仅需要在渲染逻辑上删除,还要在物理引擎上删除它的刚体。

一个值得参考的方法是在遍历物理世界所有刚体的时候,检测删除标记并删除相应的刚体:

void updateScene(float dt) {
//主循环函数的其它内容(一般是逻辑处理)
//balabala..... //模拟步长
m_dynamicsWorld->stepSimulation(dt,); auto & objectArray = m_dynamicsWorld->getCollisionObjectArray();
//更新物理世界每一个物理物体
for(int i =; i < objectArray.size();++i)
{
//清除待删除物理刚体
int entityState = reinterpret_cast<int>(objectArray[i]->getUserPointer());
//本文将待删除物理刚体的用户指针指向Entity::NoEntity(-1值)作为待删除标记,也可用其它来作为标记
if (entityState == Entity::NoEntity) {
m_dynamicsWorld->removeCollisionObject(objectArray[i]);
--i;//删除后要退回一位
continue;
} //不存在用户指针或者睡眠中,则不处理
if (!objectArray[i]->isActive()|| objectArray[i]->isStaticObject()){
continue;
}
Transform* object= reinterpret_cast<Transform*>(entityState);
if (!object){
continue;
} //更新目标物体的位置
const auto & pos = objectArray[i]->getWorldTransform().getOrigin();
object->setPosition(pos.x(), pos.y(), pos.z());
//更新目标物体的旋转角度
const auto & rotationM = objectArray[i]->getWorldTransform().getRotation();
object->setRotation(Vector4f(rotationM.getX(), rotationM.getY(), rotationM.getZ(),rotationM.getW()));
}
}

|参考

BulletPhysics 官网 https://pybullet.org/wordpress/

BulletPhysics Github https://github.com/bulletphysics/bullet3

BulletPhysics 快速入门文档: https://docs.google.com/document/d/10sXEhzFRSnvFcl3XxNGhnD4N2SedqwdAvK3dsihxVUA/edit#heading=h.2ye70wns7io3

C++ 3D物理引擎库BulletPhysics基本使用的更多相关文章

  1. 基于HT for Web 3D呈现Box2DJS物理引擎

    上篇我们基于HT for Web呈现了A* Search Algorithm的3D寻路效果,这篇我们将采用HT for Web 3D来呈现Box2DJS物理引擎的碰撞效果,同上篇其实Box2DJS只是 ...

  2. 基于HTML5的WebGL结合Box2DJS物理引擎应用

    上篇我们基于HT for Web呈现了A* Search Algorithm的3D寻路效果,这篇我们将采用HT for Web 3D来呈现Box2DJS物理引擎的碰撞效果,同上篇其实Box2DJS只是 ...

  3. 基于Babylon.js编写宇宙飞船模拟程序1——程序基础结构、物理引擎使用、三维罗盘

    计划做一个宇宙飞船模拟程序,首先做一些技术准备. 可以访问https://ljzc002.github.io/test/Spacetest/HTML/PAGE/spacetestwp2.html查看测 ...

  4. BeamNG.drive物理引擎评鉴

    BeamNG.drive是一款由BeamNG公司开发并于2013年首次发布的软体物理模拟游戏.作为模拟游戏,特别是物理模拟的粉丝,我早早就开始使用BeamNG.drive.我立即对崩溃的准确性和细节印 ...

  5. Cocos2d-js官方完整项目教程翻译:六、添加Chipmunk物理引擎在我们的游戏世界里

    添加Chipmunk物理引擎在我们的游戏世界里         一.简介                   cocos2d JS能给我们力量来创造令人印象深刻的游戏世界.但缺乏某种现实.       ...

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

    github地址:https://github.com/hua1995116/Fly-Three.js 大家好,我是秋风.继上一篇<Three.js系列:   游戏中的第一/三人称视角>今 ...

  7. 【Unity 3D】学习笔记三十六:物理引擎——刚体

    物理引擎就是游戏中模拟真是的物理效果.如两个物体发生碰撞,物体自由落体等.在unity中使用的是NVIDIA的physX,它渲染的游戏画面很逼真. 刚体 刚体是一个很很中要的组件. 默认情况下,新创的 ...

  8. 转:Bullet物理引擎不完全指南(Bullet Physics Engine not complete Guide)

    write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 前言 Bullet据称为游戏世界占有率为第三的物理引擎,也是前几大引擎目前唯一能够 ...

  9. 将 Android* Bullet 物理引擎移植至英特尔&#174; 架构

    简单介绍 因为眼下的移动设备上可以使用更高的计算性能.移动游戏如今也可以提供震撼的画面和真实物理(realistic physics). 枪战游戏中的手雷爆炸效果和赛车模拟器中的汽车漂移效果等便是由物 ...

随机推荐

  1. bzoj2437 [Noi2011]兔兔与蛋蛋

    二分图博弈果然都是一个套路,必经点必胜,非必经点必败, 但是肯定不能每走一步就重新建图判断必胜还是必败,那么我们可以这样:每走一步就把这个点删掉,然后find他原来的匹配,如果找不到,就说明他是必经点 ...

  2. BZOJ_1031_[JSOI2007]字符加密Cipher_后缀数组

    BZOJ_1031_[JSOI2007]字符加密Cipher_后缀数组 Description 喜欢钻研问题的JS同学,最近又迷上了对加密方法的思考.一天,他突然想出了一种他认为是终极的加密办法 :把 ...

  3. BZOJ_2460_[BeiJing2011]元素_线性基

    BZOJ_2460_[BeiJing2011]元素_线性基 Description 相传,在远古时期,位于西方大陆的 Magic Land 上,人们已经掌握了用魔 法矿石炼制法杖的技术.那时人们就认识 ...

  4. aes 128、192、256位,cbc、cfb、ecb、ofb、pcbc加密解密

    AES加解密总共有以下这些 算法/模式/填充 字节加密后数据长度 不满16字节加密后长度 AES/CBC/NoPadding 16 不支持 AES/CBC/PKCS5Padding 32 16 AES ...

  5. mybatis 异常Result Maps collection does not contain value for java.lang.String

    Result Maps collection does not contain value for java.lang.String 以上是我报的错. 只要报Result Maps collectio ...

  6. Javapoet源码解析

    Javapoet:是生成.java源文件的开源API  github:https://github.com/square/javapoet   以生成一个HelloWrold.java文件为例: pa ...

  7. Scala 编码习惯

    1. 不用var.var是可以被不断修改的,而val是不能被修改的.使用val而不是var能让你的程序更强壮,bug更少,更好调试,更容易测试,在并发条件下,更容易调优而获得更好的性能.数学证明我们不 ...

  8. 使用CompletableFuture实现异步编程

    在开发中会碰到一种场景,如下 Object result1 = service1.func1();//执行80ms Object result2 =service2.func2();//执行50ms ...

  9. Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步

    目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...

  10. C#删除字符串最后一个字符

    例:字符串 string str="2,3,5,7,9," 去掉最后一个逗号 ","; 常用的方法: 1.SubString()方法 str=str.SubSt ...