002.三角形の移動(Dx11版)

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

 実行結果は以下のような画面が出ます。単純な3角形が左右に移動します。

 

図0002a

 


 動画は以下になります。

 

 

【共通解説】

 Dx12、Dx11両方に共通なのはシェーダーです。DxSharedプロジェクト内にシェーダファイルというフィルタがあり、そこに記述されてます。
 今回使用するシェーダは頂点シェーダとピクセルシェーダです。VertexPositionColor型の頂点を持ち、コンスタントいバッファからの入力で、位置を変更させています。
 コンスタントバッファとは、シェーダに渡すパラメータと考えていいと思います。行列やベクトル、あるいは単なるfloat型変数なども渡すことができます。
 ですので、コンスタントバッファを作成するにはどのような変数が渡されるのかを知る必要があります。
 このサンプルの頂点シェーダは以下のようになります。
#include "INCStructs.hlsli"

cbuffer ConstantBuffer : register(b0)
{
    row_major float4x4 MatrixTransform : packoffset(c0);
    float4 Emissive : packoffset(c4);
};


PSPCInput main(VSPCInput input)
{
    PSPCInput result;

    result.position = mul(input.position, MatrixTransform);
    result.color = input.color;

    return result;
}
 ここでインクルードされているINCStructs.hlsliは頂点の型が宣言されたものです。コンスタントバッファcbuffer ConstantBufferというのが、シェーダ側のバッファです。
 ここでは、MatrixTransformという行列と、Emissiveというfloat4型の変数があります。行列のrow_majorという修飾子は、行優先という意味です。DirectXはデフォルトで行優先で操作します。ところがシェーダはデフォルトで列優先(col-major)で実行されます。そのため、通常、3D描画時などは、行列を転置させてからシェーダに入力します。
 ここでの頂点シェーダでの処理は、頂点のインプットに行列をかけてピクセルシェーダに渡しています。頂点色はそのまま渡します。
 厳密には違うのですが、頂点シェーダのリターンはそのままピクセルシェーダの入力と考えるとわかりやすいかもしれません。(ピクセルシェーダに渡されるポジションは、ピクセル座標になります)
 ピクセルシェーダは以下になります。
#include "INCStructs.hlsli"

cbuffer ConstantBuffer : register(b0)
{
    row_major float4x4 MatrixTransform : packoffset(c0);
    float4 Emissive : packoffset(c4);
};


float4 main(PSPCInput input) : SV_TARGET
{
    return saturate(Emissive + input.color);
}
 ここでの処理は頂点シェーダから渡されたカラー情報に、コンスタントバッファEmissiveを足してリターンします。ピクセルシェーダのリターンはそのままバックバッファへの書き込みとなります。

【Dx11版解説】

 BaseCrossDx11.slnを開くと、BaseCrossDx11というメインプロジェクトがあります。この中のCharacter.h/cppが主な記述個所になります。
 表示しているのは、TriangleSpriteクラスです。まず、Character.hですが、以下のようなヘッダになります。
//--------------------------------------------------------------------------------------
/// 三角形スプライト
//--------------------------------------------------------------------------------------
class TriangleSprite : public ObjectInterface, public ShapeInterface {
    ///メッシュ
    shared_ptr<MeshResource> m_TriangleMesh;
    Vec3 m_Pos;                 ///<現在の位置
    Vec3 m_MoveSpan;                ///<位置変更値
public:
    //構築と破棄
    TriangleSprite();
    virtual ~TriangleSprite();
    //初期化
    virtual void OnCreate()override;
    void OnUpdate()override;
    void OnDraw()override;
};
 Dx12版に対してかなりシンプルなのがわかります。Dx11版の場合は、初期化しなければならないリソースはDx12よりかなり少ないです。

■初期化処理■

 初期化はTriangleSprite::OnCreate関数で行います。以下が実体です。
void TriangleSprite::OnCreate() {
    //頂点を作成するための配列
    vector vertices = {
        { VertexPositionColor(Vec3(0.0f, 0.5f, 0.0f), Col4(1.0f,0.0f,0.0f,1.0f)) },
        { VertexPositionColor(Vec3(0.5f, -0.5f, 0.0f), Col4(0.0f, 1.0f, 0.0f, 1.0f)) },
        { VertexPositionColor(Vec3(-0.5f, -0.5f, 0.0f), Col4(0.0f, 0.0f, 1.0f, 1.0f)) },
    };
    m_TriangleMesh = MeshResource::CreateMeshResource(vertices, false);
}
 ここでは、頂点の配列を作成してメッシュリソースを作成しています。
 MeshResource::CreateMeshResource()関数はテンプレート関数です。ここでは、VertexPositionColor型の頂点バッファを作成して、MeshResourceを取得しています。

■更新処理■

 更新処理は、TriangleSprite::OnUpdate()に記述します。
void TriangleSprite::OnUpdate() {
    m_Pos += m_MoveSpan;
    if (abs(m_Pos.x) >= 0.5f) {
        m_MoveSpan *= -1.0f;
    }
}
 ここでは行ったり来たりの移動処理をしています。

■描画処理■

 描画はTriangleSprite::OnDraw関数で行います。以下が実体です。
