15.シェーダーを自作する(Dx11版)

1502.コンピュートシェーダーで計算する(2)


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

 

図1502a

 

 このサンプルはフルサンプル305コンピュートシェーダー(CS)を使う形に書き直したものです。
 プレイヤーが上記の回転する球に当たると、各点が飛び散ります。それらは床に到達するとそこから動かなくなります。

 

図1501b

 

 この例のように、すでに記述した動きをCSを使って書き直すのも勉強になると思います。

CSで使用するオブジェクトの準備

 今回使用するバッファやリソースビューなどのオブジェクトは以下になります。
1、データ入力用の配列
2、データ取得用の配列
3.CS入力用のエレメントバッファ
4、CS出力用のエレメントバッファ
5、3のビュー(シェーダーリソースビュー)
6、4のビュー(アクセスビュー)
7、リードバックバッファ
 それぞれPointsBallクラスのヘッダに宣言があります。Character.hにあります。以下、抜粋ですが
class PointsBall : public GameObject {
    const UINT NUM_ELEMENTS = 64 * 32;
    UINT m_NumVertices;
    struct LocalData {
        Vec4 m_LocalPosition;
        Vec4 m_WorldPosition;
        Vec4 m_Velocity;
        UINT m_State[4];
        LocalData() :
            m_LocalPosition(0.0f),
            m_WorldPosition(0.0f),
            m_Velocity(0.0f)
        {
            for (auto& v : m_State) {
                v = 0;
            }
        }
    };
    //エレメントバッファ
    ComPtr<ID3D11Buffer> m_InBuffer;
    ComPtr<ID3D11Buffer> m_OutBuffer;
    //シェーダーリソースビュー
    ComPtr<ID3D11ShaderResourceView> m_SRV;
    //アクセスビュー
    ComPtr < ID3D11UnorderedAccessView>  m_UAV;
    //リードバックバッファ
    ComPtr<ID3D11Buffer> m_ReadBackBuffer;
    vector<LocalData> m_LocalDataVec;
    vector<LocalData> m_ResultDataVec;
    vector<Mat4x4> m_MatVec;
//以下略
};
 以下、それぞれの対応です。
1、データ入力用の配列: m_LocalDataVec
2、データ取得用の配列: m_ResultDataVec
3.CS入力用のエレメントバッファ: m_InBuffer
4、CS出力用のエレメントバッファ: m_OutBuffer
5、1のビュー(シェーダーリソースビュー): m_SRV
6、2のビュー(アクセスビュー): m_UAV
7、リードバックバッファ: m_ReadBackBuffer
 m_MatVecはインスタンス描画に使う配列です。(フルサンプル305を参照のこと)。
 つまり今回のサンプルでのOnUpdate()関数での目的はm_MatVecに配列をセットすることにあります。

CSで使用するオブジェクトの構築

 各オブジェクトはPointsBall::OnCreate()関数で構築します。Character.cppにあります。以下、少々長いですが、すべて紹介します。
void PointsBall::OnCreate() {
    vector<VertexPositionNormalTexture> vertices;
    vector<uint16_t> indices;
    m_LocalDataVec.resize(NUM_ELEMENTS);
    //球体を作成
    MeshUtill::CreateSphere(1.0f, 28, vertices, indices);
    m_NumVertices = (UINT)vertices.size();
    for (size_t i = 0; i < m_NumVertices; i++) {
        LocalData tempLocalData;
        tempLocalData.m_LocalPosition = vertices[i].position;
        m_LocalDataVec[i] = tempLocalData;
    }
    m_ResultDataVec.resize(m_NumVertices);
    //描画用メッシュの作成
    float helfSize = 0.04f;
    Col4 col(1.0f, 1.0f, 0.0f, 1.0f);
    //頂点配列
    vector<VertexPositionColorTexture> meshVertices = {
        { VertexPositionColorTexture(Vec3(-helfSize, helfSize, 0),col, Vec2(0.0f, 0.0f)) },
        { VertexPositionColorTexture(Vec3(helfSize, helfSize, 0),col, Vec2(1.0f, 0.0f)) },
        { VertexPositionColorTexture(Vec3(-helfSize, -helfSize, 0),col, Vec2(0.0f, 1.0f)) },
        { VertexPositionColorTexture(Vec3(helfSize, -helfSize, 0),col, Vec2(1.0f, 1.0f)) },
    };
    //インデックス配列
    vector<uint16_t> meshIndex = { 0, 1, 2, 1, 3, 2 };
    //2次元平面とする(頂点数が少ないため)
    m_MeshRes = MeshResource::CreateMeshResource(meshVertices, meshIndex, true);
    //全体の位置関連
    auto ptrTransform = GetComponent<Transform>();
    ptrTransform->SetScale(Vec3(m_Scale));
    ptrTransform->SetRotation(Vec3(0));
    ptrTransform->SetPosition(m_Position);
    //描画コンポーネントの追加(インスタンス描画)
    auto PtrDraw = AddComponent<PCTStaticInstanceDraw>();
    PtrDraw->SetMeshResource(m_MeshRes);
    PtrDraw->SetTextureResource(L"SPARK_TX");
    PtrDraw->SetDepthStencilState(DepthStencilState::Read);
    //各頂点ごとに行列を作成
    for (size_t i = 0; i < m_NumVertices; i++) {
        Mat4x4 tempMat;
        tempMat.affineTransformation(
            Vec3(1.0f),
            Vec3(0.0f),
            Vec3(0.0f),
            m_LocalDataVec[i].m_LocalPosition
        );
        //インスタンス描画の行列として設定
        PtrDraw->AddMatrix(tempMat);
    }
    SetAlphaActive(true);

    auto Dev = App::GetApp()->GetDeviceResources();
    auto pDx11Device = Dev->GetD3DDevice();
    auto pID3D11DeviceContext = Dev->GetD3DDeviceContext();

    //エレメンツバッファ
    D3D11_BUFFER_DESC buffer_desc = { 0 };
    buffer_desc.ByteWidth = NUM_ELEMENTS * sizeof(LocalData);
    buffer_desc.Usage = D3D11_USAGE_DEFAULT;
    buffer_desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
    buffer_desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
    buffer_desc.StructureByteStride = sizeof(LocalData);
    ThrowIfFailed(
        pDx11Device->CreateBuffer(&buffer_desc, nullptr, &m_InBuffer),
        L"m_InBuffer作成に失敗しました",
        L"pDx11Device->CreateBuffer()",
        L"PointsBall::OnCreate()"
    );
    ThrowIfFailed(
        pDx11Device->CreateBuffer(&buffer_desc, nullptr, &m_OutBuffer),
        L"m_OutBuffer作成に失敗しました",
        L"pDx11Device->CreateBuffer()",
        L"PointsBall::OnCreate()"
    );
    //シェーダーリソースビュー
    D3D11_SHADER_RESOURCE_VIEW_DESC srvbuffer_desc = {};
    srvbuffer_desc.Format = DXGI_FORMAT_UNKNOWN;
    srvbuffer_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
    srvbuffer_desc.Buffer.FirstElement = 0;
    srvbuffer_desc.Buffer.NumElements = buffer_desc.ByteWidth / buffer_desc.StructureByteStride;
    srvbuffer_desc.Buffer.ElementWidth = NUM_ELEMENTS;
    ThrowIfFailed(
        pDx11Device->CreateShaderResourceView(m_InBuffer.Get(), &srvbuffer_desc, &m_SRV),
        L"m_SRV作成に失敗しました",
        L"pDx11Device->CreateShaderResourceView()",
        L"PointsBall::OnCreate()"
    );
    //アクセスビュー
    D3D11_UNORDERED_ACCESS_VIEW_DESC uavbuffer_desc = {};
    uavbuffer_desc.Format = DXGI_FORMAT_UNKNOWN;
    uavbuffer_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
    uavbuffer_desc.Buffer.NumElements = NUM_ELEMENTS;
    ThrowIfFailed(
        pDx11Device->CreateUnorderedAccessView(m_OutBuffer.Get(), &uavbuffer_desc, &m_UAV),
        L"m_UAV作成に失敗しました",
        L"pDx11Device->CreateUnorderedAccessView()",
        L"PointsBall::OnCreate()"
    );
    //リードバックバッファ
    D3D11_BUFFER_DESC readback_buffer_desc = {};
    readback_buffer_desc.ByteWidth = NUM_ELEMENTS * sizeof(LocalData);
    readback_buffer_desc.Usage = D3D11_USAGE_STAGING;
    readback_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    readback_buffer_desc.StructureByteStride = sizeof(LocalData);
    ThrowIfFailed(
        pDx11Device->CreateBuffer(&readback_buffer_desc, nullptr, &m_ReadBackBuffer),
        L"m_ReadBackBuffer作成に失敗しました",
        L"pDx11Device->CreateBuffer()",
        L"PointsBall::OnCreate()"
    );
}
 では、1つ1つ見ていきましょう。
 まず
    m_LocalDataVec.resize(NUM_ELEMENTS);
 と入力用の配列を初期化します。NUM_ELEMENTSはconstになっていて、ヘッダ部で64 * 32 == 2048になっています。これは最大値です。インスタンス描画ではこのあたりが上限かと思います。ただ、実際に作成される頂点数はもう少し少ないです。
    //球体を作成
    MeshUtill::CreateSphere(1.0f, 28, vertices, indices);
 で球を作成します。分割数は28です。これで、1653個の頂点の球ができます。
 このサンプルではこの頂点情報だけを使用します。ここはフル305と同じです。
    m_NumVertices = (UINT)vertices.size();
    for (size_t i = 0; i < m_NumVertices; i++) {
        LocalData tempLocalData;
        tempLocalData.m_LocalPosition = vertices[i].position;
        m_LocalDataVec[i] = tempLocalData;
    }
 このようにm_LocalDataVecに頂点情報のみをセットします。
    m_ResultDataVec.resize(m_NumVertices);
 でm_ResultDataVecm_NumVerticesの数にします。ここでNUM_ELEMENTSの数にしないのは、m_ResultDataVecは、インスタンス描画数に直結するからです。m_LocalDataVecの数 > m_ResultDataVecの数であれば問題ありません。
 続く描画用のメッシュの作成はCSには関係ありません。
    //描画用メッシュの作成
    float helfSize = 0.04f;
    Col4 col(1.0f, 1.0f, 0.0f, 1.0f);
    //頂点配列
    vector<VertexPositionColorTexture> meshVertices = {
        { VertexPositionColorTexture(Vec3(-helfSize, helfSize, 0),col, Vec2(0.0f, 0.0f)) },
        { VertexPositionColorTexture(Vec3(helfSize, helfSize, 0),col, Vec2(1.0f, 0.0f)) },
        { VertexPositionColorTexture(Vec3(-helfSize, -helfSize, 0),col, Vec2(0.0f, 1.0f)) },
        { VertexPositionColorTexture(Vec3(helfSize, -helfSize, 0),col, Vec2(1.0f, 1.0f)) },
    };
    //インデックス配列
    vector<uint16_t> meshIndex = { 0, 1, 2, 1, 3, 2 };
    //2次元平面とする(頂点数が少ないため)
    m_MeshRes = MeshResource::CreateMeshResource(meshVertices, meshIndex, true);
 そのあとのTransformコンポーネントの設定は、ワールド行列を取り出すためのみ使用します。
 また描画コンポーネントやインスタンス描画のための行列を初期値で作成します。
    //全体の位置関連
    auto ptrTransform = GetComponent<Transform>();
    ptrTransform->SetScale(Vec3(m_Scale));
    ptrTransform->SetRotation(Vec3(0));
    ptrTransform->SetPosition(m_Position);
    //描画コンポーネントの追加(インスタンス描画)
    auto PtrDraw = AddComponent<PCTStaticInstanceDraw>();
    PtrDraw->SetMeshResource(m_MeshRes);
    PtrDraw->SetTextureResource(L"SPARK_TX");
    PtrDraw->SetDepthStencilState(DepthStencilState::Read);
    //各頂点ごとに行列を作成
    for (size_t i = 0; i < m_NumVertices; i++) {
        Mat4x4 tempMat;
        tempMat.affineTransformation(
            Vec3(1.0f),
            Vec3(0.0f),
            Vec3(0.0f),
            m_LocalDataVec[i].m_LocalPosition
        );
        //インスタンス描画の行列として設定
        PtrDraw->AddMatrix(tempMat);
    }
    SetAlphaActive(true);
 このあとからCS用のオブジェクトの作成が始まります。
 まず
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pDx11Device = Dev->GetD3DDevice();
    auto pID3D11DeviceContext = Dev->GetD3DDeviceContext();
 でDx11デバイスを取得します。
 続いて
    //エレメンツバッファ
    D3D11_BUFFER_DESC buffer_desc = { 0 };
    buffer_desc.ByteWidth = NUM_ELEMENTS * sizeof(LocalData);
    buffer_desc.Usage = D3D11_USAGE_DEFAULT;
    buffer_desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
    buffer_desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
    buffer_desc.StructureByteStride = sizeof(LocalData);
    ThrowIfFailed(
        pDx11Device->CreateBuffer(&buffer_desc, nullptr, &m_InBuffer),
        L"m_InBuffer作成に失敗しました",
        L"pDx11Device->CreateBuffer()",
        L"PointsBall::OnCreate()"
    );
    ThrowIfFailed(
        pDx11Device->CreateBuffer(&buffer_desc, nullptr, &m_OutBuffer),
        L"m_OutBuffer作成に失敗しました",
        L"pDx11Device->CreateBuffer()",
        L"PointsBall::OnCreate()"
    );
 で入出力用のバッファを作成します。赤くなっているところはバッファサイズです。最大値で作成します。
 続いてそれぞれのビューを作成します。シェーダーを使用する場合は、バッファのビューが必要です。
    //シェーダーリソースビュー
    D3D11_SHADER_RESOURCE_VIEW_DESC srvbuffer_desc = {};
    srvbuffer_desc.Format = DXGI_FORMAT_UNKNOWN;
    srvbuffer_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
    srvbuffer_desc.Buffer.FirstElement = 0;
    srvbuffer_desc.Buffer.NumElements = buffer_desc.ByteWidth / buffer_desc.StructureByteStride;
    srvbuffer_desc.Buffer.ElementWidth = NUM_ELEMENTS;
    ThrowIfFailed(
        pDx11Device->CreateShaderResourceView(m_InBuffer.Get(), &srvbuffer_desc, &m_SRV),
        L"m_SRV作成に失敗しました",
        L"pDx11Device->CreateShaderResourceView()",
        L"PointsBall::OnCreate()"
    );
    //アクセスビュー
    D3D11_UNORDERED_ACCESS_VIEW_DESC uavbuffer_desc = {};
    uavbuffer_desc.Format = DXGI_FORMAT_UNKNOWN;
    uavbuffer_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
    uavbuffer_desc.Buffer.NumElements = NUM_ELEMENTS;
    ThrowIfFailed(
        pDx11Device->CreateUnorderedAccessView(m_OutBuffer.Get(), &uavbuffer_desc, &m_UAV),
        L"m_UAV作成に失敗しました",
        L"pDx11Device->CreateUnorderedAccessView()",
        L"PointsBall::OnCreate()"
    );
 赤くなっているところでビューを作成します。入力用はシェーダーリソースビュー、出力用はアクセスビューです。
 前項ではアクセスビューのみ作成しました。しかし、大量の計算をする場合、入力はシェーダーリソースビューを使用します。
 今回入力は一つですが、シェーダーリソースビューは複数作成できるので、何かと便利です。
 シェーダー内では、これらのビューを介して、エレメンツバッファへのアクセスを行います。
 続いてリードバックバッファです。これは、アクセスビューからデータを取り出すためのものです。最大値で作成します。
    //リードバックバッファ
    D3D11_BUFFER_DESC readback_buffer_desc = {};
    readback_buffer_desc.ByteWidth = NUM_ELEMENTS * sizeof(LocalData);
    readback_buffer_desc.Usage = D3D11_USAGE_STAGING;
    readback_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    readback_buffer_desc.StructureByteStride = sizeof(LocalData);
    ThrowIfFailed(
        pDx11Device->CreateBuffer(&readback_buffer_desc, nullptr, &m_ReadBackBuffer),
        L"m_ReadBackBuffer作成に失敗しました",
        L"pDx11Device->CreateBuffer()",
        L"PointsBall::OnCreate()"
    );

更新処理

 更新処理は前述したようにインスタンス描画に使う行列の配列m_MatVecを作成することにあります。PointsBall::OnUpdate()を以下の紹介します。
void PointsBall::OnUpdate() {
    float elapsedTime = App::GetApp()->GetElapsedTime();
    auto ptrDraw = GetComponent<PCTStaticInstanceDraw>();
    auto ptrTransform = GetComponent<Transform>();
    //全体を回転させる
    auto worldQt = ptrTransform->GetQuaternion();
    Quat spanQt(Vec3(0, 1, 0), elapsedTime);
    worldQt *= spanQt;
    ptrTransform->SetQuaternion(worldQt);
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pID3D11DeviceContext = Dev->GetD3DDeviceContext();
    CB cb = {};
    auto playerSh = GetStage()->GetSharedGameObject<Player>(L"Player");
    cb.m_PlayerPosition = playerSh->GetComponent<Transform>()->GetWorldPosition();
    cb.m_PlayerPosition.w = 1.0f;
    cb.m_WorldMatrix = ptrTransform->GetWorldMatrix();
    // paramf[0] == elapsedTime
    // paramf[1] == velocityPower
    // paramf[2] == gravity_y
    // paramf[3] == baseY
    cb.paramf[0] = elapsedTime;
    cb.paramf[1] = 3.0f;
    cb.paramf[2] = -9.8f;
    cb.paramf[3] = m_Scale * 0.02f;
    ID3D11Buffer* pConstantBuffer = ConstantBufferCalcbody::GetPtr()->GetBuffer();
    pID3D11DeviceContext->UpdateSubresource(pConstantBuffer, 0, nullptr, &cb, 0, 0);
    pID3D11DeviceContext->CSSetConstantBuffers(0, 1, &pConstantBuffer);
    pID3D11DeviceContext->CSSetShader(ComputeSaderCalcbody::GetPtr()->GetShader(), nullptr, 0);
    pID3D11DeviceContext->UpdateSubresource(m_InBuffer.Get(), 0, nullptr, &m_LocalDataVec[0], 0, 0);
    pID3D11DeviceContext->CSSetShaderResources(0, 1, m_SRV.GetAddressOf());
    pID3D11DeviceContext->CSSetUnorderedAccessViews(0, 1, m_UAV.GetAddressOf(), nullptr);
    pID3D11DeviceContext->Dispatch((UINT)m_NumVertices, 1, 1);

    //カメラのレイを作成しておく
    auto camera = OnGetDrawCamera();
    auto lay = camera->GetAt() - camera->GetEye();
    lay.normalize();
    Quat qtCamera;
    //回転は常にカメラを向くようにする
    qtCamera.facing(lay);
    //結果の読み取り
    D3D11_MAPPED_SUBRESOURCE MappedResource = { 0 };
    pID3D11DeviceContext->CopyResource(m_ReadBackBuffer.Get(), m_OutBuffer.Get());
    if (SUCCEEDED(pID3D11DeviceContext->Map(m_ReadBackBuffer.Get(), 0, D3D11_MAP_READ, 0, &MappedResource)))
    {
        memcpy(&m_ResultDataVec[0], MappedResource.pData, m_NumVertices * sizeof(LocalData));
        pID3D11DeviceContext->Unmap(m_ReadBackBuffer.Get(), 0);
        //行列の配列をクリア
        m_MatVec.clear();
        Mat4x4 worldMat;
        for (auto& v : m_ResultDataVec) {
            worldMat.affineTransformation(
                Vec3(m_Scale),
                Vec3(0.0f),
                qtCamera,
                v.m_WorldPosition
            );
            m_MatVec.push_back(worldMat);
        }
        //インスタンス行列の更新
        ptrDraw->UpdateMultiMatrix(m_MatVec);
        //結果を次の入力にコピー
        for (size_t i = 0; i < m_ResultDataVec.size(); i++) {
            m_LocalDataVec[i] = m_ResultDataVec[i];
        }
    }
    // CSの開放
    ID3D11UnorderedAccessView* ppUAViewNULL[1] = { nullptr };
    pID3D11DeviceContext->CSSetUnorderedAccessViews(0, 1, ppUAViewNULL, nullptr);
    ID3D11ShaderResourceView* ppSRVNULL[1] = { nullptr };
    pID3D11DeviceContext->CSSetShaderResources(0, 1, ppSRVNULL);
}
 まず、以下は、Transformコンポーネント処理です。デバイスも取得します。
    float elapsedTime = App::GetApp()->GetElapsedTime();
    auto ptrDraw = GetComponent<PCTStaticInstanceDraw>();
    auto ptrTransform = GetComponent<Transform>();
    //全体を回転させる
    auto worldQt = ptrTransform->GetQuaternion();
    Quat spanQt(Vec3(0, 1, 0), elapsedTime);
    worldQt *= spanQt;
    ptrTransform->SetQuaternion(worldQt);
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pID3D11DeviceContext = Dev->GetD3DDeviceContext();

