蒼水家技術録

蒼水が普段制作とか勉強とかして覚えたことを復習としてまとめてるものです。 それが最適解かどうかはわからないけど、何を目的として始めて、何が必要で、実際何をやって、どうなったか、まで全部過程を残すための技術ブログです。

Unity Shader編 #17 ノードとコードを一緒に覚えるShader Graph -ノーマルマップの実装 その2-

前回までで基本的なノーマルマップの実装ができました。今回は各種効果別にノーマルマップの適用するかしないか、を設定できるようにします。
前回の記事はこちらからどうぞ。
atelier-aomi.hatenablog.com

実行環境:Unity6(6000.0.26f1)

今回の目的

前回はとりあえず全ての効果に対して一括でノーマルマップを適用しました。
しかし、表現によってはスペキュラやマットキャップにはノーマルを適用したいけどリムライトには適用したくない、みたいな場合もあるかもしれません。
その場合に各機能別にノーマルを適用するかしないかを適用できるようにしておくことを今回の目的とします。

ノードで見る機能別適用の実装

今回の処理に共有していることは「適用するか・しないか」の2択の分岐である、という点ですね。
つまり分岐処理をすればよいわけですが、以前軽く触れていますがシェーダーでは基本的にはif文による分岐ではなくlerpによる分岐を実装することになります。
コードではlerpを実際に使用しますが、今回ノードではBranchノードを使用します。
lerpノードでも実装は可能ですがどちらにするかは思想によるかなと思います(後ほどこの点については補足します。)

プロパティの追加


各機能別で分岐をさせるにあたりBoolean型のプロパティを各機能別で用意しておきましょう。
今回は今あるシェーディング/スペキュラ/リムライト/マットキャップに対して用意してみました。

シェーディングにノーマルを適用する


ShadingのSubGraphを開き、モデル本来のNormal Vectorと前回作成したノーマルマップを適用したものをBranchのTrue/Falseに画像のように接続します。
また、SubGraph内にもApplyNormalプロパティを作成しPredicateに接続します。
これによってトグルのON/OFFで切り替えができるようになります。

スペキュラにノーマルを適用する


スペキュラについてはShadingのSubGraphを使用しているのでApply Normalのプロパティだけ追加してShadingのCustomノードに接続するだけでOKです。

リムライトにノーマルを適用する


リムライトでも同様にBranchノードに対して画像のように接続しておきます。

マットキャップにノーマルを適用する


マットキャップも同様です。使用している空間がView Spaceの違いだけで基本的には同じです。

本体シェーダーで各効果に対して適用する

各機能のSubGraph側へのノーマル適用の実装ができたので本体側に戻り各機能のCustomノードに新たに追加されたApply Normalに対してあらかじめ用意した各プロパティを接続します。
これで各機能別にノーマルマップの適用を切り替えられるようになります。

シェーディング


スペキュラ


リムライト


マットキャップ

最終的な結果が以下の動画の状態になり、各機能別に対してノーマルマップの適用ができていることがわかります。
これでノード側の実装は完了です。

コードで見る機能別適用の実装

それではコード側にも実装していきましょう。

プロパティの追加

各機能別のチェックボックスで追加します。
ちなみにシェーダーのチェックボックスは内部的にはfloat型の扱いでFlaseが0、Trueが1という値で扱われます。
いつもの書き方ではチェックボックスにはならないので[Toggle]の属性を付けましょう。
[Toggle]の属性をつけない場合は通常のFloatFieldで表示されます。

Properties
{
// 既存コードは省略
        
// Normal Map
_NormalMap ("Normal Map", 2D) = "bump" {}
_NormalIntensity ("Normal Intensity", Range(0, 3)) = 1
[Toggle] _ApplyShading ("Apply Specular", float) = 0 // 追加
[Toggle] _ApplySpecular ("Apply Specular", float) = 0 // 追加
[Toggle] _ApplyRimlight ("Apply Rimlight", float) = 0 // 追加
[Toggle] _ApplyMatcap ("Apply Matcap", float) = 0 // 追加
        
}

変数としてCBUFFERの内に宣言しておきます。
前述のとおり扱いはFloatなのでCBUFFERの内でOKです。

CBUFFER_START(UnityPerMaterial)

// 既存変数は省略
float _NormalIntensity;
float _ApplyShading; // 追加
float _ApplySpecular; // 追加
float _ApplyRimlight; // 追加
float _ApplyMatcap; // 追加

CBUFFER_END

シェーディングにノーマルを適用する

