Simplestar Game

How do I create a 3D game ?
http://simplestar.syuriken.jp/
since 17/01/2009
  • BACK
  • HOME
  • NEXT
  • |

:::::::::::023話 3Dオブジェクトの表示@:::::::::::

3D数学を勉強する際に、計算結果を映像で確認できると一層理解が深まります。
いや、そもそもの理解に役立つと考え、先に3Dオブジェクトを表示させることにしましょう。

どうも、星姫です。
先週と先々週は真ぷるスター先生が体調を崩されたのでこんなに時間が空いてしまいました。

突然ですが、世界の中心ってどこだか分かりますか?(どこだと思っていますか?)
答えは様々、「自分を中心に…」なんて言ってる方はオブジェクト座標系で物事を考えている方ですね。
地球のどこかを指定した方はワールド座標系で物事を考えている…なんのこっちゃって方は是非楽しんでいってね!

ここから本題に入ります。

まずは3Dオブジェクトの表示からです。思い返せば第17話でDirect 3Dのチュートリアルをやりましたね。
Tutorial05ではマトリックスを用いたオブジェクトの回転移動が行われていました。今回はそれをベースに作ってみましょう。

サンプルブラウザの起動の方法は覚えていますか?全てのプログラム、上図でいうここをクリックして起動します。
起動したらTutorial05.cppファイルを開いてください。以下にTutorial05.cppに書かれているエントリポイントを示します。

//--------------------------------------------------------------------------------------
// Entry point to the program. Initializes everything and goes into a message processing
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
    if( FAILED( InitWindow( hInstance, nCmdShow ) ) )
        return 0;

    if( FAILED( InitDevice() ) )
    {
        CleanupDevice();
        return 0;
    }

    // Main message loop
    MSG msg = {0};
    while( WM_QUIT != msg.message )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            Render();
        }
    }

    CleanupDevice();

    return ( int )msg.wParam;
}

可読性が高いコードですね、ウィンドウを作成する→デバイスの設定をする→描画ループ→終了処理が見ただけで分かります。
ではRender()関数で何が行われているのか見てみましょう。以下はRender()関数のコードです。

//--------------------------------------------------------------------------------------
// Render a frame
//--------------------------------------------------------------------------------------
void Render()
{
    // Update our time
    static float t = 0.0f;
    if( g_driverType == D3D10_DRIVER_TYPE_REFERENCE )
    {
        t += ( float )D3DX_PI * 0.0125f;
    }
    else
    {
        static DWORD dwTimeStart = 0;
        DWORD dwTimeCur = GetTickCount();
        if( dwTimeStart == 0 )
            dwTimeStart = dwTimeCur;
        t = ( dwTimeCur - dwTimeStart ) / 1000.0f;
    }

    // 1st Cube: Rotate around the origin
    D3DXMatrixRotationY( &g_World1, t );

    // 2nd Cube:  Rotate around origin
    D3DXMATRIX mTranslate;
    D3DXMATRIX mOrbit;
    D3DXMATRIX mSpin;
    D3DXMATRIX mScale;
    D3DXMatrixRotationZ( &mSpin, -t );
    D3DXMatrixRotationY( &mOrbit, -t * 2.0f );
    D3DXMatrixTranslation( &mTranslate, -4.0f, 0.0f, 0.0f );
    D3DXMatrixScaling( &mScale, 0.3f, 0.3f, 0.3f );

    D3DXMatrixMultiply( &g_World2, &mScale, &mSpin );
    D3DXMatrixMultiply( &g_World2, &g_World2, &mTranslate );
    D3DXMatrixMultiply( &g_World2, &g_World2, &mOrbit );

    //
    // Clear the back buffer
    //
    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red, green, blue, alpha
    g_pd3dDevice->ClearRenderTargetView( g_pRenderTargetView, ClearColor );

    //
    // Clear the depth buffer to 1.0 (max depth)
    //
    g_pd3dDevice->ClearDepthStencilView( g_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );

    //
    // Update variables for the first cube
    //
    g_pWorldVariable->SetMatrix( ( float* )&g_World1 );
    g_pViewVariable->SetMatrix( ( float* )&g_View );
    g_pProjectionVariable->SetMatrix( ( float* )&g_Projection );

    //
    // Render the first cube
    //
    D3D10_TECHNIQUE_DESC techDesc;
    g_pTechnique->GetDesc( &techDesc );
    for( UINT p = 0; p < techDesc.Passes; ++p )
    {
        g_pTechnique->GetPassByIndex( p )->Apply( 0 );
        g_pd3dDevice->DrawIndexed( 36, 0, 0 );
    }

    //
    // Update variables for the second cube
    //
    g_pWorldVariable->SetMatrix( ( float* )&g_World2 );
    g_pViewVariable->SetMatrix( ( float* )&g_View );
    g_pProjectionVariable->SetMatrix( ( float* )&g_Projection );

    //
    // Render the second cube
    //
    for( UINT p = 0; p < techDesc.Passes; ++p )
    {
        g_pTechnique->GetPassByIndex( p )->Apply( 0 );
        g_pd3dDevice->DrawIndexed( 36, 0, 0 );
    }

    //
    // Present our back buffer to our front buffer
    //
    g_pSwapChain->Present( 0, 0 );
}

