USDを使ってみた話

CGアニメでおなじみのPixer社がオープンソースで公開している汎用的なシーン記述フォーマット「Universal Scene Description (USD)」について、使ってみたので感想や引っかかったこと、メリット・デメリットなんかをつらつらと書いていきます。

USDについては元Pixer社の手島さんが今年のCEDECで基本的な部分を講演されています。

ピクサー USD 入門 新たなコンテンツパイプラインを構築する

大変わかりやすく、USDの利点もうまく語られていますので、興味がある方はご一読ください。

GitHubソースコードが公開されていますので、こちらをビルドすれば誰でも使用できるようになります。

GitHub - PixarAnimationStudios/USD: Universal Scene Description

ライセンスは改変Apache2.0ライセンスとなっているようです。
Apache2.0ライセンスは改変した場所を明示する必要があったはずなので、ゲームのランタイムで使用するにはちょっと不向きかもしれません。
まあ、ゲーム開発の場合は社内ツールとかで使うことになると思いますのであまり気にしなくても大丈夫だとは思います。

Windowsでのビルドは結構大変ですが、最近はかなり難易度が下がりました。
詳しくはこちらの記事をお読みください。

Pixar USD の Windows ビルド方法(2017/9 版) - Qiita

さて、ここまでが前提。
ここからは、MODO用の簡易メッシュエクスポータとそこから独自メッシュ形式へのコンバータを作成した際にわかったことなどをつらつらと書いていきます。
なお、私の書いたコードは以下になります。

GitHub - Monsho/ModoUsd: USD Exporter for MODO10

GitHub - Monsho/D3D12Samples

後者のUSDtoMeshは前者のエクスポータで出力したUSDファイルのみで確認していますので、他のどんなUSDファイルでも動作するかは保証しかねます。

デバッグビルドがうまくいかない

結局解決してないのがこの問題。
前述のWindowsビルド方法はRelease版のビルドはできるのですが、この方法ではデバッグ版のビルドはできません。デバッグ版のライブラリが生成されないわけです。

ライブラリはRelease版を使うとして、これをリンクするプロジェクトがデバッグ版だとどうなるかというと、単純にビルドが通りません。
原因はインクルードするヘッダファイルに_Debugなどの定義を用いて分岐しているので、USDライブラリはRelease、アプリはDebugという方法がどうもうまくいかないのではないかと思われます。

なお、無理やりDebugでビルドを通したらUSD使用時にクラッシュしたので、やっぱりちゃんとDebugビルドを作成しなければならないようです。
VSプロジェクト作成とかちゃんとやるべきだろうとは思いますが、とりあえず使ってみる分には自信のプロジェクトのDebug設定をRelease設定からコピー、最適化だけO0にするなどで対処するのが一番の近道じゃないかと思います。

サンプルはあまり多くない

まだ使っている人が少ないためだと思いますが、やはりサンプルが少ないです。
GitHubで公開されているソースコードにはいくつかのサンプルがtutorrialとして置いてあります。
extras/usd 内にいつくかあるので、まずはこれでUSDのファイルフォーマットやHello Worldなんかを学ぶとよいでしょう。

ただしこれらはPythonコードがほとんどで、C++コードはありません。
C++での使用方法も基本的には同じですが、やはりサンプルは欲しいところです。

私が参考にしたのはUnreal Engine 4のUSDインポータ、およびMayaのUSDプラグインです。
UE4の該当ソースコードは Engine/Plugins/Editor/USDImporter にあります。
また、MayaプラグインソースコードはUSDの third_party/maya 内にあります。
インポート/エクスポートプラグインや自前フォーマットへのコンバータを作成するのに十分なサンプルだと思います。
もっと複雑なことをやろうと思うとサンプルが足りなくなるのではないかとは思いますが、ゲーム開発現場だけなら何とかなるのではないでしょうか?

USDViewがとにかく便利

USDエクスポータを作成する際に非常に役に立ったのがUSDViewです。
USDをインストールすると標準でついてくるViewerで、メッシュデータの表示も可能です。
アニメーションは…どうなんでしょう?サンプルが見当たらなくて試せてません。USDViewのインターフェースを見る限りはアニメーションも可能だと思います。

