【Unity】ComputeShaderの使い方を紹介します!パート2!
前回の記事ではコンピュートシェーダーの計算結果をRenderTextureに出力しましたが、今回はGraphicsBufferを出力先にしたコンピュートシェーダーを実装します。
コンピュートシェーダーの変更
出力先をRWTexture2DからRWStructuredBufferに変更します。
#pragma kernel CSMain
RWStructuredBuffer<float4> Result;
int Width;
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
Result[id.x + id.y * Width] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
}
Widthはテクスチャの幅をスクリプトから渡します。
出力先が2DテクスチャーからGraphicsBufferになるため2次座標を1次配列のインデックスに変換します。
スクリプトの作成
GraphicsBufferを使用したスクリプトに変更します。
using System.Runtime.InteropServices;
using UnityEngine;
public class TestScript : MonoBehaviour
{
[SerializeField]
private ComputeShader computeShader;
public void Start()
{
GraphicsBuffer buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 64 * 64, Marshal.SizeOf<Vector4>());
int kernel = computeShader.FindKernel("CSMain");
computeShader.SetInt("Width", 64);
computeShader.SetBuffer(kernel, "Result", buffer);
computeShader.Dispatch(kernel, 8, 8, 1);
buffer.Release();
}
}
GraphicsBufferのコンストラクタの説明をします。
第1引数はGraphicsBuffer.Target.Structuredで構造化バッファーを指定します。
第2引数は64*64でバッファーの要素数を指定します。
第3引数は1つの要素のサイズを指定します。
Marshal.SizeOf<Vector4>()でfloat4つ分のサイズになります。
GraphicsBufferは使い終わったら明示的にRelease関数を呼び出します。
確認用のテクスチャを作る
GraphicsBufferの内容は可視的に確認できないので一度テクスチャにして表示できるようにします。
using System.Runtime.InteropServices;
using UnityEngine;
public class TestScript : MonoBehaviour
{
[SerializeField]
private ComputeShader computeShader;
public void Start()
{
GraphicsBuffer buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 64 * 64, Marshal.SizeOf<Vector4>());
int kernel = computeShader.FindKernel("CSMain");
computeShader.SetInt("Width", 64);
computeShader.SetBuffer(kernel, "Result", buffer);
computeShader.Dispatch(kernel, 8, 8, 1);
texture = new Texture2D(64, 64);
Vector4[] data = new Vector4[64 * 64];
buffer.GetData(data);
for (int i = 0; i < 64; i++)
{
for (int j = 0; j < 64; j++)
{
texture.SetPixel(i, j, data[i * 64 + j]);
}
}
texture.Apply();
buffer.Release();
}
private Texture2D texture;
void OnGUI()
{
GUI.DrawTexture(new Rect(0, 0, 64, 64), texture, ScaleMode.ScaleToFit, false);
}
}
64x64サイズの2Dテクスチャーを作成しGraphicsBufferの中身を各ピクセルに転送します。
GraphicsBuffer.GetDataで内容データを取得できます。
各ピクセルの転送をシェーダーで行う
GraphicsBufferからデータを取得しテクスチャの各ピクセルにCPUで設定するのは非常に効率が悪いので、
転送をコンピュートシェーダーで行えるようにカーネルを実装します。
#pragma kernel CSMain
#pragma kernel CSCopy
RWStructuredBuffer<float4> Result;
int Width;
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
Result[id.x + id.y * Width] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
}
RWTexture2D<float4> OutTexture;
[numthreads(8,8,1)]
void CSCopy (uint3 id : SV_DispatchThreadID)
{
OutTexture[id.xy] = Result[id.x + id.y * Width];
}
CSCopyカーネルを作成します。
出力先はRenderTextureにします。
using System.Runtime.InteropServices;
using UnityEngine;
public class TestScript : MonoBehaviour
{
[SerializeField]
private ComputeShader computeShader;
public void Start()
{
GraphicsBuffer buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 64 * 64, Marshal.SizeOf<Vector4>());
int kernel = computeShader.FindKernel("CSMain");
computeShader.SetInt("Width", 64);
computeShader.SetBuffer(kernel, "Result", buffer);
computeShader.Dispatch(kernel, 8, 8, 1);
renderTexture = new RenderTexture(64, 64, 0);
renderTexture.enableRandomWrite = true;
kernel = computeShader.FindKernel("CSCopy");
computeShader.SetBuffer(kernel, "Result", buffer);
computeShader.SetTexture(kernel, "OutTexture", renderTexture);
computeShader.Dispatch(kernel, 8, 8, 1);
buffer.Release();
}
private RenderTexture renderTexture;
void OnGUI()
{
GUI.DrawTexture(new Rect(0, 0, 64, 64), renderTexture, ScaleMode.ScaleToFit, false);
}
}
コンピュートシェーダーでピクセルを計算してテクスチャへ転送する処理を完結できるようになりました。
このようなGPGPUを実現できるようにコンピュートシェーダーをうまく利用しましょう。
2ページに渡ってコンピュートシェーダーの利用方法を紹介しました。
次回からはコンピュートシェーダーを使って実践的なGPGPUを紹介できればと思います。
関連ページ
こちらのページも合わせてご覧下さい。