【Unity】Deferred Shadingでライトを贅沢に使いたい!基本的な概念の説明とメリデメを考察

Deferred ShadingをUnityで使う上での基礎知識をまとめました。
注意点として、この記事はあくまでUnityにおけるDeferred Shadingに焦点を絞った内容となります。
またScriptable Render Pipelineは考慮していません。

Unity2018.4.0

Deferred Shadingとは?

まずDeferred Shadingの概念を簡単にまとめます。

最初にForward Renderingでは、描画対象のメッシュ一つ一つに対して、
頂点シェーダで座標変換を行い、フラグメントシェーダでライティング計算を行うことで
レンダーターゲットに結果を出力します。

f:id:halya_11:20190905003237p:plain

Deferred Shadingにおいても、まずは描画対象を頂点シェーダとフラグメントシェーダで処理します。
ただしこのとき、Forward Renderingと異なりフラグメントシェーダでライティング計算をしません。
その代わり、ライティング計算に必要な情報だけをレンダーターゲットに描き込みます。
これはすなわちDiffuse色やスペキュラ色、法線などといった情報です。

f:id:halya_11:20190903003200p:plain

これらのジオメトリ情報は別々のバッファに書き込むのでレンダーターゲットが複数必要になります。
これはMRT(Multiple Render Targets)という機能により実現されています。
またDeferred ShadingではこれらのバッファのことをG-Buffers(Geometry Buffers)と呼びます。

次にG-Bufferの情報を使ってLightingパスと呼ばれるパスで専用のシェーダを使ってライティング計算を行います。
この計算はライトの数だけ行われ、またライトの影響範囲内にあるピクセルだけが処理されます。

この部分の詳細は本記事では省きますが、Frame Debuggerを見ることでライトの形に応じたメッシュがシェーダで処理されていることが確認できます。

f:id:halya_11:20190905005645g:plain:w400

まとめると、Deferred ShadingではまずG-Bufferパスでライティングに必要な情報をG-Bufferに書き込み、
その情報とライティング用シェーダを使ってライトごとにライティング計算を行います。

f:id:halya_11:20190903003423p:plain

以上がDeferred Shadingの概要です。

Deferred Shadingを行うための設定

それではUnityでDeferred Shadingを行う設定をしてみます。
まず、検証用に適当なシーンを用意します。

f:id:halya_11:20190902094427p:plain

次に以下の手順で設定を行います。

  1. Edit > Project Settings...を選択
  2. Graphicsを選択
  3. Tier Settingsの各TierのUse Defaultsチェックボックスを外す
  4. 各TierのRendering PathをDeferredに変更する

f:id:halya_11:20190902094744p:plain

これでレンダリングパスはDeferred Shadingに切り替わりました。
今回のシーンではレンダリング結果を見ても特に変化は見られません。

f:id:halya_11:20190902094851p:plain

しかしFrame Debuggerを確認すると、しっかりDeferred用の処理が呼ばれていることが確認できます。

f:id:halya_11:20190902095024p:plain

メリット1 : 多数の光源の影響を正確に描画できる

さて前節のように、Deferred Shadingに切り替えただけでは大きな変化はありませんでした。
ではなぜわざわざDeferred Shadingを使うのでしょうか。

その答えとして、「一つのオブジェクトに対してより多くのライトの影響を比較的低負荷で与えることができる」ということがあります。
例として、前節のシーンに多くのライトを配置してみます。
まずこれをDeferred Shadingで描画してみます。

f:id:halya_11:20190902095110p:plain

各オブジェクトがきれいにライトの影響を受けていることがわかります。
次にレンダリングパスをForward Renderingに切り替えます。

f:id:halya_11:20190902095140p:plain

かなりイマイチな結果になりました。
Forward Renderingでは一つのオブジェクトに対して正しく影響させられるライトの数が少ないので、
この例の床のようにいろんな光源からの影響を受けるオブジェクトの色がおかしくなります。

このForward Renderingにおけるライトの取り扱い方は以下の記事にまとめていますので必要に応じて参照してください。

light11.hatenadiary.com