エクスポートしてポリゴン等が正常に出力されているか、各種データが正しいかを比較的簡単に確認できるViewerの存在は大変便利です。
そもそも、これがないと正常に出力されているか確認する手段がほとんどないので頼らざるを得ないという部分もあります。

エクスポータを書く際には必ずUSDViewで確認する、という手順は必要だと思います。

エラーがわかりにくい

何らかの処理を行った場合にそれがうまくいってるのかいってないのかがわかりにくいと感じました。
この辺は設計の文化の違いもあると思いますので一概に悪いというわけではないのですが、慣れないと感じたのは事実です。

まず迷ったのはパスです。
USDでは各種ノードをSdfPathというスラッシュ区切りのパスで指定します。ディレクトリ構造みたいな形ですね。
形式が形式なだけに、命名規則もほぼディレクトリと似た感じです。
試した結果、数字始まりはダメ、空白やいくつかの記号もアウトです。
NGな文字列で生成されたSdfPathでは正常なノードは生成されず、それにアクセスするとアクセス違反でクラッシュします。

また、一部のデータは与えられるデータの形式が決まっていて、それ以外のデータを提供しても何も起こりません。
クラッシュしないだけマシですが、エクスポートしてみたら何も出力されてない、という状況になります。

VSのデバッグ機能でUSDのパラメータは現段階ではまともに見ることはできないので、エラーのわかりにくさとデバッグしにくさで軽く扱うには苦労すると思います。

全体的な感想

USDは思想については大変共感できるところで、Autodesk主導でいろいろ困ったちゃんなFBXの代わりになる可能性は十分にあると思います。
しかし、まだ実践に使うには不安が残る感じです。
ソースコードは公開されているので何かあったらソースを読むという手段はあるものの、なかなか大きなものなので読むとなるとなかなか大変です。

個人的にはこのまま使っていこうとは思っていますが、将来的に見ても仕事で使うかは今のところ不明です。

余裕ができたらMODOからスケルトン情報やアニメーション情報も出力できるようにしたいとは思いますが、いつになるかは不明ですね。

Substance Designer 2017.2 の新ノード その他

引っ越し一発目は[Flood Fill]ノード以外の新ノードを紹介。

・Cube 3D

名前の通り3次元のキューブ形状を2D画像として出力できるノードです。

f:id:monsho:20171022143909p:plain

Yaw, Pitchの回転、全体のスケール、各軸のサイズを指定できます。
Roll回転はありません。
軸はZ-upとなっているので、高さを変更したい場合は[Size]→[Z]を変更しましょう。

3DのDCCツールではないため、これによって3次元形状をこねこねできるわけではないですが、新しい特殊な[Shape]ノードとしてパターン作成に使用できます。

・Shape Mapper

入力された形状を円状に配置するノードです。
[Splatter Circular]ノードと違うのは[Radius]パラメータを小さくしていった時の挙動です。

[Splatter Circular]ノードでは普通に円状に配置されるだけなのに対して、[Shape Mapper]では画像の中心に近い方向のスケールが小さくなり、画像中心に吸い寄せられるような形になります。
実装を見てみたのですが読み解けていません。せめてプログラムコードなら読み解ける可能性もあるんですが…

使い道としてはやはりパターン形状の作成でしょう。特にレリーフ的なものの表現としては色々使い道があるのではないかと思います。

・作例 その1

[Cube 3D]と[Shape Mapper]を使って花のレリーフのようなものを作ってみました。

f:id:monsho:20171022150321p:plain

[Cube 3D]を左右対称にして形状を作成し、これを[Shape Mapper]で配置しています。
実際の花びらというよりは花びらを模したレリーフという感じになります。
実際の花びらっぽい形状も作れるとは思いますが、レリーフとかの堅い感じの方が向いてる印象があります。

f:id:monsho:20171022151317p:plain

・Swirl / Swirl Grayscale

[Transform 2D]ノードのようなUIを使って特定位置に渦巻きを作成できるノードです。

f:id:monsho:20171022152105p:plain

綺麗に渦巻きが作れますが、何に使うの?と言われるとなかなか困りますね。
ウルトラマンのタイトル演出とかですかね?

