関数の作成 その2
描画関数(GLUT)
GLUTの描画関数は下記のようになります。
// 描画関数 void display(void) { // glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (double)SCREENX / (double)SCREENY, 0.1, 3000.0); gluLookAt(0, 0, 3, 0, 0, 0, 0, 1, 0); // テクスチャ if(g_TextureObject.size() >= 1) glBindTexture(GL_TEXTURE_2D, g_TextureObject[0]); else glDisable(GL_TEXTURE_2D); // COLLADAドキュメントの描画 RenderDAE(); glFlush(); }
先のInitCOLLADA()関数では、ドキュメント内のテクスチャファイルパスをすべて読み込んで、それを元にテクスチャ生成をしています。しかし、テクスチャとして実際に利用するのは最初の1枚だけにしています(今回の方法では複数ジオメトリと複数テクスチャがある場合の依存関係が分からないので)。
COLLADA DOMから頂点データを取得しての描画はRenderDAE()関数にまとめています。
RenderDAE関数
ここから、COLLADAドキュメントの描画処理を行います。
// COLLADA ドキュメントの描画 void RenderDAE() { // g_iGeometryElementCount数回描画 for(int currentGeometry=0; currentGeometry<g_iGeometryElementCount; currentGeometry++) { // 現在のジオメトリの取得 domGeometry *thisGeometry; g_dae->getDatabase()->getElement((daeElement**)&thisGeometry, currentGeometry, NULL, "geometry"); // メッシュの取得 domMesh *thisMesh = thisGeometry->getMesh(); // Triangleの場合 int triangleElementCount = (int)(thisMesh->getTriangles_array().getCount()); for(int currentTriangle=0; currentTriangle<triangleElementCount; currentTriangle++) { domTriangles* thisTriangles = thisMesh->getTriangles_array().get(0); RenderTriangle(thisMesh, thisTriangles); } } }
ここでは、先に取得したジオメトリ数を元に順番に描画を行います。順番にメッシュデータ(domMeshクラス)を取得し、RenderTriangle()関数内で三角形化されたデータの描画を行います。
三角形化されたデータの描画
// Triangleの描画 void RenderTriangle(domMesh *thisMesh, domTriangles *thisTriangles) { // int numberOfInputs = (int)getMaxOffset( thisTriangles->getInput_array()) +1;// オフセット数 int numberOfTriangles = (int)(thisTriangles->getP()->getValue().getCount() / numberOfInputs); // 要素数 // インデックスのオフセット unsigned int offset = 0; int texoffset = -255, noroffset = -255; for(unsigned int i=0; i<thisTriangles->getInput_array().getCount();i++) { if(strcmpi(thisTriangles->getInput_array()[i]->getSemantic(), "VERTEX")==0) offset = thisTriangles->getInput_array()[i]->getOffset(); if(strcmpi(thisTriangles->getInput_array()[i]->getSemantic(), "TEXCOORD")==0) texoffset = thisTriangles->getInput_array()[i]->getOffset(); if(strcmpi(thisTriangles->getInput_array()[i]->getSemantic(), "NORMAL")==0) noroffset = thisTriangles->getInput_array()[i]->getOffset(); } glBegin(GL_TRIANGLES); for(int i=0;i<numberOfTriangles;i++) { int index = thisTriangles->getP()->getValue().get( i*numberOfInputs+offset); // 法線 if(noroffset==-255) { // 法線のインデックスが頂点座標と共通の場合 glNormal3f( thisMesh->getSource_array()[1]-> getFloat_array()->getValue().get(index*3), thisMesh->getSource_array()[1]->getFloat_array()-> getValue().get(index*3+1), thisMesh->getSource_array()[1]-> getFloat_array()->getValue().get( index*3+2) ); } else { // <p></p>内に法線のインデックスが存在する場合 int norindex = thisTriangles->getP()->getValue().get( i*numberOfInputs+noroffset); glNormal3f( thisMesh->getSource_array()[1]->getFloat_array()-> getValue().get(norindex*3), thisMesh->getSource_array()[1]->getFloat_array()-> getValue().get(norindex*3+1), thisMesh->getSource_array()[1]->getFloat_array()-> getValue().get(norindex*3+2) ); } // テクスチャ座標 if(texoffset!=-255) { int texindex = thisTriangles->getP()->getValue().get( i*numberOfInputs+texoffset); glTexCoord2f( thisMesh->getSource_array()[2]->getFloat_array()-> getValue().get(texindex*2), thisMesh->getSource_array()[2]->getFloat_array()-> getValue().get(texindex*2+1) ); } // 頂点座標 glVertex3f( thisMesh->getSource_array()[0]->getFloat_array()-> getValue().get(index*3), thisMesh->getSource_array()[0]->getFloat_array()-> getValue().get(index*3+1), thisMesh->getSource_array()[0]->getFloat_array()-> getValue().get(index*3+2) ); } glEnd(); }
OpenGLの描画命令自体は、最もシンプルなglBegin()~glEnd()で法線、テクスチャ、頂点座標をセットして描画しています。
各データの取得は、引数のdomTrianglesクラスにあるインデックスデータを元にdomMeshのソース配列(thisMesh->getSource_array()で取得)から取得しています。
インデックスデータは、先に書いた通りインデックスバッファなのですが、頂点、法線、テクスチャ座標(場合によっては、他のデータも)が混在して入っています。そこで、何番目のデータが何のデータか、ということを知るために各データのオフセットを取得しています。この処理は、「インデックスのオフセット」というコメント以下になります。
今回のソース配列は、0が頂点座標、1が法線、2がテクスチャ座標として見ています。法線のインデックスは頂点座標と別な場合(四角形でデータが入っている場合に法線と頂点のインデックスを別々に保持している場合があります)があるので、処理が変わっています。これは、法線のオフセットがあるかないかで判断しています。
終了処理
最後に、終了処理になります。
OpenGLに関連した修理処理がありますが、COLLADA DOMに関してはDAE::cleanup()を呼びます。
// COLLADA関連の終了処理 void cleanupCOLLADA() { // 解放処理 for(unsigned int i=0;i<g_TextureObject.size();i++) { glDeleteTextures(1, &g_TextureObject[i]); } g_TextureObject.clear(); for(unsigned int i=0;i<g_Texture.size();i++) { g_Texture[i]->Release(); delete g_Texture[i]; } g_Texture.clear(); if(g_dae) { g_dae->cleanup(); } // 終了時に呼ぶ DAE::cleanup(); }
おわりに
今回は、COLLADA DOMの導入と簡単な読み込みを扱いました。COLLADAをエクスポートできるツールは多い(無償のものでもblenderなどがあります)ので、今回のように単純なモデルデータとして利用することは有効だと思います。
今回の記事では扱っていませんが、3Dモデルの形状やテクスチャファイルに関するデータ以外にも物理エンジン(例えば、AGEIA PhysXなど)のパラメータやCg、GLSLなどのシェーダプログラムのコードなどを関連付けることができます。こうしたデータの取り込みにはデータを実行するエンジンを整備しなければいけませんが、上手に利用できれば3DCGソフトウェア開発の大きな効率化につながると思います。
参考資料
- Khronos Group COLLADAのサイト
- 『Collada: Sailing the Gulf of 3d Digital Content Creation』 Remi Arnaud・Mark C. Barnes 著、A K Peters Ltd、2006年9月
- COLLADA TEst Model Bank
