SHOEISHA iD

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

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

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

特集記事

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

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


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

関数の作成 その1

main関数

 今回のサンプルでは、OpenGLを使って描画を行いますが、そちらの方はできるだけシンプルな処理でいいようにGLUTを使います。

int main(int argc, char *argv[])
{
    // GLUT 関連
    glutInit( &argc, argv );
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);

    glutInitWindowSize(SCREENX, SCREENY);
    glutCreateWindow( "Simple COLLADA Sample" );

    glutDisplayFunc( display );

    glEnable(GL_TEXTURE_2D);
    glEnable( GL_DEPTH );
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    
    // COLLADA関連の初期化
    if(!InitCOLLADA())
        return 0;

    glutMainLoop();

    // 終了処理
    cleanupCOLLADA();

    return 0;
}

 メインループに入る前に、COLLADA DOMを使ってファイルの読み込みなどを行うInitCOLLADA()関数を呼び出しています。メインループが終わった後の終了処理をcleanupCOLLADA()にまとめてあります。

InitCOLLADA関数

// COLLADA関連の初期化
bool InitCOLLADA()
{
    int iRet = 0;
    // COLLADA ドキュメントのロード
    g_dae = new DAE();
    iRet = g_dae->load("file:/mushroom_durer.dae");
    if(iRet!=DAE_OK)
        return false;

    // ジオメトリ数の取得
    g_iGeometryElementCount
      = g_dae->getDatabase()->getElementCount(NULL, "geometry", NULL);

    // Triangles以外だったらTriangles化
    for(int currentGeometry=0;
        currentGeometry<g_iGeometryElementCount;
        currentGeometry++)
    {
        // 現在のジオメトリの取得
        domGeometry *thisGeometry;
        g_dae->getDatabase()->getElement((daeElement**)&thisGeometry,
                                         currentGeometry, 
                                         NULL, 
                                         "geometry");

        // メッシュの取得
        domMesh *thisMesh = thisGeometry->getMesh();

        // Polylistの場合
        int polylistElemntCount
          = (int)(thisMesh->getPolylist_array().getCount());
        for(int currentPolylist=0;
            currentPolylist<polylistElemntCount;
            currentPolylist++)
        {
            domPolylist* thisPolylist
              = thisMesh->getPolylist_array().get(0);
            ConvertPolylistToTriangles( thisMesh, thisPolylist );
        }

        // Polygonsの場合
        int polygonsElementCount
          = (int)(thisMesh->getPolygons_array().getCount());
        for(int currentPolygons=0;
            currentPolygons<polygonsElementCount;
            currentPolygons++)
        {
            domPolygons* thisPolygons
              = thisMesh->getPolygons_array().get(0);
            ConvertPolygonsToTriangles( thisMesh, thisPolygons );
        }
    }

    // テクスチャ数の取得
    g_iImageElementCount
      = g_dae->getDatabase()->getElementCount(NULL, "image", NULL);
    for(int i=0;i<g_iImageElementCount;i++)
    {
        g_Texture.resize( g_Texture.size()+1);
        g_Texture[i] = new CTGATextureHelper;
        // Imageの取得
        domImage* thisImage;
        g_dae->getDatabase()->getElement((daeElement**)&thisImage,
                                         i, 
                                         NULL,
                                         "image");

        // ファイル名の取得
        daeString name
          = thisImage->getInit_from()->getValue().getFile();
        g_Texture[i]->LoadTGA( (char*)name );
    }
    
    // テクスチャ生成
    for(unsigned int i=0;i<g_Texture.size();i++)
    {
        GLuint tex;
        glGenTextures(1, &tex);
        glBindTexture(GL_TEXTURE_2D, tex);
        g_TextureObject.push_back( tex );

        glTexParameteri(GL_TEXTURE_2D, 
                        GL_TEXTURE_MAG_FILTER,
                        GL_LINEAR);

        glTexParameteri(GL_TEXTURE_2D, 
                        GL_TEXTURE_MIN_FILTER, 
                        GL_LINEAR);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    
        glTexImage2D(GL_TEXTURE_2D,
                     0,
                     GL_RGB,
                     g_Texture[i]->GetWidth(),
                     g_Texture[i]->GetHeight(),
                     0,
                     GL_RGB,
                     GL_UNSIGNED_BYTE,
                     g_Texture[i]->GetImage() );
    }

    return true;
}

 ここでは、*.daeファイルの読み込みを行います。

 DAEクラスのload()関数で、COLLADAドキュメントのロードを行います。このとき、引数はファイルパスではなくURIで指定します。

 今回のサンプルでは、読み込みに成功したら最初にジオメトリ数を取得します。ジオメトリ数が取得できたらメッシュ(domMeshクラス)の取得をします。このクラスにジオメトリデータが入っています。

 メッシュが取得できたら、ジオメトリデータを順番にチェックしてプリミティブタイプを確認します。プリミティブタイプは、点、線、三角形、四角形といった形状(実際には、形状だけでなくデータ構造がリストなのかストリップなどの違いがあります)がありますが、今回はPolylistとPolygonsの場合に、三角形化する処理を行います。OpenGLであれば、四角形プリミティブでも描画ができますが、今回のサンプルでは描画は三角形に統一します。三角形化の処理は、ConvertToTriangles()関数で行います(後述)。

 三角形化が済んだ後は、テクスチャの処理を行います。テクスチャに関しては、データベースに「Image」で問い合わせをします。イメージのデータは、domImageクラスに入っており、domImageが取得できれば、そこからテクスチャファイルパスが取得できます。今回は、サンプルで使っているデータのテクスチャがTGAと分かっているのでTGAのローダークラスに読み込みを行わせています(他の画像ファイル形式(BMPやJPEGなど)であればそれに対応したローダーを書く必要があります)。

 g_Textureで読み込みができたら、OpenGLでのテクスチャ生成を行っています。

