材料によって示される光学現象の大部分(すべてではないにしても)は、個々の光線の伝播と相互作用をシミュレートすることで再現できます。 このアプローチは科学文献で「レイトレーシング」と呼ばれ、多くの場合、リアルタイムで使用するには計算コストが高すぎます。 最新のエンジンのほとんどは強力な単純化を使用しており、フォトリアリズムを作成することは不可能ですが、かなり納得のいく近似結果を提供できます。 このチュートリアルでは、 サブサーフェススキャタリングを使用して半透明のマテリアルをシミュレートするために使用できる、 迅速で安価な説得力のあるソリューションについて説明します。
まで...
...そしてその後。
はじめに
標準のUnityマテリアルには透明モードがあり、透明なマテリアルをレンダリングできます。 このコンテキストでは、透明度はアルファブレンディングを使用して実装されます。 透明なオブジェクトが完成したジオメトリの上にレンダリングされ、その背後にあるものが部分的に表示されます。 これは多くのマテリアルで機能しますが、発生は半透明性 (場合によっては半透明性 )と呼ばれるより一般的なプロパティの特殊なケースです。 透明なマテリアルは、それらを透過する光の量にのみ影響し(左下の図)、半透明なマテリアルはその通過経路(右)を変更します。
この動作の結果は明らかです。半透明のマテリアルは、それらを通過する光線を散乱させ、オブジェクトの背後にあるものを侵食します。 この動作は、実装がはるかに難しいため、ゲームではほとんど使用されません。 透明なマテリアルは、レイトレーシングなしで、アルファブレンディングという簡単な方法で実装できます。 ただし、半透明の素材では、光線の偏差のシミュレーションが必要です。 このような計算は非常にコストがかかり、リアルタイムでレンダリングする場合に問題を起こす価値はほとんどありません。
これは、多くの場合、 表面下散乱などの他の光学現象のシミュレーションに干渉します。 半透明の材料の表面に光が当たると、その一部は内側に伝播し、分子と衝突して、抜け道を見つけます。 多くの場合、これは、ある点で吸収された光が別の点で材料から放出されるという事実につながります。 表面下散乱は、人間の皮膚、大理石、牛乳などの素材でよく見られる拡散光をもたらします。
リアルタイムの半透明性
伝送計算には2つの大きなハードルがかかります。 まず、材料内部での光線の散乱のシミュレーションが必要です。 各ビームは、材料内で数回、数百回、または数千回反射される場合があります。 2番目の障害物-ある点で吸収された光線は、他の点で放出されます。 これは小さな問題のように見えますが、実際には深刻な障害です。
これが起こる理由を理解するには、まず、ほとんどのシェーダーがどのように機能するかを理解する必要があります。 リアルタイムレンダリング業界では、ビデオプロセッサは、シェーダーがローカルプロパティのみを使用してマテリアルの最終色を計算できることを期待しています。 シェーダーは、各頂点にローカルなプロパティのみに効果的にアクセスできるように実装されます。 通常のピークとアルベドピークの方向は非常に簡単に計算できますが、隣接するピークからこれらの値を取得するのは簡単な作業ではありません。 ほとんどのリアルタイムシステムでは、何らかの方法でこれらの制限を回避し、非ローカル情報を使用せずにマテリアル内の光の伝搬をシミュレートする方法を見つける必要があります。
このチュートリアルで説明するアプローチは、GDC 2011でColin Barre-BriseboisとMarc Bouchardが「 高速で安価で説得力のある表面下散乱ルックのための半透明性の近似」レポートで示したソリューションに基づいています。 彼らのソリューションは、DICE Battlefield 3ゲームで使用されたFrostbite 2エンジンに統合されています。 コリンとマークが提示したアプローチは物理的に不正確ですが、非常に低コストでもっともらしい結果を提供します。
このソリューションの背後にある考え方は非常に簡単です。 不透明な素材は、光源からの光の影響を直接受けます。 光の方向から90度以上傾いた頂点 L 、彼らはまったく照明を得ていません(左下の写真)。 報告書に示されたモデルによれば、透過した物質に対する光の追加の影響があります。 −L 。 幾何学的に −L 照明の一部が実際にマテリアルを通過し、その背面に到達するかのように知覚できます(下の図の右)。
つまり、各光源は、反射に対する2つの異なる効果、つまり前面と背面の照明と見なされます。 マテリアルをできるだけリアルにしたいので、フロントライティングには標準のUnity PBRライティングモデルを使用します。 影響を説明する方法を見つける必要があります。 −L マテリアルの内部で発生する可能性のある分散プロセスを何らかの方法でシミュレートするようにレンダリングします。
バック半透明
上記のように、ピクセルの最終的な色は、2つの成分の合計に依存します。 最初のものは「伝統的な」照明です。 2番目は、モデルの背面を照らす仮想光源からの光の効果です。 これにより、元の光源からの光が実際にマテリアルを通過したという感覚が得られます。
これを数学的にモデル化する方法を理解するために、次の2つのケースのダイアグラム(下の図)を作成しましょう。 現在、赤い点を描いています。 マテリアルの「暗い」側にあるため、照らす必要があります −L 。 外部の観測者の観点から2つの極端なケースを分析してみましょう。 私たちはそれを見る VB と一致しています −L それに平行して、それはオブザーバーを意味します B 半透明に戻るはずです。 一方、オブザーバーは A バックライトは垂直であるため、最も少ないバックライトが表示されます −L 。
シェーダーの作成に精通している場合、そのような推論はおなじみのはずです。 Unity 5のチュートリアルPhysically Based Rendering and Lighting Modelsで似たようなものに既に遭遇しました。そこでは、 スカラー積と呼ばれる数学的演算子を使用してそのような振る舞いを実装できることを示しました。
最初の近似として、半透明性によるバックライトの量、 Iback 比例して V cdot−L 。 従来の拡散シェーダーでは、これは次のように記述されます N cdotL 。 光は単に材料から発せられ、反射しないため、計算に表面の法線を含めなかったことがわかります。
表面下の歪み
ただし、表面の法線は、光が材料から放射される角度に、少なくとも小さな影響を与える必要があります。 この手法の作成者は、 表面下歪みと呼ばれるパラメータを導入しました \デルタ 強制ベクトル −L に向ける N 。 物理学の観点から、この表面下の歪みは、出射バックライトの表面の法線からの逸脱の程度を制御します。 提案されたシステムによれば、逆透過照明の成分の強度は次のようになります。
Iback=V cdot left langleL+N delta right rangle
どこで left langleX right rangle= fracX left |X\右 | と同じ方向を指す単位ベクトルです X 。 Cg / HLSLに精通している場合、これは
normalize
機能です。
で delta=0 私たちはに戻ってきます V cdot−L 前のセクションで取得しました。 ただし、 delta=1 ビューの方向と −\左 LangleL+N\右 Rangle 。 Blinn-Fong反射計算を知っているなら、それを知っておくべきです \左 LangleL+N\右 rangle 間のベクトル L そして N 。 したがって、私たちはそれを半方向と呼びます H 。
上の図は、これまで使用してきたすべての方向を示しています。 H 紫色でマークされ、それが間にあることがわかります L そして N 。 ジオメトリに関しては、変更 \デルタ から 0 前に 1 光の知覚方向のシフトにつながります L 。 網掛け部分は、バックライトが来る方向の範囲を示しています。 次の図は、 delta=0 オブジェクトは紫色の光源から照らされて表示されます。 変更するとき \デルタ 1にすると、光源の知覚方向が紫色に変わります。
行き先 \デルタ -いくつかの半透明素材が異なる強度のバックライトを拡散する傾向のシミュレーション。 より高い値 \デルタ 、より多くのバックライトが散在しています。
ここでのHの値は、Blinn-Fongによる反射の計算でHと同じ意味を持ちますか?
いや Blinnu Fongによる反映 H として定義される \左 LangleL+V\右 rangle 。 ここでは、同じ文字を使用して示します \左 LangleL+N\右 rangle 。
デルタはLとL + Nの間に補間されていますか?
はい 値 \デルタ から 0 前に 1 の間で線形補間 L そして L+N 。 これは、従来の線形補間の定義を L そして L+N と \デルタ :
著者がL + Nを正規化しなかったのはなぜですか?
幾何学的な観点から、量 L+N 単位の長さはありません。つまり、正規化する必要があります。 最終システムでは、著者は正規化を実行しません。
最終的に、このエフェクト全体は、フォトリアリスティックまたは物理ベースではありません。 彼らの報告では、著者は、半透明性と表面下散乱の迅速な近似として使用することを意図していることを非常に明確にしました。 正規化によって結果が大きく変わることはありませんが、大幅な遅延が追加されます。
最終的に、このエフェクト全体は、フォトリアリスティックまたは物理ベースではありません。 彼らの報告では、著者は、半透明性と表面下散乱の迅速な近似として使用することを意図していることを非常に明確にしました。 正規化によって結果が大きく変わることはありませんが、大幅な遅延が追加されます。
バックライト拡散
チュートリアルのこの段階では、半透明のマテリアルをシミュレートするために使用できる方程式が既にあります。 価値 Iback 照明の最終的な効果の計算には使用できません。
ここでは、2つの主なアプローチを使用できます。 1つ目は、テクスチャを適用することです。 マテリアル内での光の散乱方法を完全に芸術的に制御する必要がある場合は、制限する必要があります Iback 〜の範囲で 0 前に 1 最終的なバックライト強度をサンプリングするために使用します。 線形変動の異なるテクスチャは、さまざまなマテリアルでの光の伝播をシミュレートします。 以下では、このシェーダーの結果を大幅に変更するためにこれを使用する方法を見ていきます。
ただし、この手法の作成者が使用するアプローチでは、テクスチャは適用されません。 その中で、曲線はCgコードによってのみ作成されます:
Iback=saturate(V cdot left langleL+N delta right rangle)p cdots
2つの新しいオプション、 p ( 度 )および s ( scale )は、曲線のプロパティを変更するために使用されます。
おわりに
記事のこのパートでは、半透明のマテリアルをレンダリングすることの技術的な問題について説明しました。 近似解とアプローチは、レポート「 高速で安価で説得力のある表面下散乱外観のための半透明性の近似」に示されています。 チュートリアルの次の部分では、Unityシェーダーでのこの効果の実装に焦点を当てます。
リアルタイムアプリケーションで部分空間の分散をシミュレートするより洗練されたアプローチに興味がある場合は、 GPU Gemsの最高のチュートリアルの1つを学習できます。
パート2
はじめに
チュートリアルの前の部分で、半透明のマテリアルの外観を近似できるメカニズムについて説明しました。 従来のサーフェスのシェーディングは、 L 。 作成するシェーダーは別のコンポーネントを追加します −L マテリアルが背面からの光源で照らされているかのように、事実上使用されます。 この場合、それはあたかも L 材料を通過します。
最後に、バックライトからの反射をモデル化するための方向依存方程式を導き出します。
Iback=saturate(V cdot left langleL+N delta right rangle)p cdots
ここで:
- L -これは、光が来る方向(光の方向 )、
- V -マテリアルを見るカメラの方向(ビューの方向 )、
- N -レンダリングするポイントでのサーフェスの方向( サーフェスに垂直 )。
材料の最終的な外観を制御するために使用できる追加のパラメータがあります。 例えば \デルタ バックライトの知覚方向を変更して、表面の法線により平行になるようにします。
そして最後に p そして s ( 次数とスケール )バックライトの分布を決定し、 Blinn-Fongに従って反射を計算する際に同じ名前のパラメーターと同様の方法で動作します。
次に、シェーダーを実装する必要があります。
標準シェーダーの機能を拡張する
上で説明したように、この効果は可能な限り現実的なものにする必要があります。 最善の解決策は、 標準シェーダー(標準シェーダー) Unityの機能を拡張することです。これにより、不透明なマテリアルに対して最初はかなり良い結果が得られます。
標準シェーダーの機能を拡張するには?
この手順に慣れていない場合は、標準のシェーダーの機能を拡張するトピックについて、私のブログで詳しく説明しています。 開始する2つの優れたチュートリアル: 3Dプリンターシェーダー効果 ( Habréでの翻訳 )およびCD-ROMシェーダー:回折格子 。
つまり、主なアイデアは、新しいサーフェスシェーダーを作成し、そのライティング機能を独自のものに置き換えることです。 ここで、Unity PBRシェーダーを使用してマテリアルがレンダリングされるように、元の標準照明関数を呼び出します。
それを作成することにより、バックライトの効果を計算し、標準の照明機能によって提供される元の色と混ぜることができます。 適切な近似の場合:
以下から始めることができます。
つまり、主なアイデアは、新しいサーフェスシェーダーを作成し、そのライティング機能を独自のものに置き換えることです。 ここで、Unity PBRシェーダーを使用してマテリアルがレンダリングされるように、元の標準照明関数を呼び出します。
それを作成することにより、バックライトの効果を計算し、標準の照明機能によって提供される元の色と混ぜることができます。 適切な近似の場合:
以下から始めることができます。
#pragma surface surf StandardTranslucent fullforwardshadows #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; #include "UnityPBSLighting.cginc" inline fixed4 LightingStandardTranslucent(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi) { // fixed4 pbr = LightingStandard(s, viewDir, gi); // ... // "pbr", // ... return pbr; } void LightingStandardTranslucent_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi) { LightingStandard_GI(s, data, gi); } void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo , fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic smoothness o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; }
この効果で使用する新しい照明関数に
StandardTranslucent
という名前を付けます。 バックライトは元の照明と同じ色になります。 強度
I
のみを制御できます
#pragma surface surf StandardTranslucent fullforwardshadows #include "UnityPBSLighting.cginc" inline fixed4 LightingStandardTranslucent(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi) { // fixed4 pbr = LightingStandard(s, viewDir, gi); // ( ) float I = ... pbr.rgb = pbr.rgb + gi.light.color * I; return pbr; }
なぜPBR値の範囲に制限がないのですか?
2色を追加するときは、値が超えないように注意する必要があります 1 。 これは通常、
関数によって実装され、各色成分を次の範囲に制限します。 0 前に 1 。
カメラがHDR ( 高ダイナミックレンジ、拡張ダイナミックレンジ )サポートを使用している場合、値は高くなります 1 ブルームなどの後処理効果に使用されます。 このシェーダーでは、ブルームフィルターが最終レンダリング中に適用されるため、最終色を飽和させません。
saturate
関数によって実装され、各色成分を次の範囲に制限します。 0 前に 1 。
カメラがHDR ( 高ダイナミックレンジ、拡張ダイナミックレンジ )サポートを使用している場合、値は高くなります 1 ブルームなどの後処理効果に使用されます。 このシェーダーでは、ブルームフィルターが最終レンダリング中に適用されるため、最終色を飽和させません。
バックライト
チュートリアルの最初の部分で説明した方程式に従って、次のコードを記述できます。
inline fixed4 LightingStandardTranslucent(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi) { // fixed4 pbr = LightingStandard(s, viewDir, gi); // --- --- float3 L = gi.light.dir; float3 V = viewDir; float3 N = s.Normal; float3 H = normalize(L + N * _Distortion); float I = pow(saturate(dot(V, -H)), _Power) * _Scale; // pbr.rgb = pbr.rgb + gi.light.color * I; return pbr; }
上記のコードは、記事の最初の部分の方程式を直接実装したものです。 結果として生じる半透明効果は非常に妥当なように見えますが、材料の厚さとは関係ありません。 したがって、管理するのは非常に困難です。
局所的な厚さ
明らかに、バックライトの量は、材料の密度と厚さに大きく依存します。 理想的には、マテリアル内のライトが移動した距離を知り、それに応じて弱める必要があります。 下の図からわかるように、同じ入射角を持つ3つの異なるビームは、材料内で非常に異なる距離を進みます。
シェーダーの観点からは、ローカルジオメトリまたは光線の履歴のいずれにもアクセスできません。 残念ながら、この問題をローカルで解決する方法はありません。 局所的な厚さの外部マップを使用するのが最善です。 これは、マテリアルの対応する部分の「厚さ」を定義する表面に関連付けられたテクスチャです。 実際の厚さは光が落ちる角度に依存するため、「厚さ」の概念は任意に使用されます。
上の図は、円の赤い点に関連する「厚さ」のユニークな概念がないことを示しています。 光が実際に通過する材料の量は、光の入射角に依存します L 。 つまり、半透明性を実現するためのこのアプローチ全体が物理的な正確さを追求するのではなく、プレイヤーの目を欺くということを覚えておく必要があります。 以下( source )局所的な厚さの適切なマップが表示され、像のモデルで視覚化されています。 白の色合いは、半透明の効果が強くなり、厚さの概念に近い部分に対応します。
局所的な厚さのマップを生成する方法は?
この手法の著者は、任意のモデルから局所的な厚さのマップを自動的に作成する興味深い方法を提案しました。 これを行うには、次の手順を実行します。
このプロセスのロジックは、背面でアンビエントオクルージョンをレンダリングするときに、ほぼ「 オブジェクト内のすべてのライトトランスポートを平均化できる」 ことです。
テクスチャの代わりに、厚さを頂点に直接保存できます。
- モデル面を反転
- アンビエントオクルージョンテクスチャにレンダリングする
- テクスチャの色を反転
このプロセスのロジックは、背面でアンビエントオクルージョンをレンダリングするときに、ほぼ「 オブジェクト内のすべてのライトトランスポートを平均化できる」 ことです。
テクスチャの代わりに、厚さを頂点に直接保存できます。
最終版
これで、材料の局所的な厚さを考慮する必要があることがわかりました。 これを行う最も簡単な方法は、サンプリング可能なテクスチャマップを作成することです。 これは物理的に不正確ですが、説得力のある結果が得られます。 さらに、ローカルの厚さは、アーティストがエフェクトを完全に制御できるようにエンコードされます。
この実装では、ローカルの厚さはsurf関数でサンプリングされた追加のテクスチャの赤いチャンネルに保存されます:
float thickness; void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo , fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic smoothness o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; thickness = tex2D (_LocalThickness, IN.uv_MainTex).r; }
テクスチャがライティング関数でサンプリングされないのはどうですか?
この値を
変数に保存することにしました。照明関数は後でこの変数にアクセスします。 個人的には、後でライティング機能を必要とするテクスチャをサンプリングする必要がある場合は、常にこれを行うよう努めています。
別の方法で行いたい場合は、ライティング関数でテクスチャを直接サンプリングできます。 この場合、UV座標を渡し(おそらく
を展開すること
)、
代わりに
を使用する
が
ます。 この関数は、2つの追加の座標を受け取ります。 この場合、「ゼロ」に設定できます。
thickness
変数に保存することにしました。照明関数は後でこの変数にアクセスします。 個人的には、後でライティング機能を必要とするテクスチャをサンプリングする必要がある場合は、常にこれを行うよう努めています。
別の方法で行いたい場合は、ライティング関数でテクスチャを直接サンプリングできます。 この場合、UV座標を渡し(おそらく
SurfaceOutputStandard
を展開すること
SurfaceOutputStandard
)、
tex2Dlod
代わりに
tex2D
を使用する
tex2Dlod
が
tex2D
ます。 この関数は、2つの追加の座標を受け取ります。 この場合、「ゼロ」に設定できます。
thickness = tex2Dlod (_LocalThickness, fixed4(uv, 0, 0)).r;
ColinとMarkは、最終的なバックライトの強度を計算するためのわずかに異なる方程式を提案しました。 厚さと追加の減衰パラメータの両方を考慮に入れます 。 さらに、常に存在する環境の追加コンポーネントを使用する可能性を許可しました:
inline fixed4 LightingStandardTranslucent(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi) { // fixed4 pbr = LightingStandard(s, viewDir, gi); // --- --- float3 L = gi.light.dir; float3 V = viewDir; float3 N = s.Normal; float3 H = normalize(L + N * _Distortion); float VdotH = pow(saturate(dot(V, -H)), _Power) * _Scale; float3 I = _Attenuation * (VdotH + _Ambient) * thickness; // pbr.rgb = pbr.rgb + gi.light.color * I; return pbr; }
最終結果は次のとおりです。
おわりに
この記事で説明されているアプローチは、GDC 2011でColin Barre-BriseboisとMarc Bouchardがレポート「 高速で安価で説得力のある表面下散乱ルックのための半透明性の近似」で提示したソリューションに基づいています。
プロジェクトの開始に必要なすべてのファイル(シェーダー、テクスチャ、モデル、シーン)は、 Patreonのページからダウンロードできます[approx。 transl。:10ドル] 。