GPGPU
UE4
Blueprint
UnrealEngine

[UE4]ブループリントだけでGPGPUをしよう ~ その3 演算の精度を上げよう ~

今回は前回の記事の最後で触れた「マテリアルBPでGPGPUを実現しようとした際、計算結果が途中で止まる」と言った問題の回避方法についての記事です。

僕自身、この問題についてちゃんと調べて、原因について把握しているわけではありませんが、
なんとなくTextureRenderTarget2Dに値を色として書き込む時に小数点第何以下の値は切り捨てられてて
あまり細かい計算の制度は悪いせいかなとざっくりと考えます。
その時に小数点の細かい計算についての精度をもっとあげたいなーとした時、(ゆーっくりとオブジェクトが移動するようなモーションを実現したい時など)下記の様な方法でとりあえず一定以上演算の精度を上げる事ができます。

例えば、「1234.5678」といった値を扱う場合

値をRGBに書き込む場合
(1) 値をまず整数部(1234)と小数点部(0.5678)に分けます。
(2) 小数点部を10000倍にして整数にします
(3) 2つの値(1234と5678)をテクスチャのRとGに書き込みます。
1234 → R
5678 → G

値をRGBから取り出す場合
(1) RとGから値を取り出します。
R → 1234
G → 5678
(2) Gから取り出した値を0.0001倍します。
(3) 2つの値(1234と0.5678)を足します。
これでテクスチャに書き込まれたRGBから元の値を復元できました。

この様にすれば本来書き込むことができない細かい小数点を取り扱う事が可能です。
デメリットとしては取り扱う値が多くなるとテクスチャに書き込む為のRenderTargetを沢山用意しないと
いけなくなる点です。
しかもUE4のTextureRenderTarget2Dは現状ではどうもアルファ値に書き込む事ができず、
本来であれば値1をRとG, 値2をBとAに書き込みたい所でしたがこの制限のせいで
一つの値毎にRenderTargetを1つ用意しないといけなくなりました。
なおB余りますので、何か整数の値を書き込む用には使えますね、、

でわ、実際の実装を見ていきましょう。
基本的には前回と同じですが、今回はマテリアルを沢山用意しないといけなくなったという点だけが
変わります。

Actorを継承したBP_GPGPUTestという名前のブループリントクラスを作ります。
M_GPGPUTestという名前のマテリアルを作ります。
M_InitPosXという名前のマテリアルを作ります。
M_InitPosYという名前のマテリアルを作ります。
M_InitPosZという名前のマテリアルを作ります。
M_PosXという名前のマテリアルを作ります。
M_PosYという名前のマテリアルを作ります。
M_PosZという名前のマテリアルを作ります。

[BP_GPGPUTest]
BP_GPGPUTestを開きInstanced Static Meshコンポーネントを追加しましょう。
Instanced Static MeshコンポーネントのStatic Meshには適当にStatic MeshのCubeを設定しておきましょう。

次にキャプチャの通りに変数も用意しましょう。

Public : Integer : Num = 2
Public : Float : Margin = 50
Public : Integer : TextureWidth = 512
Public : Integer : TextureHeight = 512
Public : Float : Size = 100
Private : Vector : IndexZeroPosition = 0, 0, 0
Private : Material Instance Dynamic : Result Material = None
Private : Material Instance Dynamic : Position Material = None
Private : Texture Render Target 2D (Array) : Result Material
Private : Integer : SourceIndex = 0
Private : Integer : DestIndex = 1
1.PNG
Construction Scriptの中身の全容は上記の通りですが、見えないので下記に分割してキャプチャを掲載します。

実装については基本的には前回記事と同じですので説明は割愛します。
何かしら躓いた場合には前回の記事をご参考ください。
2.PNG

3.PNG

4.PNG

ここだけ少し違います。前回はInstanced Static Meshに設定されているMaterialに対してアクセスして
設定をしていましたが、今回はResutlMaterialというのに直接アクセスして設定していますが、意味的には
まったく同じです。後々複雑になった時に僕がリファクタリングしていてこの方が都合が良かった為に
こうなってますが、挙動としては同じなのであまり気にしないでください。
5.PNG

6.PNG

7.PNG

8.PNG

9.PNG

10.PNG

この様にPositionのXYZをGPGPUで計算したいだけなのにマテリアルを沢山用意しないといけなくなりました・・今後RotationやScale、各Velocityの値も同じように取り扱いたいとなると途端に大量のマテリアルを
用意しないといけなくなる事は容易に想像できますね、、orz
11.PNG

初期の値を書き込む用のマテリアルです。
この内容はM_InitPosX, M_InitPosY, M_InitPosZの3つとも同じ内容になります。
意味は前述したとおり、「初期の値0.0を整数部と小数部を分けてRGに書き込む」といった内容になってます。
0のノードの部分を別の値に変更してみてください。
12.PNG

M_PosYとM_PosZの内容です。
意味は「前フレームで渡された値を読み込んで値を復元して、何かしらの処理後、整数部と小数部を分けてRGに書き込む」といった内容になってますが、ここでは特に何もせず前フレームの値をそのまま次フレームに持ち越してます。
13.PNG

動作確認の為にM_PosXにだけ違う実装を施してみましょう。
「IndexIDが0の場合には0.001だけインクリメントする」といった内容になっています。
なお、ここで出てくるGetInstanceIDByTexCoordというノードは前回記事で作成したMaterial Functionです。
14.PNG

結果は以下の様に、左したのキューブ(IndexIDが0番)のキューブの緑色の表記の文字が2.0で止まらずに
そのままインクリメントし続けると成功です。
これで前回記事で問題視していた件について回避できた事がわかると思います。
15.PNG

次回は、マテリアルBP内では乱数を取得する方法と、ついに各キューブを動かしてみる
といった内容の記事を書きたいと思います。

それではまた。