前言:最近在接触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. I/O----复制文本文件

    文件 "我的青春谁做主.txt" 位于 D 盘根目录下,要求将此文件的内容复制到 C:/myPrime.txt 中. package io.day03; import java.i ...

  2. watch.go

    package: } ,) ), ), ) ) , ];]]] :] ].Err()]:] ].Kv.ModRevision         w.resuming = append(w.resumin ...

  3. Hive 查询元数据库获取某个分区的count数

    =========查询分区的大小========= select d.NAME,t.TBL_NAME,p.PART_NAME,prm.PARAM_KEY,prm.PARAM_VALUE from TB ...

  4. shell脚本添加实例化参数

    通过shell脚本给GMP系统添加一个环境变量参数dateSwitchTimeInterval 1. insert.sh #!/bin/sh . ~/apphome/aic_export.sh #连接 ...

  5. 自行实现 dotnet core rpc

    前言 小李:“胖子,上头叫你对接我的数据好了没有?” 胖子:“那是你的事,你都不提供数据源,我咋接?” 小李:“你想要什么样的数据源?” 胖子:“我想要一个调用简单点的!” 小李:“我这个数据源是在l ...

  6. 基于SpringBoot从零构建博客网站 - 整合lombok和mybatis-plus提高开发效率

    在上一章节中<技术选型和整合开发环境>,确定了开发的技术,但是如果直接这样用的话,可能开发效率会不高,为了提高开发的效率,这里再整合lombok和mybatis-plus两个组件. 1.l ...

  7. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  8. MIP 脚本域名地址变更公告

    尊敬的 MIP 开发者: MIP 团队为了解决 MIP-Cache 页面下 cookie 相互覆盖问题,增强站点品牌露出,在 2017 年 8 月将 MIP 的脚本域名和 MIP-Cache 页面域名 ...

  9. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

  10. MySQL 复制 - 性能与扩展性的基石 3:常见问题及解决方案

    主备复制过程中有很大可能会出现各种问题,接下来我们就讨论一些比较普遍的问题,以及当遇到这些问题时,如何解决或者预防问题发生. 1 数据损坏或丢失 问题描述:服务器崩溃.断电.磁盘损坏.内存或网络错误等 ...