簡単!自作ポストエフェクトComputeShader入門
要件
RenderTextureに対してComputeShaderでポストエフェクトをかけていきます。
例として描画される景色をRGBに分割したエフェクトをかけていきます。
レンダリングパイプラインをあまり意識せず、シンプルに実装していきます。
ComputeShaderはUnityShaderと比較して文法に関する前提知識が要らないため導入が楽です。
実装
まず、C#側で準備を進めます。
画面の描画を取得するために入力用のRenderTextureを作成してカメラに登録します。
Camera camera;
RenderTexture image_in;
//入力先画像の生成
image_in = new RenderTexture( size.x, size.y, 0, RenderTextureFormat.ARGB32);
image_in.enableRandomWrite = true;
image_in.name = "CameraInput";
image_in.Create();
//カメラに登録
camera.targetTexture = image_in;
次に出力用のRenderTextureを作ります。
先ほどと同様に作成してCanvasに登録します。
canvasRawImage = image_out;
この処理は別記事で紹介したRenderTextureをCanvasに登録するものと同じです。
ここまでで入力用のカメラ画像と出力用の空画像ができました。
ここからShaderでポストエフェクトをかける手順を記述します。
現状:カメラ入力 画面出力
次:カメラ入力 -> Shader -> 画面出力
とりあえずComputeShaderを作ります。
Projectビューで Create > Shader > ComputeShader で作成。
ダブルクリックで開くとほぼ何も書かれていない関数が実装されていると思います。
これをc#から呼び出します。
c#側で宣言
[SerializeField] private ComputeShader rgbShader;
inspectorからComupute Shaderをアタッチします。
ComputeShaderに入力用と出力用の画像を参照する変数を関数外に宣言します。引数のようなものと考えてもらって構いません。
Shader側
RWTexture2D<float4> image_in;
RWTexture2D<float4> image_out;
次にc#からComputeShaderの変数の関連付けをします。
c#側
rgbShader.SetTexture(0, "image_in", image_in);
rgbShader.SetTexture(0, "image_out", image_out);
最後にループ実行される関数内に以下の実行関数を記述します。
rgbShader.Dispatch(0, outputSize.x / (int)threadGroupSize[0], outputSize.y / (int)threadGroupSize[1], (int) threadGroupSize[2]);
ここまででComputeShaderを実行する流れができました!
お疲れ様です!
..本命はここからですね。
ここからポストエフェクトの内容を記述していきます。
以下の内容に関しては個々で好きな画像処理を施しても良いと思います。
どんなエフェクトがかけたいかを考えて、CG系の知識からどう計算すれば画像データが変わるか記述します。
今回は簡単で変化をわかりやすくするために、RGB値を直接いじっていこうと思います。
以下がコード全文だ!!
pragma kernel CSMain
RWTexture2D<float4> image_in ;
RWTexture2D<float4> image_out ;
float3 shiftPixelRGB ;
[numthreads(8, 8, 1)] //8*8スレッドで回す
void CSMain(uint3 dtid : SV_DispatchThreadID)
{
image_out[dtid.xy] = float4(image_in[dtid.xy + shiftPixelRGB.x].x, image_in[dtid.xy + shiftPixelRGB.y].y, image_in[dtid.xy + shiftRGB.z].z, 1);
}
実際の処理は一行ですね。
何故こういう形になるか解説します。
ComputeShaderの処理はGPU上でいくつかのスレッドに分かれて実行されます。
参考サイト:http://neareal.com/2601/
つまりは、for文のxy二重ループもといxyz三重ループのようなものとして捉えられます。
これによって引数であるuint3 dtidが変換していきます。
変化する値の範囲は前述したc#のDispatchの引数とShaderのnumthreadによって変わります。
dtid.xyを今回は画像上の位置として使用しています。
これにあらかじめ用意したshiftPixelRGBのそれぞれに対応する分だけxy方向に移動させた座標を算出。
入力画像のRGB値を持ってきています。
image_in.x,y,zはそれぞれR,G,Bにあたります。
それらをfloat4の型に入れて出力画素image_outに入れている。という記述です。
位置の算出と画素の代入を一気にやってるので本来は行を分けるべきかもしれません。あるいはもっとスマートな書き方があるかもしれません。
画像処理もとい数学的知識があればスマートにかけるはずなので今後に期待です。
以上で実行できます。
終わりに
後は、どうしたら画像が加工できるのかCGの知識を身につけることでポストエフェクトが作れます。
今回はループ関数の中にDispatch、わ記述しましたが、一定期間だけポストエフェクトをかけたいのならコルーチンを使えばできます。
また、本来はUnityのレンダリングパイプラインに関する深い知識を持つことで
入力用と出力用の画像を分けずにRender Textureをレンダリング途中でShaderによって書き換えることができます。