014.インスタンス描画(Dx11版)

 このサンプルはSimplSample014というディレクトリに含まれます。
 BaseCrossDx11.slnというソリューションを開くとDx11版が起動します。

 実行結果は以下のような画面が出ます。

 

図0014a

 


 動画は以下になります。

 

 

【サンプルのポイント】

 今項のサンプルはインスタンス描画です。インスタンス描画というのは1回の描画命令にワールド行列を複数渡すことで、複数のインスタンスの描画を実装します。通常の描画より劇的に速くなります。

【共通解説】

 Dx12、Dx11両方に共通なのはシェーダーです。DxSharedプロジェクト内にシェーダファイルというフィルタがあり、そこに記述されてます。
 今回使用するシェーダは頂点シェーダとピクセルシェーダです。VertexPositionNormalTexture型の頂点を持つものです。コンスタントバッファもあります。

 更新処理は動きは同じですが、Dx12版の更新処理で説明します。OnUpdate()関数には、更新する方法が記述されています。
 今回描画するオブジェクトは階層になってます。1つ1つの立方体は構造体でCubeObject構造体です。そして、そのインスタンスを管理するのがCubeObjectGroupクラスです。

【Dx11版解説】

 BaseCrossDx11.slnを開くと、BaseCrossDx11というメインプロジェクトがあります。この中のCharacter.h/cppが主な記述個所になります。

■初期化■

 Dx11版の初期化は、Dx12版のようにリソース初期化はありません。頂点バッファを作成し、スケール、位置などを初期化します。
 各々の立方体の配列の初期化も同様です。

■更新処理■

 CubeObjectGroup::OnUpdate()関数です。Dx12版との違いは、インスタンスのワールド行列の更新に、頂点の変更と同じような処理を行う部分です。
void CubeObjectGroup::OnUpdate() {
    if (m_CubeObjectVec.size() >= m_MaxInstance) {
        throw BaseException(
            L"インスタンス上限を超えてます",
            L"if(m_CubeObjectVec.size() >= m_MaxInstance)",
            L"CubeObjectGroup::OnUpdate()"
        );

    }

    float ElapsedTime = App::GetApp()->GetElapsedTime();
    for (auto& v : m_CubeObjectVec) {
        v.m_Posision += v.m_Velocity * ElapsedTime;
        Quat QtSpan(v.m_QuaternionRot, v.m_QuaternionVelocity * ElapsedTime);
        v.m_Quaternion *= QtSpan;
        v.m_Quaternion.normalize();
        if (v.m_Posision.length() >= 2.0f) {
            v.Refresh();
        }
    }

    //デバイスの取得
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pDx11Device = Dev->GetD3DDevice();
    auto pID3D11DeviceContext = Dev->GetD3DDeviceContext();
    //インスタンスバッファにマップ
    D3D11_MAP mapType = D3D11_MAP_WRITE_DISCARD;
    D3D11_MAPPED_SUBRESOURCE mappedBuffer;
    //行列のマップ
    if (FAILED(pID3D11DeviceContext->Map(m_MatrixBuffer.Get(), 0, mapType, 0, &mappedBuffer))) {
        // Map失敗
        throw BaseException(
            L"行列のMapに失敗しました。",
            L"if(FAILED(pID3D11DeviceContext->Map()))",
            L"CubeObjectGroup::OnUpdate()"
        );
    }
    //行列の変更
    auto* matrices = (Mat4x4*)mappedBuffer.pData;
    Mat4x4 World;
    for (size_t i = 0; i < m_CubeObjectVec.size(); i++) {
        //ワールド行列の決定
        World.affineTransformation(
            m_CubeObjectVec[i].m_Scale,         //スケーリング
            Vec3(0, 0, 0),      //回転の中心(重心)
            m_CubeObjectVec[i].m_Quaternion,        //回転角度
            m_CubeObjectVec[i].m_Posision       //位置
        );
        //転置する
        World.transpose();
        matrices[i] = World;
    }
    //アンマップ
    pID3D11DeviceContext->Unmap(m_MatrixBuffer.Get(), 0);
}
 赤くなっているのが、各々のワールド行列(メッシュ)を更新している個所です。

■描画処理■

 CubeObjectGroup::OnDraw()関数です。
