Version: 2020.3
言語: 日本語
ランタイムのシェーダーの置き換え
ビルトインシェーダー

コンピュートシェーダー

コンピュートシェーダーは、通常のレンダリングパイプラインとは異なって、グラフィックスカード上で実行するプログラムです。これは超並列 GPGPU アルゴリズム、またはゲームレンダリングの一部を加速させるために使用できます。効果的に使用するためには、しばしば GPU アーキテクチャおよび並列アルゴリズム、さらには DirectComputeOpenGL ComputeCUDAOpenCL に関する深い知識が必要です。

Unity のコンピュートシェーダーは、DirectX 11 DirectCompute テクノロジーと良く似ています。コンピュートシェーダーが使用できるプラットフォームは以下の通りです。

  • DirectX 11 または DirectX 12 グラフィックス API と Shader Model 5.0 GPU を伴う Windows と Windows ストア

  • Metal グラフィックス API を使用する macOS と iOS

  • Vulkan API を伴う Android、Linux、Windows プラットフォーム

  • 現段階で一般的に使用されている OpenGL プラットフォーム (Linux または Windows の OpenGL 4.3、Android の OpenGL ES 3.1)。Mac OS X は OpenGL 4.3 をサポートしません。

  • 現段階で一般的に使用されている多くのコンソール (Sony PS4、Microsoft Xbox One)

コンピュートシェーダーのサポートの有無はランタイムに SystemInfo.supportsComputeShaders を使って確認できます。

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

通常のシェーダー と同様に、コンピュートシェーダーは、プロジェクトのアセットファイルで .compute ファイル拡張子を持ちます。これらは DirectX 11 スタイルの HLSL 言語で書かれ、最低数の #pragma コンパイラーディレクティブを持ち、コンピュートシェーダーカーネルとしてどの関数をコンパイルするかを示します。

こちらは、コンピュートシェーダーファイルの基本例です。出力テクスチャを赤で塗りつぶします。

// test.compute

#pragma kernel FillWithRed

RWTexture2D<float4> res;

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

言語は標準の DX11 HLSL で、#pragma kernel FillWithRed ディレクティブが追加されています。1つのコンピュートシェーダーアセットファイルには、呼び出し可能な1つ以上の コンピュートカーネル が含まれていなければなりません。そして、その関数は #pragma ディレクティブ で示されます。 ファイルにはそのほかのカーネルが存在する可能性があります。その場合は、単に 複数の #pragma kernel を追加するだけです。

複数の#pragma kernel を使用する場合は、#pragma kernel ディレクティブと同じ行に// のコメントは許可されていないことに注意してください。使用すると、コンパイルエラーの原因となります。

#pragma kernel 行はオプションとして、カーネルをコンパイルしている間に複数のプリプロセッサーマクロを後に続けて定義することが可能です。サンプルとしては、

# pragma kernel KernelOne SOME_DEFINE DEFINE_WITH_VALUE=1337
# pragma kernel KernelTwo OTHER_DEFINE
// ...

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

スクリプトで ComputeShader 型の変数を定義し、アセットへの参照を割り当てます。こうすると ComputeShader.Dispatch 関数を使用してそれらを呼び出すことができます。 詳細は、ComputeShader class を参照してください。

ComputeBuffer クラスはコンピュートシェーダーと密接に関係し、任意のデータバッファ (DX11 用語の 「構造化バッファー」) を定義します。レンダーテクスチャ は、「ランダムアクセス」フラグが設定されている場合 (DX11 の「unordered access view」)、コンピュートシェーダーからの書き込みも可能です。 詳細については、RenderTexture.enableRandomWrite を参照してください。

コンピュートシェーダーでのテクスチャサンプラー

テクスチャとサンプラーは、Unity では別々のオブジェクトではありません。そのため、コンピュートシェーダでそれらを使用するには、以下の Unity 特有のルールに従う必要があります。

  • テクスチャ名と同じ名前を使用し、最初にsampler と表記します (たとえば、Texture2D MyTex; SamplerState samplerMyTex)。 この場合、サンプラーはテクスチャの filter/wrap/aniso 設定に初期化されます。

  • あらかじめ定義されたサンプラーを使用してください。 このため、名前にはLinear または Point (フィルタモード用) と、Clamp または Repeat (ラップモード用) が必要です。 たとえば、SamplerState MyLinearClampSampler は、リニアフィルターモードとクランプラップモードを持つサンプラーを作成します。

