Unity2019以降はシェーダーを自作しようと思うと古来よりあるshader code直接書く方法とShaderGraphを使う方法があります。
体感的にはLighting(BRDF)に依存しないadhocで使い捨てな見た目を作りたいときにいShaderGraphを使うと楽かなと思ってます。今回はBRDFにRimライティング項を追加するのでshader code書こうと思います。
事前に↑こちらで勉強。ありがたい記事です。
SimpleLitRim.shader
Properties
{
~略
[HDR] _RimColor ("Rim Color", Color) = (0,0,0)
[NoScaleOffset] _RimTexture ("Rim Texture", 2D) = "white" {}
_RimLightingMix ("Rim Lighting Mix", Range(0, 1)) = 0
[PowerSlider(4.0)] _RimFresnelPower ("Rim Fresnel Power", Range(0, 100)) = 1
_RimLift ("Rim Lift", Range(0, 1)) = 0
}
SubShader
{
~略
Pass
{
Name "ForwardLit"
Tags { "LightMode" = "UniversalForward" }
~略
#include "./MKFWToonInput.hlsl"
#include "./MKFWToonForwardPass.hlsl"
ENDHLSL
}
~略
CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.SimpleLitRimShader"
}
PropertiesはMToonからのコピペ、それ以外はSimpleLitを見ながら勘で書き換えました。
.shaderはマテリアル画面で設定するテクスチャとかのプロパティを宣言、また描画パイプラインの一連のPassを全て記述する箇所みたいですね。SimpleLitだとMeta(ライトマップベイク用)も含め5Passですね
SimpleLitRimInput.hlsl
シェーダーコードで使う変数をここで定義します
~略
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half4 _SpecColor;
half4 _EmissionColor;
half _Cutoff;
half4 _RimColor;
half _RimLightingMix;
half _RimFresnelPower;
half _RimLift;
CBUFFER_END
TEXTURE2D(_SpecGlossMap); SAMPLER(sampler_SpecGlossMap);
TEXTURE2D(_RimTexture); SAMPLER(sampler_RimTexture);
~略
CBufferというのがポイントですね。確かSRPBatcherが有効になるための条件だった気がします。CBufferって何の略でしょうね、constant bufferでしょうか?
SimpleLitRimForwardPass.hlsl
color値を取得する処理をここに書きます。一番大事な部分ですね。今回はvertex shader, fragment shader自体はShaderLitの関数そのまま使ってます。代わりにfragment shaderであるLitPassFragmentSimple関数内で呼んでいたUniversalFragmentBlinnPhong関数ではなく自作のSimpleLitRimFragmentを呼ぶようにしてます。この関数自体はLitpassFragmentSimpleの上に書きました(下に書くとコンパイルエラーです
BRDFなんでもっと気のきいた名前にすべきでえしたね
half4 SimpleLitRimFragment(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha)
{
Light mainLight = GetMainLight(inputData.shadowCoord);
MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));
half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
half3 diffuseColor = inputData.bakedGI + LightingLambert(attenuatedLightColor, mainLight.direction, inputData.normalWS);
half3 specularColor = LightingSpecular(attenuatedLightColor, mainLight.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
#ifdef _ADDITIONAL_LIGHTS
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
diffuseColor += LightingLambert(attenuatedLightColor, light.direction, inputData.normalWS);
specularColor += LightingSpecular(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
}
#endif
#ifdef _ADDITIONAL_LIGHTS_VERTEX
diffuseColor += inputData.vertexLighting;
#endif
// parametric rim lighting
half3 staticRimLighting = 1;
half3 mixedRimLighting = diffuseColor;
half3 rimLighting = lerp(staticRimLighting, mixedRimLighting, _RimLightingMix);
half3 rim = pow(saturate(1.0 - dot(inputData.normalWS, inputData.viewDirectionWS) + _RimLift), _RimFresnelPower) * _RimColor.rgb;
diffuseColor += rim * rimLighting;
half3 finalColor = diffuseColor * diffuse + emission;
#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
finalColor += specularColor;
#endif
return half4(finalColor, alpha);
}
書いてから思ったのですが、このBRDF部分は_RimColorといったシェーダー変数に依存すべきではないですね。だって他のshaderから呼び出されるかもしれないんですから。引数部分にrim系パラメータを渡す設計にすべきでした。
SimpleLitRimShader.cs
カスタムインスペクタです。なくても動きます。でもクールな見た目と入力ミスを防ぐという目的ではあった方がいいです。ここはSimpleLitShader.csをほぼ丸コピしたので省きます
SimpleLitRimGUI.cs
カスタムインスペクタ、OnGUIで描画する部分です。実際は継承されてるんで記述は最小限です
public static void DoRimArea(SimpleLitRImProperties properties, MaterialEditor materialEditor, Material material)
{
BaseShaderGUI.TextureColorProps(materialEditor, Styles.rimMapText, properties.rimTexture, properties.rimColor, true);
var rimLightingMix = properties.rimLightingMix.floatValue;
rimLightingMix = EditorGUILayout.Slider(Styles.rimLightingMixText, rimLightingMix, 0f, 1f);
properties.rimLightingMix.floatValue = rimLightingMix;
var rimFresnelPower = properties.rimFresnelPower.floatValue;
rimFresnelPower = EditorGUILayout.Slider(Styles.rimFresnelPowerText, rimFresnelPower, 0f, 100f);
properties.rimFresnelPower.floatValue = rimFresnelPower;
var rimLift = properties.rimLift.floatValue;
rimLift = EditorGUILayout.Slider(Styles.rimLiftText, rimLift, 0f, 1f);
properties.rimLift.floatValue = rimLift;
}
DoSpecularArea関数をコピペして描きました。SimpleLitRimProperties構造体に色々追加とかStylesにヘルプテキスト追加とかありますがまぁ適当に
SRPBatcher対応を確認
not compatibleなら理由書かれてるんで対応します。僕の場合はDepthOnlyとかのPassの#includeがSimpleLitInput.hlslのままでした。
まとめ
リムライティング計算部分はMToonを参考にしました。
URP(というかSRP)では昔あったSetPassでCPU負荷あがるよー問題はSRP Batcherのおかげでほぼ無視できるくらいになりました。これからは異なるShaderが点在する方が問題になりそうですね。なるべく統一されたuberシェーダーを書いて使いまわすのがよさそうです