~プログラミング~ DirectX 11で立体を表示しよう その2

C++言語でDirectX 11を使ったアプリケーションを開発してみよう。

 

前回は変換行列を用意して3次元のデータをモニターに表示するための2次元座標へ変換するところまで出来ました。今回は3次元のデータをシェーダーへ渡し表示させる事に挑戦します。


立方体モデルを作る

立方体のデータは以下のように定義してみました。

struct Vertex {
    float pos[ 3 ];
    float col[ 4 ];
};

Vertex g_VertexList[] {
    { { -0.5f,  0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
    { {  0.5f,  0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
    { { -0.5f, -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
    { {  0.5f, -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } },

    { { -0.5f,  0.5f,  0.5f }, { 0.0f, 1.0f, 1.0f, 1.0f } },
    { { -0.5f, -0.5f,  0.5f }, { 0.0f, 1.0f, 1.0f, 1.0f } },
    { {  0.5f,  0.5f,  0.5f }, { 0.0f, 1.0f, 1.0f, 1.0f } },
    { {  0.5f, -0.5f,  0.5f }, { 0.0f, 1.0f, 1.0f, 1.0f } },

    { { -0.5f,  0.5f,  0.5f }, { 1.0f, 1.0f, 0.0f, 1.0f } },
    { { -0.5f,  0.5f, -0.5f }, { 1.0f, 1.0f, 0.0f, 1.0f } },
    { { -0.5f, -0.5f,  0.5f }, { 1.0f, 1.0f, 0.0f, 1.0f } },
    { { -0.5f, -0.5f, -0.5f }, { 1.0f, 1.0f, 0.0f, 1.0f } },

    { {  0.5f,  0.5f,  0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
    { {  0.5f, -0.5f,  0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
    { {  0.5f,  0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
    { {  0.5f, -0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } },

    { { -0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f, 1.0f, 1.0f } },
    { {  0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f, 1.0f, 1.0f } },
    { { -0.5f,  0.5f, -0.5f }, { 1.0f, 0.0f, 1.0f, 1.0f } },
    { {  0.5f,  0.5f, -0.5f }, { 1.0f, 0.0f, 1.0f, 1.0f } },

    { { -0.5f, -0.5f,  0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
    { { -0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
    { {  0.5f, -0.5f,  0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
    { {  0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
};

WORD g_IndexList[] {
     0,  1,  2,     3,  2,  1,
     4,  5,  6,     7,  6,  5,
     8,  9, 10,    11, 10,  9,
    12, 13, 14,    15, 14, 13,
    16, 17, 18,    19, 18, 17,
    20, 21, 22,    23, 22, 21, 
};


D3D11_INPUT_ELEMENT_DESC g_VertexDesc[] {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0,                            0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

 

頂点バッファとインデックスバッファは以下のように作成しています。

    D3D11_BUFFER_DESC vbDesc;
    vbDesc.ByteWidth           = sizeof( Vertex ) * 24;
    vbDesc.Usage               = D3D11_USAGE_DEFAULT;
    vbDesc.BindFlags           = D3D11_BIND_VERTEX_BUFFER;
    vbDesc.CPUAccessFlags      = 0;
    vbDesc.MiscFlags           = 0;
    vbDesc.StructureByteStride = 0;

    D3D11_SUBRESOURCE_DATA vrData;
    vrData.pSysMem          = g_VertexList;
    vrData.SysMemPitch      = 0;
    vrData.SysMemSlicePitch = 0;

    hr = m_pDevice->CreateBuffer( &vbDesc, &vrData, &m_pVertexBuffer );
    if ( FAILED( hr ) ) return hr;
        

    D3D11_BUFFER_DESC ibDesc;
    ibDesc.ByteWidth           = sizeof( WORD ) * 6 * 6;
    ibDesc.Usage               = D3D11_USAGE_DEFAULT;
    ibDesc.BindFlags           = D3D11_BIND_INDEX_BUFFER;
    ibDesc.CPUAccessFlags      = 0;
    ibDesc.MiscFlags           = 0;
    ibDesc.StructureByteStride = 0;

    D3D11_SUBRESOURCE_DATA irData;
    irData.pSysMem          = g_IndexList;
    irData.SysMemPitch      = 0;
    irData.SysMemSlicePitch = 0;

    hr = m_pDevice->CreateBuffer( &ibDesc, &irData, &m_pIndexBuffer );
    if ( FAILED( hr ) ) return hr;

定数バッファを作る

立方体の座標はローカル座標で作られています。

頂点シェーダーは各頂点の座標をローカル座標からプロジェクション座標へ変換しなければなりません。

つまり、頂点シェーダーへ変換行列を渡す必要があります。

 

このようなときは定数バッファというものを使って頂点シェーダーへデータを引き渡します。

 

 

定数バッファへ引き渡すための構造体を以下のように定義してみました。

struct ConstantBuffer {
    XMFLOAT4X4 world;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;
};

ワールド・ビュー・プロジェクションの変換行列を頂点シェーダーへ渡します。

※DirectXMathライブラリでは、演算に使う場合はXMMATRIXを、入れ物として使う場合はXMFLOAT4X4を使うというルールらしい

定数バッファはCreateBuffer関数を使って以下のように作成します。

BindFlagsにはD3D11_BIND_CONSTANT_BUFFERを指定しましょう。

    D3D11_BUFFER_DESC cbDesc;
    cbDesc.ByteWidth           = sizeof( ConstantBuffer );
    cbDesc.Usage               = D3D11_USAGE_DEFAULT;
    cbDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
    cbDesc.CPUAccessFlags      = 0;
    cbDesc.MiscFlags           = 0;
    cbDesc.StructureByteStride = 0;

    hr = m_pDevice->CreateBuffer( &cbDesc, NULL, &m_pConstantBuffer );
    if ( FAILED( hr ) ) return hr;

定数バッファへ値をセット

ワールド・ビュー・プロジェクションの変換行列を作成したら、ID3D11DeviceContext::UpdateSubresourceメソッドを使って定数バッファの中へ値をセットします。

        XMMATRIX worldMatrix = XMMatrixTranslation( 0.0f, 0.0f, 0.0f );

        XMVECTOR eye         = XMVectorSet( 2.0f, 2.0f, -2.0f, 0.0f );
        XMVECTOR focus       = XMVectorSet( 0.0f, 0.0f,  0.0f, 0.0f );
        XMVECTOR up          = XMVectorSet( 0.0f, 1.0f,  0.0f, 0.0f );
        XMMATRIX viewMatrix  = XMMatrixLookAtLH( eye, focus, up );

        float    fov         = XMConvertToRadians( 45.0f );
        float    aspect      = m_Viewport.Width / m_Viewport.Height;
        float    nearZ       =   0.1f;
        float    farZ        = 100.0f;
        XMMATRIX projMatrix  = XMMatrixPerspectiveFovLH( fov, aspect, nearZ, farZ );

        ConstantBuffer cb;
        XMStoreFloat4x4( &cb.world,      XMMatrixTranspose( worldMatrix ) );
        XMStoreFloat4x4( &cb.view,       XMMatrixTranspose( viewMatrix ) );
        XMStoreFloat4x4( &cb.projection, XMMatrixTranspose( projMatrix ) );
        m_pImmediateContext->UpdateSubresource( m_pConstantBuffer, 0, NULL, &cb, 0, 0 );

変換行列についてはこちらを参照してください。

XMMATRIXからXMFLOAT4X4へ値を格納するにはXMStoreFloat4x4関数を使います。

 

C++で扱う2次元配列とHLSLが扱う2次元配列では行と列の順番の考え方が異なるようです。
この為、行列をシェーダーへ渡す時にはXMMatrixTranspose関数を使って転置行列にしてから定数バッファーセットする必要があります。


頂点シェーダーで定数バッファを受け取る

C++側で定数バッファを使って行列を渡したら、頂点シェーダーで行列を受け取る部分を考えます。

頂点シェーダーは以下のようになります。

struct VS_IN
{
    float4 pos : POSITION0;
    float4 col : COLOR0;
};

struct VS_OUT
{
    float4 pos : SV_POSITION;
    float4 col : COLOR0;
};

cbuffer ConstantBuffer
{
    float4x4 World;         //ワールド変換行列
    float4x4 View;          //ビュー変換行列
    float4x4 Projection;    //透視射影変換行列
}

VS_OUT vs_main( VS_IN input )
{
    VS_OUT output;

    output.pos = mul(input.pos,  World);
    output.pos = mul(output.pos, View);
    output.pos = mul(output.pos, Projection);
    output.col = input.col;
    return output;
}

HLSLで定数バッファを受け取るためにはcbuffer宣言を使います。

渡す側の構造体と同じようにcbufferを使って受け取る行列を作成します。

 

vs_main()の中では、受け取った行列を使って座標変換を行っています。

mul関数を使ってワールド・ビュー・プロジェクションの順に座標を変換します。

 

 

最後に忘れずコンパイルしておきましょう。

シェーダーのコンパイルについてはこちらを参照してください。


描画

ここまでデータの準備が出来たら後は描画です。

ID3D11DeviceContext::VSSetConstantBuffersメソッドで定数バッファをパイプラインにセットして描画を実行します。

    float clearColor[ 4 ] = { 0.0f, 0.0f, 0.0f, 1.0f }; //red,green,blue,alpha

    UINT strides = sizeof( Vertex );
    UINT offsets = 0;
    m_pImmediateContext->IASetInputLayout( m_pInputLayout );
    m_pImmediateContext->IASetVertexBuffers( 0, 1, &m_pVertexBuffer, &strides, &offsets );
    m_pImmediateContext->IASetIndexBuffer( m_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );
    m_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
    m_pImmediateContext->VSSetConstantBuffers( 0, 1, &m_pConstantBuffer );
    m_pImmediateContext->VSSetShader( m_pVertexShader, NULL, 0 );
    m_pImmediateContext->RSSetViewports( 1, &m_Viewport );
    m_pImmediateContext->PSSetShader( m_pPixelShader, NULL, 0 );
    m_pImmediateContext->OMSetRenderTargets( 1, &m_pRenderTargetView, m_pDepthStencilView );

    m_pImmediateContext->ClearRenderTargetView( m_pRenderTargetView, clearColor );
    m_pImmediateContext->ClearDepthStencilView( m_pDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0 );
    m_pImmediateContext->DrawIndexed( 36, 0, 0 );

 

 

 

うまく出来たらこんな感じに表示されると思います。

 

ソースコードはこちら