水ノ茉の宣伝
準備中...
ゲームを作る予定なの水ノ茉
シリーズ説明
本シリーズで最終的に検証したい内容は、DMXで制御されたバカみたいな量のライト、それらすべてをキャラクターに当てても破綻しないレンダリングパイプラインの構築です。
実装の手段は選ばないため、普段展開している【UE5】◯◯◯ – Unreal Engine ◯.◯と比べると多少敷居が高く、また、実装の再現性を提供しないこともあるため、いつも以上に趣味嗜好が全開となります。
始まり
本シリーズで技術的に検証したいことの最後です。
- ディファードライティングな独自パス群
- テンポラルに正規化した陰影
- 多層マルチパスによる透かし表現
複数のマルチパスで構成したリッチでエッチな半透明な表現です。
眉や瞳の透かしの基本
透かしは大体この3つの方法がベターです。
- 半透明で頑張る
- Forward+で頑張る
- マルチパスで頑張る
上から順に実装コストが安いが制限が多く、下に行くほど実装コストが高いが制限が少ない感じです。
すべての実装方法が手元にあるので、記憶の整理がてら、ついでに軽く紹介しますか。
半透明で頑張る
UnityURPでよくある2パス手法です。
1パス目に深度を書きこんで、2パス目に深度テストしながらカラーを書き込む方法です。
Unityは記法的にパスという表記が正しいですが、ふと冷静に考えると、割と混乱する命名ですね。
まずは始点であるカラーと深度バッファです。


1パス目は深度のみを書き込みます。カラーはWriteMaskで書き込みを封じます。



2パス目は深度テストしながらカラーを書き込むだけです。



初心者だとDepthを書きながらColorも書けばいいじゃんという当然の疑問が湧くかもしれませんが、半透明でそれをやると前後関係が解決していない状態でColorを書き込むことになります。そうすると、見えちゃいけない場所が見えるという、不思議な見た目を生み出してしまいます。そのため、半透明な見た目を作る場合には、DepthとColorの書き込みは別々に分けて行うのが基本なのです。
半透明パスなので当然アルファブレンドが出来ます。適当なGバッファに透明度を突っ込んでおいて、カラーを書き込む際に参照すると、こんな感じの透かし表現が簡単に実装できます。
必要に応じて半透明パスに入る前にGバッファをコピーしておけば、半透明パスでGバッファを書きながら進められるため、ポストエフェクトとの相性も担保できます。コピーする理由はReadとWriteで競合しちゃうからですね。

とっても簡単なこの方法ですが、簡単故の欠点が幾つかあります。
例えばディレクショナルライトしか当てられなかったり、影が落とせなかったりすることです。
無論、MaterialParameterCollectionにパラメータを突っ込んで手動計算したり、ローカルライトを活用したり、マテリアルの半透明ライティングを活用したり、SceneCaptureでお手製のシャドウマップを作ったり、色々と工夫を凝らせばその限りではありませんが、基本的な環境ではディレクショナルライトしか当てられません。
そのため、ディファード環境で周りのエフェクトが光っているのに、透かしメッシュだけが光らずに浮いてしまったり、影に入っているのに、暗くならなかったり、ライトに関連する不都合が発生します。
実装に掛かる時間よりビルド時間の方が長いくらい、それほど簡単な実装コストを考えると妥当なデメリットですけどね。
筆者さんはモック程度であれば毎回この実装を採用しています。
本当にコスパが良いのです。
狂犬姉(?)妹の妹さんの方を勢いだけで再現しようとした回の背景技術もこれです。
Forward+で頑張る
注意点としてUnityのForward+と同等かは不明です。
一番近い設計思想がForward+な気がしたので、記憶する上での識別名として勝手に拝借しています。
透かしのReceiveとCast以外のMeshのDepthWriteを終わらせます。CoffeeLiveと同様に専用のバッファにカラーを書き込むスタイルを採用しているため、現時点ではカラーバッファはBlackです。真っ黒な画像で正常です。


透かしのReceiveのMeshをDepthWrite&StencilWriteします。眉と瞳を透かそうとしています。


透かしのCastのMeshをStencilTestしながらDepthWriteします。StencilTestは先ほど書き込んだStenciValueをNotEqualの場合のみ深度を書き込む感じです。StenciValueが書き込まれている箇所は深度が更新されていないことが分かるでしょう。


カラーを書き込みます。一般的なパイプラインでは、髪の毛の後ろにあるはずの眉や瞳は描かれないことが普通ですが、先ほどのStencilTestを通った場合のみ深度書き込みという前提があるため、眉や瞳もこのように描かれます。


