SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

COLLADA DOMを使った3Dモデルデータの読み込み

OpenGLでCOLLADAドキュメントを利用する


ダウンロード バイナリファイル (1.5 MB)
ダウンロード ソースコード (1.5 MB)

関数の作成 その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ソフトウェア開発の大きな効率化につながると思います。

参考資料

  1. Khronos Group COLLADAのサイト
  2. 仕様書(サイト自体は英語ですが、日本語ドキュメントもあります)などが公開されています。
  1. Collada: Sailing the Gulf of 3d Digital Content Creation』 Remi Arnaud・Mark C. Barnes 著、A K Peters Ltd、2006年9月
  2. COLLADAドキュメントのフォーマット自体を解説した書籍(英語)です。COLLADA DOMなどについての解説はありませんがデータがどのように配置されているかなどを知ることが出来ます。
  1. COLLADA TEst Model Bank
  2. COLLADAのテスト用のモデルデータなどを配布しているサイトです。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

高橋 誠史(タカハシ マサフミ)

僻地の大学院生です。GPUプログラミングとそのニュースを集めたサイトShader.jpを開いています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/843 2007/01/22 09:53

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー