UnityのシェーダーにComputeShaderというものがあります
昔に触りにくそうという理由で諦めてしまったので、同じ道を辿る人が少しでも減るように超入門的なComputeShaderについての内容を書いていきます
環境
Unity 2017.4.1f1 64bit
オブジェクトを動かす
UnityのCubeを動かしてみましょう
これをとりあえず右にComputeShaderを使って動かします
ComputeShaderを書いてみる
シェーダーファイルを作る
最初の中身はこんな感じ
#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を受け渡しするようになっていますが・・・
RWTexture2D<float4> Result;
今回は座標を受け渡ししたいので
RWStructuredBuffer<float> Result
にしましょう(横移動だけの予定なのでfloatだけ)
基本的にGPUのデータをCPU側に渡すために使います
次にCPU側からGPU側に現在の座標を渡せるようにするために
ComputeShaderに float positionX;
を追加します
#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)
現在の処理はデフォルトの
Result[id.xy] =
float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
となっていますが、今回は座標を移動させたいので
Result[0] = positionX + 0.01f;
としましょう
これでComputeShader側のやるべきことは終わりです!
ComputeShaderのソース全体
#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を設定できるように変数を作ります
[SerializeField]
private ComputeShader m_ComputeShader;
[SerializeField]
private Transform m_Cube;
ComputeShaderにはProjectビューから、さきほど作ったComputeShaderを入れてください
Transformには移動させたいCubeのTransformを入れます
ComputeBufferの設定
ここからC#でComputeShaderを扱うためのメイン処理を書いていきます
先ほどComputeShaderで書いた RWStructuredBuffer<float> Result;
を受け取るための変数を用意します
private ComputeBuffer m_Buffer;
そして開始時に初期化します
void Start ()
{
m_Buffer = new ComputeBuffer(1, sizeof(float));
m_ComputeShader.SetBuffer(0, "Result", m_Buffer);
}
詳しい説明
m_Buffer = new ComputeBuffer(1, sizeof(float));
この行でComputeShaderの結果を受け取るためのバッファー(要素float1個分)を用意しています
m_ComputeShader.SetBuffer(0, "Result", m_Buffer);
そしてそのバッファーをComputeShaderに設定しています
これでCPUとGPUが繋がりました!
毎フレームデータを更新する
Update関数の中でデータを更新していきます
まずはCPU側のCubeの座標をGPUに送ります
m_ComputeShader.SetFloat("positionX", m_Cube.position.x);
処理の内容としてはComputeShaderにFloatをSetする関数を使って、positionXにm_Cubeのposition.Xを渡しているという内容です
これでGPU側がCPU側のCube座標について知ることができました!
GPU側に必要な情報は全て渡し終えたので計算処理をしてもらいます
m_ComputeShader.Dispatch(0, 8, 8, 1);
ComputeShaderのDispatchを使うことで処理を開始できます
(ひとまず基礎的なことだけを知るためなので引数については省略)
Dispatchを呼んだことでGPU側でCubeを移動させる処理が完了しました!
しかしCPU側にそのデータは存在していないため、m_Cubeに適用することができません
なので取得をします
var data = new float[1];
m_Buffer.GetData(data);
詳しい説明
var data = new float[1];
受け取るデータはX軸の座標のみなのでfloatの要素数1の配列を用意します
m_Buffer.GetData(data);
用意した配列にm_Bufferに入っているデータを入れます
これでGPU側のデータをCPU側で使えるようになりました!
移動させる
Cubeを取得したデータを元に移動させます
float positionX = data[0];
var boxPosition = m_Cube.position;
boxPosition.x = positionX;
m_Cube.position = boxPosition;
無事GPUで計算した座標に移動させることができました!
おめでとうございます!
いらなくなったBufferを解放する
ちゃんと解放しましょう
private void OnDestroy()
{
m_Buffer.Release();
}
C#側のソース全体
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();
}
}