このコードは行列(Matrix)の知識が無いと難しいですね。コードを読んで、どんな動きをするか想像できました?

初見はなかなか疲れます。
ではコードの解析を上から順に行ってみます。

まずは次のコードの意味から

    // Update our time
    static float t = 0.0f;
    if( g_driverType == D3D10_DRIVER_TYPE_REFERENCE )
    {
        t += ( float )D3DX_PI * 0.0125f;
    }
    else
    {
        static DWORD dwTimeStart = 0;
        DWORD dwTimeCur = GetTickCount();
        if( dwTimeStart == 0 )
            dwTimeStart = dwTimeCur;
        t = ( dwTimeCur - dwTimeStart ) / 1000.0f;
    }

これは、変数 t を更新しているコードですね。
時間が経つにつれて値が大きくなっていくようです。

続いてこちら

    // 1st Cube: Rotate around the origin
    D3DXMatrixRotationY( &g_World1, t );

    // 2nd Cube:  Rotate around origin
    D3DXMATRIX mTranslate;
    D3DXMATRIX mOrbit;
    D3DXMATRIX mSpin;
    D3DXMATRIX mScale;
    D3DXMatrixRotationZ( &mSpin, -t );
    D3DXMatrixRotationY( &mOrbit, -t * 2.0f );
    D3DXMatrixTranslation( &mTranslate, -4.0f, 0.0f, 0.0f );
    D3DXMatrixScaling( &mScale, 0.3f, 0.3f, 0.3f );

    D3DXMatrixMultiply( &g_World2, &mScale, &mSpin );
    D3DXMatrixMultiply( &g_World2, &g_World2, &mTranslate );
    D3DXMatrixMultiply( &g_World2, &g_World2, &mOrbit );

なにやら計算を行い g_World1と g_World2の二つのマトリックスを設定するコードですね。
コメントより、これらは二つのボックスの位置を表す行列と推測できます。変数 t によって位置が変わります。

次のこれは…

    //
    // Clear the back buffer
    //
    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red, green, blue, alpha
    g_pd3dDevice->ClearRenderTargetView( g_pRenderTargetView, ClearColor );

    //
    // Clear the depth buffer to 1.0 (max depth)
    //
    g_pd3dDevice->ClearDepthStencilView( g_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );

バックバッファと深度バッファのクリアですか。
モニタへの描画の仕組みを知ると、理解できると思います。
ちょっとWebで勉強できる場所を探したんですけど、なかなか良いサイトが見当たらなくて
あれですね、私が勉強した本を紹介しておきます。

「はじめての3Dゲーム開発(改定版)」

今はこちらがおススメでしょうか→「ゲームプログラミング入門」
とにかく、この二つをクリアしておかないと、前回描画した絵が残されてしまい、意図した結果が得られません。

では次にいきます。

    //
    // Update variables for the first cube
    //
    g_pWorldVariable->SetMatrix( ( float* )&g_World1 );
    g_pViewVariable->SetMatrix( ( float* )&g_View );
    g_pProjectionVariable->SetMatrix( ( float* )&g_Projection );

ここでは、「ワールド」→「ビュー」→「プロジェクション」の流れでマトリックスの設定を行っています。
詳しく知りたい方はこちら

「Crawl」の

「Crawl 1-9 座標変換其の四」

「ワールド」…オブジェクトの位置を決める。
「ビュー」…カメラから見たオブジェクトの位置を決める。
「プロジェクション」…3次元座標から画面の2次元座標へ「投影」する。 

