ホーム
チュートリアル
第一回
第二回
第三回
第四回
第五回
第六回
      
チュートリアル番外編
第一回
第二回
第三回
第四回
拡張ソース
TrueType表示クラス
(チュートリアル第六回参照)
掲示板
メール
(-nospamを外してください)

チュートリアル

第二回 モデルを出してみよう

前回、真っ白(真っ青というべきか)なウィンドウを出すまではこぎつけましたが、あれではどこが3Dエンジンなのかわかりませんね?そこで今回は3Dプログラムらしく、モデルの一つも出してみようかと思います。

新しいお友達、3D空間の偉い人

まずは前回同様、Win32アプリケーションとして新しいプロジェクトを作りましょう。
プロジェクトを作ったら、前回作ったmain.cppをプロジェクトディレクトリにコピーして、プロジェクトに追加してください。
必要ならIrrlicht.dllもコピーしておきましょう。

ISceneManager登場

準備ができたら、main.cppに以下のコードを追加します。

/*
 * モデルを出してみよう
 */

#ifdef WIN32
#include <windows.h>
#endif
#include <irrlicht.h>
using namespace irr;

using namespace core;
using namespace video;
using namespace scene;

#pragma comment(lib, "Irrlicht.lib")

#ifdef WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main()
#endif
{
    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false, 0);
    IVideoDriver *Driver = Device->getVideoDriver();
   ISceneManager *Scene = Device->getSceneManager();

    while(Device->run())
    {
        Driver->beginScene(true, true, SColor(0,100,100,160));

        Driver->endScene();
    }
    Device->drop();

    return 0;
}

まず、今回新たにネームスペースsceneのサービスを使うことになるので、それを宣言しておきます。
続いて、IVideoDriverに続いてISceneManagerオブジェクトを取得します。
前回、IVideoDriverは低レベルの描画を管理するクラスだという話をしましたが、
今回追加するISceneManagerは高レベルの描画を管理します。
もう少し噛み砕くと、IVideoDriverは板一枚、線一本という単位で描画を行いますが、
ISceneManagerはモデル、マップ、カメラといった単位で描画を行います。

irr::scene::ISceneManager* irr::IrrlichtDevice::getSceneManager ()
IrrlichtDeviceが内包するISceneManagerオブジェクトへのポインタを返します

どんどんNodeを入れようね

カメラがなくちゃ始まらない

3D空間を画面に表示する場合、空間のどこかにカメラが置かれており、そのカメラを覗いた風景を画面に描画する、という概念が基本になります。
Irrlichtの場合もその原則は同じですので、3D空間にカメラを配置しましょう。
以下のコードを追加してください

    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false, 0);
    IVideoDriver *Driver = Device->getVideoDriver();
    ISceneManager *Scene = Device->getSceneManager();

    Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

    while(Device->run())

ISceneManagerは、内部にNodeと呼ばれる3D部品を追加していくことで空間情報を定義していきます。
ここでは、座標(0,10,-40)の位置に座標(0,0,0)の方向を向いたカメラ、というNodeを追加します。

ICameraSceneNode * irr::scene::ISceneManager::addCameraSceneNode (
    ISceneNode *                        parent = 0,
    const core::Vector3df &             position = core::vector3df(0,0,0),
    const core::Vector3df &             lookat = core::vector3df(0,0,100),
    s32                                 id = -1
)
ISceneManagerに新しくカメラをNodeを追加します
ISceneNode *parent このカメラNodeの「親」となるNodeを指定します。特に指定しない場合は0とします。
「親」として設定されたNodeが移動するとそれにあわせてこのNodeも移動します。
カメラNodeの場合、普通は0でよいでしょう。
Vector3df & position このカメラの3D空間中の座標位置をベクトル形式で設定します。
Vector3df & lookat このカメラがどの方向を見ているか(どの座標を見つめているのか)をベクトル形式で指定します。
s32 id コリジョン判定の時使いますので、詳しくはその時説明します。今はデフォルトでOKです。