三角形化(Polylist)

// 三角形化(Polylist)
void ConvertPolylistToTriangles(domMesh *thisMesh,
                                domPolylist *thisPolylist)
{
    // 4角形を3角形化
    domTriangles *thisTriangles
      = (domTriangles *)thisMesh->createAndPlace("triangles");
    unsigned int triangles = 0;
    thisTriangles->setMaterial(thisPolylist->getMaterial());
    domP* p_triangles = (domP*)thisTriangles->createAndPlace("p");

    for(int i=0;
        i<(int)(thisPolylist->getInput_array().getCount()); i++)
    {
        thisTriangles->placeElement(
          thisPolylist->getInput_array()[i]->clone() );
    }

    int numberOfInputs
      = (int)getMaxOffset(thisPolylist->getInput_array()) + 1;
    int numberOfPrimitives
      = (int)(thisPolylist->getVcount()->getValue().getCount());

    unsigned int offset = 0;

    for(int j = 0; j < numberOfPrimitives; j++)
    {    
        int triangleCount
          = thisPolylist->getVcount()->getValue()[j] -2;
        
        int idx = numberOfInputs;
        
        // 三角形ごとにデータをコンバート
        for(int k = 0; k < triangleCount; k++)
        {
            // 頂点1
            for(int l = 0; l < numberOfInputs; l++)
            {
                int a = thisPolylist->getP()->getValue()[offset + l];
                float b;
                memcpy(&b, &a, sizeof(float));
                p_triangles->getValue().append(
                  thisPolylist->getP()->getValue()[offset + l]);
            }
            // 頂点2
            for(int l = 0; l < numberOfInputs; l++)
            {
                p_triangles->getValue().append(
                  thisPolylist->getP()->getValue()[offset + idx + l]);
            }
            // 頂点3
            idx += numberOfInputs;
            for(int l = 0; l < numberOfInputs; l++)
            {
                p_triangles->getValue().append(
                  thisPolylist->getP()->getValue()[offset + idx + l]);
            }
            //thisTriangles->setCount(thisTriangles->getCount()+1);
            triangles++;
        }
        offset
         += thisPolylist->getVcount()->getValue()[j] * numberOfInputs;
    }

    thisTriangles->setCount( triangles );
}

 COLLADAドキュメント内でのPolylist要素は、多角形リストを表し、頂点データを格納します。ただ、真面目に多角形のリスト構造にすると効率が悪いので、インデックスを使うことで重複頂点がないようにしています(いわゆるインデックスバッファです)。

 Polylistのインデックスデータの記述は下記のように記載されています。

Polylistの例
<polylist material="Blue" count="6">
    <input semantic="VERTEX" offset="0" source="#box-Vertex"/>
    <input semantic="NORMAL" offset="1" source="#box-Normal"/>
    <vcount>4 4 4 4 4 4</vcount>
    <p>
    0 0 2 1 3 2 1 3  0 4 1 5 5 6 4 7
    6 8 7 9 3 10 2 11  0 12 4 13 6 14 2 15
    3 16 7 17  5 18 1 19  5 20 7 21 6 22 4 23
    </p>
