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()中的代碼實現自己的事件監聽就可以了。


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