なおしのこれまで、これから

学んだこと・感じたこと・やりたいこと

ComputeShaderを勉強してみる その2

始めに

前回ComputeShaderの基礎を簡単に勉強しました。 このときはXJINEさんのブログを参考にしています。

shitakami.hatenablog.com

neareal.com


前回はComputeShaderの基礎、実行、データの受け渡しを行いました。 今回はテクスチャをComputeShaderに渡して処理を実行するところをまとめようと思います。

今回のブログは引き続きXJINEさんのブログを読みながらまとめていこうと思います。

neareal.com



関数(カーネル)の定義

サイトの引用です。

RWTexture2D<float4> textureBuffer;

[numthreads(8, 8, 1)]
void KernelFunction_A(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    float width, height;
    textureBuffer.GetDimensions(width, height);

    textureBuffer[dispatchThreadID.xy] = float4(dispatchThreadID.x / width,
                                                dispatchThreadID.x / width,
                                                dispatchThreadID.x / width,
                                                1);
}

細かい解説は後程としてnumthreads(8, 8, 1)から 8 * 8 * 1 = 64スレッド実行されます。

テクスチャを用意する

CoputeShader側

ComputeShaderでテクスチャを用意する際は以下のように書きます。(サイト引用

RWTexture2D<float4> textureBuffer;

次にC#でテクスチャを作り、ComputeShaderに渡します。


C#スクリプト

C#スクリプトでは次のようにしてテクスチャを作成します。(サイト引用

…
RenderTexture renderTexture_A;
…
void Start()
{
    this.renderTexture_A = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGB32);
    this.renderTexture_A.enableRandomWrite = true;
    this.renderTexture_A.Create();


最初にRenderTextureのコンストラクタについて解説します。Unity公式マニュアルを参考にします。

public RenderTexture (int width, int height, int depth, RenderTextureFormat format);

コンストラクタの第一引数、第二引数ではテクスチャの幅と高さを渡しています。 今回は width = 512, height = 512 ということで 512 * 512 = 262144ピクセルのテクスチャが作成されます。

第三引数ではデプスバッファーのビット数を渡しています。 今回は 0 が代入されているのでZバッファは作成されません。こちらもUnity公式マニュアルを参考しています。

最後に第四引数でテクスチャのフォーマットを指定しています。今回指定しているフォーマットはARGB32でチャンネルあたり8ビットのテクスチャが作成されます。その他、様々なフォーマットがあるので用途にあったものを設定する必要があるかもしれません。Unity公式マニュアル


次にこの行について

this.renderTexture_A.enableRandomWrite = true;

この行を書くことによってランダムアクセス書き込みを有効にしています。

最後にCreate()メソッドでRenderTextureを作成しています。



C#からComputeShaderを実行する

ComputeShaderを実行する前に関数(カーネル)のスレッド数を所得します。以下サイト引用。

struct ThreadSize
{
    public int x;
    public int y;
    public int z;

    public ThreadSize(uint x, uint y, uint z)
    {
        this.x = (int)x;
        this.y = (int)y;
        this.z = (int)z;
    }
}

ThreadSize kernelThreadSize_KernelFunction_A;

void Start()
{
…
    uint threadSizeX, threadSizeY, threadSizeZ;

    this.computeShader.GetKernelThreadGroupSizes
    (this.kernelIndex_KernelFunction_A,
     out threadSizeX, out threadSizeY, out threadSizeZ);

    this.kernelThreadSize_KernelFunction_A
    = new ThreadSize(threadSizeX, threadSizeY, threadSizeZ);
…

注意しなければならないことはスレッド数を受け取る変数の型がuintである、out修飾子を使ってスレッド数を受け取る所です。 普段のコーディングではあまり使わないので意識しないのでいけません。

GetKernelThreadGroupSizeメソッドで所得したスレッド数は構造体ThreadSizeのインスタンスに保存します。


次にC#からComputeShaderを実行します。(サイト引用

void Update()
{
    this.computeShader.SetTexture(this.kernelIndex_KernelFunction_A, "textureBuffer", this.renderTexture_A);
    this.computeShader.Dispatch
        (this.kernelIndex_KernelFunction_A,
         this.renderTexture_A.width / this.kernelThreadSize_KernelFunction_A.x,
         this.renderTexture_A.height / this.kernelThreadSize_KernelFunction_A.y,
         this.kernelThreadSize_KernelFunction_A.z);

    plane_A.GetComponent<Renderer>().material.mainTexture = this.renderTexture_A;

最初にComputeShaderにテクスチャを渡します。


次にグループ数を指定して実行します。

    this.computeShader.Dispatch
        (this.kernelIndex_KernelFunction_A,
         this.renderTexture_A.width / this.kernelThreadSize_KernelFunction_A.x,
         this.renderTexture_A.height / this.kernelThreadSize_KernelFunction_A.y,
         this.kernelThreadSize_KernelFunction_A.z);

グループ数はテクスチャの幅、高さをスレッド数で割っています。なので、グループ数は (64, 64, 1) となります。

f:id:vxd-naoshi-19961205-maro:20191109004444p:plain

ComputeShaderでの処理

[numthreads(8, 8, 1)]
void KernelFunction_A(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    float width, height;
    textureBuffer.GetDimensions(width, height);

    textureBuffer[dispatchThreadID.xy] = float4(dispatchThreadID.x / width,
                                                dispatchThreadID.x / width,
                                                dispatchThreadID.x / width,
                                                1);
}

始めに注目すべき点は、引数がSV_DispatchThreadIDであることです。これは (グループID * スレッド数)+ スレッドIDで求めることが出来ます。 なので、SV_DispatchThreadIDがテクスチャの座標を表します。

次にGetDimensionsメソッドについて。このメソッドでテクスチャの幅と高さを取得しています。

最後にテクスチャに色を付けています。SV_DispatchThreadID.x / widthをしているので右に行けば行くほど、黒くなっています。

処理の結果は参考にしているこちらのサイトから見ることができます。

neareal.com



最後に

個人的にComputeShaderをちゃんと勉強したいなと思っていたのでとてもいい機会になりました。時間があれば前回作成した雪シェーダーをComputeShader使って改良しようと思います。

shitakami.hatenablog.com