CSへの入力

 CSへの入力は、コンスタントバッファ各点の情報があります。
 コンスタントバッファは全体の情報を入れます。それは以下です。
1、プレイヤーのワールド位置
2、球全体のワールド行列
3、前ターンからの時間(elapsedTime)
4、プレイヤーと当たったときのはじける強さ(velocityPower)
5、重力係数(gravity_y)
6、最終落下位置(baseY)
 です。それを
    CB cb = {};
    auto playerSh = GetStage()->GetSharedGameObject<Player>(L"Player");
    cb.m_PlayerPosition = playerSh->GetComponent<Transform>()->GetWorldPosition();
    cb.m_PlayerPosition.w = 1.0f;
    cb.m_WorldMatrix = ptrTransform->GetWorldMatrix();
    // paramf[0] == elapsedTime
    // paramf[1] == velocityPower
    // paramf[2] == gravity_y
    // paramf[3] == baseY
    cb.paramf[0] = elapsedTime;
    cb.paramf[1] = 3.0f;
    cb.paramf[2] = -9.8f;
    cb.paramf[3] = m_Scale * 0.02f;
    ID3D11Buffer* pConstantBuffer = ConstantBufferCalcbody::GetPtr()->GetBuffer();
    pID3D11DeviceContext->UpdateSubresource(pConstantBuffer, 0, nullptr, &cb, 0, 0);
    pID3D11DeviceContext->CSSetConstantBuffers(0, 1, &pConstantBuffer);
 のように設定します。
 その後、シェーダを設定します。
    pID3D11DeviceContext->CSSetShader(ComputeSaderCalcbody::GetPtr()->GetShader(), nullptr, 0);
 続いてビューを設定します。
    pID3D11DeviceContext->UpdateSubresource(m_InBuffer.Get(), 0, nullptr, &m_LocalDataVec[0], 0, 0);
    pID3D11DeviceContext->CSSetShaderResources(0, 1, m_SRV.GetAddressOf());
    pID3D11DeviceContext->CSSetUnorderedAccessViews(0, 1, m_UAV.GetAddressOf(), nullptr);
 ここで入力は赤くなっているように、m_InBufferから取得しますが、m_UAVシェーダから取得用なので更新する必要はありません。
 最後に
    pID3D11DeviceContext->Dispatch((UINT)m_NumVertices, 1, 1);
 とCSを実行します。ここで第一引数の(UINT)m_NumVerticesは重要です。