問題は次の描画の命令ですね。

    //
    // Render the first cube
    //
    D3D10_TECHNIQUE_DESC techDesc;
    g_pTechnique->GetDesc( &techDesc );
    for( UINT p = 0; p < techDesc.Passes; ++p )
    {
        g_pTechnique->GetPassByIndex( p )->Apply( 0 );
        g_pd3dDevice->DrawIndexed( 36, 0, 0 );
    }

これ…どう思います?
日本語訳するなら、レンダリング技術の記述内容を取得して
技術パスの回数だけその技術の適用+登録されているインデックスリストの描画…かな?
ちゃんと学びたい方は、次の公式の文書を参考に勉強してください。

「チュートリアル 2:三角形のレンダリング」

つまりどこかで3次元の頂点情報と、三角形を構成するように
それらの頂点を結ぶ順番を記述したインデックスリストがあって
それを事前にデバイスへセットしていたとのこと。
それの記述されている場所は、InitDevice関数とある。

InitDevice関数の一部を以下に示します。

    // Create vertex buffer
    SimpleVertex vertices[] =
    {
        { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR4( 0.0f, 1.0f, 0.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR4( 0.0f, 1.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR4( 1.0f, 0.0f, 0.0f, 1.0f ) },
        { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR4( 1.0f, 0.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR4( 1.0f, 1.0f, 0.0f, 1.0f ) },
        { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR4( 1.0f, 1.0f, 1.0f, 1.0f ) },
        { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ) },
    };
    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DEFAULT;
    bd.ByteWidth = sizeof( SimpleVertex ) * 8;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    bd.MiscFlags = 0;
    D3D10_SUBRESOURCE_DATA InitData;
    InitData.pSysMem = vertices;
    hr = g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer );
    if( FAILED( hr ) )
        return hr;

    // Set vertex buffer
    UINT stride = sizeof( SimpleVertex );
    UINT offset = 0;
    g_pd3dDevice->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );

    // Create index buffer
    // Create vertex buffer //@simplestar: ←多分公式のミスかと
    DWORD indices[] =
    {
        3,1,0,
        2,1,3,

        0,5,4,
        1,5,0,

        3,4,7,
        0,4,3,

        1,6,5,
        2,6,1,

        2,7,6,
        3,7,2,

        6,4,5,
        7,4,6,
    };
    bd.Usage = D3D10_USAGE_DEFAULT;
    bd.ByteWidth = sizeof( DWORD ) * 36;
    bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags = 0;
    bd.MiscFlags = 0;
    InitData.pSysMem = indices;
    hr = g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pIndexBuffer );
    if( FAILED( hr ) )
        return hr;

    // Set index buffer
    g_pd3dDevice->IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R32_UINT, 0 );

    // Set primitive topology
    g_pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

手続きのコードは煩雑だけど手順としては次の流れで理解できると思います。

    // Create vertex buffer
    // Set vertex buffer
    // Create index buffer
    // Set index buffer
    // Set primitive topology

ああ、プリミティブトポロジの説明がまだでしたね。
なんぞや?という方は以下のページを参照してみましょう。

「プリミティブ トポロジ (Direct3D 10)」

一応、描画に必要な情報は、全て揃ったようですが、すこし覚えることが多い気がします。

さて、頂点情報の設定とインデックス情報の設定方法がなんとなく見えてきたところで
モデルデータから頂点情報とインデックス情報を作成して、どんな結果が得られるのか見てみましょう。

ん?…モデルデータの頂点の並びってどうなっているのでしょうか?
モデリングツール…今は

「メタセコイア」

だけしか習得していませんが、他に

「Blender」

がフリーのモデリングソフトで有名なんです。(会社の同期に教わりました)
blenterについてはまだ、使い勝手とかわからないので
簡単なチュートリアルを作ってみます。

メタセコイアでの作業がblenderでもできるのなら
シェアの大きいBlenderに乗り換えるつもりです。
次回、お楽しみに!

2010/05/23 初記。
2010/05/24 追加。
2010/05/30 追加。
2010/06/05 追加。

:::::::::::023話 3Dオブジェクトの表示@:::::::::::

  • BACK
  • HOME
  • NEXT
ホームページ制作 フリー素材 無料WEB素材
Copyright (C) Simplestar All Rights Reserved.