</polylist>

 <vcount></vcount>でくくられているところは、n角形のnにあたる数値が入っています。この例では、6つの4角形があることになります。

 <p></p>でくくっている部分に頂点データのインデックスのリストがあります。<input>要素には、このリストのデータをどのように解釈するかが記載されています。ここでは、頂点座標と法線のインデックスだということが分かります。<p>内のインデックスデータは、頂点座標と法線のインデックスが一緒に入っていますが、これはoffsetで分離します。

 この例では、2個の数値が1頂点分のインデックスとし、頂点座標がオフセット0、法線が1なので、順番に、頂点座標、法線、頂点座標、法線、頂点座標、法線……のようになります。このインデックスを元に頂点座標の配列や法線などのデータを参照します。

 この関数では、多角形のインデックス構造を三角形に修正する処理を行っています。OpenGLでは、四角形の描画が可能ですが、描画ルーチンを1つにするためにこの処理を行っています。

三角形化(Polygons)

// 三角形化(Polygons)
void ConvertPolygonsToTriangles(domMesh *thisMesh, domPolygons *thisPolygons)
{
    // 3角形化
    domTriangles *thisTriangles
      = (domTriangles *)thisMesh->createAndPlace("triangles");
    thisTriangles->setCount( 0 );
    thisTriangles->setMaterial(thisPolygons->getMaterial());
    domP* p_triangles
      = (domP*)thisTriangles->createAndPlace("p");

    for(int i=0;
        i<(int)(thisPolygons->getInput_array().getCount()); i++)
    {
        thisTriangles->placeElement(
          thisPolygons->getInput_array()[i]->clone() );
    }
    int numberOfInputs
      = (int)getMaxOffset(thisPolygons->getInput_array()) +1;
    int numberOfPrimitives
      = (int)(thisPolygons->getP_array().getCount());

    for(int j = 0; j < numberOfPrimitives; j++)
    {
        domP * thisPrimitive = thisPolygons->getP_array()[j];
        int elementCount = (int)(thisPrimitive->getValue().getCount());
        if((elementCount%numberOfInputs) != 0)
        {
//            cerr<<"Primitive "<<j<<" has an element count "
//            <<elementCount<<" not divisible by the number of 
//            inputs "<<numberOfInputs<<"\n";
            continue;
        }
        else
        {
            int triangleCount = (elementCount/numberOfInputs)-2;

            int idx = numberOfInputs;
            // 三角形にデータをコンバート
            for(int k = 0; k < triangleCount; k++)
            {
                // 頂点1
                for(int l = 0; l < numberOfInputs; l++)
                {
                    p_triangles->getValue().append(
                      thisPrimitive->getValue()[l]);
                }
                // 頂点2
                for(int l = 0; l < numberOfInputs; l++)
                {
                    p_triangles->getValue().append(
                      thisPrimitive->getValue()[idx + l]);
                }
                // 頂点3
                idx += numberOfInputs;
                for(int l = 0; l < numberOfInputs; l++)
                {
                    p_triangles->getValue().append(
                      thisPrimitive->getValue()[idx + l]);
                }
                thisTriangles->setCount(thisTriangles->getCount()+1);
            }
        }
    }
}

 こちらも多角形なのですが、Polylistとはちょっと異なる記述です。下記に実際の記述例を掲載しました。

Polygonsの例
<polygons material="Blue" count="6">
    <input semantic="VERTEX" offset="0" source="#box-Vertex"/>
    <input semantic="NORMAL" offset="1" source="#box-Normal"/>
    <p>0 0 2 1 3 2 1 3</p>
    <p>0 4 1 5 5 6 4 7</p>
    <p>6 8 7 9 3 10 2 11</p>
    <p>0 12 4 13 6 14 2 15</p>
    <p>3 16 7 17 5 18 1 19</p>
    <p>5 20 7 21 6 22 4 23</p>
</polygons>

 Polygonsでは<p></p>で1つのプリミティブを構成しています。n角形のnを判断するには<p></p>内のデータを<input>数で除算すると分かります。

 PolylistとPolygonsはデータの構造と取得の仕方が異なりますが、三角形化自体の考え方はあまり変わりません。

次のページ
関数の作成 その2

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

特集記事連載記事一覧

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

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

イベント

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

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

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

メールバックナンバー