モデルさんいらっしゃい

カメラを配置したら、今度は被写体の用意をします。
今回はIrrlichtのサンプルに入っているデータを流用させてもらいましょう。
Irrlichtを解凍したディレクトリないのmediaというディレクトリにサンプルデータが入っていますので、そこからsydney.md2をプロジェクトのディレクトリにコピーしてください。
拡張子md2のファイルというのはQuake2形式のキャラクタモデルファイルになります。
コピーが終わったら、以下のコードを追加しましょう

    Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

    IAnimatedMesh *SydneyMesh = Scene->getMesh("sydney.md2");
    IAnimatedMeshSceneNode *Sydney = Scene->addAnimatedMeshSceneNode( SydneyMesh );

    while(Device->run())

Irrlichtでは、一般に3DモデルはIAnimatedMeshオブジェクトにロードします。
モデルファイルをロードしてIAnimatedMeshオブジェクトを作るサービス関数getMesh()がISceneManegerに用意されています。
IAnimatedMeshが出来上がったら、ISceneManager::addAnimatedMeshSceneNode()関数を使用してNodeとして追加します。

IAnimatedMesh * irr::scene::ISceneManager::getMesh (
    const c8 *                           filename
)
モデルファイルを読み込んで、IAnimatedMeshオブジェクトを生成し、そのポインタを返します。
失敗した場合は0(NULL)を返します。
c8 * filename 読み込むべきモデルファイル名を指定します。
読込可能なファイル形式は、.obj、.3ds、.md2、.ms3d、.x、.bsp形式です(0.6.0の時点)。
IAnimatedMeshSceneNode * irr::scene::ISceneManager::addAnimatedMeshSceneNode (
    IAnimatedMesh *                     mesh,
    ISceneNode *                        parent = 0,
    s32                                 id = -1,
    const core::Vector3df &             position = core::vector3df(0,0,0),
    const core::Vector3df &             rotation = core::vector3df(0,0,0),
    const core::Vector3df &             scale = core::vector3df(1.0f,1.0f,1.0f)
)
ISceneManagerにIAnimatedMeshを新しいNodeとして追加します
IAnimatedMesh * mesh Nodeとして設定するIAnimatedMeshを指定します。
ISceneNode *parent このNodeの「親」となるNodeを指定します。特に指定しない場合は0とします。
「親」として設定されたNodeが移動するとそれにあわせてこのNodeも移動します。
キャラクタに武器モデルを持たせたい場合に、キャラクタモデルを武器モデルの「親」として設定したりとか、そのように使います。
s32 id コリジョン判定の時使いますので、詳しくはその時説明します。今はデフォルトでOKです。
Vector3df & position このNodeの3D空間中の座標位置をベクトル形式で設定します。
Vector3df & rotation このNodeがどちらを向いているのか、X,Y,Zの角度表現で指定します。
Vector3df & scale このNodeの縮尺をX,Y,Zのベクトル表現で指定します。
(1.0f,1.0f,1.0f)で等倍になり、(0.5f,0.5f,0.5f)で半分の大きさになります。

役者は揃った

カメラと被写体が揃いましたから、いよいよ描画をしてみましょう。
以下のコードを追加してください

    while(Device->run())
    {
        Driver->beginScene(true, true, SColor(0,100,100,160));
        Scene->drawAll();
        Driver->endScene();
    }

beginScene()とendScene()関数の間に、ISceneManager::DrawAll()関数を記述します。
これを実行することで、ISceneManagerに属する全てのNodeの描画処理が行われます

void irr::scene::ISceneManager::drawAll ()
ISceneManager中の全てのNodeに関しての描画処理を行います
IVideoDriver::begenScene()とendScene()の間に記述してください

これでひとまずはモデルの表示に必要な最低限の手続きが整いましたから、一度実行してみましょう。
DLLの準備はOKですね?

うまくいけば、画面中央に影絵状態でお姉ちゃんが表示され、アニメーションするはずです。

テクスチャでお化粧

