DirectX11 GPUパーティクル(ComputeShader)の実装

DirectX11を使ったComputeShaderパーティクルシステムの実装を説明しているサイトがほとんどないので、自分試した結果をシェアしたいと思います。

※ コード一部は自作フレームワークの関数利用していますので、使う際自分のフレームワークに直してください。

結果はこんな感じです！(100000パーティクル)
![コメント 2020-12-09 212822.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451854/f4d0f632-52da-a91b-31e2-95795d47c0c7.png)



# コンピュートシェーダー(ComputeShader)

まずなぜコンピュートシェーダーでやるかについて簡単に説明します。

![LCsQasgpmFGL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451854/961228b5-0299-3a4f-9f75-141f4d90e481.png)


# 流れ
![LCsQel04nQu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451854/d5c728c6-b476-49a9-e281-e311b7ade9be.png)

#コード部分
```c++:Particle.h
// パーティクル用頂点レイアウト
struct VERTEX_3D_PARTICLE
{
	D3DXVECTOR3 Position;
	D3DXVECTOR2 TexCoord;
};
//今回使うパーティクルの資料例
struct ParticleCompute {
	// 座標
	D3DXVECTOR3 pos;
	// 速度
	D3DXVECTOR3 vel;
	// ライフ
	float life;
};

// コンピュートシェーダー
ID3D11ComputeShader mComputeShader;
// パーティクル
ParticleCompute* mparticle;
// バッファ
ID3D11Buffer* mVertexBuffer;
ID3D11Buffer* mpParticleBuffer;
ID3D11Buffer* mpResultBuffer;
ID3D11Buffer* mpPositionBuffer;
// SRV
ID3D11ShaderResourceView* mpParticleSRV;
ID3D11ShaderResourceView* mpPositionSRV;
// UAV
ID3D11UnorderedAccessView* mpResultUAV;

```

```c++:Particle.cpp

void Init(){

   //頂点資料入れる (Sizeは自分で決める)
   VERTEX_3D_PARTICLE vertex[4];
   vertex[0].Position = D3DXVECTOR3(-Size, Size, 0.0f);
   vertex[0].TexCoord = D3DXVECTOR2(0.0f, 0.0f);
   vertex[1].Position = D3DXVECTOR3(Size, Size, 0.0f);
   vertex[1].TexCoord = D3DXVECTOR2(1.0f, 0.0f);
   vertex[2].Position = D3DXVECTOR3(-Size, -Size, 0.0f);
   vertex[2].TexCoord = D3DXVECTOR2(0.0f, 1.0f);
   vertex[3].Position = D3DXVECTOR3(Size, -Size, 0.0f);
   vertex[3].TexCoord = D3DXVECTOR2(1.0f, 1.0f);

   //パーティクル資料生成(Amountは最大数)
   mParticleAmount = Amount;
   mparticle = new ParticleCompute[Amount];

   //パーティクルの資料入れる（固定かランダムとか自分で作る）
   for (int i = 0; i < setting->Amount; i++) {
       mparticle[i].vel = D3DXVECTOR3(0,1,0); // 速度
       mparticle[i].life = 300.0f; // ライフ（フレーム）
       mparticle[i].pos = D3DXVECTOR3(0,0,0); // 座標
   }

   D3D11_BUFFER_DESC bd;
   ZeroMemory(&bd, sizeof(bd));
   bd.Usage = D3D11_USAGE_DYNAMIC;
   bd.ByteWidth = sizeof(VERTEX_3D_PARTICLE) * 4;
   bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
   bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

   D3D11_SUBRESOURCE_DATA sd;
   ZeroMemory(&sd, sizeof(sd));
   sd.pSysMem = vertex;

   // ID3D11Device
   Renderer::GetDevice()->CreateBuffer(&bd, &sd, &mVertexBuffer);

   /*
   バッファ生成 
   D3D11_BUFFER_DESCの設定：
　 desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
   desc.ByteWidth = elementSize * count;
   desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
   desc.StructureByteStride = elementSize;
   desc.Usage = D3D11_USAGE_DYNAMIC;
   desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
   */
   Renderer::CreateStructuredBuffer(sizeof(ParticleCompute), (UINT)mParticleAmount, nullptr, &mpParticleBuffer);
   Renderer::CreateStructuredBuffer(sizeof(D3DXVECTOR3), (UINT)mParticleAmount, nullptr, &mpPositionBuffer)
   Renderer::CreateStructuredBuffer(sizeof(ParticleCompute), (UINT)mParticleAmount, nullptr, &mpResultBuffer);

   // SRV生成
   Renderer::CreateBufferSRV(mpParticleBuffer, &mpParticleSRV);
   Renderer::CreateBufferSRV(mpPositionBuffer, &mpPositionSRV);
   // UAV生成
   Renderer::CreateBufferUAV(mpResultBuffer, &mpResultUAV);

  // コンピュートシェーダー作成
  {
     FILE* file;
     long int fsize;

     file = fopen("ParticleCS", "rb");
     fsize = _filelength(_fileno(file));
     unsigned char* buffer = new unsigned char[fsize];
     fread(buffer, fsize, 1, file);
     fclose(file);

     Renderer::GetDevice()->CreateComputeShader(buffer, fsize, nullptr, &mComputeShader);

     delete[] buffer;
  }
}

void Update(){
   // パーティクルの資料をバッファに入れる
   D3D11_MAPPED_SUBRESOURCE subRes;
   Renderer::GetDeviceContext()->Map(mpParticleBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subRes);
   ParticleCompute* pBufType = (ParticleCompute*)subRes.pData;
   memcpy(subRes.pData, mparticle, sizeof(ParticleCompute) * mParticleAmount);
   Renderer::GetDeviceContext()->Unmap(mpParticleBuffer, 0);
   
   //　コンピュートシェーダー実行
   ID3D11ShaderResourceView* pSRVs[1] = { mpParticleSRV };
   Renderer::GetDeviceContext()->CSSetShaderResources(0, 1, pSRVs);
   Renderer::GetDeviceContext()->CSSetShader(&mComputeShader, nullptr, 0);
   Renderer::GetDeviceContext()->CSSetUnorderedAccessViews(0, 1, &mpResultUAV, 0);
   Renderer::GetDeviceContext()->Dispatch(256, 1, 1);

   // 戻った計算結果をバッファに入れる
   ID3D11Buffer* pBufDbg = Renderer::CreateAndCopyToBuffer(mpResultBuffer);
   D3D11_MAPPED_SUBRESOURCE subRes;
   Renderer::GetDeviceContext()->Map(pBufDbg, 0, D3D11_MAP_READ, 0, &subRes);
   ParticleCompute* pBufType = (ParticleCompute*)subRes.pData;
   memcpy(mparticle, pBufType, sizeof(ParticleCompute) * mParticleAmount);
   Renderer::GetDeviceContext()->Unmap(pBufDbg, 0);
   pBufDbg->Release();

   // 座標を座標バッファに入れる(頂点シェーダーで使う)
   Renderer::GetDeviceContext()->Map(mpPositionBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subRes);
   D3DXVECTOR3* pBufType = (D3DXVECTOR3*)subRes.pData;
   for (int v = 0; v < mParticleAmount; v++) {
      pBufType[v] = mparticle[v].pos;
   }
   Renderer::GetDeviceContext()->Unmap(mpPositionBuffer, 0);
}

void Render(){
   // ビルボード
   Camera* camera = Application::GetScene()->GetGameObject<Camera>(CameraLayer);
   D3DXMATRIX view = camera->GetViewMatrix();
   D3DXMATRIX invView;
   D3DXMatrixInverse(&invView, NULL, &view);
   invView._41 = 0.0f;
   invView._42 = 0.0f;
   invView._43 = 0.0f;

   // ワールド座標、スケールなどの処理
   D3DXMATRIX world, scale, trans;
   D3DXMatrixScaling(&scale, Scale.x, Scale.y, Scale.z);
   D3DXMatrixTranslation(&trans, Position.x, Position.y, Position.z);
   world = scale * invView * trans;
   Renderer::SetWorldMatrix(&world);

   // インプットレイアウト設定など
   Renderer::SetInputLayout(1);
   UINT stride = sizeof(VERTEX_3D_PARTICLE);
   UINT offset = 0;
   // VS、PSシェーダー設定
   Shader::Use(SHADER_TYPE_VSPS::Particle);
   // 描画
   Renderer::GetDeviceContext()->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);
   Renderer::GetDeviceContext()->PSSetShaderResources(0, 1, &mTexture); // テクスチャ設定（あれば）
   Renderer::GetDeviceContext()->VSSetShaderResources(2, 1, &mpPositionSRV); // VSに入れる座標設定
   Renderer::GetDeviceContext()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
   Renderer::GetDeviceContext()->DrawInstanced(4, mParticleAmount, 0, 0);
   // インプットレイアウト設定
   Renderer::SetInputLayout(0);
}
```

