今回はUE4での半透明マテリアルの特徴について記事にしてみます。
最初に結論を言ってしまうのであれば、現在の半透明マテリアルについては”期待するな”。
PS2の頃から開発をしているデザイナさんなどは半透明でいろいろな表現をしようと思うかもしれませんが、考え方を変えないとUE4での美しい映像表現は難しくなります。
Deferred Shadingの知識を持っている方であれば、半透明オブジェクトと不透明オブジェクトが同等には扱えないことを理解してもらえると思いますが、そのあたりも含めて解説していきます。
不明な点等ありましたら質問していただければ答えられる部分には答えますので、よろしくお願いします。
さて、UE4で実装されているDeferred Shadingと半透明マテリアルの問題点を簡単ながら解説していきます。
以前にも解説しましたが、UE3まではForward Renderingという技法が採用されていました。
これは3Dゲーム開発の現場では慣れ親しんだ手法で、"モデルを描画するパス"=="ライティングパス"という技法です。
モデルが描画されると、その段階でライティングが行われた結果がフレームバッファに描画される、というわけです。
Deferred Shadingと呼ばれる技法はこれとは違い、"モデルを描画するパス"=="GBufferに書き込むパス"であり、"ライティングパス"とは!=の関係にあります。
GBufferはライティングに必要な情報を保持する複数のバッファであり、ここに情報を書き込む段階ではライティングされた結果、つまり、最終的な映像は作成されていないということになります。
ここで半透明マテリアルについて考えてみます。
半透明マテリアルはアルファブレンディング、加算合成などいくつかの合成方法はありますが、ほぼどれでも共通している部分として、すでにバッファに書かれている情報を合成計算に利用する、というものがあります。
例えばアルファブレンディングの計算式を見てましょう。
FinalColor = SrcColor * Alpha + DestColor * (1 - Alpha)
この計算式において、FinalColorは合成結果、SrcColorが書き込むカラー(つまり半透明マテリアルのカラー)、DestColorがフレームバッファのカラー、Alphaが不透明度となります。
不透明度が1.0、すなわち完全な不透明のマテリアルであればFinalColorはSrcColorと一致します。
この計算式からもわかるように、半透明マテリアルを利用する場合はDestColorがわかっている必要があり、そのためには半透明マテリアルより奥にある物体はライティング計算が行われた、最終的な出力カラーが求められている必要があるわけです。
Forward Renderingのエンジンにおいても、半透明マテリアルは結構悩みの種ではありました。
しかし、最初に不透明マテリアルを描画し、そのあとにZソートした半透明マテリアルを後ろから順番に描画することである程度の問題を解決することができました。
Deferred Shadingでも基本は同じで、不透明マテリアルをGBufferに描画し、ライティングパスを起動してライティングを行った結果をフレームバッファに描画、その後に半透明マテリアルを描画します。
ですが、通常は半透明マテリアルの描画にはGBufferは用いず、半透明マテリアルだけForward Renderingを行うことになります。
なぜなら、GBufferの1ピクセルには1つの情報しか入れることはできませんが、半透明マテリアルがあるピクセルに複数書き込まれるような状態になった場合は手前の情報だけを保持すればいい、ということにはならないからです。
ならば半透明マテリアルを1つ描画したらライティングパスを起動して最終結果を求めて合成、これを半透明マテリアルの数だけ行うという方法はどうでしょう?
この方法ではそもそもDeferred Shadingによる多光源シーンの高速化が無意味になってしまいます。
結局、半透明マテリアルはForward Renderingで描画し、多光源シーンでも光源を限定しておいた方がいいということになるわけです。
UE4でももちろんこのような形で半透明マテリアルは実装されており、そのために半透明マテリアルにはかなりの制限がかけられています。
半透明マテリアルに対しても多光源計算を行う手法としてForward+ Renderingという手法もあるのですが、こちらはDX11世代以上が必要になるためか、UE4では採用されていません。
では、続きを読むで具体的なUE4での半透明マテリアルの使用方法、注意点などを解説していきます。
まず、半透明マテリアルにする場合は、マテリアルのDetailsタブで[Material] -> [Blend Mode]を[Translucent]に変更する必要があります。
これでマテリアルは半透明として扱われますが、注意点として、デフォルトでは半透明物はライティングされません。
ライティングを行いたい場合、Detailsタブの[Translucency] -> [Translucency Lighting Mode] を [TLM Volumetric Directional] か [TLM Surface] にする必要があります。
この2つにどんな違いがあるのかはソースコードを追っていないため正確にはわかりませんが、パッと見て気づいた点としては、[TLM Volumetric Directional]はスペキュラ計算が省かれているような印象があります。
また、半透明マテリアルはリフレクションがキューブマップのみで実装されているようです。
Metallicを1.0、Roughnessを0.0でOpaqueとTranslucentを比較した映像が下のスクリーンショットです。
Opaqueではスクリーン空間リフレクションが有効になっていますが、Translucentでは無効になっています。
ライティングについてもかなりの制限が行われていて、影響下にあるライトをいくつかの平行光源にまとめているのではないかと思われます。
これについても詳しい動作はソースコードを読む必要がありそうです。
このように、不透明マテリアルと比べると半透明マテリアルの待遇の悪さがわかりやすいです。
この待遇の問題はポストプロセスや深度バッファ関連にも影響しています。
深度バッファ関連はForward Renderingにおいても問題になりやすい部分で、特に半透明マテリアル同士が重なるような場面において深刻に発生します。
以下の画像は2つの半透明オブジェクトが交差しているシーンです。
三面図を見ていただければわかると思いますが、ボックスを円柱が貫いている状態であることがわかります。
ボックスも円柱も色が違うだけで半透明マテリアルです。不透明度は1.0で、完全な不透明ですね。
さて、この状態での深度バッファはどうなっているかをEditor上のBuffer Visualizationで見てみましょう。
カメラ位置は先の画像と全く同一なので、本来であれば画面上方にボックスと円柱の深度が描画されているわけですが、半透明マテリアルであるために描画されていません。
つまり、半透明マテリアルは深度バッファが描画されず、ピクセル単位のオクルージョン・カリングが行われないということです。
オクルージョン・カリングとは、あるものが描画される際、別のものによって遮蔽されている場合はこれを描画しないというものです。
深度バッファは通常、ピクセル単位でこのオクルージョン・カリングを行うことを目的として実装されていますが、そのためには深度バッファに現在の最も手前の深度が描画されていなければいけません。
深度バッファに何も描き込まれていなければオクルージョン・カリングは行われないということになります。
では、半透明マテリアルのオブジェクトはどうやって前後を判断するのでしょうか?
それはZソートです。
そのオブジェクトのある基準点がカメラから遠いものから順番に描画されるわけです。
これはForward Renderingでは基本的な手法ですが、UE4でも当然このように実装されています。
そして、この手法で発生する問題はそのままUE4でも発生します。
それが以下の画像です。
貫いているはずの円柱がボックスより手前に描画されしまっています。
これはZソートによってボックスが奥、円柱が手前と認識され、その順番で描画されてしまうためです。
他の描画エンジンであれば深度バッファへの書き込みを行うかどうかをマテリアルごとに指定できるようになっていることもしばしばあります。
それによって、交差する可能性があるマテリアルや不透明度の高い、実体がはっきりしているマテリアルでは深度バッファを描き込むように設定するなどが可能だったりします。
しかし、UE4では残念ながらこのような設定はなく、深度バッファの比較を無視する設定くらいしかありません。
なお、ピクセルごとの正確な半透明処理を実現する手段として"Order Independent Transparency"という技術もあるのですが、今のところこれが使われたゲームというのはスクウェア・エニックスさんの『トゥームレイダー』の髪の毛の表現くらいしか知りません。
さすがにフルHD解像度で完全完璧なOITを実装するのは現状では難しいと思います。
次にポストプロセス関係の話ですが、最初に考えるべきなのは被写界深度(DOF)です。
UE4のDOFは通常、半透明マテリアルには影響を与えません。
そのため、単純な半透明マテリアルはDOFの中で浮いた存在になってしまいます。
DOFを有効にするにはDetailsタブの[Translucency] -> [Enable Separate Translucency] をOFFにする必要があります。
こうすると半透明マテリアルでもDOFが有効になります。
ただ、ぼけ方の計算を行う深度は不透明オブジェクトのものを使用しているようです。
そのため、例えば半透明マテリアルがピントの合っている位置にあったとしてもその奥の不透明オブジェクトのピントが合っていないと不透明オブジェクトの形でくりぬかれるようにぼけたりぼけなかったりするようです。
この[Enable Separate Translucency]はONになっていると不透明のライティング結果を保存するフレームバッファとは別のバッファに半透明オブジェクトを描画してからフレームバッファに合成するような処理を行っています。
この理由は、たぶん半透明オブジェクトの描画が重かった場合にSeparate Translucencyバッファのサイズを小さくできるという利点があるからだと思います。
ただ、この手法の欠点としては、半透明マテリアルが複数重なっている場合に計算結果が正しくならないという点にあるでしょう。
また、ポストプロセスマテリアルを使用する場合、ポストプロセスを実行するタイミングを3種類から選択できます。
このうちの[Before Translucency]はSeparate Translucencyの描画の前、DOFの後の処理になるようです。
[Enable Separate Translucency] がOFFのマテリアルは前述のポストプロセスの影響を受けますが、OFFの場合は影響を受けません。
ポストプロセスとDOF、半透明を多用するゲームを作成する際にはこの辺の処理がおかしくならないように注意しなければいけないでしょう。
まあ、個人的な意見としては、半透明オブジェクトはとにかく使用しないで別の表現方法がないかを考える方がいいかと思います。
難しいことですが、現在のUE4では半透明マテリアルの処理がかなりしょっぱい感じで、日本のゲーム開発者が好む形にはなっていないと感じます。
例えば、サードパーソン視点のゲームでカメラがプレイヤーキャラに埋まってしまう場合がありますよね?
この場合、日本の開発者はカメラが近づいたら半透明で綺麗に消えてほしい、と考えると思います。
しかし、海外のゲームではこの辺は結構割り切っていて、ある程度カメラが近づいたら一瞬にしてキャラが消えます。
UBIさんの『Watch Dogs』はそうでしたね(このゲームもDeferred Renderingだったはず)。
他にも、敵を倒した際に半透明で消えるというような表現は最近ではあまりなく、パッと消えたり穴が開くように消えていったりしますね。
この辺も半透明を使わない表現としては有効だと思います。
そもそも敵の死体をそのまま残す、というのも海外のゲームではよく見受けられます。気づいたら消えてた、って感じで。
UE4に限らず、Deferred Renderingを採用しているゲームエンジンでは半透明は不遇な存在です。
使用する場合は十分注意し、できるだけ使用しない表現を考案することが重要ではないでしょうか。