Deferred Shadingにはこのように、多数の光源の影響を正確に描画できるというメリットがあります。

メリット2 : G-Bufferをポストエフェクトで使える

二つ目のメリットとして、G-Bufferをポストエフェクトに使えるということがあります。
Forward Renderingではレンダリング結果以外をポストエフェクトに使おうとするとその情報を別途描画する必要がありますが、
Deferred Shadingでは法線などG-Bufferに書き込まれている情報をそのままポストエフェクトに使用できます。

これにより、例えばPost ProcessingではFXAAやSSRといったポストエフェクトが使えるようになります。

light11.hatenadiary.com

デメリット1 : 半透明のオブジェクトを処理できない

さてDeferred Shadingには多数の光源の影響を正確に描画できるという大きなメリットがある一方、いくつかのデメリットも存在します。

まず大きめのデメリットとして、半透明のオブジェクトを処理できないということがあります。
これはG-Bufferパスでジオメトリ情報を二次元のテクスチャに描き込んでしまうことによりオブジェクト同士の重なりの情報が失われるためです。

UnityではレンダリングパスをDeferred Shadingに設定していても、半透明描画だけはForward Renderingで描画されます。
この挙動を確認するために先ほどのシーンの手前に半透明のCubeを配置してみます。

f:id:halya_11:20190902095606p:plain

これをFrame Debuggerで見ると以下のようになります。

f:id:halya_11:20190902095550p:plain

不透明描画がDeferred Shadingによって行われ、半透明描画がForward Renderingによって行われていることが確認できました。

デメリット2 : MSAAが使えない

デメリットの二つ目として、MSAAが使えないということがあります。
レンダリングパスをDeferredに設定するとCameraコンポーネントにこの旨の警告が出ます。

f:id:halya_11:20190902095241p:plain

MSAAについて詳しくは以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

上記のことから、Deferred Shadingでアンチエイリアスを使いたい場合にはポストエフェクトを使うことになりそうです。
Post-Processingでアンチエイリアスを扱う方法は以下の記事にまとめています。

light11.hatenadiary.com

デメリット3 : レンダラごとに影を受ける設定ができない

三つ目のデメリットとして、レンダラごとに影を受けるかどうかの設定ができないということがあります。
例えばMesh RendererのReceive Shadowsは、Deferred Shadingを有効にしていると非アクティブになります。

f:id:halya_11:20190902095409p:plain

影響するライトが多いほど負荷がかかるので注意

Deferred Shadingを使うときのパフォーマンス上の注意点として、
ピクセルに影響するライトが多いほど処理負荷が大きくなるということがあります。

例えば、画面全体に影響するライトが多数存在していると処理負荷が大きくなってしまいます。
そのため、ディレクショナル以外のライトは影響する距離に特に気を配って設定するべきです。

f:id:halya_11:20190905010650p:plain

モバイルでも使えるのか?

次にDeferred Shadingのモバイルにおけるハードウェア要件について考えます。
マニュアルによると、モバイルではOpenGL ES 3.0であれば動作するようです。

docs.unity3d.com

そしてこのOpenGL ES 3.0は大体のモバイル端末で動きます。

light11.hatenadiary.com

以上のことから、これからの時代は普通にモバイルでDeferred Shadingを使えるといってもいいのではないかと思います。

ライティング用のシェーダを差し替える

さてG-Buffersに書き込むためのシェーダはもちろん各レンダラのマテリアルに設定されています。
が、Lightingパスに使うためのシェーダはどこにあるのでしょうか?

これは、Project SettingsのGraphics設定下部のBuilt-in Shader Settingsから設定できます。
デフォルトではUnityが内部的に用意しているシェーダが使われています。

もし独自のライティング用シェーダを使いたければ、このDeferredをCustomにしてシェーダを設定します。

f:id:halya_11:20190905011341p:plain

なお、Unityが用意しているビルトインシェーダはDownloadページからダウンロードできます。

unity3d.com

ビルトインシェーダをダウンロードしたフォルダの中にあるInternal-DeferredShading.shaderという名前のものがそれです。

関連

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

参考

docs.unity3d.com