CSの実行

 今回のCSは、以下の処理を行います。
1、各点のローカルデータをワールド位置に座標変換
2、もしプレイヤーが当たったら飛び出す処理
3、地面についてらもう動かなくなる処理
3、最終的に各点のワールドポジションを出力する
 という処理です。
 以下にシェーダーを紹介しますCSCalcbody.hlslです。
//--------------------------------------------------------------------------------------
// コンスタントバッファ
//--------------------------------------------------------------------------------------
cbuffer CB : register(b0)
{
    row_major float4x4 g_world;
    float4 g_playerPosition;
    float4  g_paramf;
    // g_paramf[0] == elapsedTime
    // g_paramf[1] == velocityPower
    // g_paramf[2] == gravity_y
    // g_paramf[3] == baseY
};

//--------------------------------------------------------------------------------------
// 各点のデータ
//--------------------------------------------------------------------------------------
struct LocalData {
    float4 m_LocalPosition;
    float4 m_WorldPosition;
    float4 m_Velocity;
    uint4  m_State;
};


//シェーダリソースビュー
StructuredBuffer<LocalData> inPosVelo : register(t0);
//アクセスビュー
RWStructuredBuffer<LocalData> outPosVelo : register(u0);


[numthreads(64, 1, 1)]
void main( uint3 DTid : SV_DispatchThreadID )
{
    //入力からデータをして出力にコピー
    outPosVelo[DTid.x].m_LocalPosition = inPosVelo[DTid.x].m_LocalPosition;
    outPosVelo[DTid.x].m_LocalPosition.w = 1.0;
    outPosVelo[DTid.x].m_State = inPosVelo[DTid.x].m_State;
    uint state = outPosVelo[DTid.x].m_State.x;
    if (state == 2) {
        //落下した時以外はワールド位置を計算
        outPosVelo[DTid.x].m_WorldPosition = inPosVelo[DTid.x].m_WorldPosition;
    }
    else {
        //落下した時以外はワールド位置を計算
        outPosVelo[DTid.x].m_WorldPosition = mul(outPosVelo[DTid.x].m_LocalPosition, g_world);
    }
    outPosVelo[DTid.x].m_Velocity = inPosVelo[DTid.x].m_Velocity;
    //ステートをチェックして場合によってはワールドポジションを変更
    float baseY = g_paramf[3];
    float velocityPower = g_paramf[1];
    float4 gravity = float4(0, g_paramf[2], 0,0);
    float elapsedTime = g_paramf[0];
    float3 playerPos = float3(g_playerPosition.xyz);
    float3 targetPos = float3(outPosVelo[DTid.x].m_WorldPosition.xyz);
    if (state == 0) {
        float len = length(playerPos - targetPos);
        if (len < 0.4f) {
            //lenが0.4未満なら衝突してると判断
            //ステートを変更
            //衝突していたら球から飛び出すように速度を設定
            outPosVelo[DTid.x].m_State.x = 1;
            float3 velo = playerPos - targetPos;
            outPosVelo[DTid.x].m_Velocity.xyz = velo.xyz;
            outPosVelo[DTid.x].m_Velocity.w = 0.0;
            normalize(outPosVelo[DTid.x].m_Velocity);
            outPosVelo[DTid.x].m_Velocity.y = 1.0f;
            outPosVelo[DTid.x].m_Velocity *= velocityPower;
        }
    }
    else if (state == 1) {
        //飛び散る状態
        if (targetPos.y <= baseY) {
            //落下終了
            outPosVelo[DTid.x].m_State.x = 2;
            outPosVelo[DTid.x].m_WorldPosition.y = baseY;
        }
        else {
            //落下中
            outPosVelo[DTid.x].m_Velocity += gravity * elapsedTime;
            outPosVelo[DTid.x].m_LocalPosition += outPosVelo[DTid.x].m_Velocity * elapsedTime;
            outPosVelo[DTid.x].m_LocalPosition.w = 1.0;
            outPosVelo[DTid.x].m_WorldPosition = mul(outPosVelo[DTid.x].m_LocalPosition, g_world);
        }
    }
}
 まず
