PhysX物理引擎(编程入门)
发表于2016-06-02
PhysX物理引擎(编程入门)
--PhysX,Hello World!
Author: 华文广 E-MAIL: huawenguang@sina.com DATE:06/7/20
Hi,大家好,好久没有写过东西了.最近在研究物理引擎,在网上搜索了一下,发现相关的技术文章特别少,于是我心血来潮,决定给有兴趣向这方面发展的朋友写一篇入门教程,希望有所帮助。
如果你是一名超级游戏爱好者,那想必你会听说过PPU。要是你不知道什么是PPU,那也不要紧,但至少你要知道什么是“物理加速卡”。
Ageia是PhysX物理芯片的开发商,一家名不见经传的公司,成为敢吃螃蟹的第一人。说不定不久的将来,我们的计算机里会出现CPU,GPU,PPU三足鼎立的局面,而物理编程,也将成为游戏程序员的必修课程。本文是PhysX编和的入门教程。
一、安装
在国际上,出名的物理引擎有Havok,Vortex,ODE,Novodex,Takamak等等,其中ode是一个免费开源的物理引擎,而Novodex就是PhysX的前身,被Ageia收购之后,改名为PhysX,是一个可以免费用于非商品用途的引擎。在这里选用PhysX来作为入门教程,主要是因为,它的帮助比较丰富,而且开发包可以免费获得。
关于PhysX sdk的安装.首先要进入http://support.ageia.com下载SDK,注意的是Ageia的SDK只对注册用户开放下载。注册是免费的,但好像要经过审核才会开通,不过一般都会通过的。我注册的时候好像是第二天才收到开通邮件。有两个安装文件是必须下载的System Software.exe和PhysX 2.3.3 SDK Core.exe前一个是底层驱动,后一个是程序内核,最新的SDK是2.4.1,但是只针对商业客户开放。对于初学者来说,最好把PhysX 2.3.3 SDK Training Pragrams.exe也一起下载,里面包含了从初级到高级的一系列教程,对学习这个引擎很有帮助。把所有东西下载下来之后,接着是安装了,安装很简单,一路next下去就可以了,但是为了让VC中设置方便一点,建设把PhysX 2.3.3 SDK Core.exe的安装路径改短一点,例如我的就是安装在D:PhysX中。
安装好了之后,后开始对VC编译环境进行设置。
首先,在Tools→Options→Directories→Inclund Fik中加入以下目录.
D:PhysXSDKSPhysicsinclude
D:PhysXSDKSFounddationinclude
D:PhysXSDKSPhysXLoaderinclude
然后在…àLibrary Fiks中加入以下目录:
D:PhysXsdksLIBWin32
以上用到的“D:PhysX”指的是sdk安装目录,以你机器中的安装路径为准,本教程的示例程序用到了opengl和glut作为渲染引擎,你的计算机如何没有安装glut库,那也请先到www.opengl.org上下载一个安装上去。在这里就不打算深入讨论glut了,没有基础的朋友可以先自学一下。
二、PhysX概述
首先来介绍一下PhysX编程的几个术语以及它们之间的相互联系。
1、Scene场景:就像演员表演都需要一个舞台一样, PhysX的所有物理运动都在这个scene中进行。
2、Actor角色:在场景中,所有参与运算的实体都是一个角色或许我这样表达不是很正确,大家慢慢体会吧!
3、bosy刚体:用来记录物体之间世界交互的各种系数,如速度,阻尼等.
4、shape形状:描述和表达某一角色的外形,PhysX中提供4种基本形状,盒子,球,胶囊以及平面。
从上面图可以看到,PhysX编程其实很简单,首先,定义各种不同的角色(actor),然后指定每个角色的形状(shape)属性和刚体(body)属性,最后是把这些角色都加入到场景(scene)空间中去,这样就可以构造出一个完整的物理世界。下面我将详细描述编程的步骤.
三、编程实现
1、创建scene,
1 2 3 4 5 | NxsceDesc sceneDesc: SceneDesc.grauity = gDefaultGravity; //指定重力加速度(-9.81f) SceneDesc.broadphase = NX_BROADPHASE_COHERENT; SceneDesc.collisionDetection= true ; //是否开启碰撞检测 Gscene =gPhysicsSDK→createScene(sceneDesc); |
首先我们要创建一个场景的描述(Descriptor),PhysX SDK就利用这个场景描述结构来创建生成一个场景实例.
描述(Descriptor)在整个SDK编程过程中,会被广泛地使用。描述其实就是一个数据结构,主要是用来保存各种在创建实体时所需要的相关信息。你可以调整描述体中各种参数来达到不同的效果,当然你可以不作任何修改,这样的话实体在创建时会使用描述体的默认值。
在本例子中,我们创建一个指定了重力加速以及碰撞检测算法的场景实例。PhysX SDK中提拱了三种碰撞检测算法提拱给大家选择.这里选用的是“broad phase-coheret collison detoction”。
2、给场景(scene)增加物理材质(Materials)
物理材质指的是某一具体物体的表面属性和碰撞属性,这些属性可以确定一个物体和另一个物体发生碰撞时,是如何在该的物体上反弹,滑动或者滚动的。
你可以给场景中的所有物体指定一个相同的默认物理材质。
1 2 3 4 5 6 7 8 9 | //创建默认材质 Nxmaterial* defaultMaterial=gscene → getMaterialFromIndex(0); Default Material→setRestitution(0.9); //还原系数为0的时候没有还原. DefaultMaterial→setStaticFriction(0.5); //静摩擦系数. DefaultMaterial→setDynamicFricfion(0.5); //动摩擦系数. |
以上材质的系数最小值都是0,最大值是1,如果要实现一个物体落在地上会自动弹跳,那就得把还原系数设得大一点。
3、创建地面
在本程序例子中,只有两个角色实体,地面和盒子.我们首先来看如何创建地面.
1 2 3 4 5 6 7 | NxPlane shapeDesc planeDesc; NxActorDesc actorDesc; actorDesc.shapes.pushBack(&phane Desc); gscene→createActor(AcforDesc); |
创建一个地面角色,这可能是角色创建的最简单的方法了,只用到了四行代码,首先分别创建一个平面形状描述和角色描述,两个描述都不作任何修改,也就是使用它们的默认值.平面的中心位于世界坐标原点(0,0,0)处,而法线则是指向y轴的正方向。
第二步,把平面描述添加到角色描述中的形状列表中去,从这里我们也可以看到,一个角色是可以包含多个形状物体的。
第三步,就是把角色加到场景(scene)中去,也许你会留意到,前面我们所说的一个角色实体必须包括形状描述和刚体描述,两大部份,为什么这里只有形状描述呢?其实,刚体描述也是存在的,当你没有为它指定的时候,角色创建时会自动生成一个默认的刚体描述。一个刚体的默认值是这样的:它不会移动但是会把与它发生碰撞的物体反弹回去。因为它的质量是无限大的。
4、创建盒子
前面介绍了如何创建一个地面,这是场景中最简单的一个角色了,下面我们将要创建一个稍为复杂一点的角色,一个盒子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Int size=5 NxBodyDesc BodyDesc; BodyDesc.angularDamping=0.5f; BodyDesc.linearVelocity=NxUec3(0.0f,0.0f,0.0f) NxBoxShapeDesc BoxDesc; BoxDesc.dinesions=NxUec3( float (size), float (size), float (size)); NxActorDesc BoxActorDesc; BoxActorDesc.shapes.pushBack(&BosDesc); BosActorDsec.body= &BodyDesc; BoxActorDesc.desity=0.10f; BoxActorDesc.globalpose.t=NxVec3(0.0.20.0.0.0); Gscene→createActor(BoxActorDesc)→userData=(viud*)size; |
这里我们创建了一个叫“Box”的场景角我。我们可以看到,盒子角色完整地包含了形状和刚体两大部份。和创建平面角色不同的是盒子角色描述中多了“desity”,“globalpose”两个分量,分别指的是密度和初始位置,SDK会根据密度和体积来自动计算角色的质量。
“globalpose”指的是在世界位标中的相对位置,值得注意的是:
PhysX中,与坐标尺寸相关的数值,其单位都是“米”(m)。
5、绘制与运动
完成了以上的准备工作之后,接下来便是检验成果的最后冲刺了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Whik(nbActors--) { NxActor*actor=*actors++; If(!actor->userData) continue ; glpushMatrix(); float glamat[16]; actor->getGlobalPose().getColumnMajor44(glmat); glColor4f(1.0f,1.0f,1.0f,1.0f); glMultMatrix(glmat); glutWireCube( float ( int (actor→userData))*2.0f); glPopMatrix(); } |
上面是绘制场景的程序,这里因为不需要绘制地面,因此第一行跳过平面角色,直接绘制盒子.
OK,现在我们可以让程序运行起来了,在窗口可以看见生成的一个立方体盒子.但是为什么那个盒子不会落下来,不会运动呢?这是因为我们还没有加入实时运算函数。在绘制盒子之前加入以下三行:
1 2 3 4 5 | Gscene→fetchResults(NX_RIGID_BODY_FINFSHED); gsceng→Simulate(1/60.0f); gscene→flushstream(); |
这样,盒子就会产生自由落体运动,其中simulate(1/60.0)是一个积分函数,用来求位移.这里用到了固定间隔时间1/60.0秒,其实最好是使用一些系统时间函数,来计算上一次刷屏到现在的时间,这样会让物体运动更加逼真。
四、总结
这是一个PhysX物理引擎的Hello World入门程序,为了让大家更清晰地看到程序总体框架,我把程序的功能尽量写得简单。在接下来的一段时间里,我会写一些复杂的相关教程,希望各位网友友持。当然,我也是一边学一边写,难免会出现错差,如果你们发现我的文章有问题的话,请E-mail:huawenguang@sina.com 告诉知我,也欢迎在这方面有共同兴趣的朋友来信交流.
特别感谢我身边一个朋友的支持!
五、源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | // A minimal Novodex application test. // 以下代码,先安装好PhysX SDK,及按要求配置好路径之后才能编译。 // 建义用使用VC2003以上版本,VC6.0在我这里有一个“return”错误,把“return”去掉就可以编译通过。 // 运行的时候如果提示缺少DLL文件,请在 // 或者拷贝到系统systems32/ 文件夹中 // NxBoxes by Pierre Terdiman (01.01.04) // author: huawenguang@sina.com #define NOMINMAX #ifdef WIN32 #include #include #include #elif LINUX #include #include #elif __APPLE__ #include #include #elif __CELLOS_LV2__ #include #endif #include // Physics code #undef random #include "NxPhysics.h" //#include "ErrorStream.h" #pragma comment( lib, "PhysXLoader.lib" ) static bool gPause = false ; static NxPhysicsSDK* gPhysicsSDK = NULL; static NxScene* gScene = NULL; static NxVec3 gDefaultGravity(0.0f, -9.81f, 0.0f); static float gRatio=1.0f; static void InitNx() { // Initialize PhysicsSDK gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, 0, NULL); if (!gPhysicsSDK) return ; gPhysicsSDK->setParameter(NX_MIN_SEPARATION_FOR_PENALTY, -0.05f); // Create a scene NxSceneDesc sceneDesc; sceneDesc.gravity = gDefaultGravity; sceneDesc.broadPhase = NX_BROADPHASE_COHERENT; sceneDesc.collisionDetection = true ; gScene = gPhysicsSDK->createScene(sceneDesc); NxMaterial * defaultMaterial = gScene->getMaterialFromIndex(0); defaultMaterial->setRestitution(0.9f); defaultMaterial->setStaticFriction(0.1f); defaultMaterial->setDynamicFriction(0.1f); // Create ground plane NxPlaneShapeDesc PlaneDesc; PlaneDesc.d = -5.0f; NxActorDesc ActorDesc; ActorDesc.shapes.pushBack(&PlaneDesc); gScene->createActor(ActorDesc); //CreateCube(NxVec3(0.0,20.0,0.0),5); // Create body ////////////////////////////////////////////////////////////// int size = 5; NxBodyDesc BodyDesc; BodyDesc.angularDamping = 0.5f; // BodyDesc.maxAngularVelocity = 10.0f; BodyDesc.linearVelocity = NxVec3(0.0f,0.0f,0.0f); NxBoxShapeDesc BoxDesc; BoxDesc.dimensions = NxVec3( float (size), float (size), float (size)); NxActorDesc BoxActorDesc; BoxActorDesc.shapes.pushBack(&BoxDesc); BoxActorDesc.body = &BodyDesc; BoxActorDesc.density = 0.10f; BoxActorDesc.globalPose.t = NxVec3(0.0,20.0,0.0); gScene->createActor(BoxActorDesc)->userData = ( void *)size; } static void RenderCallback() { // Clear buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Setup camera glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0f, 1.0, 1.0f, 10000.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0.0, 5.1, 50.0, 0.0, 0.0, 0.0, 0.0f, 1.0f, 0.0f); gScene->fetchResults(NX_RIGID_BODY_FINISHED); gScene->simulate(1/60.0f); gScene->flushStream(); // Keep physics & graphics in sync int nbActors = gScene->getNbActors(); NxActor** actors = gScene->getActors(); while (nbActors--) { NxActor* actor = *actors++; if (!actor->userData) continue ; glPushMatrix(); float glmat[16]; actor->getGlobalPose().getColumnMajor44(glmat); glMultMatrixf(glmat); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glutWireCube( float ( int (actor->userData))*2.0f); glPopMatrix(); } glutSwapBuffers(); } int main( int argc, char ** argv) { // Initialize Glut printf ( "PhysX, Hello World!" ); glutInit(&argc, argv); glutInitWindowSize(512, 512); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); int mainHandle = glutCreateWindow( "PhysX, Hello World!" ); glutSetWindow(mainHandle); glutDisplayFunc(RenderCallback); glutIdleFunc(RenderCallback); // Setup default render states glClearColor(0.3f, 0.4f, 0.5f, 1.0); glEnable(GL_DEPTH_TEST); glEnable(GL_COLOR_MATERIAL); glEnable(GL_CULL_FACE); glEnable(GL_LIGHTING); // Physics code InitNx(); // ~Physics code // Run glutMainLoop(); if (gPhysicsSDK && gScene) gPhysicsSDK->releaseScene(*gScene); gPhysicsSDK->release(); return 0; } |