Edited at

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

More than 1 year has passed since last update.

UnityのシェーダーにComputeShaderというものがあります

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

環境

Unity 2017.4.1f1 64bit


オブジェクトを動かす

UnityのCubeを動かしてみましょう

NoName_2018-7-11_12-59_No-00.png

これをとりあえず右にComputeShaderを使って動かします


ComputeShaderを書いてみる


シェーダーファイルを作る

NoName_2018-7-11_13-1_No-00.png

最初の中身はこんな感じ

#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のソース全体


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を設定できるように変数を作ります

[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;

再生すると右に移動していく

NoName_2018-7-11_12-59_No-00.png

無事GPUで計算した座標に移動させることができました!

おめでとうございます!


いらなくなったBufferを解放する

ちゃんと解放しましょう

private void OnDestroy()

{
m_Buffer.Release();
}


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();
}
}