cbuffer CB : register(b0)
{
    row_major float4x4 g_world;
    float4 g_playerPosition;
    float4  g_paramf;
    // g_paramf[0] == elapsedTime
    // g_paramf[1] == velocityPower
    // g_paramf[2] == gravity_y
    // g_paramf[3] == baseY
};
 はコンスタントバッファです。C++側とデータ構造を合わせます。また
//--------------------------------------------------------------------------------------
// 各点のデータ
//--------------------------------------------------------------------------------------
struct LocalData {
    float4 m_LocalPosition;
    float4 m_WorldPosition;
    float4 m_Velocity;
    uint4  m_State;
};
 は各点のデータ形式です。これもC++側と合わせます。続く
//シェーダリソースビュー
StructuredBuffer<LocalData> inPosVelo : register(t0);
 は入力用の点のデータで
//アクセスビュー
RWStructuredBuffer<LocalData> outPosVelo : register(u0);
 は出力用のものです。
 このCSには、ステートの概念があり、
0: 回転状態
1: プレイヤーと当たって落ちる状態
2: 落下後の状態
 があります。
 実行コードは
[numthreads(64, 1, 1)]
void main( uint3 DTid : SV_DispatchThreadID )
 で始まります。ここで64というのは、スレッド数です。
 CSの原理は例えばテクスチャリソース(シェーダーリソース)のX、Y、Zの値を再利用しているよなものです。
 この部分を64,1,1とするのはスレッドを64個利用するの意味です。じつはこれを1,1,1としてもこのサンプルは動きます。