現状の影絵モード(?)もこれはこれで味がありますが、やはりちゃんと顔やら服やら見えて欲しいですね。
というわけで、テクスチャマッピングをしましょう。
まず、mediaディレクトリからテクスチャファイル、sydney.bmpをコピーしてきてください。
コピーが終わったら以下のコードを追加しましょう。

    IAnimatedMesh *SydneyMesh = Scene->getMesh("sydney.md2");
    IAnimatedMeshSceneNode *SydneyNode = Scene->addAnimatedMeshSceneNode( SydneyMesh );

    SydneyNode->setMaterialFlag(EMF_LIGHTING, false);
    SydneyNode->setMaterialTexture( 0, Driver->getTexture("sydney.bmp") );

    SydneyNode->setMD2Animation(EMAT_RUN);

    while(Device->run())

まず、キャラクタが真っ黒な理由は、3D空間中に光源がどこにもないからです。光源についてはは次回以降に取り扱うことにして、今回はモデルの光源処理自体をOFFにしてしまうことで対処しましょう。
その後、テクスチャを貼り付けます。テクスチャをファイルからロードして生成するためには、IVideoDriver::getTexture()関数を使います。

void irr::scene::ISceneNode::setMaterialFlag (
    video::E_MATERIAL_FLAG              flag,
    bool                                newvalue
)
ISceneNodeの表面処理についての各種設定をON/OFFします
video::E_MATERIAL_FLAG flag 設定する表面処理を指定します。設定できるのは以下のものです。
EMF_WIREFRAME ONだとワイヤーフレーム描画になります。デフォルトOFFです。
EMF_GOURAUD_SHADING グロー/フラットシェーディングを選択します。ONでグローになります。デフォルトONです。
EMF_LIGHTING ONで光源計算を有効にします。デフォルトONです
EMF_ZBUFFER ONの場合Zバッファ比較を行います。デフォルトONです。
EMF_ZWRITE_ENABLE ONだとZバッファ書込みを行います。デフォルトONです。半透明の物体を描くときはOFFにしましょう。
EMF_BACK_FACE_CULLING ONの場合裏面を向いたポリゴンを描画しません。デフォルトONです。
EMF_BILINEAR_FILTER ONだとテクスチャのピクセル計算にバイリニアフィルタを使用します。デフォルトONです。
EMF_TRILINEAR_FILTER ONだとテクスチャのピクセル計算にトライリニアフィルタを使用します。これがONの場合、バイリニアフィルタより優先されます。デフォルトOFFです。
EMF_FOG_ENABLE ONだとフォッグ処理を有効にします。デフォルトOFFです。
bool newvalue この値がtrueの場合、指定した設定をONに、falseだとOFFにします。
void irr::scene::ISceneNode::setMaterialTexture  (
    s32                                 textureLayer,
    video::ITexture *                   texture
)
ISceneNodeのテクスチャに指定したITextureオブジェクトを割り当てます。
s32 textureLayer 設定するテクスチャ番号を指定します。
テクスチャ番号として有効なのは、0からMATERIAL_MAX_TEXTUREまでです。
通常は0番しか使いませんが、特殊なマッピングを行う場合に1番以降を指定する場合があります。
video::ITexture 割り当てるITextureオブジェクトのポインタを指定します。
irr::video::ITexture * irr::video::IVideoDriver::getTexture  (
    const c8                            filename
)
テクスチャファイルを読み込んで、ITextureオブジェクトを生成し、そのポインタを返します。
c8 filename 読み込むテクスチャファイル名を指定します。
読み込み可能なフォーマットは.bmp、.jpg、.tga、.pcx、.psd形式になります。
圧縮bmp及び圧縮tga形式には対応していません。

ついでに、現状はアニメーションが立ちパターンから死にパターンまで一気に再生される状態でしたが、走りパターンだけをループするように変更しましょう。Irrlichtでは、Quake2仕様に準拠したMD2ファイルであれば、selMD2Animation()関数を使用して、状態を示す定数を一発与えるだけで簡単にアニメーションパターンを指定できます。が、これはあくまでMD2形式限定ですので、普段はフレーム番号を指定してアニメーションパターンを決めるのが一般的です。これについては次回以降で取り扱います。

