C++, ComputeShader, HLSL, Unreal Engine 4

[UE4][ComputeShader][HLSL][C++]Unreal Engine 4で(RW)StructuredBufferを用いたComputeShaderを利用する(その1:イントロ)

UnityではGPGPUをフル活用するためにComputeShaderを用いた美しい可視化事例がたくさんあり、特にIndie Visual LabさんによるUnity Graphics Programming (vol.1, vol.2)は素晴らしい書籍です。
その他にも、例えば凹みTipsさんのブログ記事など、素敵な解説記事がいくつもあります。

参考記事:
Unity で Compute Shader を使ったスクリーンスペース衝突有りの GPU パーティクルを作ってみた

で、せっかくなのでUnreal Engine 4でも似たようなことをやってみたいわけですが、少なくとも私が探した限りでは、情報がほっっっとんどありません。
そもそものComputeShaderの使い方自体の解説記事も少ないのですが、Unreal Engine 4でGPGPU関連が流行らない理由はもう1つあると思っております。
それは、

たとえComputeShaderを使ってGPGPUを活かした演算が出来ても、その結果を直接表示するためのAPIがUnreal Engine 4には(おそらく)全くない

ことです。
これ、残念過ぎます。
例えばGPGPUで複雑なメッシュの頂点座標を計算したとして、GPUメモリ上のデータを直接Unreal Engine 4の描画パイプラインに渡せないという、激しいボトルネックに直面することになります。
これを実現するためには、Procedural Mesh Componentを改造して、直接GPUメモリポインタを指定出来るようにするしかありません。
大量のパーティクルや矢印(流速の可視化など)を表示する際には、Instanced Static Meshの各トランスフォーム値をGPU上のみで完結させて高速描画をしたいところですが、残念ながらその方法も(たぶん)用意されていません。
超無理やりやろうとすると、こちらの参考記事のようなことをする必要があります。

参考記事:
[UE4]ブループリントだけでGPGPUをしよう ~ その1 インスタンスIDを割り振る ~
[UE4]ブループリントだけでGPGPUをしよう ~ その2 テクスチャに位置情報を書き込もう ~
[UE4]ブループリントだけでGPGPUをしよう ~ その3 演算の精度を上げよう ~

テクスチャ値を用いるという古典的な手法で何とかするしかないように思います。
最新のパーティクルシステムであるNiagaraを使うと何か出来たりする…のでしょうか?調べていないのでわかりません。
ご存知の方がいらっしゃいましたらぜひ教えてください。
テクスチャに値を書き込むところまではComputeShaderとUnreal C++とをうまく繋げればGPU上のみで完結させることもどうやら出来そうなのですが、
上記記事でも指摘されているように、Unreal EngineのInstanced Static Meshの実装が非常に中途半端で、Instance IDの取得をBluePrints上から行うことが出来ません。

何とかやろうとすると、上記記事のような超無理やりな実装になります。

参考記事:
Per Instance Id For Static Mesh Instances – UE4 AnswerHub

時々英語フォーラムでも質問や要望が出ているようですが、残念ながら採用されていないようです。

おそらくはUInstancedStaticMeshComponentUInstancedStaticMeshComponent::UpdateInstanceTransformあたりの実装を見て改造すれば出来ないことはないのだと思いますが…。心が折れそうです。

…というわけで、現状Unreal Engine 4でComputeShaderを使えても、視覚的な効果を得るためにまた一苦労、いや五苦労くらいしそうですのであまりメリットが無いかもしれませんが、それでもロジック部分で大量の単純計算をGPU上でまわしたいときなどがあれば(そしてCPU, GPU間の転送時間を考慮してもGPGPUを使うメリットがあるならば)、使い方を知っておくと良いのかなと思い、公式記事や公式解説が非常に貧弱で困りつつ、有志の方々によって公開されている数少ないサンプルを頼りに試行錯誤した結果、何となく使い方がわかってきましたので、超シンプルなコードとともに紹介します。

参考にしたのは以下の解説記事及びGitHubプロジェクトです。

参考記事:
1. UE4 にグローバル シェーダーを追加してみよう

パッと見ではわかりやすくて親切な記事で、実際、基本を学ぶにはとても良いのですが、痒いところに手が届いていません。
例えば、上記記事中のコード

void RenderMyTest(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FLinearColor& Color);