詳細は、サンプラー状態 を参照してください。

クロスプラットフォームのサポート

通常のシェーダーと同様に、Unity はコンピュートシェーダーを HLSL から他のシェーダー言語に翻訳することができます。したがって、最も簡単なクロスプラットフォームのビルドのためには、コンピュートシェーダーを HLSL で書くきます。ただし、これを行う場合には、いくつかの要素を考慮する必要があります。

クロスプラットフォームでの最良の実践法

DirectX 11 (DX11) は、他のプラットフォーム (MetalOpenGL ES など) ではサポートされていない多くの操作をサポートしています。 したがって、DX11 だけの環境を考えるより、サポートが少ないプラットフォームで、シェーダーの挙動を明確に定義する必要があります。 考慮すべき点は以下のとおりです。

  • アウトオブバンドのメモリアクセスが悪い点。DX11 は、読み込み時に常にゼロを返し、問題なくデータの一部を読み込むかもしれませんが、サポートが少ないプラットフォームでは、これを行う際に GPU がクラッシュする可能性があります。DX11 特有のハック、スレッドグループサイズの倍数と一致しないバッファーサイズ、バッファーの先頭や末尾から隣接するデータ要素を読み取ろうとすること、などの同様の非互換性に注意してください。

  • リソースを初期化する点。 新しいバッファーとテクスチャの内容は未定義です。 プラットフォームの中にはすべてゼロを示すものもありますが、そうでないものでは、NaN を含めどんな値になることもありえます。

  • コンピュートシェーダーが宣言するすべてのリソースをバインドします。シェーダーが分岐のために現在の状態でリソースを使用しないことが確実にわかっていても、リソースがバインドされていることを確認する必要があります。

プラットフォーム特有の相違

  • Metal (iOS と tvOS プラットフォーム用) は、テクスチャのアトミック操作をサポートしません。Metal は、バッファーの GetDimensions クエリもサポートしません。必要な場合は、バッファーサイズの情報を定数としてシェーダーに渡します。

  • OpenGL ES 3.1 (Android, iOS, tvOS プラットフォーム用) は、一度に 4 つのコンピュートバッファーしかサポートしません。実際の実装では、より多くをサポートすることもありますが、一般的には OpenGL ES 用に開発する場合は、それぞれのバッファーに各データを格納するよりも、関連するデータを構造体でグループ化することを検討する必要があります。

HLSL 専用、または、GLSL 専用のコンピュートシェーダー

通常、コンピュートシェーダーファイルは HLSL で記述され、自動的に、必要なすべてのプラットフォームにコンパイルまたは変換されます。ただし、他の言語への変換を避けたり (つまり、HLSL プラットフォームのみを維持する)、GLSL コンピュートコードを記述することも可能です。

以下の情報は、HLSL 専用、または、GLSL 専用のコンピュートシェーダーにのみ適用され、クロスプラットフォームビルドには適用されません。これは、この情報によってコンピュートシェーダーソースが一部のプラットフォームから除外される可能性があるためです。

  • CGPROGRAM キーワードと ENDCG キーワードで囲まれたコンピュートシェーダーソースは、非 HLSL プラットフォームでは処理されません。

  • GLSLPROGRAM キーワードと ENDGLSL キーワードで囲まれたコンピュートシェーダーソースは、GLSL ソースとして扱われ、そのまま出力されます。これは、OpenGL または GLSL プラットフォームをターゲットとする場合にのみ機能します。また、自動的に変換されたシェーダーはバッファーの HLSL データレイアウトに従いますが、記述された GLSL シェーダーは GLSL レイアウトルールに従うということにも注意する必要があります。

バリアントとキーワード

グラフィックスシェーダーと同じように、キーワードを使ってコンピュートシェーダの複数のバリアントを作ることができます。

バリアントとキーワードに関する一般的な情報については、シェーダーバリアントとキーワード を参照してください。これらの機能をコンピュートシェーダに実装する方法については、HLSL でのシェーダーとキーワードの宣言と使用 および ComputeShader のスクリプトリファレンス を参照してください。

ランタイムのシェーダーの置き換え
ビルトインシェーダー