void TriangleSprite::OnDraw() {
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pD3D11DeviceContext = Dev->GetD3DDeviceContext();
    auto RenderState = Dev->GetRenderState();

    //コンスタントバッファの準備
    SpriteConstantBuffer sb;
    sb.Emissive = Col4(0.0f, 0.0f, 0, 1.0f);
    Mat4x4 mat;
    mat.translation(m_Pos);
    sb.World = mat;
    //コンスタントバッファの更新
    pD3D11DeviceContext->UpdateSubresource(CBSprite::GetPtr()->GetBuffer(), 
            0, nullptr, &sb, 0, 0);

    //ストライドとオフセット
    UINT stride = sizeof(VertexPositionColor);
    UINT offset = 0;
    pD3D11DeviceContext->IASetVertexBuffers(0, 1, 
        m_TriangleMesh->GetVertexBuffer().GetAddressOf(), &stride, &offset);
    //描画方法(3角形)
    pD3D11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    //コンスタントバッファの設定
    ID3D11Buffer* pConstantBuffer = CBSprite::GetPtr()->GetBuffer();
    ID3D11Buffer* pNullConstantBuffer = nullptr;
    //頂点シェーダに渡す
    pD3D11DeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
    //ピクセルシェーダに渡す
    pD3D11DeviceContext->PSSetConstantBuffers(0, 1, &pConstantBuffer);
    //シェーダの設定
    pD3D11DeviceContext->VSSetShader(VSPCSprite::GetPtr()->GetShader(), nullptr, 0);
    pD3D11DeviceContext->PSSetShader(PSPCSprite::GetPtr()->GetShader(), nullptr, 0);
    //インプットレイアウトの設定
    pD3D11DeviceContext->IASetInputLayout(VSPCSprite::GetPtr()->GetInputLayout());

    //ブレンドステート
    pD3D11DeviceContext->OMSetBlendState(RenderState->GetOpaque(), nullptr, 0xffffffff);
    //デプスステンシルステート
    pD3D11DeviceContext->OMSetDepthStencilState(RenderState->GetDepthNone(), 0);
    //ラスタライザステート
    pD3D11DeviceContext->RSSetState(RenderState->GetCullBack());

    //描画
    pD3D11DeviceContext->Draw(m_TriangleMesh->GetNumVertices(), 0);
    //後始末
    Dev->InitializeStates();

}
 ここではもっぱらデバイスコンテキストクラス(pD3D11DeviceContext)に対する描画準備や描画命令の発行になります。
 ここで気を付けたいのはコンスタントバッファの扱いです。Dx12版ではコンスタントバッファはリソースを作成して、コピーも自分で行わなければなりませんでした。
 しかし、Dx11版では
    //コンスタントバッファの準備
    SpriteConstantBuffer sb;
    sb.Emissive = Col4(0.0f, 0.0f, 0, 1.0f);
    Mat4x4 mat;
    mat.translation(m_Pos);
    sb.World = mat;
    //コンスタントバッファの更新
    pD3D11DeviceContext->UpdateSubresource(CBSprite::GetPtr()->GetBuffer(), 
            0, nullptr, &sb, 0, 0);
 というブロックと
    //コンスタントバッファの設定
    ID3D11Buffer* pConstantBuffer = CBSprite::GetPtr()->GetBuffer();
 で取得したポインタを
    //コンスタントバッファの設定
    ID3D11Buffer* pConstantBuffer = CBSprite::GetPtr()->GetBuffer();

    //中略

    //頂点シェーダに渡す
    pD3D11DeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
    //ピクセルシェーダに渡す
    pD3D11DeviceContext->PSSetConstantBuffers(0, 1, &pConstantBuffer);
 という形で渡しています。このメカニズムは、CBSpriteクラスというコンスタントバッファクラスによって実装されます。
 このクラスはProjectShader.h/cppにマクロによって実装されます。以下がヘッダです。
//スプライト用コンスタントバッファ構造体
struct SpriteConstantBuffer
{
    Mat4x4 World;
    Col4 Emissive;
    SpriteConstantBuffer() {
        memset(this, 0, sizeof(SpriteConstantBuffer));
    };
};
DECLARE_DX11_CONSTANT_BUFFER(CBSprite, SpriteConstantBuffer)
 このDECLARE_DX11_CONSTANT_BUFFERマクロはシェーダ同様、マクロでCBSpriteクラスを宣言します。
 実体のほうは
IMPLEMENT_DX11_CONSTANT_BUFFER(CBSprite)
 の1行で実装できます。この実装によりCBSpriteクラスシングルトンとして実体化されます。ですから
    CBSprite::GetPtr()->GetBuffer()
 のような記述でコンスタントバッファの記憶領域にアクセスできます。

 ProjectShader.h/cppにはコンスタントバッファのほかに頂点シェーダとピクセルシェーダのマクロが記述されます。以下がその宣言です。
DECLARE_DX11_VERTEX_SHADER(VSPCSprite, VertexPositionColor)
DECLARE_DX11_PIXEL_SHADER(PSPCSprite)
 実体は以下です。
IMPLEMENT_DX11_VERTEX_SHADER(VSPCSprite, 
    App::GetApp()->m_wstrRelativeShadersPath + L"VSPCSprite.cso")
IMPLEMENT_DX11_PIXEL_SHADER(PSPCSprite, 
    App::GetApp()->m_wstrRelativeShadersPath + L"PSPCSprite.cso")
 これらのマクロにより、描画時に
    //シェーダの設定
    pD3D11DeviceContext->VSSetShader(VSPCSprite::GetPtr()->GetShader(), nullptr, 0);
    pD3D11DeviceContext->PSSetShader(PSPCSprite::GetPtr()->GetShader(), nullptr, 0);
 のような記述になります。