bool irr::scene::IAnimatedMeshSceneNode::setMD2Animation  (
    EMD2_ANIMATION_TYPE                anim
)
md2形式を読み込んだIAnimatedMeshSceneNodeのアニメーションパターンを設定します。
md2形式以外を読み込んでいる場合には何も起こりません。
設定に成功したらtrueを、md2形式でない等の理由で設定に失敗したらfalseを返します。
EMD2_ANIMATION_TYPE anim 設定するアニメーションパターンを以下の中から設定します。
各パターンの意味合いについて私自身がわかってない部分が結構ありますので
md2形式について詳しい方教えてくださいorz
EMAT_STAND 立ち静止
EMAT_RUN 走り
EMAT_ATTACK 攻撃
EMAT_PAIN_A やられパターンA
EMAT_PAIN_B やられパターンB
EMAT_JUMP ジャンプ
EMAT_FLIP 宙返り(?)
EMAT_SALUTE 屈伸(?)
EMAT_FALLBACK 落下(?)
EMAT_WAVE 手を振る(?)
EMAT_POINT 指し示す
EMAT_CROUCH_STAND しゃがみ静止
EMAT_CROUCH_WALK しゃがみ移動
EMAT_CROUCH_ATTACK しゃがみ攻撃
EMAT_CROUCH_PAIN しゃがみやられ
EMAT_CROUCH_DEATH しゃがみダウン
EMAT_DEATH_FALLBACK 後方ダウン
EMAT_DEATH_FALLFORWARD 前方ダウン
EMAT_DEATH_FALLBACKSLOW ダウン(ゆっくり)
EMAT_BOOM 倒れ(?)

それでは実行してみましょう。うまくいけば、真っ黒じゃない(って、この服黒ですねorz)Sydney姐さんが走っているはずです。

今回はここまでにしておきましょう。ここまでのソースの最終状態を再掲しておきます。

/*
 * モデルを出してみよう
 */

#ifdef WIN32
#include <windows.h>
#endif
#include <irrlicht.h>
using namespace irr;

using namespace core;
using namespace video;
using namespace scene;

#pragma comment(lib, "Irrlicht.lib")

#ifdef WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main()
#endif
{
    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false, 0);
    IVideoDriver *Driver = Device->getVideoDriver();
    ISceneManager *Scene = Device->getSceneManager();

    Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

    IAnimatedMesh *SydneyMesh = Scene->getMesh("sydney.md2");
    IAnimatedMeshSceneNode *SydneyNode = Scene->addAnimatedMeshSceneNode( SydneyMesh );

    SydneyNode->setMaterialFlag(EMF_LIGHTING, false);
    SydneyNode->setMaterialTexture( 0, Driver->getTexture("sydney.bmp") );
    SydneyNode->setMD2Animation(EMAT_RUN);

    while(Device->run())
    {
        Driver->beginScene(true, true, SColor(0,100,100,160));
        Scene->drawAll();
        Driver->endScene();
    }
    Device->drop();

    return 0;
}

サンプルと同じモデルじゃつまんねぇ、という人はこのへんからでもフリーのmd2モデルをダウンロードしてみて、差し替えてみるといいでしょう。pk3形式になっているかもしれませんが、pk3の正体は実はただのzipファイルですから、zipに対応した解凍ツールで解凍できるはずです。
もちろん自分でMD2ファイルを作っても全然かまいません。が、MD2モデルを作るのはかなり大変です。また、ボーンを持っていないので武器を持たせたりとかそういうことが苦手ですので、番外編としてXファイルの作成について解説していきたいと思います。(探せばQuake2用のモデル作成について取り扱っているサイトがあるかもしれません、てか、知ってたら教えてください

トップページへ 第一回へ 第三回へ