ComputeShaderでテクスチャを塗りつぶす

Unity

実行環境

  • Unity 2022.3.28f1
  • Graphics API: DirectX 11
  • OS: Windows 11

やること

手順

  1. 可視化のための準備
    • RenderTextureの作成
    • PlaneにRenderTextureを設定
  2. コンピュートシェーダーアセットの作成
  3. コンピュートシェーダーの実行

可視化のための準備

RenderTextureの作成

ComputeShaderでテクスチャに対して書き込み処理を行う場合は通常のTextureではなくrandom write flag が有効化されたRenderTextureでなければいけません。

今回はテクスチャに書き込むので、RenderTextureを用意します。

解像度はデフォルトの256×256です. (後で必要になる)

// ここに画像

PlaneにRenderTextureを設定

今回はRenderTextureを分かりやすく可視化するためにPlaneにRenderTextureを設定したマテリアルを割り当てます。

やり方: Planeを用意 -> RenderTextureをシーン上のPlaneに対してDrag & Drop

コンピュートシェーダーアセットの作成

アセットの作成

Project内で右クリックし、Create > Shader > Compute Shaderからコンピュートシェーダーアセットを作成する。

作成したコンピュートシェーダー内には以下のマニュアルに書くコードをコピーした。
https://docs.unity3d.com/ja/2022.3/Manual/class-ComputeShader.html

コピーしたコードの5行目のnumthreadsの数を(32,32,1)に変更している

#pragma kernel FillWithRed

RWTexture2D<float4> res;

[numthreads(32,32,1)]
void FillWithRed(uint3 dtid: SV_DispatchThreadID)
{
    res[dtid.xy] = float4(1,0,0,1);
}

このコードは指定したテクスチャの特定の座標の色を赤色に設定する。
座標はDispatcherThreadIDを用いて指定する。

ThreadとThreadGroup

スレッドはプロセッサ利用の最小単位です。
GPUはCPUと違い数千個のスレッドを使い並列に処理できます。
※ RTX4090は16384個のCUDA Core (プロセッサ) を持つ

そしてDirectXではThreadとThreadの集合体であるThreadGroupが存在します。
それぞれ(x, y, z)の3次元座標としてindexが振られます。

以下はThreadGroupとThreadの関係を図示したものです。
(https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/sv-dispatchthreadidより引用)

DispatchThreadIDについて

図のようにThreadを一意に定めるには、ThreadGroupに割り当てられたIDとThreadに割り当てられたIDの二つが必要です。
DispatchThreadIDは、この二つのIDを組み合わせたIDで実行中のスレッドを一意に識別できます。

ComputeShaderでの処理内容

ComputeShaderで書いた処理はGPUの各スレッドで並列実行されます。
そして実行時に使用するスレッド数はindexの振り方と同じように(x,y,z)で指定します。(参考1)
なので実行するスレッド数は x * y * z個になります。

ただしDirectX11の場合、一つのスレッドグループごとにスレッドは1024個しか利用できません。 (参考2)

そのため、ここでは[numthreads(32, 32, 1)]を指定し1024個のスレッドを利用するように設定しています。そして残りはThreadGroup側で調節します。


参考
1. https://learn.microsoft.com/ja-jp/windows/win32/direct3d11/direct3d-11-advanced-stages-compute-shader

2. https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/sm5-attributes-numthreads

コンピュートシェーダーの実行

コンピュートシェーダーの実行はC#スクリプトから行います。
今回は以下のようなコードを書きました。
RenderTextureをComputeShaderのresプロパティに指定し、compute shaderを実行します.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ComputeShaderRunner : MonoBehaviour
{
    [SerializeField] private ComputeShader computeShader;
    [SerializeField] private RenderTexture renderTexture;
    // Start is called before the first frame update
    void Start()
    {
        var kernelId = computeShader.FindKernel("FillWithRed");
        computeShader.SetTexture(kernelId, Shader.PropertyToID("res"), renderTexture);
        computeShader.Dispatch(computeShader.FindKernel("FillWithRed"), 8, 8, 1);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Compute Shaderの実行方法

UnityではCompute Shaderを実行するには、compute shaderインスタンスに対してDispatchメソッドを呼び出す必要があります。

Dispatchメソッドでは実行するカーネルと、スレッドグループの大きさを指定する必要があります。

実行するカーネルの取得

ComputeShaderのカーネルは先ほどのコンピュートシェーダーで宣言したカーネル名を利用します。
今回の場合だと FillWithRedがカーネル名になります。

#pragma kernel FillWithRed

カーネルIDはcomputeShaderインスタンスに対してFindKernelメソッドを呼び出すことで取得できます。

スレッドグループの大きさの指定

次にスレッドグループの大きさを指定します。
スレッドグループの大きさを指定する際は、適切なDispatchThreadIDを得られるように設定します。

DispatchThreadIDは、スレッドグループのindex * スレッドグループのサイズ + スレッドIDで求めることが出来ます。 (それぞれ3次元ベクトルであることに注意)

今回の場合、スレッドグループの大きさは(32, 32, 1)です。
書き込むテクスチャの解像度は256 * 256であるため、x座標, y座標共に 0 ~ 255の値を取る必要があります。

以上からスレッドグループの大きさは (x, y, z) = (8, 8, 1)と指定すれば良いです。

いざ実行

先ほどのコードを適当なシーンオブジェクトに追加し、RenderTextureとCompute Shaderアセットを以下のようにInspectorから割り当て実行します。

以下のように赤いPlaneが表示されれば動作成功です。

総評

今回はUnityのマニュアルに紹介されていたコンピュートシェーダーのコードを実行してみました。
マニュアルにシェーダーのコードは書いてありますが、実行方法等は省略されていたので少しとっつきにくかったです。

しかし触ってみると、動かすだけなら案外難しくないと感じました。
ここまでお手軽にGPGPUを体験できたのは嬉しいです。
ComputeShaderを生かすことが出来れば、高度なレンダリング等を実現できるので今後も色々使ってみようかなと思いました。

以上。終わり。

コメント

タイトルとURLをコピーしました