[UE4]ブループリントだけでGPGPUをしよう ~ その2 テクスチャに位置情報を書き込もう ~

今回は<a href="http://qiita.com/selflash/items/c937308299d93340f7c7">前回の記事</a>でも少し説明した、


今回は位置情報をテクスチャに書き込んで利用するというものをやります。

Material BP内では変数を持つことができす、位置情報を変数にとって置いて次フレームで参照することはできないと前回
話しましたが、テクスチャに書き込むことでそれが可能になります。
簡単に言うとテクスチャを変数として利用するようなものですね。

openGL等ではシェーダーでの処理結果を直ぐに画面に表示せず、FBO(FrameBufferObject)に取っておくという事ができます。
これがオフスクリーンレンダリングです。
これをテクスチャに対してオフスクリーンレンダリングするといった事が出来たりするんですね。
何を言っているのかわからない方は<a href="http://qiita.com/edo_m18/items/95483cabf50494f53bb5">ここ</a>とか<a href="https://wgld.org/d/webgl/w040.html">ここ</a>等の素晴らしい解説を見るといいと思います。

位置情報の計算をシェーダーで行い、結果をテクスチャにオフスクリーンレンダリングする。
次フレームでそのテクスチャをシェーダー内に渡して前フレームの位置情報を参照しつつ計算を行い、
結果をまたテクスチャにオフスクリーンレンダリングする。
という事で前フレームの計算結果の参照用と、今フレームの計算結果を書き込む用とで最低2枚のテクスチャが必要です。
2枚のテクスチャを読み取り用、書き込み用とで交互にうまい事ピンポンして使いまわします。
詳しいことは<a href="https://wgld.org/d/webgl/w083.html">ここ</a>にわかりやすく書かれている素晴らしい記事があるのでそちらを見た方が早いです。

UE4でこのFBOに相当する機能を探していたのですがどうやらTexture Render Taget 2D(描画ターゲット)がそれに
あたいしそうです。ですが、MRT(Multi Render Target)が使えないなど少し物足りないので、
例えば、位置情報の他にスケールや、ライフ、回転、Velocityなどテクスチャに取っておきたいパラメータが増える
度に大量のTenxture Render Target 2Dを用意することにはなりそうです、、


具体的な流れはというと、

[用意]
1  最終的に大量のキューブの頂点を移動させる為のマテリルを用意します。(M_Resultマテリアル)
2  初期の値を書き込む用のマテリアルを用意します。(M_InitPosマテリアル)
3  位置情報計算用のマテリアルを用意します。(M_Posマテリアル)
4  Texture Render Target 2Dを2枚用意します。(Position Render Target 2D)
5  Render Target(書き込み用)にDraw Material To Render Targetノードを使ってM_InitPosマテリアルBP内の結果を書き込みます。
6  2枚のRender Target(書き込み用、読み取り用)をSwapさせます。

[毎フレームの処理]
7  M_PosマテリアルにRender Target(読み取り用)を渡します。
8  M_PosマテリアルBP内で位置を計算します。
9  Draw Material To Render Targetノードを使って計算結果をRender Target(書き込み用)に書き込みます。
10 2枚のRender Target(書き込み用、読み取り用)をSwapさせます。
11 M_ResultマテリアルにRender Target(読み取り用)を渡します。
12 M_ResultマテリアルBP内ではRender Target(読み取り用)からInstanceIDに基づいて位置情報を読み取って
   (つまりをInstanceIDを元にUVを計算して任意のピクセル情報を参照する)各キューブの頂点を移動させます。
13 7 ~ 12の繰り返し

とまぁ、ざっとこんな感じです。





では行きます。
途中までは前回と同じ流れで進めます。

適当にレベルを作ります。
Actorを継承したBP_GPGPUTestという名前のブループリントクラスを作ります。
M_GPGPUTestという名前のマテリアルを作ります。
M_InitPosという名前のマテリアルを作ります。
M_Posという名前のマテリアルを作ります。






[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](https://qiita-image-store.s3.amazonaws.com/0/21126/c0cc3ee3-29a6-6540-8d68-682e14398f8c.png)

Construction Scriptの中身の全容は上記の通りですが、見えないので下記に分割してキャプチャを掲載します。

Texture Render Target 2Dを生成するのですが、ここでの設定は僕も試行錯誤です、、
上手くいっている様に見えるのでこれとしています。
今回のようなGPGPUテクニックを使う際に気を付けるのは
「浮動小数点数テクスチャを用いて負の数値も含めた高精度の値として保存する事」
なのですが、Compression SettingsはDefaultでもFloatRGBAとなっている為、特にそのままでいいのかと思います。
このCompression Settingsは「テクスチャのビルド時に使用する圧縮設定です」とあるのですがこれが何を意味しているのか
わかっていない為もしかすると関係ないかもしれません。
その他の設定もsRGBはチェック入れた方がいいか外した方がいいかわかっていません、、
Filterに関してもほんとにNearestで良いのか自信はないです。
Filterの設定別の説明については<a href="https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0">ここ</a>見るといいです。
これらの設定はいろいろ試して特に動いているからこれでいいやといった何ら確信の内設定ですので、この設定の方が良いよ！等あればどなたか教えて欲しいです、、

widthとheightを512としているという事は、テクスチャの1pixelにそれぞれ座標情報に書き込むわけなので、512 x 512 = 262144分の座標情報を保持する事ができるという事になります。
![2.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/4531a977-f1fc-75d2-4e54-b47a84dd1e92.png)
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/4d603628-96b6-c5ac-86b2-dbbf5bc80713.png)


