はじめに
RenderTextureに座標情報、速度などその他様々なデータを詰めてComputeShaderで計算を行い、その結果をCPU側で受け取りたいということは多くあると思います.このようなことを実現するためには読み込み可能かつ、書き込み可能なRenderTexture(以後RWTextureという)を用意してデータをそこに詰め込んでいくことが一番楽でしょう.(読み込み用,書き込み用とTextureを分けるのはめんどくさいので..)
しかしUnityではただRenderTextureを生成してそのデータを送信するだけではRWTextureとしてComputeShaderで処理することが出来ないのでそのことについて記述していきます.
ComputeBufferつかえばよくない?
もちろんその通りです.ComputeBufferを使用してRWStructuredBufferとしてComputeShader内で扱うのが一番楽でしょう.自分的にはRenderTextureをCPUとComputeShaderの間での架け橋にしたい時はちゃんとデータが処理されているか確認したい時です.つまりどういうことかというと、UnityではCanvas/RawImage
やGUI.DrawTexture
などでGameView上へTextureの描画をすることができるのプレビューとしてもRenderTextureを使うことができるということです.
こんな感じでこの例ではAnimation中のモデルの頂点座標と法線方向
UnorderedAccessView(UAV)とは
任意の場所にアクセスが出来るTextureのView(フォーマット)のことです.このことは「出力リソースの任意の位置に書き込み可能」ということと等しい意味を持ちます.つまりRWTextureとしてComputeShaderで処理するためにはCPU側でTextureを生成する時にこの設定をしてあげないといけないということです.
また補足とし読み込みしか行えないViewを**ShaderResourceView(SRV)**といいます.
スクリプト
RenderTextureをComputeShaderへ送信してRWTextureとしてComputeShaderで受け取る場合(UAVとしてTextureを扱う)は enableRandomWrite = true
とする必要がある.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class dispatch : MonoBehaviour
{
[SerializeField] RenderTexture buffer;
[SerializeField] ComputeShader cs;
int kernel;
void Start()
{
buffer.Release();
buffer.enableRandomWrite = true;
kernel = cs.FindKernel("calc");
cs.SetTexture(kernel, "buffer", buffer);
}
void Update()
{
cs.SetFloat("time", Time.realtimeSinceStartup);
uint x, y, z;
cs.GetKernelThreadGroupSizes(kernel, out x, out y, out z);
cs.Dispatch(kernel, 1, 1, 1);
}
}
#pragma kernel calc
RWTexture2D<float4> buffer;
float time;
[numthreads(64,1,1)]
void calc (uint id : SV_DispatchThreadID)
{
float c = frac(time/10.0+float(id/100.0));
buffer[float2(id, 0.5)] = float4(c, 0.0, 0.0, 1.0);
}
以上のようなコードを使えばuvの座標に応じてグラデーションのようにBufferの中の情報が更新されていくことがわかるかと思います.
まとめ
ComputeShaderでRWTextureを使いたいならTextureをUAVにする(enableRandomWrite = true
)