[numthreads(1, 1, 1)]
void main( uint3 DTid : SV_DispatchThreadID )
 としても、OKなわけです。
 スレッドというのは並行して動作する単位なので、この数が多い方が効率が良いはずなのですが、GPUの能力に左右されます。
 実行関数本体では
    //入力からデータをして出力にコピー
    outPosVelo[DTid.x].m_LocalPosition = inPosVelo[DTid.x].m_LocalPosition;
    outPosVelo[DTid.x].m_LocalPosition.w = 1.0;
    outPosVelo[DTid.x].m_State = inPosVelo[DTid.x].m_State;
 といった感じで入力出力にコピーします。
 その後、ステートに合わせ、outPosVelo[DTid.x].m_WorldPositionを計算します。
 計算内容は基本的にフルサンプル305と一緒です。

CSから情報の取得

 再びPointsBall::OnUpdate()です。
 CSを実行後は
    //カメラのレイを作成しておく
    auto camera = OnGetDrawCamera();
    auto lay = camera->GetAt() - camera->GetEye();
    lay.normalize();
    Quat qtCamera;
    //回転は常にカメラを向くようにする
    qtCamera.facing(lay);
    //結果の読み取り
    D3D11_MAPPED_SUBRESOURCE MappedResource = { 0 };
    pID3D11DeviceContext->CopyResource(m_ReadBackBuffer.Get(), m_OutBuffer.Get());
    if (SUCCEEDED(pID3D11DeviceContext->Map(m_ReadBackBuffer.Get(), 0, D3D11_MAP_READ, 0, &MappedResource)))
    {
        memcpy(&m_ResultDataVec[0], MappedResource.pData, m_NumVertices * sizeof(LocalData));
        pID3D11DeviceContext->Unmap(m_ReadBackBuffer.Get(), 0);
        //行列の配列をクリア
        m_MatVec.clear();
        Mat4x4 worldMat;
        for (auto& v : m_ResultDataVec) {
            worldMat.affineTransformation(
                Vec3(m_Scale),
                Vec3(0.0f),
                qtCamera,
                v.m_WorldPosition
            );
            m_MatVec.push_back(worldMat);
        }
        //インスタンス行列の更新
        ptrDraw->UpdateMultiMatrix(m_MatVec);
        //結果を次の入力にコピー
        for (size_t i = 0; i < m_ResultDataVec.size(); i++) {
            m_LocalDataVec[i] = m_ResultDataVec[i];
        }
    }
    // CSの開放
    ID3D11UnorderedAccessView* ppUAViewNULL[1] = { nullptr };
    pID3D11DeviceContext->CSSetUnorderedAccessViews(0, 1, ppUAViewNULL, nullptr);
    ID3D11ShaderResourceView* ppSRVNULL[1] = { nullptr };
    pID3D11DeviceContext->CSSetShaderResources(0, 1, ppSRVNULL);
 となります。
    pID3D11DeviceContext->CopyResource(m_ReadBackBuffer.Get(), m_OutBuffer.Get());
 でバッファ間をコピーして
    if (SUCCEEDED(pID3D11DeviceContext->Map(m_ReadBackBuffer.Get(), 0, D3D11_MAP_READ, 0, &MappedResource)))
    {
 とマップに成功したら
        memcpy(&m_ResultDataVec[0], MappedResource.pData, m_NumVertices * sizeof(LocalData));
 とm_ResultDataVecにコピーします。
 あとは、
        //行列の配列をクリア
        m_MatVec.clear();
        Mat4x4 worldMat;
        for (auto& v : m_ResultDataVec) {
            worldMat.affineTransformation(
                Vec3(m_Scale),
                Vec3(0.0f),
                qtCamera,
                v.m_WorldPosition
            );
            m_MatVec.push_back(worldMat);
        }
        //インスタンス行列の更新
        ptrDraw->UpdateMultiMatrix(m_MatVec);
 とm_MatVecを再設定します。この際、描画用メッシュをカメラのほうを向かせます。
 最終的には
        //インスタンス行列の更新
        ptrDraw->UpdateMultiMatrix(m_MatVec);
 が更新処理の完成です。
 そして
        //結果を次の入力にコピー
        for (size_t i = 0; i < m_ResultDataVec.size(); i++) {
            m_LocalDataVec[i] = m_ResultDataVec[i];
        }
 と、次のCSへの入力に、結果をコピーします。
 m_LocalDataVecの数よりm_ResultDataVecの数は小さいので安全に実行されます。

まとめ

 今回は、CSを使ったちょっと複雑な処理を紹介しました。
 実際に、ちょっと重いかなと感じた処理をCSを使って高速化することに利用できると思います。
 すでに記述された処理を、部分的にでもCSに書き直すことで、高速化をはかれると思います。
 次項尾では、描画の高速化の手法でジオメトリシェーダを紹介します。