Vector Morph / Vector Morph Grayscale

[Warp]ノードに似ていますが、歪ませるためのノイズはノーマルマップを用います。
この仕組みはFlow Mapとも呼ばれていて、水の流れなどをノーマルの方向で指定するための技術です。
これまでのSDでも[Pixel Processor]を利用することで実現できましたが、より簡単になったと考えていいでしょう。

f:id:monsho:20171022153708p:plain

・作例 その2

ゲーム制作で使用されるFlow Mapは同じマップから生成される互い違いの歪んだテクスチャをブレンドして作成します。
SDには$timeという時間を指定するFloat値が存在しており、関数内で使用することが可能です。
ただし、Substance Playerでなければこの値を変化させることができないというイマイチな機能ですが、Playerを使うと連番のテクスチャも出力できます。
エフェクトでこのような流れを作った連番テクスチャを作成したい場合には$timeと[Vector Morph]を使って望みのテクスチャを作成できます。

f:id:monsho:20171022162926p:plain

関数は2つの[Vector Morph]の[amount]パラメータと、[Blend]ノードの[Opacity]ノードで実装しています。

[Vector Morph]上側

f:id:monsho:20171022163053p:plain

[Vector Morph]下側

f:id:monsho:20171022163132p:plain

[Blend]

f:id:monsho:20171022163155p:plain

Substance Playerで時間を変化させればうまくいく…はず(試してない)

・その他

[Histgram Select]ノードはある位置からある範囲までのヒストグラムを0~1の値に伸張し、それ以外を0にするノードです。
使い道としては高さブレンドのアルファ値として使用できます。
[Position]パラメータで指定したヒストグラムが1.0、[Range]パラメータで指定した範囲が[Position]の両端に展開される感じのようです。

[Arc Pavement]ノードはアーチ形状のタイルを作成するノードです。
使い方が限られてくる感はありますが、ヨーロッパの石畳とか作る上では便利かもしれません。

あと、[Splaetter Circular]ノードが刷新しています。
以前のものと比べるとかなり使いやすくなってる感じです。

 今回は以上です。

Substance Designer 2017.2 の新ノード Flood Fill

最近、ブログを書くのが面倒でほとんどTwitterで済ませてしまっていました。

なんとかブログを書こうという気力がほんのちょっとだけ湧いたので頑張って書いています。

いやまあ、記事を書くこと自体より、画像のコピペができないとかそういうのが面倒ってのが大きいです。

そのうち引っ越すかもしれませぬ…

今回はSubstance Designer 2017.2 が登場したので、そこで追加されているFlood Fillノードについてまとめました。

・Flood Fillとは?

Flood Fillアルゴリズムというものがあります。

これは画像処理アルゴリズムの1つで、画像内の特定アイランドに色を充填するためのアルゴリズムです。

簡単に言ってしまえばPhotoshopのペンキ缶ですね。

SDの[Flood Fill]ノードはこのアルゴリズムを用いてアイランドごとに情報を持たせているものと思われます。

・何ができる?

白黒で何らかのパターンを作るのはSDではよく行われますが、それをそのままハイトマップやノーマルマップにしてしまうとあまりにも整列しすぎていて不自然です。

だからと言って適当なノイズを与えるだけだとそれはそれで不自然だったりします。

出来ればアイランドごとにIDを持たせるような印象で情報を割り振り、それを元にして他のマップを生成したいという場面があります。

[Tile Generator]ノードなどはその手の情報をランダムに生成してくれる機能がありますが、[Cells 3]のようなノイズノードではアイランドごとに情報をランダムに変更する機能はありません。

[Flood Fill]ノードとそれに続くノードを用いることでこのような情報をあとから生成することができるようになった、というのが今回のアップデートの大きなところです。

・使い方

まず、[Flood Fill]ノードに白黒のパターン画像を接続します。

すると、なんだか不可思議な画像が出来上がります。

この画像はこのままでは基本的に使えませんが、ここには各アイランドに関する情報が保存されているようです。

どのような情報が保存されているかは不明ですが、アイランドのバウンディングボックスのサイズなんかは入っているようです。

この[Flood Fill]ノードの出力を[Flood Fill to ~]ノードに繋ぐと、その繋いだノードに合わせた情報を取得することができます。

