[C#,Unity] Unityのコンピュートシェーダーでオブジェクトを動かしてみる 

UnityのシェーダーにComputeShaderというものがあります
昔に触りにくそうという理由で諦めてしまったので、同じ道を辿る人が少しでも減るように超入門的なComputeShaderについての内容を書いていきます

環境
Unity 2017.4.1f1 64bit

# オブジェクトを動かす
UnityのCubeを動かしてみましょう
![NoName_2018-7-11_12-59_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/270131/b0353632-d4fe-6587-746b-1ef62021a170.png)
これをとりあえず右にComputeShaderを使って動かします

## ComputeShaderを書いてみる
### シェーダーファイルを作る
![NoName_2018-7-11_13-1_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/270131/a8b98b6a-d245-c648-28eb-d636c2fac534.png)

最初の中身はこんな感じ

```glsl
#pragma kernel CSMain
RWTexture2D<float4> Result;

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] =
        float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
}
```

### データの受け渡し準備をする(ComputeShader)
最初はTexture2Dを受け渡しするようになっていますが・・・

```glsl
RWTexture2D<float4> Result;
```

今回は座標を受け渡ししたいので

```glsl
RWStructuredBuffer<float> Result
```

にしましょう(横移動だけの予定なのでfloatだけ)
基本的にGPUのデータをCPU側に渡すために使います

次にCPU側からGPU側に現在の座標を渡せるようにするために
ComputeShaderに `float positionX;` を追加します

```glsl
#pragma kernel CSMain
RWStructuredBuffer<float> Result;
float positionX;

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] =
        float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
}
```
ComputeShader側での受け渡し準備はこれで完了

### 移動処理を書く(ComputeShader)
現在の処理はデフォルトの

```glsl
Result[id.xy] =
    float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
```
となっていますが、今回は座標を移動させたいので

```glsl
Result[0] = positionX + 0.01f;
```
としましょう
これでComputeShader側のやるべきことは終わりです！

### ComputeShaderのソース全体

```glsl:MoveCompute.compute
#pragma kernel CSMain
RWStructuredBuffer<float> Result;
float positionX;

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[0] = positionX + 0.01f;
}
```

## C#側でComputeShaderを使う
### スクリプト生成
UnityのCreate->C# Scriptからスクリプトを作りましょう
その後適当なオブジェクトにAddComponentしてください

### SerializeFieldの定義
今回は座標を移動させるためのComputeShaderと、現在の位置を取得するためのTransformを設定できるように変数を作ります

```c#
[SerializeField]
private ComputeShader m_ComputeShader;

[SerializeField]
private Transform m_Cube;
```
ComputeShaderにはProjectビューから、さきほど作ったComputeShaderを入れてください
Transformには移動させたいCubeのTransformを入れます

### ComputeBufferの設定
ここからC#でComputeShaderを扱うためのメイン処理を書いていきます
先ほどComputeShaderで書いた `RWStructuredBuffer<float> Result;` を受け取るための変数を用意します

```c#
private ComputeBuffer m_Buffer;
```

そして開始時に初期化します

```c#
void Start ()
{
    m_Buffer = new ComputeBuffer(1, sizeof(float));
    m_ComputeShader.SetBuffer(0, "Result", m_Buffer);
}
```

詳しい説明

```c#
m_Buffer = new ComputeBuffer(1, sizeof(float));
````

この行でComputeShaderの結果を受け取るためのバッファー（要素float１個分）を用意しています

```c#
m_ComputeShader.SetBuffer(0, "Result", m_Buffer);
```

そしてそのバッファーをComputeShaderに設定しています

これでCPUとGPUが繋がりました！

### 毎フレームデータを更新する
Update関数の中でデータを更新していきます
まずはCPU側のCubeの座標をGPUに送ります

```c#
m_ComputeShader.SetFloat("positionX", m_Cube.position.x);
```

処理の内容としてはComputeShaderにFloatをSetする関数を使って、positionXにm_Cubeのposition.Xを渡しているという内容です

これでGPU側がCPU側のCube座標について知ることができました！

GPU側に必要な情報は全て渡し終えたので計算処理をしてもらいます

```c#
m_ComputeShader.Dispatch(0, 8, 8, 1);
```

ComputeShaderのDispatchを使うことで処理を開始できます
(ひとまず基礎的なことだけを知るためなので引数については省略)

Dispatchを呼んだことでGPU側でCubeを移動させる処理が完了しました！
しかしCPU側にそのデータは存在していないため、m_Cubeに適用することができません
なので取得をします

```c#
var data = new float[1];
m_Buffer.GetData(data);
```

詳しい説明

```c#
var data = new float[1];
```
受け取るデータはX軸の座標のみなのでfloatの要素数1の配列を用意します

```c#
m_Buffer.GetData(data);
```

用意した配列にm_Bufferに入っているデータを入れます
これでGPU側のデータをCPU側で使えるようになりました！

### 移動させる
Cubeを取得したデータを元に移動させます

```c#
float positionX = data[0];

var boxPosition = m_Cube.position;
boxPosition.x = positionX;
m_Cube.position = boxPosition;
```

再生すると右に移動していく
![NoName_2018-7-11_12-59_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/270131/afd7a7d4-2561-3e9c-902b-ebe676755342.png)

無事GPUで計算した座標に移動させることができました！
おめでとうございます！

### いらなくなったBufferを解放する
ちゃんと解放しましょう

```c#
private void OnDestroy()
{
    m_Buffer.Release();
}
```

### C#側のソース全体

```c#:MoveCube.cs
using UnityEngine;

public class MoveCube: MonoBehaviour
{
    [SerializeField]
    private ComputeShader m_ComputeShader;

    [SerializeField]
    private Transform m_Cube;

    private ComputeBuffer m_Buffer;

	void Start ()
    {
        m_Buffer = new ComputeBuffer(1, sizeof(float));
        m_ComputeShader.SetBuffer(0, "Result", m_Buffer);
    }

	void Update ()
    {
        m_ComputeShader.SetFloat("positionX", m_Cube.position.x);

        m_ComputeShader.Dispatch(0, 8, 8, 1);

        var data = new float[1];
        m_Buffer.GetData(data);

        float positionX = data[0];

        var boxPosition = m_Cube.position;
        boxPosition.x = positionX;
        m_Cube.position = boxPosition;
    }

    private void OnDestroy()
    {
        m_Buffer.Release();
    }
}
```