前言:最近在接触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. LinkedBlockingQueue简介

    LinkedBlockingQueue是一个单向链表实现的阻塞队列,先进先出的顺序.支持多线程并发操作. 相比于数组实现的ArrayBlockingQueue的有界,LinkedBlockingQue ...

  2. 随手一记,maven打包

    <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-depen ...

  3. C++中的静态类型和动态类型的定义

    当我们使用存在继承关系的类型时,必须将一个变量或者其他表达式的静态类型与该表达式表示对象的动态类型区分开来. 表达式的静态类型在编译时总是已知的,它是变量声明时的类型或者表达式生成的类型: 而动态类型 ...

  4. bzoj5252 [2018多省省队联测]林克卡特树

    斜率优化树形dp?? 我们先将问题转化成在树上选K+1条互不相交路径,使其权值和最大. 然后我们考虑60分的dp,直接维护每个点子树内选了几条路径,然后该点和0/1/2条路径相连 然后我们会发现最后的 ...

  5. SpringBoot集成Security,JWT,Swagger全分析

    GitHub地址: https://github.com/li-jun0201/springsecuritydemo本项目采用SpringBoot1.5.9, SpringSecurity,JWT, ...

  6. Android--性能测试关注的指标

    性能测试过程中,出现的一些问题可直接导致了用户对当前app的使用率和卸载率,如果app使用时卡顿严重或者加载页面慢,cpu占用率高,导致app闪退等问题,在测试过程中,则需特别关注性能方面的体验,ap ...

  7. ceph osd 自动挂载的N种情况

    直接上干货: ceph自动挂载原理 系统启动后,ceph 通过扫描所有磁盘及分区的 ID_PART_ENTRY_TYPE 与自己main.py中写死的osd ready 标识符来判断磁盘(及其分区)是 ...

  8. TensorFlow之DNN(三):神经网络的正则化方法(Dropout、L2正则化、早停和数据增强)

    这一篇博客整理用TensorFlow实现神经网络正则化的内容. 深层神经网络往往具有数十万乃至数百万的参数,可以进行非常复杂的特征变换,具有强大的学习能力,因此容易在训练集上过拟合.缓解神经网络的过拟 ...

  9. 单例模式--java代码实现

    单例模式 单例模式,顾名思义,在程序运行中,实例化某个类时只实例化一次,即只有一个实例对象存在.例如在古代,一个国家只能有一个皇帝,在现代则是主席或总统等. 在Java语言中单例模式有以下实现方式 1 ...

  10. asp.net core系列 45 Web应用 模型绑定和验证

    一. 模型绑定 ASP.NET Core MVC 中的模型绑定,是将 HTTP 请求中的数据映射到action方法参数. 这些参数可能是简单类型的参数,如字符串.整数或浮点数,也可能是复杂类型的参数. ...