透かしのCastのMeshをStencilTestしながらDepthWriteします。先とは異なりStencilValueがEqualの場合のみ深度を書きこみます。これで前後関係が完全な状態になります。


透かしのCastのMeshをDepthTest&StencilTestしながらColorWriteをします。StencilTestの条件は同様にStencilValueがEqualの場合です。カラーを書き込む際にアルファブレンドをすることで透かしが適用できました。


Forward+なので透かしを適用しつつも、キャラ全体のフェードをすることも出来ちゃいます。ディザ行列という昔ながらの手法を使わずに今時のハード性能に追随した表現が出来るのです。開発中のGrimでは、この半透明のフェード表現をしたいという理由だけで、パイプラインにForward+を採用しています。尚筆者の気分次第でDeferredに変わるルート分岐もあります。
求める見た目を顕現させる手法としてForward+はかなり優れているのですが、当然ながら代償は処理負荷です。
一般的に半透明な表現はオーバードローが重いとされています。Forward+ではそれに加えて、ピクセルごとに対応するライトの最大数でfor文を回す必要があります。これとオーバードローの組み合わせが負荷を爆増させるのです。実際のところ、深度を書き込みながら進めているので、オーバードロー自体の負荷はエフェクトとかに比べると全然軽いです。シンプルにライト計算が重いのです。UEではGridCellなどでループを抑える最適化が標準で施されていますが、結局はライトが多い地点では負荷が目立ちます。
そういう諸々を考えると多光源な環境では、制約があるにしてもディファードが適しているんですよね。そんなディファードの制約を力技で解決しようとしているのが今回の趣旨のひとつですが。
マルチパスで頑張る
パーツごとにライティングしてアルファブレンドする方法です。ゲームの描画パイプラインというより、ペイントツールの仕組みに近いですね。この例え話だけでも負荷が高そうなことが汲み取れることでしょう。
CoffeeLiveの現状のパイプラインがこれです。

それがこうなるイメージです。

ね?冗談抜きで負荷が狂っている手法です。
でもペイントツールのように絵を組めるので、やりたいことは全て実現可能です。尚コスト。
ライトファンクションの対応
毎度のことながら過去の私が残した残作業の後始末です。
おそらくライトファンクションが最後です。
DMXはライトファンクションが必須なんですよね。
単層マルチパス
前髪の後ろにある眉や瞳の透かし処理をマルチパスで実装していきます。

Toon Multiple 1st / 2nd Passフラグの追加
ベースやデプス、アウトラインやオフセットシャドウの各メッシュをマルチパスのどの位置、レイヤーに投下させるか判断するフラグを追加します。

各種パスのマルチパス対応
過去に作成したパスのマルチパス対応をします。
筆者の開発環境はVRAMが24GBほどと、それなりに余裕があるので、マルチパスのGバッファはテンポラルバッファではなく、FSceneTexturesにTStaticArrayで保持させています。

お目目と眉が別パスな関係で若干恐怖映像ですが、各パスごとにライティングされました。
各パスを不透明でコンポジット
各マルチパスの深度の比較結果に従って最終的なトゥーンバッファを作成します。マルチパスに換装する前の画像を撮り忘れましたが、おそらく同じ結果です。

近づいて見ても問題なさそうですね。

透かしのCastとReceiveピンを追加
透かしのレシーバーとキャストのピンを追加します。実質メインパスである1stにCastとReceiveの両方を用意する必要はないはずですが、実装するのが楽しくて設計を終わらせる前に走り出しちゃったので少し手探りです。

エンジン改造のビルド時間はデメリットでありメリットです。開発機の性能に依存しますが、筆者の環境だと30分も掛からないので設計を見直したり、新しく組んだりするのにちょうどいい時間なのです。長すぎず、短すぎないので、沼り過ぎないのです。あとはアーカイブを書き記すのにも最適な時間です。まぁそれが原因で日によって文体や構成が変わっちゃうんですけどね。計画性を持ってコンテンツを展開なんて飽き性と浮気性を兼ね備えた筆者には不可能なのです。勢いだけで今後も展開です。負の性格を直すのなんて無理だし直す気もないので上手いこと利用するしかねぇのです。本当なら水ノ茉のゲーム開発に時間を割かなきゃいけないところ『飽きた』の3文字でCoffeeLiveに浮気ですからね。そして実はCoffeeも密かに飽きてきました。好きなことをし過ぎても、飽きちゃうのよね。我ながら本当に悩ましい性格です。
Boothで販売されている子たちはモーフで表情制御をすることが多いので、目の後ろにいくつかメッシュが連なっていることがあります。この状態でマテリアル単位で透かし度合いを決めると見た目が破綻してしまうので、透かし度合いをGバッファに格納する必要があるのです。
眉と瞳の透かし
眉と瞳にCastを設定して、前髪にReceiveを設定します。
ふむ。冷静に考えたらこの問題はGバッファ単体では解決できませんね。