2017-10-21_18h12_39.png

[Flood Fill]ノードに続くノードは5種類あります。

[Flood Fill to BBox Size]は各アイランドのバウンディングボックスサイズをアイランドに割り当てます。アイランドの大きさを求めることができるわけです。

[Flood Fill to Gradient]は各アイランドにグラデーションをかけることができます。

グラデーションの方向とランダム性を指定することができるため、かなり使い勝手がいいのではないでしょうか?

[Flood Fill to Position]は各アイランドの中心座標を色として割り当てます。

[Flood Fill to Random Color/Grayscale]は各アイランドにランダムにカラー/グレースケールを割り当てます。

これらのノードは[Flood Fill]ノードから繋げなければほとんど意味がないノードです。

かならず[Flood Fill]と一緒に使いましょう。

・作例

以前のSubstance勉強会の時に作った石畳は石の形状を作成するのにちょっと面倒な方法を採用していました。

2017-10-21_22h19_18.png

[Histgram Shift]を用いて無理やりノーマルマップを作り、そこからハイトマップに戻す方法ですね。

勉強会ではこのようなハイトマップを2種類作り、ブレンドしていました。

この方法でも悪くはないのですが、胡散臭い形状ができてしまうこともあって調整が面倒です。

[Flood Fill]を使ってよりスマートで安定的な形状を作る方法がAllegorithmicの公式動画にあります。

2017-10-21_22h38_11.png

[Flood Fill to Gradient]ノードを用いて各アイランドに傾斜をつけます。

この傾斜をそれぞれ別方向で3つ作り、すべてをMinブレンドします。

するとこのようなノーマルマップが生成できます。

2017-10-21_22h41_12.png

安定的な星型の形状ができました。

この状態だとあまりにも綺麗すぎるので、

・[Flood Fill to Gradient]の[Multiply by Bounding Box Size]パラメータを調整して星の中心をアイランドの重心付近から移動させる

・[Flood Fill to Random Grayscale]を使ってマスクを作り、星の足の数をアイランドごとに配置する

などの方法でよりランダム性を出すことができるでしょう。

・不具合?

不具合なのかアルゴリズム的な仕様なのかは不明ですが、[Flood Fill]を使用した場合に一部のアイランドのエッジ部分にノイズが乗ることがあります。

2017-10-21_22h49_29.png

[Flood Fill]の入力にゴミが残っているというわけでもなく、角度的な問題という感じもしませんが、何らかの理由でこのようなノイズが出てしまいます。

形状を変えたりすれば出なくなったりするのですが、出てしまうのがそもそも困るわけで。

対処の方法としては、[Flood Fill]へ入力するノードに[Blur HQ Grayscale]で少しだけブラーをかけてやると出なくなったりします。

ブラーの強度は0.2くらいなら形状に影響も与えず、ノイズ問題も解消できるようです。

ただし、どんな形状に対してもノイズ問題を解決できるかは不明なので注意してください。

以上、[Flood Fill]ノードの紹介でした。

ベースパスでレイマーチング

家弓メソッドを利用したレイマーチングはGitHubにアップしてあるMaterialCollection内にサンプルが存在していたりします。

しかし、こちらのサンプルは基本的にライティングなどをマテリアル内で行うことを前提として作成しました。

そのため出力はEmissiveのみ、Unlitで行うことを前提としています。

というか、ポストプロセスで利用するのが基本、という感じで作成しています。

少し前に、ある人物からこんなことを聞かれました。

「レイマーチングで描画したものに影を落とすとしたらどうやります?」

それは面白そうだ、やってみよう。ということでやってみました。

家弓メソッドを用いた距離関数の切り替え手法とレイマーチングの行い方は説明しません。

MaterialCollectionの中を見ていただければわかるのではないかと思います。

今回はその結果をEmissiveに出力するのではなく、ベースパスでちゃんと法線を持ったオブジェクトとして描画してみようと思います。

まずマテリアルの Shading Model は Default Lit にします。Blend Mode は Masked にしましょう。まずはここから。

次に距離関数を設定したマテリアルファンクションとレイマーチング命令を内包したマテリアルファンクションを加算します。