``` hlsl:ParticleCS.hlsl

// パーティクル構造体
struct ParticleCompute
{
    float3 pos;
    float3 vel;
    float life;
};

// CS設定
struct CSInput
{
    uint3 groupThread : SV_GroupThreadID;
    uint3 group : SV_GroupID;
    uint groupIndex : SV_GroupIndex;
    uint3 dispatch : SV_DispatchThreadID;
};

// In
StructuredBuffer<ParticleCompute> particle : register(t0);
// Out
RWStructuredBuffer<ParticleCompute> BufOut : register(u0);

#define size_x    256
#define size_y      1
#define size_z      1

[numthreads(size_x, size_y, size_z)]
void CSFunc(const CSInput input)
{
    int index = input.dispatch.x;
         
    float3 result = particle[index].pos + particle[index].vel;
        
    BufOut[index].pos = result;
    BufOut[index].life = particle[index].life - 1.0f;
    BufOut[index].vel = particle[index].vel;
  
}
```

``` hlsl:ParticleVS.hlsl

struct VS_IN_PARTICLE
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    uint InstanceID : SV_InstanceID;
};

// DrawInstance用座標
StructuredBuffer<float3> Position : register(t2);

void main(in VS_IN_PARTICLE In, out PS_IN_PARTICLE Out)
{
       
    matrix wvp;
    wvp = mul(World, View);
    wvp = mul(wvp, Projection);
   
    In.Position.xyz += Position[In.InstanceID];
  
    Out.Position = mul(In.Position, wvp);
    Out.WorldPosition = mul(In.Position, World);
    Out.TexCoord = In.TexCoord;

}
```

``` hlsl:ParticlePS.hlsl

struct PS_IN_PARTICLE
{
    float4 Position : SV_POSITION;
    float4 WorldPosition : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

Texture2D g_Texture : register(t0);
SamplerState g_SamplerState : register(s0);

void main(in PS_IN_PARTICLE In, out float4 outDiffuse : SV_Target)
{
    outDiffuse = g_Texture.Sample(g_SamplerState, In.TexCoord);
}

```