切り替えをする前提なのでshadeは通常版とノーマルマップ適用版をそれぞれ用意しておくことになり、qシェーディングでは主に1.2影部分の処理に影響するので以下のようになります。
書き分けたこれらをあとでlerpで分岐処理することになります。

// タンジェント空間からワールド・ビュー空間への変換
float3 normalizeNormal = normalize(IN.normalWS); // 本来のNormalはあらかじめ正規化しておくこと
float3x3 tangentToWorld = float3x3(IN.tangentWS, IN.bitangentWS, IN.normalWS);
float3 normalWS = normalize(mul(normalMap, tangentToWorld));
float3 normalVS = TransformWorldToViewDir(normalWS);
normalVS = normalize(normalVS);

// 法線とライト方向の内積
float3 lightDir = normalize(lightDirection);
float shade = saturate(dot(normalizeNormal, lightDir)); // 通常版
float shadeApplyNormal = saturate(dot(normalWS, lightDir)); // ノーマルマップ適用版

// 中略
// 1影/2影境界
float fistShadowSmoothness = smoothstep(firstShadowStep - _1stShadowSmoothness, firstShadowStep + _1stShadowSmoothness, lerp(shade, shadeApplyNormal, _ApplyShading)); // lerp処理に変更
float secondShadowSmoothness = smoothstep(secondShadowStep - _2ndShadowSmoothness, secondShadowStep + _2ndShadowSmoothness, lerp(shade, shadeApplyNormal, _ApplyShading)); // lerp処理に変更

スペキュラにノーマルを適用する

スペキュラで使用していたshadeを先ほど用意したノーマルマップ適用の有無をlerpで切り替えるように書き換えます。

// スペキュラ計算
float4 specMask = SAMPLE_TEXTURE2D(_SpecularMask, sampler_SpecularMask, IN.uv);
float spec = pow(lerp(shade, shadeApplyNormal, _ApplySpecular), _SpecularPower); // lerp処理に変更
spec = smoothstep(0.5 - _SpecularSmoothness, 0.5 + _SpecularSmoothness, spec);
spec *= specMask;

リムライトにノーマルを適用する

リムライトも同様にlerpの切り替え処理に書き換えます。

// リムライト計算
float3 viewDir = normalize(IN.viewDirWS);
float rimDot = 1.0 - saturate(dot(lerp(normalizeNormal, normalWS, _ApplyRimlight), viewDir)); // lerp処理に変更
float rimPower = pow(rimDot, _RimLightPower);
float rimLight = smoothstep(0.5 - _RimLightSmoothness, 0.5 + _RimLightSmoothness, rimPower);
float4 rimlightmask = SAMPLE_TEXTURE2D(_RimLightMask, sampler_RimLightMask, IN.uv);

マットキャップにノーマルを適用する

マットキャップの場合はmatcapUVを加工する必要があるため以下のように書き換えます。

// マットキャップ計算
float2 matcapUV = lerp(IN.normalVS, normalVS, _ApplyMatcap); //UV加工の前にlerp処理
matcapUV = matcapUV.xy * 0.5 + 0.5;
float4 matcapTex = SAMPLE_TEXTURE2D(_MatcapTexture, sampler_MatcapTexture, matcapUV);
float4 matcapMask = SAMPLE_TEXTURE2D(_MatcapMask, sampler_MatcapMask, IN.uv);
float3 matcapColor = matcapTex.rgb * _MatcapColor.rgb * _MatcapIntensity * matcapMask.rgb;

これでコード側も同様の挙動を出来るようになりました。

補足:BranchとLerpのお話


今回処理の分岐にノードベース側ではBranchを使っていますが、ここはLerpで上画像のように実装しても問題なく実装出来ます。
この辺はほんと思想によるというだけなので自由で良いと思います。
違いとしてはBranchの場合はBooleanを渡すので0か1しか渡せないのに対してLerpではfloatを渡せるので2つのうちどちらか、ではなく適用度合いとしてスライダーで処理させることもできたりもします。

おわりに

これで各機能別にノーマルマップを適用できるようになりました。
これによって物理的には正しくないけどちょっと変わった面白い表現などもできるようになったりするかもしれません。
また、今回は前回と違うノーマルマップを適用してみました。
一旦これでノーマルマップについての実装は終わりになります。

次回は下動画のように各機能による光沢が現状1つの色にしかなっていないため違和感がある状態なのでこれをメインカラーになじませる、といった実装を行っていく予定です。

次回記事

atelier-aomi.hatenablog.com

Unity Shader編記事一覧はこちらからどうぞ。
atelier-aomi.hatenablog.com

<PR>