実行環境
- Unity 2022.3.28f1
- Graphics API: DirectX 11
- OS: Windows 11
やること
- Compute ShaderでTextureを赤色に塗りつぶす
- 動作させるシェーダーは以下のマニュアルにある
test.compute
- https://docs.unity3d.com/ja/2022.3/Manual/class-ComputeShader.html
- 動作させるシェーダーは以下のマニュアルにある
手順
- 可視化のための準備
- RenderTextureの作成
- PlaneにRenderTextureを設定
- コンピュートシェーダーアセットの作成
- コンピュートシェーダーの実行
可視化のための準備
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側で調節します。
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を生かすことが出来れば、高度なレンダリング等を実現できるので今後も色々使ってみようかなと思いました。
以上。終わり。
コメント