渋谷ほととぎす通信

「Unityをわかりやすく」初心者のためのゲーム作りブログ

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

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

こんにちは、Unityエンジニアのオオバです。

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

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

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

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

Unityでコンピュートシェーダーファイルを作るとこういうコードになります。

Unity2018.2.11f1で作成したComputeShaderのデフォルト状態 · GitHub

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

#pragma kernel CSMain

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

RWTexture2D Result;  

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

[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

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

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

Helloworld.compute · GitHub

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

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

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

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

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

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 ジョブシステムと親しいものを感じます。

Unity C#JobSystemをとりあえずやってみる序

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

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

👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

最後に

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

参考

ひとこと

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

オススメ記事
検証環境
  • Unity2018.2.11f1
  • macOS HighSierra 10.13.6