ゲーム開発
Unity
UnrealEngine
C++
ゲーム数学
ゲームAI
サウンド
アニメーション
GBDK
制作日記
3DCG
Houdini
Blender
USD
グラフィックス
テクノロジ
ツール開発
フロントエンド関連
サーバサイド関連
ソフトウェア設計
ハードウェア関連
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
その他
都会のエレキベア
ラーメン日記
四コマ漫画
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • ゲーム数学
    • ゲームAI
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • 3DCG
    • Houdini
    • Blender
    • USD
    • グラフィックス
  • テクノロジ
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • ソフトウェア設計
    • ハードウェア関連
    • おすすめ技術書
  • 音楽
    • 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
記事をSNSで共有する
X
Facebook
LINE
はてなブックマーク
Pocket
LinkedIn
Reddit

著者の各種アカウント
フォローいただけると大変励みになります!

関連記事
【Three.js】カスタムシェーダーでトゥーン+背面法アウトラインを実装する
2026-02-15
【Three.js】Three.js入門 - シーン構築・モデル読み込み・ポストプロセスまで
2026-02-15
【Houdini21.0】3Dビル群っぽいブログヘッダー画像を作成する
2026-01-10
【Houdini21.0】Solaris徹底入門:USD構成を意識した基本的な作業フローについてまとめる
2025-12-31
【ゲーム数学】第十回 p5.js(+α)で学ぶゲーム数学「複素数とフラクタル」
2025-11-02
【プロシージャル】Pythonで学ぶ波動関数崩壊アルゴリズム(Wave Function Collapse)
2025-06-22
【UE5.5】Nanite、Lumen、VSMの概要についてまとめる
2025-05-12
【Unity】Timeline × Excelでスライドショーを効率よく制作する
2024-10-31