PhysX3.3.4 snippets—SnippetHelloWorld (3)

    这一篇我们讨论renderGeometry()函数是怎样使用OpenGL实现shape的渲染的。

    该函数传递的参数是PxGeometryHolder类型,它保存了shape的Geometry。函数内部通过switch语句判断Geometry的类型来进行gl绘图,eBOX,eSPHERE等可以通过OpenGL的一条语句画出,但是复杂几何体就不行了。那么PhysX中的网格shape类型eTRIANGLEMESH和eCONVEXMESH是如何绘制的呢?这里一是要弄明白PhysX-Geometry中这两种数据结构,二是要掌握OpenGL顶点数组的渲染方式

    首先对于后者,顶点数组渲染方式的思想是将三维网格的所有顶点座标存放在OpenGL的一个数组中,用一条专门的函数语句来访问这个数组,这样可以避免因为重复调用简单绘图语句,节省开销。除了顶点数组,OpenGL中还有存储顶点法向量的数组等8个顶点数组定义。实现顶点数组方式一般分为三步:启用数组,指定顶点数组数据,解引用与渲染。详细的函数可以参考OpenGL红宝书。

    eCONVEXMESH部分:代码注释如下

case PxGeometryType::eCONVEXMESH:
		{
			
			//Compute triangles for each polygon.
			const PxVec3 scale = h.convexMesh().scale.scale;
			PxConvexMesh* mesh = h.convexMesh().convexMesh;  //获取convexMesh
			const PxU32 nbPolys = mesh->getNbPolygons();     //多边形个数
			const PxU8* polygons = mesh->getIndexBuffer();   //顶点索引数组
			const PxVec3* verts = mesh->getVertices();		 //顶点数组
			PxU32 nbVerts = mesh->getNbVertices();			 
			PX_UNUSED(nbVerts);

			PxU32 numTotalTriangles = 0;        //三角形图元计数        
			for(PxU32 i = 0; i < nbPolys; i++)  //外循环,访问所有多边形
			{
				PxHullPolygon data;             
				mesh->getPolygonData(i, data);	//获取多边形数据

				const PxU32 nbTris = data.mNbVerts - 2;   //三角形个数等于顶点数-2
				const PxU8 vref0 = polygons[data.mIndexBase + 0];   //该多边形的首顶点
				PX_ASSERT(vref0 < nbVerts);
				for(PxU32 j=0;j<nbTris;j++)     //内循环,循环次数等于多边形内所有三角形个数
				{
					const PxU32 vref1 = polygons[data.mIndexBase + 0 + j + 1];  //剩余两个顶点
					const PxU32 vref2 = polygons[data.mIndexBase + 0 + j + 2];

					//generate face normal:
					PxVec3 e0 = verts[vref1] - verts[vref0];  //两条边的向量
					PxVec3 e1 = verts[vref2] - verts[vref0];

					PX_ASSERT(vref1 < nbVerts);
					PX_ASSERT(vref2 < nbVerts);

					PxVec3 fnormal = e0.cross(e1);  //法向量
					fnormal.normalize();  //OpenGL的法向量需要归一化
			
					if(numTotalTriangles*6 < MAX_NUM_MESH_VEC3S)
					{
						gVertexBuffer[numTotalTriangles*6 + 0] = fnormal;      //交替存储顶点的法向量和座标
						gVertexBuffer[numTotalTriangles*6 + 1] = verts[vref0];
						gVertexBuffer[numTotalTriangles*6 + 2] = fnormal;
						gVertexBuffer[numTotalTriangles*6 + 3] = verts[vref1];
						gVertexBuffer[numTotalTriangles*6 + 4] = fnormal;
						gVertexBuffer[numTotalTriangles*6 + 5] = verts[vref2];
						numTotalTriangles++;  //总三角形个数+1
					}
				}
			}
			glPushMatrix();
			glScalef(scale.x, scale.y, scale.z);
			glEnableClientState(GL_NORMAL_ARRAY);  //启用法向量数组
			glEnableClientState(GL_VERTEX_ARRAY);  //启用顶点数组
			glNormalPointer(GL_FLOAT, 2*3*sizeof(float), gVertexBuffer);      
			glVertexPointer(3, GL_FLOAT, 2*3*sizeof(float), gVertexBuffer+1);  //指定顶点数组的数据
			glDrawArrays(GL_TRIANGLES, 0, numTotalTriangles * 3);  //解引用与渲染
			glPopMatrix();
		}
		break;

对于该类型shape的解析工作是将它转换成OpenGL可以渲染的三角形基本图元,并将顶点和法向量存储在顶点数组中。首先从PxGeometryHolder类型h变量中获取convexMesh的基本信息,包括三部分:多边形个数nbPolys,顶点座标数组vertices,索引数组indexBuffer

nbPolys主要负责控制循环次数,因为接下来要对每个多边形进行解析。vertices和indexBuffer配合使用,后者的每一个元素都对应于一个顶点的索引值,即vertices中的元素下标。但是indexBuffer是按照多边形的次序存储的,即先存储多边形0的n0个顶点索引值,接着是多边形1的n1个顶点索引值…如何访问每个多边形呢?通过PxHullPolygon类型对象,这个类的成员包括平面方程参数、该多边形的顶点个数、在indexBuffer中的偏移量mIndexBase(详细可以参照API文档),获取该多边形中顶点座标的方式如下

const PxU8* faceIndices = indexBuffer + face.mIndexBase;
    for(PxU32 j=0;j<face.mNbVerts;j++)
    {
        vertices[offset+j] = convexVerts[faceIndices[j]];
        normals[offset+j] = PxVec3(face.mPlane[0], face.mPlane[1], face.mPlane[2]);
    }

这样我们就可以设计程序来获取最终渲染所需的三角形图元顶点数组了!在每个多边形中,snippetHelloWorld保持首顶点不变,依次访问所有三角形。另外,法向量的计算通过了求三角形两个边向量×积的方式,其实我们也可以像上面的代码一样直接通过PxHullPolygon类型的mPlane[4]成员获取,它的前三个值就是平面法向量。在编程的时候,还要注意数据类型的大小,小心计算偏移量,以Px开头的数据类型定义可以在API中查找到,我们要搞清楚它与OpenGL和C++中数据类型的关系。

    eTRIANGLE类型就更简单了,索引数组indexBuffer直接按照顺序依次存储了每一个三角形的顶点索引值,因此直接访问就可以了。需要注意的是,这里indexBuffer值可能是16位或32位的,需要做一个判断,否则以32位的index查找16位index的顶点数组会发生错误。

    至此,SnippetHelloWorld代码就解释完了。除了学习之外,它的代码结构完全可以当成一个简单的PhysX框架来用。像本篇所讨论的renderGeometry()就是一个通用的渲染PhysX-shape的函数,其它还有一些窗口、相机的函数都是可以直接用的。大致说来,我们只要重写initPhysics()和renderCallback()中的代码来创建不同的场景并进行渲染,修改keyboardCallback()和mouseCallback()中的代码实现自己的事件监听就可以了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章