C++ 3D物理引擎库BulletPhysics基本使用
前言:最近在接触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基本使用的更多相关文章
- 基于HT for Web 3D呈现Box2DJS物理引擎
上篇我们基于HT for Web呈现了A* Search Algorithm的3D寻路效果,这篇我们将采用HT for Web 3D来呈现Box2DJS物理引擎的碰撞效果,同上篇其实Box2DJS只是 ...
- 基于HTML5的WebGL结合Box2DJS物理引擎应用
上篇我们基于HT for Web呈现了A* Search Algorithm的3D寻路效果,这篇我们将采用HT for Web 3D来呈现Box2DJS物理引擎的碰撞效果,同上篇其实Box2DJS只是 ...
- 基于Babylon.js编写宇宙飞船模拟程序1——程序基础结构、物理引擎使用、三维罗盘
计划做一个宇宙飞船模拟程序,首先做一些技术准备. 可以访问https://ljzc002.github.io/test/Spacetest/HTML/PAGE/spacetestwp2.html查看测 ...
- BeamNG.drive物理引擎评鉴
BeamNG.drive是一款由BeamNG公司开发并于2013年首次发布的软体物理模拟游戏.作为模拟游戏,特别是物理模拟的粉丝,我早早就开始使用BeamNG.drive.我立即对崩溃的准确性和细节印 ...
- Cocos2d-js官方完整项目教程翻译:六、添加Chipmunk物理引擎在我们的游戏世界里
添加Chipmunk物理引擎在我们的游戏世界里 一.简介 cocos2d JS能给我们力量来创造令人印象深刻的游戏世界.但缺乏某种现实. ...
- 造个海洋球池来学习物理引擎【Three.js系列】
github地址:https://github.com/hua1995116/Fly-Three.js 大家好,我是秋风.继上一篇<Three.js系列: 游戏中的第一/三人称视角>今 ...
- 【Unity 3D】学习笔记三十六:物理引擎——刚体
物理引擎就是游戏中模拟真是的物理效果.如两个物体发生碰撞,物体自由落体等.在unity中使用的是NVIDIA的physX,它渲染的游戏画面很逼真. 刚体 刚体是一个很很中要的组件. 默认情况下,新创的 ...
- 转:Bullet物理引擎不完全指南(Bullet Physics Engine not complete Guide)
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 前言 Bullet据称为游戏世界占有率为第三的物理引擎,也是前几大引擎目前唯一能够 ...
- 将 Android* Bullet 物理引擎移植至英特尔® 架构
简单介绍 因为眼下的移动设备上可以使用更高的计算性能.移动游戏如今也可以提供震撼的画面和真实物理(realistic physics). 枪战游戏中的手雷爆炸效果和赛车模拟器中的汽车漂移效果等便是由物 ...
随机推荐
- 终于将 SQL Server 成功迁移至 MySQL8.0 啦!!!
之前一直使用 SQL Server 作为主数据库而不是 MySQL ,原因之一是单机 SQL Server 性能比 MySQL 强很多,另一个原因是之前客户的系统管理员大多只有 SQL Server ...
- BZOJ_5249_Luogu_P4364_[2018多省省队联测]_IIIDX_九省联考2018_JLOI2018_线段树
BZOJ_5249_[2018多省省队联测]IIIDX_线段树 Description [题目背景] Osu听过没?那是Konano最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐 ...
- Description Resource Path Location Type Cannot change version of project facet Dynamic Web Module to 2.3.
报错信息:Description Resource Path Location Type Cannot change version of project facet Dynamic Web Modu ...
- CSS3 之 童年的纸飞机
今天我们来折纸飞机(可以飞出去的那种哦) 基本全用css来实现,只有一小部分的js 首先看一下飞机的构造 灰色区域为可折叠区域 白色区域为机身 三角形由border画出来的再经过各种平移翻转变成上图 ...
- 用Python学分析 - t分布
1. t分布形状类似于标准正态分布2. t分布是对称分布,较正态分布离散度强,密度曲线较标准正态分布密度曲线更扁平3. 对于大型样本,t-值与z-值之间的差别很小 作用- t分布纠正了未知的真实标 ...
- 02 JVM 从入门到实战 | 什么样的对象需要被 GC
引言 上一篇文章 JVM 基本介绍 我们了解了一些基本的 JVM 知识,本篇开始逐步学习垃圾回收,我们都知道既然叫垃圾回收,那回收的就应该是垃圾,可是我们怎么知道哪些对象是垃圾呢? 哪些对象需要被回收 ...
- Github:修改Github仓库中项目语言类型
前述 有的时候我们把项目上传到github仓库上时语言会显示错误语言 比如一个java项目可能因为有js文件的存在而被识别为js项目 这种时候我们就要手动去修改Github的项目语言类型 解决办法 在 ...
- 如何在MySQL中查询每个分组的前几名【转】
问题 在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等. 在orcale等数据库中可以使用partition语句来解决,但在mysql中就比较麻烦了.这次翻译的文章就是专门 ...
- shiro的DelegatingFilterProxy怎么找到ShiroFilterFactoryBean
首先看到web.xml中的配置 <context-param> <param-name>contextConfigLocation</param-name> < ...
- Odoo Tech World 2018(上海)互联网开源技术大会通告
会议概述 点击进入活动报名通道 高成本的软件开发,耗时的系统安装,繁琐的操作培训… 这一系列问题都是企业数字化管理的痛点, "软件"成为发展数企业数字化转型的瓶颈, 无论是小厂家或 ...