トップ > 【Unity】ComputeShaderの使い方を紹介します!パート2!
更新日 2022/7/26

【Unity】ComputeShaderの使い方を紹介します!パート2!

ComputeShaderの使い方の続きの記事になります。

前回の記事ではコンピュートシェーダーの計算結果を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&lt;Vector4&gt;());

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で内容データを取得できます。

ComputeShader_Play

各ピクセルの転送をシェーダーで行う

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を実現できるようにコンピュートシェーダーをうまく利用しましょう。

ComputeShader_Play

2ページに渡ってコンピュートシェーダーの利用方法を紹介しました。
次回からはコンピュートシェーダーを使って実践的なGPGPUを紹介できればと思います。


関連ページ


Copyright ©2022 - 2024 うにぉらぼ