Help us understand the problem. What is going on with this article?

[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();
    }
}
IKD-Member
Perlが大好きですがC#とC++を書いている人です. クライアントもサーバーもいけます. ゲーム作るのが好きですがソフトも作ります. [主に使ってるやつ]Perl,C#,C++,JavaScript,Java / Unity,OpenGL,XNA [使えなくもないやつ]ActionScript,Ruby,Python / DirectX
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away