ue419.jpg

加算した結果はあとで別の値に対して加算して、ちゃんとコード生成が行われるようにしましょう。

私が作成したレイマーチング用の関数はCustomノードで使用することを前提としており、1つの関数からワールド座標、ワールドノーマル、レイが衝突したかどうかのフラグを取得することが出来ます。

しかしCustomノードは1つのノードで出力可能な値は1つだけです。1つのCustomノードで3つの戻り値を返すことが出来ないのです。

なので同じ命令を実行し、出力する値をそれぞれ違うものを出力するようにします。

ue420.jpg

衝突したかどうかは [GetDistanceFieldMask] というCustomノードをチェックします。

このノードの出力は、衝突していたら 1.0 を、そうでなければ 0.0 を出力するように出来ています。

なのでそのままOpacity Maskに入力すればOKです。

Normal も基本的にそのままマテリアル入力に接続しますが、DistanceField関数を定義しているノードを有効化するためにそれらのノードとの加算を取ります。

ue421.jpg

また、接続するノーマルはワールド空間ノーマルなので、マテリアルパラメータの [Tangent Space Normal] はOFFにしておきましょう。

最後に座標ですが、実際に描画されているオブジェクトとDistance Fieldから深度方向のオフセットを求めます。

レイマーチングの計算で得られた座標と元のワールド座標をカメラ空間に変換し、その差をDepth Offsetに接続します。

ue422.jpg

あとはカラーを適当な値で出力すればOKです。

このマテリアルをアサインするメッシュは適当な板でOKです。

この板は常にカメラの目の前に置くようにすると、シーン全体でレイマーチングを行うようになります。

ue423.jpg

コリジョンはなしにしておいたほうが良いでしょう。

今回の手法はベースパスで処理されるため、ライティングもシャドウレシーブも行われます。

ただ、シャドウキャストはうまくいきません。シャドウキャストはOFFにしておくのが良いでしょう。

もちろん、実際に描画されている物体にコリジョンはありませんので、衝突させたくてもうまくいきません。

とまあ…正直なところ、使い物にならない技術かな、と思います。

エンジンを拡張してポストプロセスを実装する おまけ

この記事はポストプロセスをエンジン改造で実装する際にハマった罠について書いています。

エンジン改造してまでポストプロセスの実装なんてしないよ、という方には不要かもしれません。

コンソールゲーム機ではフレームバッファの解像度をユーザが変更できる機能は通常存在しません。

処理落ち回避のために動的にフレームバッファの解像度を変更するゲームがあったりはしましたが、あまり存在していません。

しかし、PCゲームでは解像度を変更する機能は普通に存在しています。

ましてやUE4やUnityのようなゲームエンジンはエディタ上でゲームを動かせるため、エディタ上のゲームViewのサイズが変更されることも多くあります。

解像度が変更された場合、通常であればフレームバッファや、ポストプロセスなどで処理を行うためのオフスクリーンバッファは解像度に合わせて作り直されるのが一般的です。

UE4でも基本はそうなのですが、ある状態変化の場合はフレームバッファが作り直されません。

その状態というのは、解像度が小さくなった場合です。

まずはこの画像をご覧ください。

ue416.jpg

これはUE4のエディタを小さめにして使っていた場合の途中のレンダーターゲットです。

では次。

ue417.jpg

こちらはエディタを最大化した際のレンダーターゲットです。

当然ですが解像度は縦も横も上がっているので、フレームバッファやオフスクリーンバッファは作り直されています。

では最後。

ue418.jpg

エディタの最大化を解除して、最初と同じウィンドウサイズに変更した場合のレンダーターゲットです。

見ての通り、最大化したサイズのまま、描画される範囲だけが小さくなっています。

この動作はシッピングされたゲームでも同様の動作をするのかは不明ですが、少なくともエディタ上ではこのように動作します。

ポストプロセスを実装する際、フレームバッファとサイズの異なるバッファを使用することはよくあります。主に縮小バッファとして。

しかし、単純にバッファサイズのポリゴンを描画するような方法を取ってしまうと正常に描画されなかったり無駄なピクセルシェーダが動いてしまったりします。

なので、正しいサイズで、正しい範囲に描画を行わなければなりません。