Create Dynamic Material Instanceに指定しているマテリアルは先ほど作ったM_GPGPUTestというマテリアルです。
![3.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/25d71c17-4204-8669-a1c5-f0aca79983de.png)

![4.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/3d6bf41c-63ac-99a5-b229-cb0af2f599ce.png)

Texture WidthとTexture Heightを渡している所が前回とは異なります。
今になって気づいたのですがIndexZeroPositionというのは前回も今回もいりませんでした、、
これは最終的にキューブを動かす時に欲しい値なので無視してください・・次回以降に活躍するパラメーターです、、
![5.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/e56b9669-8961-69e2-a91c-0e16e4a74fd1.png)

イベントグラフの実装です。
初めに述べた処理の7 ~ 13のステップの処理がここで行われています。
![6.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/0741278c-e8a9-ef3e-e82f-500b26e2600a.png)




次に、
Materialの中身の実装の前に、よく使う処理をまとめておく為にもMaterial Functionを用意しましょう。

[GetInstanceIDByPosition]
前回記事内でもあった、グリッド上に並べられたオブジェクトの頂点位置からInstanceIDに相当する値を求めるMaterial Functionです。
![7.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/65d14610-55f4-49a9-b6f3-27807e2d7e5e.png)

[GetInstanceIDByTexCoord]
名前の通りTexCoordからInstanceIDに相当する値を求めるMaterial Functionです。
![8.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/0383d7bf-7260-1170-49a4-2d116ea4a161.png)

[CalcuUVByInstanceID]
こちらも名前の通りInstanceIDからTexCoordを求めるMaterial Functionです。
![9.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/f1de855c-5bd0-6961-4e8a-0e37e8395fa0.png)

作成したMaterial Functionはそれぞれ詳細タブのExpose to Libraryにチェックを入れる事で各Material BP内で呼び出し可能になります。
ノードの入出力ピンが後に出てくる僕のキャプチャと順番が異なっている場合は、各入出力ノードのSort Priorityを適切に設定していない為だと思います。特に順番が異なっていても問題ないのでここでの説明は割愛します。

[M_InitPos]
M_InitPosの中身のノードはこんな感じです。
計算にしか使いませんのでUsed with Instanced Static Meshesにチェックを入れる必要はありません。
同じ理由でシェーディングもUnlitで十分です。
注意点としてはAllow Negative Emissive Colorにチェックを入れていないと負の値を扱うことができません。
![10.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/bbac6426-6a3b-cd12-b121-7f59a51bcbfb.png)

[M_Pos]
基本的な設定はM_InitPosマテリアルと同様です。
この中でやっている事はInstanceIDが0番のものについてはX座標を0.001づつ足してます。
![11.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/adc9b5b8-f4d7-fd1a-d41a-4e96150e8086.png)

[M_GPGPUTest]
こちらはUsed with Instanced Static Meshesにチェックを入れてください。
この中でやっている事は、M_Posマテリアル内で計算した結果をTexture Render Targetを介して取得し、InstanceID, X, Y, Zの
各パラメーターをデバッグ用に表示させています。
![12.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/f5a2e682-dc8f-4eee-e717-893ee8dde5cd.png)

BP_GPGPUTestをレベルに配置し、NumとMarginに適当な値を入れましょう。
![13.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/c858530d-aeb3-528e-cac9-fddbbac18bb7.png)


いざ実行！
すると下記の様な感じになると思います。
各キューブ上に表示されている値は上から順番にInstanceID, X, Y, Zの値です。
Y, Zに入っている値はM_InitPosマテリアル内で設定した値がちゃんと反映されています。
Xについては、左下のInstanceIDが0番のキューブだけ数字が増えていっているのがわかると思います。
![14.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/da26127a-b17b-f213-74b7-da93faf0012e.png)

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">記事用ツイート1 <a href="https://t.co/ZkV0dAKayh">pic.twitter.com/ZkV0dAKayh</a></p>&mdash; selflash (@selflash) <a href="https://twitter.com/selflash/status/892736803395878912">2017年8月2日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


この値をそのまま各キューブの頂点の移動に使えばおもうままにGPGPUで位置を制御できる！

と思いきや、ちょっと待った！！！！！！！


動画を最後まで見ていただけると気づくかと思いますが、Xの値が2.0で止まってしまいました・・！？
どうも0.001という細かな値でインクリメントさせると2.0で必ず止まってしまいます。
これを0.01でインクリメントさせると2.0で止まることはないのですが、また大きな値でどこかで止まってしまいます。。


![15.PNG](https://qiita-image-store.s3.amazonaws.com/0/21126/b9ffd586-b924-e124-10d3-10bb38804a57.png)


この問題については僕もちゃんとは原因がわかっていません、、

そして回避する方法なのですが、少し手間なのですが下記の動画の通り解決策はあります。
こちらも同じくGPGPUを使用してXを0.001づつインクリメントさせていますが、2.0で止まる事はありません。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">記事用ツイート2 <a href="https://t.co/63A49gPFvm">pic.twitter.com/63A49gPFvm</a></p>&mdash; selflash (@selflash) <a href="https://twitter.com/selflash/status/892737013652135936">2017年8月2日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


これについては次回の記事で回避方法と実装方法を書きたいと思います。

UE4でのGPGPUは一夜にしてならず・・・
それではまた次回