Unity シェーダー スペキュラー

Unityシェーダーチュートリアル  ライトマップ後にスペキュラーを上乗せする

シェーダー

弊社はVRで建物を作ることがよくあります。

特に屋内の場合はリアルな影や陰影を表現するために、

リアルタイムGIではなくライトマップのベイクが不可欠です。

 

しかし、ライトマップというモノは「そこがどれだけ明るいか」という情報しかなく、

ベイク後はリアルタイムライトが効かなくなってしまうので、

質感の肝である「鏡面反射」を表現することができずに、のっぺりとした画になりがちです。

(Directional Specular がなくなってしまいました。Unity4の時は重宝したものです。)

Unity シェーダー スペキュラー

 

それをカバーするために、Reflectiopn Probe を配置するのですが、何か物足りません。

Unity シェーダー スペキュラー

 

やはり、リアルタイムライトのシャープなスペキュラーが欲しいところです。

 

 ライトを追加すればいいじゃん

スペキュラーを出したいのなら、新たにライトを追加すれば良いのでは?

そうです、ライトが無いと何も始まりません。

 

なので、単純にライトマップを焼いた時のライトをそのままコピーします。

Unity シェーダー スペキュラー

 

確かにスペキュラーがでました。

しかし、当然ながらディフューズも上乗せされてしまうので、せっかくのライトマップが台無しです。

多くのCG制作ソフトには、スペキュラーオンリーライトが存在しますが、

残念ながら Unity には存在しません。(マイナスライトとかも欲しい)

 

と言う訳で、今回は Standard シェーダーのコードを少し改造して、

ライトマップ後にリアルタイムのスペキュラーのみが乗るようにしてみたいと思います。

 

 シェーダーのライティングモデルについて

改造を始める前に、ライティングモデルについて簡単に説明します。

Surface シェーダーを記述する時には必ず以下のようにライティングモデルを指定します。


CGPROGRAM

#pragma surface surf standard fullforwardshadows

ENDCG

 

Unity5 では基本的には Standard(物理ベースモデル) しか使いませんが、

これ以外にも、Lambert(ランバート)と BlinnPhong(ブリンフォン)というモデルが、

デフォルトで用意されています。(Unity4 でははこの2つを使っていました。)

 

Lambert を使用すると、ディフューズのみで、スペキュラーは無視されます(計算しません)。

Unity シェーダー スペキュラー

 

BlinnPhong を使用すると、ディフューズとスペキュラーがでます。

Unity シェーダー スペキュラー

 

物体に到達した光がどのように反射(吸収)されるのか(どんな見た目になるのか)、

という計算をするのが、ライティングモデルです。

 

つまり、Standard モデルの計算式の中で、

ライトマップによるディフューズライトによるスペキュラーはそのままに、

ライトによるディフューズ部分だけを削除すれば、

ライトマップ後にスペキュラーのみを上乗せできるのではないか?という事です。

 

 Standard ライティングモデルのコードを改造する

 デフォルトのシェーダーファイルを持ってくる

ビルトインのシェーダーコードは,Unity のインストールフォルダの中に入っています。

[インストールフォルダ → Editor → Data → CGIncludes] を開いてください。

 

数十個のシェーダーファイルの中に、UnityPBSLighting.cginc というファイルがあります。

このファイルをプロジェクトにインポートします。

同フォルダに適当にシェーダーファイルを作って、以下のように読み込みをします。


CGPROGRAM

#pragma surface surf Standard fullforwardshadows

#include "UnityPBSLighting.cginc"

ENDCG

 

これで、インストールフォルダの方ではなく、プロジェクトにインポートした方のコードを参照します。

 

 UnityPBSLighting.cginc を探る

UnityPBSLighting.cginc の中に以下のようなコードが含まれています。


※ 記述を省略しています

inline half4 LightingStandard (・・・) {・・・}

inline half4 LightingStandard_Deferred (・・・) {・・・}

 

これが、Standard シェーダーのライティングコードです。

上が Forward レンダリング用、

下が Deferred レンダリング用 です。

 

この2つのコードの中に、いかにも怪しい共通部分があります。


UNITY_BRDF_PBS (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);

 

ためしに、引数の s.Albedo0 にして保存してみると・・・

Unity シェーダー スペキュラー

 

当たりです。鏡面反射を残してすべて黒くなりました。

つまり、 UNITY_BRDF_PBS の中で、引数として渡されたアルベド(マテリアルの色)に対し、

ライトマップとリアルタイムライトのディフューズ処理がされているようです。

ここから、リアルタイムライトのディフューズ処理のみを削除すれば目的の画になりそうです。

 

ここで一つ注意。このままコードを改造してしまうと、

その他の Standard シェーダーにも影響してしまうので、

スペキュラー上乗せ用のライティングコードとして、

以下のようにコピペし、シェーダーに指定しましょう。


inline half4 LightingStandard (・・・) {・・・}

inline half4 LightingStandard_Deferred (・・・) {・・・}

 

// 上の2つをそのままコピペして、名前を変えておく

※ 記述を省略しています

inline half4 LightingStandard2 (・・・) {・・・}

