在上一篇中提到renderLoop函数实现了基于OpenGL和Glut的程序框架,其中涉及PhysX的最重要的两个函数是initPhysics()和renderCallback()。这一篇我们分析这两个函数,讨论其中涉及的PhysX核心对象和scene->actor->shape的场景结构。本文的内容大量参考官方Guide和API文档,同时它们也是很好的学习工具,文中一些细节的内容可以从这两份文档中获取,因此没有详细解释。
initPhysics()函数: 初始化顶层对象并创建初始场景的PhysX对象
首先是两个顶层对象:
1. PxFoundation:You need to have an instance of this class to instance the higher level SDKs. 每个应用一般只有一个,最高层成员
2. PxPhysics:Abstract singleton factory class used for instancing objects in the Physics SDK 代表SDK的类
PxCreatePhysics():Creates an instance of the physics SDK. 调用该指令可以注册SDK中的所有代码模块,还有其它函数可以注册部分可选项。
因此,一般的PhysX初始化代码是gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback);
PxProfileZoneManager* profileZoneManager = &PxProfileZoneManager::createProfileZoneManager(gFoundation);
gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(),true,profileZoneManager);
其次是PxSceneDesc类型
PxSceneDesc:Scene descriptor 描述一些场景特性的数据结构, 是PxPhysics的createScene函数的参数。事实上,PhysX中采用了许多作用类似的后缀为Desc的数据结构。
最后两个函数createStack()和createDynamic()分别创建场景中的box堆和飞出的球。
renderCallback()函数:渲染
在讨论渲染之前,我们先要搞清楚PhysX场景的组成。我们采用的是PhysX的API创建场景中的实体,再从这些PhysX对象中获取渲染所需的数据,利用OpenGL进行渲染,才获得了我们最终看到的结果。事实上,换一种说法就是,这里渲染的工作仅仅是让这些PhysX代码实现的物理过程可视化而已,snippets中的许多例子是没有渲染的。
snippetHelloWorld场景中的对象大致分为三类,从上到下依次为scene类型、actor类型、shape类型。
PxScene:A scene is a collection of bodies, particle systems and constraints which can interact. 场景
Actor:PxActor is the base class for the main simulation objects in the physics SDK actor就是PhysX中的物体,PxActor是所有actor类型的基类,Guide的rigid body一节中有个图描述了这些类型和他们的关系。
PxShape:Abstract class for collision shapes. 形状,几何,类似的理解...这个类可以提取actor的几何形状,渲染就靠它了。显然,它和碰撞时有关的,不过本例并没有用到。
它们三者之间通过类似的代码语句建立起彼此的联系,把场景组织起来
PxShape* shape = gPhysics->createShape(PxBoxGeometry(halfExtent, halfExtent, halfExtent), *gMaterial);
PxRigidDynamic* body = gPhysics->createRigidDynamic(t.transform(localTm));
body->attachShape(*shape);
gScene->addActor(*body);
简单来说,它们三者的关系就是:一个scene里面有许多actor,每个actor的形状由shape来定义。renderCallback函数提取了场景中的所有actor,并调用了renderActors()子函数来对每一个actor关联的所有shape进行渲染,代码如下:
void renderActors(PxRigidActor** actors, const PxU32 numActors, bool shadows, const PxVec3 & color)
{
PxShape* shapes[MAX_NUM_ACTOR_SHAPES]; //shape数组
for(PxU32 i=0;i<numActors;i++) //外循环,循环次数等于actor的个数
{
const PxU32 nbShapes = actors[i]->getNbShapes();
PX_ASSERT(nbShapes <= MAX_NUM_ACTOR_SHAPES);
actors[i]->getShapes(shapes, nbShapes); //获取该actor的shape
bool sleeping = actors[i]->isRigidDynamic() ? actors[i]->isRigidDynamic()->isSleeping() : false; //不可或缺的一行,如果actor在sleeping或者actor不是Dynamic类型,那么就把它设为
//sleeping状态,在每个simulation循环中不对它进行处理
for(PxU32 j=0;j<nbShapes;j++) //内循环,循环次数等于shape的个数
{
const PxMat44 shapePose(PxShapeExt::getGlobalPose(*shapes[j], *actors[i])); //等于获取OpenGL中的模型变换矩阵
PxGeometryHolder h = shapes[j]->getGeometry(); //获取Geometry
if (shapes[j]->getFlags() & PxShapeFlag::eTRIGGER_SHAPE)
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
// render object
glPushMatrix();
glMultMatrixf((float*)&shapePose); //gl模型-视图变换
if(sleeping)
{
PxVec3 darkColor = color * 0.25f; //静止物体(sleeping)用暗色绘制
glColor4f(darkColor.x, darkColor.y, darkColor.z, 1.0f);
}
else
glColor4f(color.x, color.y, color.z, 1.0f);
renderGeometry(h); //绘图子函数!
glPopMatrix();
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
if(shadows) //阴影
{
const PxVec3 shadowDir(0.0f, -0.7071067f, -0.7071067f);
const PxReal shadowMat[]={ 1,0,0,0, -shadowDir.x/shadowDir.y,0,-shadowDir.z/shadowDir.y,0, 0,0,1,0, 0,0,0,1 };
glPushMatrix();
glMultMatrixf(shadowMat);
glMultMatrixf((float*)&shapePose);
glDisable(GL_LIGHTING);
glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
renderGeometry(h);
glEnable(GL_LIGHTING);
glPopMatrix();
}
}
}
}
还是一样,如果对这里的OpenGL不够熟悉请参考红宝书。代码不是很难理解,注释也已经很清楚了,再说明两点
1) Sleeping对象的不同渲染
helloWorld将sleeping的actor渲染为了暗色。isSleeping函数实现了这个判断,这里面涉及PhysX的核心概念,simulate该如何理解?
2) OpenGL视图变换矩阵的获取
从shapePose函数和PxMat44等矩阵和向量类型可以看出PhysX与图形渲染的完美兼容。
为了在OpenGL中绘制不同的shape,snippetRender.cpp中实现了一个 renderGeometry()子函数并在OpenGL的绘图语句位置调用,下一篇我们来详细讨论这里面的内容!