Unity 4.0: Compute Shaderを使ってみる

※画像はクリックすると大きなサイズになります.

1.はじめに

Unity 4.0になってWindows版はDirect3D11対応になりました.Direct3D11対応になったことで,テセレーションなどのシェーダモデル5.0のテクニックなどがUnityで使用できるようになりましたが,Compute Shaderも使えるようになったので,とりあえず使い方を調べてみました.なお,今回はCompute Shader自体はかなり簡単にだけ触れます.詳細な入門記事は今後やってきたいと思います.
Compute Shaderは,以下のような用途やシチュエーションに使えるんじゃ無いかと思います.

  • 画像処理・画像認識・ポストエフェクト
    • テクスチャは元々GPU上に乗ってるリソースなので相性がいい
  • 物理シミュレーションやアニメーション
    • 水面シミュレーションや弾性変形など
    • 独自のパーティクル演算
  • その他レンダリングテクニックへの応用
  • Unity上でCPUよりもGPUの並列計算を使用したい
    • UnityのC#やJavascriptで大量の演算やるのは大変なのでGPUで高速化

などですね.
今回のサンプルは下記のUnitypackageになります.画像1のようにCompute Shaderで3×3ラプラシアンフィルタをものになります.
今回のサンプルUnityComputeShaderSample,zip

unitycs001

図1. 今回のサンプル左のLena画像は無処理,右のLena画像はCompute Shaderでフィルタ処理をかけたもの

2.Unityのドキュメント

今回の話題で参考にしたUnityのドキュメントは以下です.必要な情報は大体あるのですが,細かい部分で注意点があったのでそれは記事中で触れていきます.

3.Unityのエディター内での準備

とりあえず,いきなりCompute Shaderを書く前にUnityのエディター内での準備について説明します.

Hierarchyビュー

まずは,図2のHierarchyビューに追加したものです.Directional lightとMain Cameraはそのままライトとカメラで特殊なことはしていません.PlaneとPlaneCSはどちらもGameObjectから作成したPlaneなのですが,PlaneはLena画像をテクスチャにしたマテリアルを貼り付けたもので,PlaneCSはそれにスクリプトやComputeShaderを適用したものです.図1で言うとPlaneが左側で,PlaneCSが右側ですね.
unitycs002
図2.Hierarchyビュー

Projectビュー

続いて,図3のProjectビューですね.とりあえずdiffuseマテリアルは左側のPlane用ですね.テクスチャ貼って表示するだけ用です.右のPlaneCSには,CSLaplace.csとLaplaceShader.computeを貼り付けています.
Compute Shaderをアセットとして追加する場合には,ここで右クリック→「Create」から追加できます.
unitycs003
図3.Projectのビュー

PlaneCSのInspector

PlaneCSのInspectorは図4のような感じです.赤枠で囲った部分がスクリプトを割り当てた部分ですが,入力テクスチャとCompute Shaderはそれぞれスクリプト内でpublic化してるのでInspectorから割り当てすることができるようになっています.
unitycs004
図4PlaneCSのInspector

テクスチャの注意点

Unityではテクスチャが読まれたときに自動的に圧縮フォーマット設定になるのですが,今回はInspectorで非圧縮フォーマットに変えました.
図5.の赤枠内でFormatをTrueColorにします.
なぜ,圧縮フォーマットにしないかというとHLSLのComputeShader内でテクスチャを引っ張る際にSamplerを使う方法とTextureのLoad命令や配列式のアクセスをする方法があるのですが,圧縮フォーマットの場合はSamplerを使ってサンプリングしないと圧縮の展開ができないと言う問題があって,今回のケースではサンプルをシンプルにするためにSamplerを使わない方法にしました.
unitycs005
図5.テクスチャのInspector

4.Compute Shaderの記述

今回のCompute Shaderコードは下記です.

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
Texture2D< float4 > srcTexture;
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D< float4 > Result;
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
	float4 col = srcTexture[id.xy] * 8;
	for(int x=-1;x<=1;x++)
	{
		for(int y=-1;y<=1;y++)
		{
			col+=srcTexture[id.xy+int2(x,y)] * -1;
		}
	}
    Result[id.xy] =  col + srcTexture[id.xy];
}

