前言:最近在接触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. C++中char类型的溢出问题

    C++中什么经常会运用到char类型,也会将char类型作为循环语句的循环条件,但往往这里最容易出现错误,容易出现溢出,进入死循环.这里我们就来简单介绍下为什么会出现这种情况. 首先,了解下char类 ...

  2. 【莫比乌斯反演】BZOJ2820 YY的GCD

    Description 求有多少对(x,y)的gcd为素数,x<=n,y<=m.n,m<=1e7,T<=1e4. Solution 因为题目要求gcd为素数的,那么我们就只考虑 ...

  3. 【最小生成树】UVA1494Qin Shi Huang's National Road System秦始皇修路

    Description During the Warring States Period of ancient China(476 BC to 221 BC), there were seven ki ...

  4. BZOJ_1954_Pku3764 The xor-longest Path_Trie树

    Description 给定一棵n个点的带权树,求树上最长的异或和路径 把根到点路径上点权异或和求出来,然后变成了Trie树裸题.   代码: #include <cstdio> #inc ...

  5. 解决linux netcore https请求使用自签名证书忽略安全检查方法

    当前系统环境:centos7 x64. dotnet 2.0. 不管是 ServicePointManager.ServerCertificateValidationCallback = (a, b, ...

  6. Oracle系列-锁表与解锁解决方案(大招版)-解决问题才是王道

    [Oracle系列-锁表与解锁解决方案(大招版)] --1查看被锁的表 select b.owner,b.object_name,a.session_id,a.locked_mode from v$l ...

  7. 并发的核心:CAS 是什么?Java8是如何优化 CAS 的?

    大家可能都听说说 Java 中的并发包,如果想要读懂 Java 中的并发包,其核心就是要先读懂 CAS 机制,因为 CAS 可以说是并发包的底层实现原理. 今天就带大家读懂 CAS 是如何保证操作的原 ...

  8. The following untracked working tree files would be overwritten by merge

    git pull的时候遇到这样的问题: The following untracked working tree files would be overwritten by merge balabal ...

  9. SpringBoot进阶教程(三十)整合Redis之Sentinel哨兵模式

    Redis-Sentinel是官方推荐的高可用解决方案,当redis在做master-slave的高可用方案时,假如master宕机了,redis本身(以及其很多客户端)都没有实现自动进行主备切换,而 ...

  10. es6学习笔记-async函数

    1 前情摘要 前段时间时间进行项目开发,需求安排不是很合理,导致一直高强度的加班工作,这一个月不是常说的996,简直是936,还好熬过来了.在此期间不是刚学会了es6的promise,在项目有用到pr ...