今回ピクセルシェーダで実装したディフュージョンフィルタでは他のポストプロセスに習って DrawPostProcessPass() という命令を使用しています。

この命令は出力先のレンダーターゲットに対して適切なUV座標を持った板を描画してくれる命令です。

ここに適切な値を渡してやればバッファサイズがどんなに変わってもちゃんと描画されますが、適切でないとうまくいきません。

なので必要なパラメータを少し紹介します。

float X, Y, SizeX, SizeY

これはレンダーターゲットに対するViewportの左上座標とサイズです。単位はピクセル

float U, V, SizeU, SizeV

これはポストプロセスを適用する元のバッファのUV値の左上座標とサイズです。こちらも単位はピクセル

FIntPoint TargetSize

これはレンダーターゲットのバッファの縦横のサイズです。単位はピクセルで、与えるのは整数値です。

FIntPoint TextureSize

これは元のバッファの縦横のサイズです。単位はピクセル、整数値で与えます。

この関数は簡単なポストプロセスには比較的簡単に適用できるのですが、複数のサイズが異るバッファを使った場合はうまくいかない可能性も出てきます。

複数のテクスチャにおいて使用する正規化されたUV値が同一でOKならさほど問題はないです。ちなみに、ディフュージョンフィルタでは同一UV値でOKだったので正常動作しています。

比率がぜんぜん違うテクスチャを複数枚使う場合はこの命令を参考に自分たちの用途に見合った命令を作成しましょう。

というわけで、ちょっとしたおまけでした。

エンジンを拡張してポストプロセスを実装する CS編

この記事は裏UE4 Advecnt Calender 2016の15日目の記事です。

UE4のポストプロセスはマテリアルを利用して比較的簡単に拡張が可能です。

しかしながら、いくつかの問題点も存在しています。

問題の1つにCompute Shaderが使用できない、というものがあります。

Compute Shaderはラスタライザを使用せずにGPUの高速な演算機能を使用する手段で、DX11世代以降で追加されたシェーダです。

特にDX12やVulkanではグラフィクス処理と並列に処理を行うことが出来るコンピュートパイプと呼ばれるパイプラインが存在し、これを利用した非同期コンピュートはグラフィクス用途でもそれ以外の用途でも使用できます。

グラフィクス処理を行うパイプラインでも使用することは出来ますが、ここで使用する場合はコンテキストスイッチなどが発生するようで、場合によってはラスタライザを経由するより処理速度が落ちます。

しかし、Compute Shaderには共有メモリが存在します。

Pixel Shaderは各ピクセルごとに独立して処理が行われます。

そのため、テクスチャのサンプリングはそれぞれのピクセルごとに行われ、別々のピクセルで同一テクセルをサンプリングしたとしてもその情報を共有することは出来ません。

Compute Shaderは一連の処理を複数回行う際、複数の処理の束をグループとしてまとめることが出来ます。

グループ内の処理は基本的には独立しているのですが、グループ単位で少ないながらも共有メモリが使用でき、また、グループ内の処理の同期をとることが出来ます。

これを利用することで、別々のピクセルでの処理に同一テクセルを大量にサンプリングされる場面で共有メモリを介して高速化することが可能です。

今回は以前ポストプロセスマテリアルで実装したSymmetric Nearest Neighbourフィルタをエンジンを改造してCompute Shaderで実装しました。

ただ、大変残念なお知らせですが、普通にPixel Shader使うより遅いです。

コンテキストスイッチが発生してるなどで処理速度が遅くなっている可能性が高いですが、まだ何が原因かは調べていません。

余裕があったら調べようとは思いますが、わかっているのはPixel Shaderでの実装より倍遅かったです。

メモリ帯域が狭いGPUの場合は共有メモリによる高速化が効いてくるはずですが、GTX1070ではさほど効果がないようです。

ue415.jpg

PostProcessSNNが今回実装したCS版SNNで、その下の "M_SNN_Filter" がポストプロセスマテリアルで実装したPixel Shader版のSNNです。

SNNCopyという描画パスについては後にご説明します。

では、続きからで実際の実装方法を見ていきましょう。

続きを読む

エンジンを拡張してポストプロセスを実装する PS編