普段,HLSLでCompute Shaderを書いてる方だとあまり特殊な記述は見かけないと思うのですが,

#pragma kernel CSMain

はUnityオリジナルの記述になります. CSMainのコードを見ていただくとエントリーポイントとなる関数の関数名と同じだと気づくと思います.この記述はUnityのスクリプト側にこのCompute Shaderの開始関数を伝えるために必要なキーワードになります.コメントを読むと1つのファイルに複数のCompute Shaderを記述することができるということがわかりますが,複数記述する際には#pragma kernel複数書くことになります.
なお,今回のシェーダではスレッドグループ内のスレッド数は(8,8,1)ということで8×8単位のタイル処理でやります.

5.Unityのスクリプト側の動作

続いて,C#側のコードの記述です.

using UnityEngine;
using System.Collections;
public class CSLaplace : MonoBehaviour {

public Texture srcTexture;
public ComputeShader cs;

private RenderTexture destTexture;

// Use this for initialization
void Start () {

destTexture = new RenderTexture( srcTexture.width, srcTexture.height, 0, RenderTextureFormat.ARGB32 );
destTexture.enableRandomWrite = true;
destTexture.Create();

}

// Update is called once per frame
void Update () {

if(!SystemInfo.supportsComputeShaders)
{

Debug.LogError(“Compute Shader is not Support!!”);
return ;

}

cs.SetTexture(0, “srcTexture”, srcTexture);
cs.SetTexture(0, “Result”, destTexture);

cs.Dispatch(0, srcTexture.width/8,srcTexture.height/8,1);

renderer.material.mainTexture = destTexture;

}

}

publicなメンバとしてComputeShaderクラスのcsがありますが,こうしておくと前述の通りInspectorでCompute Shaderのコードを適用できます.
privateなメンバのRenderTextureクラスのdestTextureはCompute Shaderの結果を書き出すテクスチャです.通常のTextureクラスはCompute Shaderで読み込みはできますが,書き込みはできません.
Start関数では,destTextureのインスタンス生成と初期化をしていますが,この際に一番大事なことはRenderTexture.enableRandomWriteをTrueにすることです.これがTrueでないRenderTextureはComputeShaderで書き込みができません.
Update関数では,srcTextureをCompute Shaderに渡して計算結果をdestTextureに書き出します.テクスチャのセットはComputeShader.SetTexture関数で行います.第2引数の名前はCompute Shaderのテクスチャ名です.TextureとRWTextureで区別はありません.
ComputeShader.DispatchはCompute Shaderの発行です.Direct3D11のDispatch関数と同じくスレッドグループ数を渡します.今回のシェーダでは各スレッドグループのスレッド数が(8,8,1)なのでスレッドグループ数は(テクスチャの幅/8, テクスチャの高さ/8, 1)でいきます.
ComputeShader.SetTextureやComputeShader.Dispatchの第1引数は今回のサンプルでは0ですが,Compute Shaderのシェーダを記述したファイルの中に複数の関数を記述していた場合には,そのIDを入れます.今回は1つしかシェーダが無いので0でいいということになります.
最後に,Compute Shaderの計算結果を格納したテクスチャをマテリアルのmainTextureにしてます.これで画面に出ますね.

6.まとめ

あんまりCompute Shaderそのものの解説はしませんでしたが,ひとまずCompute ShaderがUnityで使えるようになりました.
感想としては,「楽」ですね.シェーダ使うまでの準備は少ないので.RenderTextureがすんなり使えるので,今回みたいなメッシュに貼るテクスチャに適用する以外にもポストエフェクトには良さそうです.とりあえず個人的には,Compute Shaderのプロトタイピングが楽にできるのでいい感じです.
それから,今回はテクスチャに対する処理を記述したのですが計算したバッファをテクスチャ以外の用途に使うこともできるようです.その際には,ComputeBufferクラスを使っていくと良いようです.まだあんまりComputeBufferクラスの使い方が把握できてないので,その辺は使い方がわかったら書いていきます.