inline half4 LightingStandard2_Deferred (・・・) {・・・}


CGPROGRAM

// コピーした方のライティングモデルを指定

#pragma surface surf Standard2 fullforwardshadows

#include "UnityPBSLighting.cginc"

ENDCG

 

 UNITY_BRDF_PBS の正体

UnityPBSLighting.cginc の冒頭部分に以下の様な記述があります。


#if !defined (UNITY_BRDF_PBS)

#if SHADER_TARGET < 30

#define UNITY_BRDF_PBS BRDF3_Unity_PBS

#elif defined(UNITY_PBS_USE_BRDF3)

#define UNITY_BRDF_PBS BRDF3_Unity_PBS

#elif defined(UNITY_PBS_USE_BRDF2)

#define UNITY_BRDF_PBS BRDF2_Unity_PBS

#elif defined(UNITY_PBS_USE_BRDF1)

#define UNITY_BRDF_PBS BRDF1_Unity_PBS

#elif defined(SHADER_TARGET_SURFACE_ANALYSIS)

#define UNITY_BRDF_PBS BRDF1_Unity_PBS

#else

#error something broke in auto-choosing BRDF

#endif

#endif

 

Standard シェーダーは、高スペックのPCから低スペックのモバイルまで、

それなりのパフォーマンスで、大体同じような見た目になるように、

プラットフォームによって、自動的にコードが変わる設計になっています。

 

よって、BRDF3_Unity_PBSBRDF2_Unity_PBSBRDF1_Unity_PBS 全てが、

UNITY_BRDF_PBS の正体のなり得るのですが、

今回は一番スペックの高い BRDF1_Unity_PBS を改造してみようと思います。

 

 BRDF1_Unity_PBS のコードの在りか

UnityPBSLighting.cginc のどこを探しても BRDF1_Unity_PBS の記述は見つかりません。

UnityStandardBRDF.cginc という別ファイルに記述されています。

 

例によって、このファイルも同じフォルダにインポートすれば良いのかと思いきや、

どうしても、インストールフォルダ側のファイルを参照してしまう様で、うまくいきません。

 

なので、今回は仕方なく、

必要個所だけを UnityPBSLighting.cginc 内にコピペして改造する事にしました。

きっと、他に正しいやり方があるのだろう・・・

 

 UnityStandardBRDF.cginc の必要個所をコピペ

UnityStandardBRDF.cginc では以下のように3つのファイルを参照しています。


#include "UnityCG.cginc"

#include "UnityStandardConfig.cginc"

#include "UnityLightingCommon.cginc"

 

この3つは、UnityPBSLighting.cginc では、もともと参照していないので、

ただ必要個所をコピペしただけではエラーになりそうです。

追加で参照しておきましょう。


#include "UnityShaderVariables.cginc"

#include "UnityStandardConfig.cginc"

#include "UnityLightingCommon.cginc"

#include "UnityGBuffer.cginc"

#include "UnityGlobalIllumination.cginc"

 

// 追加で参照

#include "UnityCG.cginc"

#include "UnityStandardConfig.cginc"

#include "UnityLightingCommon.cginc"

 

そして、UnityStandardBRDF.cginc 内の BRDF1_Unity_PBS をそのままコピペして、

関数名を変え、新たに追加したライティングコード側で使用するようにしましょう。


※ 記述を省略しています

half4 BRDF1_Unity_PBS_2 (・・・) {・・・}

 

※ 記述を省略しています

inline half4 LightingStandard2 (・・・) {

// half4 c = UNITY_BEDF_PBS (・・・); 元の方はコメントアウトしておく

half4 c = BRDF1_Unity_PBS_2 (・・・);

}

inline half4 LightingStandard2_Deferred (・・・) {

// half4 c = UNITY_BEDF_PBS (・・・); 元の方はコメントアウトしておく

half4 c = BRDF1_Unity_PBS_2 (・・・);

}

 

 BRDF1_Unity_PBS_2 を改造

ようやくここまでたどり着きました。

あとは、コピペした BRDF1_Unity_PBS_2 内の

ライトマップによるディフューズライトによるスペキュラーはそのままに、

ライトによるディフューズ部分だけを削除します。

 

BRDF1_Unity_PBS_2 関数内の最後の方に、以下の様な記述があります。


half3 color = diffColor * (gi.diffuse + light.color * difffuseTerm)

+ specularTerm * light.color * FresnelTerm (specColor, lh)

+ surfaceReduction * gi.specular * fresnelLerp (specColor, grazignTerm, nv);

return half4 (color, 1);

 

赤くハイライトしたところが、ライトによるディフューズの加算部分なので、

ここだけ削除します。すると・・・

Unity シェーダー スペキュラー

 

ライトマップによる高品質なディフューズの上に、

ライトによるスペキュラーのみが上乗せされました。成功です。

 

 おわりに

ライトマップをベイクすると、大幅にドローコールが削減できます。

(特に、すべてのライトが Baked モードだった場合)

 

しかし、スペキュラーのためにライトを追加しているので、

使いどころやライトの数を気にしつつ、パフォーマンスが落ちない程度に、試してみてください。

 

Show Comments0

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)