この記事はUE4 Advent Calendar 2016の15日目の記事です。

UE4ではポストプロセスの作成にポストプロセスマテリアルを使用します。

通常のマテリアルと同様にマテリアルエディタで作成でき、マテリアルインスタンスも使用できます。

ポストプロセスのパラメータをランタイムで変更する場合はパラメータコレクションを利用する必要がありますが、BPを利用すればレベルごとの設定などもそれほど難しくなく便利です。

しかしいくつかの問題点が存在します。

そのうちの1つで、個人的に最も大きいのがオフスクリーンバッファを使用できないことです。

ポストプロセスを本格的に作成する場合、複数の画像をコンポジットする必要が出てきます。

ポストプロセスマテリアルでもG-Bufferとして保存されている画像はコンポジットすることが出来ますが、それらから生成した複数の画像をコンポジットするということが出来ません。

なのでポストプロセスマテリアル内でそれらすべての画像の加工からコンポジットまで行う必要があります。

それで実装可能であれば問題ないのでは?と思う方もおられるかもしれませんが、加工が重い処理の場合は速度的にどうなの?とか、クオリティは保てるの?などの疑問も出てきます。

それらを解決する方法としてエンジン側にポストプロセスを作成、埋め込むという方法があります。

もちろんエンジン改造となるので改造コストは安くないですし、特に改造後のエンジンバージョンアップに対応するコストが馬鹿になりません。

ただ、ポストプロセス部分は割と独立してる感のある部分なので、既存のポストプロセスの順番を入れ替えるよりマージコストは低いのではないかと思います。

というわけで、今回は以前ポストプロセスマテリアルで実装したディフュージョンフィルタを、エンジン改造によってよりクオリティの高いものにする実験を行いました。

ディフュージョンフィルタは以下のようなパスで実装されます。

ue411.jpg

元の画像の色を2乗して、このバッファをガウスブラー、2乗カラー画像とブラー画像をスクリーンブレンドし、最後に元画像との間で色の最大値を取ります。

ポストプロセスマテリアルの実装ではカラーの2乗~比較(明)までの処理が1パスで処理されていました。

問題点を上げるとするなら、ガウスブラーを行う前にかならずサンプリングしたテクセルを2乗する必要があるという点です。

2乗した画像が別に存在するなら、ただそれをガウスブラーすればいいだけなのですが、ポストプロセスマテリアルでは1パスですべての処理を終わらせなければならないのでこのような実装になります。

また、ガウスブラーの実装にも問題があります。

ガウスブラーのGPU実装はX軸方向のガウスフィルタとY軸方向のガウスフィルタを別パスで、つまり2パスで行うことが多いです。

この方が高速で、且つ綺麗にブラー出来ます。

ポストプロセスマテリアルではこの手法は使えないので、ボックスブラーよろしくボックス状にテクセルをサンプリングしてガウスブラーを掛けなければなりません。

綺麗にしようと思うとサンプリング数がかなり増加して速度に問題が発生します。

最後の問題点は縮小バッファが使えないことです。

ガウスブラーは画面をぼかしてしまうので、低解像度でもさほど問題ない場合があります。

カラーの2乗を行う際に縮小バッファに描画し、これをガウスブラーするのであればより高速ですが、ポストプロセスマテリアルではそのようなことが不可能です。

今回の実装はボカシの綺麗さだけではなく、速度的にも解像度によっては有利です。

こちらは比較的高解像度での処理速度比較です。

ue412.jpg

DiffusionPow2~Diffusionまでが今回入れた処理です。

2乗カラーをガウスブラーしてダウンサンプルしてガウスブラー…という感じでガウスブラーを3回行った結果をぼかし画像として使用しています。

合計が0.57msでその下にあるポストプロセスマテリアルでの実装より少しだけ高速です。

ただし、解像度が低い場合はポストプロセスマテリアル実装のほうが高速です。

もちろん、クオリティはエンジン改造のほうが高いですね。

というわけで、続きからこの実装方法について書いていきます。

なお、.usf, .h, .cppファイルをそれぞれ1つずつ追加している点にご注意ください。

追加してプロジェクトに登録する方法は書いていません。

続きを読む