見えちゃいけない箇所が見えないようにお顔のメッシュもマルチパスのデプスパスに投下です。実質的にオーバードローになりますが、頂点変形をしていない場合は、GPUコストが最小限なシェーダーをアタッチするようにしているので許容できるレベルです。追加対策として、速度とステンシル書き込みを封じれば更に軽量化を望めます。

深度を書き込んだことで暴露してしまう問題は無事抑制です。カメラをぐるぐる動かしたり、後ろから見た場合も問題なしです。
CastとReceiveによる透明度制御もいい感じですね。デプス投下することを考えたらCastとReceiveは片方あればいいかもですね。というかパスごとに設定する必要もないのかな。このあたりはリファクタする時に改めて考えるとします。
半透明は描画コストが異常なのでエンジニア的には可能な限り避けてほしい表現ではあるんですが、やっぱり見た目が一気に映えますよねぇ。エロゲのCG絵でもよく見られる表現ではあるので、普通に好きなんですが、負荷と描画パスが複雑になるから、好きなんだけど、避けたい表現という、複雑な気持ちなのです。見る分には好きです。触る分にはご遠慮です。

衣装を探す
多層マルチパスで映えそうな半透明が盛り込まれている衣装を探します。
今回の気分転換に付き合ってもらっているこの子。見た目も可愛くて、モデリングも高品質で文句がないんだけど、高品質が故に衣装がバカほど多いのです。探すのが割と面倒なのです。Boothの公式が規約のフォーマットを提供して、それを元に絞り込み機能を実装すれば効率化出来るんですけどね。運営の怠慢ですね。

いい感じの見つけました。
えっまって。かわいいんだけど。なにこれ。やばい。

スカートのボーン数が異なるので愚直に伝播できないという致命的な問題を抱えていますが、それ以外はおそらく完璧です。

多層マルチパス
眉や瞳の透かし処理が無事に完了しました。
次は本命である衣装を含めた多層なマルチパスです。
単層だと、眉、前髪、衣装、この3つのメッシュが重なる場合に、後景のオブジェクトが描画されなくなる欠陥が露わになります。想像に難くないと思いますが、愚直に多層にすれば解消します。当然、処理負荷が爆増しますがね。UEさんは汎用ゲームエンジンが故にトゥーン描画には不要なパスが多く、それらを加味してマルチパスなんて組んだらそりゃ処理負荷が耐え切れません。自明ですね。今回は独自パス群というチューニングし放題なパイプラインを組んでいるのでこういう無茶をすることが出来るのです。たぶん耐えてくれるはずです。おそらく。きっと。
マルチパスの多層化
なにも知らない描画エンジニアが見たら泡吹いて倒れそうなレベルで草。

マルチパスの内訳
不透明なオブジェクトを描画するグループです。トーンマッパの打ち消し計算を含んだ出力なので見た目がちょっと不気味な点は気にしないで下さい。

先ほどの眉や瞳、白目といった前髪に被った箇所だけ透かすグループです。お顔のメッシュだけデプスパスに投下しているため、モーフの表情制御で使用するようなメッシュの前後関係問題も解決済みです。

半透明な衣装を描画するグループです。

半透明な衣装を描画する別グループです。重なりが発生する以上は同じ衣装でも別グループにしないと消失問題が起きちゃいます。

衣装を半透明で描画
各パスでライティングした結果をコンポジットするとこんな感じです。
ふふん。完璧です。

衣装の透明度を調整するとこんな感じです。半透明の後景は前景の透明度を考慮してレシーブシャドウをした方が良さそうですね。パイプラインを組み直せばライティングまでにマルチパスの全てのGバッファを確定させることが出来そうですが、まぁ、面倒ですね。
眉と瞳と前髪の透過表現と半透明な衣装が両立することも確認できました。これで多層にしたことの目的が果たせましたね。

それにしても可愛いね。この子。ルックデヴは素体が高品質だと気分が上がりますね。

