渋谷ほととぎす通信

新しいこと、枯れたこと問わず大庭が興味を持ったものを調査、生活の効率を求める完全趣味の技術ブログ。基礎を大事にしています。

Unityでコンピュートシェーダを始めてみた



コンピュードシェーダのUnity公式マニュアルを見ながら、Mac環境で始めてみたいと思います。
ちなみに、筆者は全くコンピュートシェーダを書いたことがないので、初心者目線で行きます。

前提としてコンピュートシェーダとは、頂点シェーダやフラグメントシェーダと違い、GPUを描画に使うのではなく、演算処理をさせるためのシェーダです。
※GPGPUとも呼ばれています
今までやってきた描画周りのシェーダと大きく作法として違うのはMaterialオブジェクトが不要だということです。コンポーネントにSerializeFieldでアタッチしたり、Resource.Loadで呼んだりなどして、コンピュートシェーダオブジェクトに対し、C#で直接処理を書きます。

またコンピュートシェーダは実行環境に制限があります。今回はMacで作業しますが、Mac環境であればMetalは必須です。
参考 : Metal - Unity マニュアル

とりあえず何もかもが初めてなので、大量のキューブを回転させるというシンプルな処理を書いてみます。


Unityでコンピュートシェーダファイルを作るとこういうコードになります。
※拡張子に.computeをつけるとコンピュートシェーダと認識されます


1つずつメモっていくと。

#pragma kernel CSMain

#pragmaコンパイルディレクティブを使ってCSMainをカーネルと定義しています。
コンピュートシェーダでは実行関数をカーネルと呼ぶようです。


RWTexture2D<float4> Result;

頭のRWが混乱の元ですが、コンピュートシェーダ内でTexture2Dを使うための変数です。RWは読み書きが可能ということを指します。コンピュートシェーダ内で書き込みしない場合はRWを取りTexture2D<float4>と書くこともできます。


[numthreads(8,8,1)]

実行するスレッド数の指定です。この場合だと8 * 8 * 1 = 64スレッド使うということになります。
またスレッドとは別でスレッドグループという概念が存在しますが、以下の記事がとても丁寧なのでコチラを参考に。とても勉強になりました。ありがとうございます。


void CSMain (uint3 id : SV_DispatchThreadID)

出ましたカーネルです。SV_DispatchThreadIDは、HLSLでおなじみのセマンティクスです。

  • SV_GroupThreadID
  • SV_GroupID
  • SV_DispatchThreadID
  • SV_GroupIndex

と指定することが出来ます。説明自体は先に紹介した記事をどうぞ。


今回は大量のキューブをただ回すために、デフォルトのコンピュートシェーダを少しいじってみます。

回転値を更新するのでテクスチャは使用しません。
RWStructuredBuffer<float> Resultと、float型を扱うコンピュートバッファを持たせています。
このシェーダを実行するとResult内の値が1増えて格納されます。

コンピュートバッファはGPU内に保持され、GPUからアクセスされるバッファです。
またCPUからもアクセスして結果を取得したり、値をセットしたりすることも出来ます。

ということで、CPU側の処理(C#)を書いていきます。
成果物のコードを貼っておきます。

長ったらしいですが、大したことは書いていません。
ポイントを抜粋しておきます。

コンピュートシェーダとコンピュートバッファの紐づけ

CPUからGPUに対し、コンピュートバッファの生成と、コンピュートシェーダにコンピュートバッファのセットを行うコードが以下です。

// コンピュートバッファの作成
_buffer = new ComputeBuffer(_cubeCount, sizeof(float));
// シェーダとバッファの関連付け
_computeShader.SetBuffer(_mainKernel, "Result", _buffer);

シェーダの起動

Update関数内でシェーダを実行し、回転値を更新させています。

_computeShader.Dispatch(_mainKernel, threadGroupX, 1, 1);

シェーダの実行結果を取得

回転値の更新が終了したら、GPUから更新後の結果を受け取るためにGetDataメソッドを使います。

var data = new float[_cubeCount];
// 更新結果を取得
_buffer.GetData(data);

この当たりは、以前やったUnity ジョブシステムと親しいものを感じます。


実行するとこんな感じです。

f:id:esakun:20181214221304g:plain

最後に

はじめてのコンピュートシェーダが動いてよかったです。ただし、このサンプルは処理内容が簡単すぎて並列処理させる必要は無いです。
大量のキューブを動かすことに関しては、ほぼGPGPUの恩恵はないと思うので、次回もう少しGPGPUの意義を感じるサンプルを作ってみたいと思います。

環境

  • Unity2018.2.11f1
  • macOS HighSierra 10.13.6

参考

ひとこと

今日Unity2018.3.0f2が正式リリースされてテンションが上り気味です。
GPGPUをやっていくうえで、コチラの書籍を呼んでおきたいなと。正月中の読書に良いかも。