ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20230105_01

      【Unity】ComputeShaderの基本的な使い方についてまとめる

      UnityグラフィックスシェーダーGPGPU
      2023-01-07

      マイケル
      マイケル
      みなさんこんにちは!
      マイケルです!
      エレキベア
      エレキベア
      こんにちクマ〜〜
      マイケル
      マイケル
      今回は、UnityでのComputeShaderの使い方についてまとめていきます!
      エレキベア
      エレキベア
      シェーダーなのクマか??
      マイケル
      マイケル
      名前はシェーダーとなっているけど、実際は 描画処理以外の汎用的な計算をGPUに行わせるもの になるんだ。
      GPUは大量の簡単な計算を行うのが得意なため、そこを任せることによって効率化することができるというわけさ!
      エレキベア
      エレキベア
      負荷が大きい処理をGPUに任せ流ことができるのクマね
      マイケル
      マイケル
      上手く使えば重い処理も効率よく行うことができるから、是非覚えてみよう!
      なお、今回使用したUnityのバージョンは下記になります。
      Unity 2021.3.1f1
      エレキベア
      エレキベア
      楽しみクマ〜〜〜

      ComputeShaderの概念

      マイケル
      マイケル
      改めてComputeShaderとは何なのかですが、
      描画処理以外の汎用的な計算をGPUに行わせるもの になります。
      こちらはGPGPUという概念と同じで、実現するための方法の一つです。
      エレキベア
      エレキベア
      GPGPUはグラフィックス技術の書籍にも書いてあったクマね〜〜

      ↑書籍の紹介記事

      マイケル
      マイケル
      GPUを使うと汎用的な計算に使うと何がいいのかについては、まずCPUとGPUの違いを知る必要があります。
      CPUとGPUではそれぞれ得意分野が異なっていて、CPUが高速でコアが少ないのに対してGPUは低速でコアが多いのが特徴です。
      エレキベア
      エレキベア
      GPUのコアは低速なのクマね・・・
      それなのにGPUに任せちゃっていいのクマ??
      マイケル
      マイケル
      よく例えられるのがCPUは1人のスーパーマンで、GPUは大量の凡人、といったものがあるよ。
      数が多いから簡単な計算を何回も行う必要がある場合等に有効だね!
      逆に数が少なかったり複雑な処理だと返って逆効果になる場合もあるからそこは注意が必要だ!
      エレキベア
      エレキベア
      アザラッシ・・・
      マイケル
      マイケル
      ComputeShaderはHLSLで記述できて、
      下記のような単位で処理がまとめられています。
      用語 概念
      カーネル GPUで実行される1つの処理
      スレッド カーネルを実行する単位 (1スレッド1カーネル)
      グループ スレッドを実行する単位 (1グループ複数スレッド)
      エレキベア
      エレキベア
      これは使いながら覚えるしかなさそうクマね
      マイケル
      マイケル
      実際に触りながら覚えていこう!

      GPUで簡単な計算を行う

      マイケル
      マイケル
      それでは早速簡単な計算を行ってみましょう!
      今回は Shader > Compute Shader から HeavyProcess.compute という名前で作成します。
      マイケル
      マイケル
      こちらに 簡単な計算処理を実装した例 が下記になります。
      #pragma kernel HeavyProcess
      
      RWStructuredBuffer<int> intBuffer;
      int intValue;
      
      [numthreads(64,1,1)]
      void HeavyProcess(uint3 groupID : SV_GroupID, uint3 groupThreadID : SV_GroupThreadID)
      {
          intBuffer[groupThreadID.x] = 0;
          for (int i = 0; i < 100; i++)
          {
              intBuffer[groupThreadID.x] += intValue;
          }
      }
      
      エレキベア
      エレキベア
      これは確かHLSLで記述されているのだったクマね
      マイケル
      マイケル
      「#pragma kernel」でカーネルを指定して、「numthreads」でスレッド数を指定します。
      スレッドは(X,Y,Z)の形式で指定して参照することで使用します。
      今回の場合はXに64を指定しているので、各スレッドで「groupThreadID.x」に0〜63の数値が入ってくる形になります。
      マイケル
      マイケル
      引数の「SV_GroupThreadID」にはスレッドのIDが渡されてきます。
      「SV_GroupID」は、グループのIDが渡されますが、こちらの数は後述のC#側で指定します。
      エレキベア
      エレキベア
      いろいろ覚えることはありそうクマが、まあさっきの図の話クマね
      マイケル
      マイケル
      このComputeShaderをC#から呼び出す方法は下記になります。
      using UnityEngine;
      
      public class HeavyProcess : MonoBehaviour
      {
          [SerializeField] private ComputeShader computeShader;
          private int _kernelHeavyProcess;
          private ComputeBuffer _intComputeBuffer;
      
          private void Start()
          {
              // カーネル取得
              _kernelHeavyProcess = computeShader.FindKernel("HeavyProcess");
      
              // Buffer領域を確保
              _intComputeBuffer = new ComputeBuffer(64, sizeof(int));
      
              // パラメータの設定
              computeShader.SetInt("intValue", 1);
              computeShader.SetBuffer(_kernelHeavyProcess, "intBuffer", _intComputeBuffer);
          }
      
          private void Update()
          {
              // GroupIDを指定して数回実行
              // (64,1,1)スレッド * (1,1,1)グループ
              computeShader.Dispatch(_kernelHeavyProcess, 1, 1, 1);
      
              // 実行結果
              var result = new int[64];
              _intComputeBuffer.GetData(result);
              Debug.Log("RESULT: HeavyProcess");
              for (var i = 0; i < 64; i++)
              {
                  Debug.Log(i + ": " + result[i]);
              }
          }
      
          private void OnDestroy()
          {
              // バッファの解放
              _intComputeBuffer.Release();
          }
      }
      
      マイケル
      マイケル
      ComputeShaderをアタッチしておき、各処理を呼び出します。
      ざっくりと
      ・「ComputeShader.FindKernel」でカーネルを取得
      ・「ComputeShader.SetXXX」でパラメータを設定
      ・「ComputeShader.Dispatch」で処理を実行
      といった流れになります。
      エレキベア
      エレキベア
      Dispatchのタイミングで処理が実行されるクマね
      マイケル
      マイケル
      Dispatchの際に指定するのがグループ数で、例えば(2,1,1)を指定した場合は
      (64,1,1) * (2,1,1) = (128,1,1) のスレッドが実行されます。
      エレキベア
      エレキベア
      ここでグループ数を指定するクマね
      マイケル
      マイケル
      これを実行すると、下記のように計算結果がログ出力されるはずです。
      エレキベア
      エレキベア
      単純な計算結果クマね

      演算結果をテクスチャに描画する

      マイケル
      マイケル
      次は演算結果をテクスチャに描画する方法についてです!
      こちらは下記の UnityGraphicsProgrammingSeries を参考にさせていただきました。

      GitHub – UnityGraphicsProgrammingSeries

      エレキベア
      エレキベア
      UnityGraphicsProgrammingSeriesにはお世話になってるクマ〜〜
      マイケル
      マイケル
      こちらのComputeShader側の実装は下記のようになります。
      RWTexture2Dでテクスチャバッファを宣言して、float4型で描画色を設定しています。
      #pragma kernel DrawTexture
      
      RWTexture2D<float4> textureBuffer;
      
      // SV_DispatchThreadID: あるカーネルを実行するスレッドが全てのスレッドのどこに位置するか
      // SV_Group_ID * numthreads + GroupThreadID
      [numthreads(8,8,1)]
      void DrawTexture(uint3 dispatchThreadID : SV_DispatchThreadID)
      {
          // X方向になるほど濃くなるようにする
          float width, height;
          textureBuffer.GetDimensions(width, height);
          textureBuffer[dispatchThreadID.xy] = float4(
              dispatchThreadID.x / width,
              dispatchThreadID.x / width,
              dispatchThreadID.x / width,
              1);
      }
      
      マイケル
      マイケル
      引数で指定している「SV_DispatchThreadID」が少しややこしいのですが、「実行するスレッドが全てのスレッドのどこに位置するか(X,Y,Z)」を示しています。
      計算式でいうと「SV_Group_ID * numthreads + GroupThreadID」になります。
      エレキベア
      エレキベア
      座標位置が渡されるイメージクマかね
      マイケル
      マイケル
      テクスチャは2Dのため今回はnumthreadsは(8,8,1)で、XYそれぞれ8スレッドずつで指定しています。
      これをC#側から呼び出す処理は下記の通り!
      using UnityEngine;
      
      public class DrawTexture : MonoBehaviour
      {
          [SerializeField] private ComputeShader computeShader;
          [SerializeField] private Renderer planeRenderer;
      
          private int _kernelDrawTexture;
          private RenderTexture _renderTexture;
      
          private struct ThreadSize
          {
              public readonly int X;
              public readonly int Y;
              public readonly int Z;
              public ThreadSize(uint x, uint y, uint z)
              {
                  X = (int) x;
                  Y = (int) y;
                  Z = (int) z;
              }
          }
          private ThreadSize _kernelThreadSize;
      
          private void Start()
          {
              // RenderTextureの生成
              _renderTexture = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGB32);
              _renderTexture.enableRandomWrite = true;
              _renderTexture.Create();
      
              // カーネル取得
              _kernelDrawTexture = computeShader.FindKernel("DrawTexture");
      
              // スレッドサイズの取得
              computeShader.GetKernelThreadGroupSizes(_kernelDrawTexture,
                  out var threadSizeX,
                  out var threadSizeY,
                  out var threadSizeZ);
              _kernelThreadSize = new ThreadSize(threadSizeX, threadSizeY, threadSizeZ);
      
              // テクスチャの設定
              computeShader.SetTexture(_kernelDrawTexture, "textureBuffer", _renderTexture);
      
              // カーネルの実行
              // 水平方向のグループ数: 512 / 8 = 64
              // それぞれのスレッドで設定する範囲を分担する
              computeShader.Dispatch(
                  _kernelDrawTexture,
                  _renderTexture.width / _kernelThreadSize.X,
                  _renderTexture.height / _kernelThreadSize.Y,
                  _kernelThreadSize.Z);
      
              // テクスチャの設定
              planeRenderer.material.mainTexture = _renderTexture;
          }
      }
      
      マイケル
      マイケル
      実行の流れはそこまで変わりませんが、カーネル実行の際に指定するグループ数「幅/スレッド数」になります。
      そうすることでスレッド実行ごとに描画する範囲を分担して処理することができます。
      エレキベア
      エレキベア
      段々スレッド数とグループ数の関係が分かってきたクマね〜〜〜
      マイケル
      マイケル
      こちらを実行すると下記のように表示されるはずです!
      ↑右方向へのグラデーションが描画される
      エレキベア
      エレキベア
      綺麗クマ〜〜〜

      おわりに

      マイケル
      マイケル
      というわけで今回はComputeShaderの使い方でした!
      どうだったかな??
      エレキベア
      エレキベア
      難しそうなイメージだったクマが、やってみると案外簡単だったクマね
      マイケル
      マイケル
      今回の例以外でも、下記のライフゲームの実装サンプルなんかも処理負荷の効率化が確認できて面白かったよ!

      【Unity】Compute Shaderについて勉強してみた – 株式会社ロジカルビート

      エレキベア
      エレキベア
      どんな処理をComputeShaderに移すかが中々悩ましいクマが、
      これはやりやすくてよさそうクマね
      マイケル
      マイケル
      それでは今日はこの辺で!
      アデューーー!!
      エレキベア
      エレキベア
      クマ〜〜〜〜

      【Unity】ComputeShaderの基本的な使い方についてまとめる〜完〜


      UnityグラフィックスシェーダーGPGPU
      2023-01-07

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【書籍紹介】「コンピュータグラフィックス」に出てくる用語をまとめる【CGエンジニア検定】
      2024-07-13
      【UE5】Niagara SimulationStageによるシミュレーション環境構築
      2024-05-30
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
      2024-04-14
      【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-30
      【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
      2024-03-30
      【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-28