プレデプス及びディファードの欠点
PreDepthはオーバードローを抑制するという観点では大変すばらしいですが、あくまで不透明な場合です。半透明だとこのように後景の深度テストが偽になって描画されなくなります。プレデプスをせずにベースパスでカラーと深度を一緒に書き込むことで回避自体は可能ですが、これを回避すると今度はディファードライティングの問題にぶち当たります。後景のGバッファが存在しないのです。こればかりはForward+でしか解消する術を私は知りません。真に完璧な半透明を作るにはやはりForward+を顕現させないと難しそうですね。その辺りはGrimで発散できるので今回は仕様的な限界として諦めるとします。出来ないことを実際に組んで思い知ることも一興なのです。
処理負荷を少し覗く
まだボトルネックが色々と残って改善の余地がある状態を前提として軽く負荷を覗いてみます。
最適化は負債を溜めておいて最後に一括返済派です。その都度進めることが理想なんでしょうが、結局のところ仕様変更に振り回されたら場合によっては全てが無に帰します。そういうことされると普通にムカつくし気分が落ち込むので、最後にやるのが精神衛生上は良いと思うのです。
Developなのでシェーダーに不要な実装が残留していることも前提です。PSOの処理を一切していないのでおそらくTESTビルドが通らないのです。いつか実装しないとですね。めんどう。
DMXはライトシャフトをVolumetricFogに依存せずに半透明メッシュで表現しているのでそのあたりでオーバードローが過剰に発生しているのでしょう。あとは各パラメータの伝達にSetScalar, VectorParameterを使用しているのところがボトルネックになっている気がしますね。この部分はエンジン改造すればスレッド間の負荷を最小限に出来るので気が向いたら改造して処理負荷差分を測って実用性を見ておきたいですね。CoffeeLive以外で使う予定はないですが、ぶっちゃけこの辺りは抱えていても有益な使い所がない技術なので、気が向いたら作業範囲次第では別ネタとして公開しましょうか。
ライトが30個前後でPrePassとMainPassで多重計算していることを考えると想像よりは軽いですね。各パスのシェーダーは、使用している機能を最小限に抑えて書き直したものを使用しているので、そのあたりの恩恵も地味に大きそうですね。やはり独自パス群は素晴らしいです。負荷やルック的には素晴らしいのですが、ここまで魔改造な環境は個人や内輪でしか導入できないというのが悩みどころです。使いこなす上である程度の技術的な知識が必要になりますが、アーティストとエンジニアでは、有している知識や技術が結構違うため、知識的な前提を要求するようなモノは基本的には提供できません。それが原因で力を抑制されるから欲求不満になるんですよね。

おわり!!!
これでCoffeeLiveで検証したい技術的項目はすべて満たせました。
残りはこのパイプラインを元に作りたいルックを決めて、それに沿ってシェーダー作成とパイプラインの微調整、最後に最適化ですね。
ルックの方針決めは要件定義と同等で時間が掛かるのと、再来週にはCEDECが待ち受けているので、流石に次回は日が空くことでしょう。というか開けないと作り込みが出来ないので開けてくださいね、未来の私。私のことなのでやろうと思えば、火力を焚けば一瞬で出来るでしょうが、絶対に疲れるから過去の私からは非推奨とだけ忠告しておきます。

完成後のスクショタイムは開発者の特権って感じで楽しい。
あとマジでこの子可愛いのよな。
多光源はライトを当てればリッチになりますが、素体が簡素だとライトの方が強すぎて、負けちゃうこともあります。そのあたりを感じさせないのでマジで作り良いです。どこから撮っても可愛くて、困っちゃうわ。

セルルック関連
えちえち GPU Cloth Simulation
GPU挙動が怪しいのでParallelForで動作確認。GPUで起きている問題の再現性は無いですね。UEさんは最適化を頑張った結果としてAsyncComputeがデフォルトで有効になっているので、検証レベルでは意外と面倒なことが起きるんですよね。AddPassでPassFlags指定しないと実行順序とんでもないことになってるのかなぁ。
CPUとGPU実装を別々のファイルに書き直したらバグがひとつ直ったけど、大回転するようになった。草。
大回転はCPUでも条件を揃えれば再現性を確保できたので、それを元に遡るように修正方法を試したら無事、直りました。これでCPUとGPUのProceduralMeshのような整った頂点を対象としたクロスは確立できました。次は本命のStaticMeshとSkeletalMeshの頂点情報から制約構築ですね。スカートを揺らせるのが先か、本シリーズの終焉が先か、チキンレースです。