void CubeObjectGroup::OnDraw() {
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pD3D11DeviceContext = Dev->GetD3DDeviceContext();
    auto RenderState = Dev->GetRenderState();


    //ストライドとオフセット
    //形状の頂点バッファと行列バッファを設定
    UINT stride[2] = { sizeof(VertexPositionNormalTexture), sizeof(Mat4x4) };
    UINT offset[2] = { 0, 0 };

    ID3D11Buffer* pBuf[2] = { m_CubeMesh->GetVertexBuffer().Get(), m_MatrixBuffer.Get() };
    pD3D11DeviceContext->IASetVertexBuffers(0, 2, pBuf, stride, offset);
    //インデックスバッファのセット
    pD3D11DeviceContext->IASetIndexBuffer(m_CubeMesh->GetIndexBuffer().Get(),
         DXGI_FORMAT_R16_UINT, 0);

    //描画方法(3角形)
    pD3D11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    //シェーダの設定
    pD3D11DeviceContext->VSSetShader(VSPNTInstance::GetPtr()->GetShader(), nullptr, 0);
    pD3D11DeviceContext->PSSetShader(PSPNTStatic::GetPtr()->GetShader(), nullptr, 0);
    //インプットレイアウトの設定
    pD3D11DeviceContext->IASetInputLayout(VSPNTInstance::GetPtr()->GetInputLayout());

    //ブレンドステート
    //透明処理しない
    pD3D11DeviceContext->OMSetBlendState(RenderState->GetOpaque(), nullptr, 0xffffffff);
    //デプスステンシルステート
    pD3D11DeviceContext->OMSetDepthStencilState(RenderState->GetDepthDefault(), 0);
    //テクスチャとサンプラーの設定
    ID3D11ShaderResourceView* pNull[1] = { 0 };
    pD3D11DeviceContext->PSSetShaderResources(0, 1, 
        m_TextureResource->GetShaderResourceView().GetAddressOf());
    ID3D11SamplerState* pSampler = RenderState->GetLinearClamp();
    pD3D11DeviceContext->PSSetSamplers(0, 1, &pSampler);
    //ラスタライザステート(表面描画)
    pD3D11DeviceContext->RSSetState(RenderState->GetCullBack());

    //ビュー行列の決定
    Mat4x4 View, Proj;
    View = XMMatrixLookAtLH(Vec3(0, 2.0, -5.0f), Vec3(0, 0, 0), Vec3(0, 1.0f, 0));
    //転置する
    View.transpose();
    //射影行列の決定
    float w = static_cast<float>(App::GetApp()->GetGameWidth());
    float h = static_cast<float>(App::GetApp()->GetGameHeight());
    Proj = XMMatrixPerspectiveFovLH(XM_PIDIV4, w / h, 1.0f, 100.0f);
    //転置する
    Proj.transpose();
    //コンスタントバッファの準備
    PNTStaticConstantBuffer sb;
    sb.World = Mat4x4();    //ワールド行列はダミー
    sb.View = View;
    sb.Projection = Proj;
    //ライティング
    Vec4 LightDir(0.5f, -1.0f, 0.5f, 0.0f);
    LightDir.normalize();
    sb.LightDir = LightDir;
    //ディフューズ
    sb.Diffuse = Col4(1.0f, 1.0f, 1.0f, 1.0f);
    //エミッシブ加算。
    sb.Emissive = Col4(0.4f, 0.4f, 0.4f, 0);
    //コンスタントバッファの更新
    pD3D11DeviceContext->UpdateSubresource(CBPNTStatic::GetPtr()->GetBuffer(),
         0, nullptr, &sb, 0, 0);

    //コンスタントバッファの設定
    ID3D11Buffer* pConstantBuffer = CBPNTStatic::GetPtr()->GetBuffer();
    ID3D11Buffer* pNullConstantBuffer = nullptr;
    //頂点シェーダに渡す
    pD3D11DeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
    //ピクセルシェーダに渡す
    pD3D11DeviceContext->PSSetConstantBuffers(0, 1, &pConstantBuffer);
    //描画
    pD3D11DeviceContext->DrawIndexedInstanced(m_CubeMesh->GetNumIndicis(), 
        m_CubeObjectVec.size(), 0, 0, 0);
    //後始末
    Dev->InitializeStates();
}
 赤くなっているのがインスタンス描画特有の部分です。Dx12版の場合は、メッシュのストライドとオフセットはメッシュのビューに入ってますが、Dx11版の場合は描画時に設定します。また、Dx12版DrawIndexedInstanced()関数を常に使用しますが、Dx11版の場合は通常はDrawIndexed()関数インスタンス描画のときだけDrawIndexedInstanced()関数を使用しますので、注意しましょう。

【まとめ】

 今回のインスタンス描画というのは、実は、昔からあるテクニックでDx11やDx12ではじめて対応したものではありません。シェーダもそんなに変更する必要がなく(頂点シェーダのみの変更で良い)、気軽に高速化を図れるので、知っておくと何かと便利と思います。