の、FRHICommnadListとERHIFeatureLevel::Typeをどこからどうやって取得すればよいのか全く分かりません。
また、同じく上記記事中のFMyTestPSクラスは、(確か)UE4.18以上、少なくともUE4.19では確実に構文エラーになります。
詳しくは後述しますが、FGlobalShaderクラスの仕様が変更になっており、

static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment);

と書いても動きません。
正しくは

static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& PermutationParams, FShaderCompilerEnvironment& OutEnvironment);

に変更になっています。
しかも、この変更、公式説明を探しても見つからず、外部の記事(Adding Global shaders to UE4 v2.0)でやっとわかるっていう…。

2. プラグインとして新規グローバル シェーダーを作成する | Unreal Engine

最初の記事よりずっと詳しいのですが、やはりFGlobalShaderの記述が古いままで、少なくともUE4.19以上では確実に構文エラーで動きません。
また、ComputeShaderを扱っているわけではないので、これだけ見てもStructuredBufferやRWStructuredBufferなどの使い方は全く分かりません。

3. Unreal Engine 4 Rendering Part 1: Introduction
4. Unreal Engine 4 Rendering Part 2: Shaders and Vertex Data

Unreal Engine 4開発チームの方による、UE4のレンダリングワークフローについての神記事。非常にわかりやすいです。特にPart 2では、HLSLコードとC++コードとの連携がどうなっているのかなど、詳しく解説されています。
欠点は、この記事を見つけるまでに超絶時間がかかったこと。こんな良記事なのに、なんでUnreal Engine 4の公式ドキュメントに書かれてないんだよ…!

参考GitHubプロジェクト:
5. https://github.com/Temaran/UE4ShaderPluginDemo

Unreal EngineでのComputeShaderを調べようとしてまず最初に出てくるのがおそらくこのプロジェクト。
コードを追っていくとだいぶ理解できるようになり、Uniform Bufferの使い方もわかります。
ただ、いかんせんコードが古いです。
実はUE4.17用のPull Requestがあるので、少なくともそちらを見たほうが良いです。
https://github.com/Temaran/UE4ShaderPluginDemo/pull/9

StructuredBufferもRWStructuredBufferも使っておらず、いきなりRWTexture2Dを用いているのも難点。

6. https://github.com/ufna/VaOcean/tree/master

知っている人は知っているthe ocean surface simulation plugin for Unreal Engine 4です。
StructuredBufferやRWStructuredBufferが色々使われており非常に勉強になります。
特にStructuredBufferを用いる場合は、後述するFStructuredBufferRHIRefFShaderResourceViewRHIRefをローカル変数ではなくメンバ変数として持つことができる、ということを理解できるのはこのプロジェクトコードのみです。

7. https://github.com/IntelSoftware/ue4-parallel

なんとIntel公式で、Unreal Engine 4用のComputeShaderプロジェクトが公開されていました…!
全然知らなかったのですが、知らなかったのは私だけなのでしょうか…?

シミュレーションの解説もかなりガチです。
Code Sample: An Approach to Parallel Processing with Unreal Engine* | Intel® Software
Unreal Engine 4 Parallel Processing School of Fish | Intel® Software

ただ、最初に解説したように、Unreal Engine 4ではGPUメモリ上のデータを直接トランスフォーム値として使うことが出来ないため、Intelさまのこのサンプルでも、計算結果は一旦TArrayの形でCPU側に戻しており、とても勿体ないです。
それでもこの記事中に書かれているように、CPUシングルスレッドでは2FPSだったものがComputeShaderを使うことにより30FPSにまで高速化されています。

流石Intelさまだけあって、コードもとてもしっかりしています。
このプロジェクトでのみ、FRenderCommandFenceもきちんと使われております(このプロジェクトを見ていなかったら、使うことが出来なかったです…)。

というわけでコードは基本的にはこのIntelさまのプロジェクトを参考にしていますが、RWStructuredBufferしか使われていないため、StructuredBufferの使い方についてはVaOceanのプロジェクトを参考に勉強しました。
(Epic Gamesがこういうサンプル書いてくれると良いのですが…)

…あれ、イントロだけで長文になってしまった…。
ので、具体的な実装は次回、次々回の記事(その2:C++側シェーダクラスその3:実際に用いる)にまわすことにします。

コメントを残す

メールアドレスが公開されることはありません。