UniversalRPには標準でPBRMasterNodeがありますが、Fogの処理を変えたかったりほんの少し処理変えたかったりしたかったので自作のMasterNodeを作ってみました
速攻で仕様変わっていたらすみません...
Unity2019.4+ShaderGraph7.5.0+UniversalRP7.5.0
8/24追記
ShaderGraph9.x.xでは仕様が全く違うためここで作ったものは動きません...
はじめに
ShaderGraphのクラスはprivateになっているため、何かしらの方法でアクセスできるようにする必要があります
2個めはかなり無理矢理な手法になってしまうので直接スクリプトを追加する方針にしました
github.com
↑このリポジトリはクソデカなので落とすときは気をつけてください
最低限必要なファイルを複製する
My~とか適当な名前をつけて以下のファイルを複製します
PBRMasterGUI.cs
生成されたシェーダコードに使用されるMaterialEditor
PBRMasterNode.cs
MasterNode実体 Slotの追加や設定で使用するシリアライズパラメータもここでもたせる
PBRSettingsView.cs
PBRSubShader.cs
SubShader生成時の文字列生成ロジック(includeファイルとか)
これだけであとは型を置き換えればMasterNodeが完成します
ここからは機能を追加してみます
Culling設定をMaterial側に持たせる
PBRMasterNodeを使用する場合、Culling設定がSetting内にあるため、両面描画したいだけなのにシェーダを複製する必要があります(多分)
そこでCullパラメータをProperty化してMaterialで設定できるようにしてみます
PBRMasterNodeからTwo Sidedを削除
Settingsに入っていると紛らわしいのでまずはNode設定からは削除します
bool GenerateShaderPass(MyPBRMasterNode masterNode, ShaderPass pass, GenerationMode mode, ShaderGenerator result, List<string> sourceAssetDependencyPaths) { // TwoSideはMaterial側で設定する UniversalShaderGraphUtilities.SetRenderState(masterNode.surfaceType, masterNode.alphaMode, false, ref pass); // apply master node options to active fields var activeFields = GetActiveFieldsFromMasterNode(masterNode, pass); return GenerationUtils.GenerateShaderPass(masterNode, pass, mode, activeFields, result, sourceAssetDependencyPaths, UniversalShaderGraphResources.s_Dependencies, UniversalShaderGraphResources.s_ResourceClassName, UniversalShaderGraphResources.s_AssemblyName); }
削除するとPBRSubShader.csのGenerateShaderPassでエラーが出ますが、変わりにfalseを入れておきます(特に使用されないのでなんでもOK)
SetRenderStateメソッド自体は `Cull Off` などの文字列を入れてくれる便利メソッドです
SubShaderにCull設定を追加
前項でTwoSided設定を使わないようにしたので自前で追加します
PBRSubShader.csにForwardPassの設定があるのでここに追記します
ShaderPass m_ForwardPass = new ShaderPass { ... CullOverride = "Cull[_Cull]", }
~Overrideという名前のフィールドでShaderLab用の機能が定義されているので埋め込みたい文字列を入れるだけです(かんたん)
ShaderPropertiesにCull設定を追加
初期状態ではGraphEditorで追加したProperty項目のみがPropertiesに埋め込まれるので自分で追加する必要があります
追加するのは簡単でPBRMasterNode.csのCollectShaderPropertiesをoverrideして実装します
public override void CollectShaderProperties(PropertyCollector properties, GenerationMode generationMode) { base.CollectShaderProperties(properties, generationMode); // 追加のProperty properties.AddShaderProperty(new Vector1ShaderProperty { // Material上の表示名 displayName = "Cull Mode", // Property名 overrideReferenceName = "_Cull", // 数値の扱いをEnumにする floatType = FloatType.Enum // Enumの扱いをCSharpの型から指定 enumType = EnumType.CSharpEnum, // EnumType.CSharpEnumを指定した場合使用されるEnumの型 cSharpEnumType = typeof(CullMode), // 初期値 value = (int) CullMode.Back, }); }
今回はVector1ShaderPropertyを使用していますが、他のプロパティタイプも全て用意されています
Fogを自前のものに置き換える
標準のFogは機能が微妙なので自前のものに置き換えてみます
fogのmulti_compileを更新
multi_compile系はSubShaderの機能なのでPBRSubShader.cs内を書き換えます
まずmulti_compile_fogはいらないのでpragmasから削除します
pragmas = new List<string>() { "prefer_hlslcc gles", "exclude_renderers d3d11_9x", "target 3.0", "multi_compile_instancing", },
次に新しい定義を追加します
こちらはEnum定義されているので書き間違いがないですね
keywords = new KeywordDescriptor[] { new KeywordDescriptor() { displayName = "Custom Fog", referenceName = "CUSTOM_FOG", type = KeywordType.Boolean, definition = KeywordDefinition.MultiCompile, scope = KeywordScope.Global, }, ... }
hlsl複製
シェーダファイルにkeywordが追加されたのでそれを参照するためにhlslファイル書き換える必要があります
Editor/ShaderGraph/Includes/PBRForwardPass.hlsl
このhlslファイルはvert関数とfrag関数が用意されておりMasterNodeがシェーダファイルを生成する際にこのファイルを参照します
次に生成時に使用するシェーダの読み込み先を変更します
SubShader単位の設定なのでkeywordsと同じくPBRSubShader.cs内を書き換えます
ShaderPass m_ForwardPass = new ShaderPass { // Definition displayName = "My PBR Forward", referenceName = "SHADERPASS_FORWARD", lightMode = "UniversalForward", // vert,frag関数が定義されているファイルを指定 passInclude = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/MyPBRForwardPass.hlsl",
ここで指定したhlslファイルはvert, fragが定義されたファイルですが、それ以外の関数を使用する場合はincludesの方に追加します
// Pass setup includes = new List<string>() { "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl", "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl", "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl", "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl", "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderGraphFunctions.hlsl", "Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderVariablesFunctions.hlsl", // ここに呼び出したい関数が定義されたhlslファイルのパスを追加する },
ここで指定したhlslファイル内の関数は全て使えるのでvert, frag内から意識せずに呼び出すことができます
シェーダ改修
最後にhlsl内のfragを書き換えます
// Unity標準のFog関数から自前の密度計算をしたFog関数に置き換え float3 cameraPositionWS = GetCameraPositionWS(); color.rgb = ApplyDensityFog(cameraPositionWS, inputData.positionWS - cameraPositionWS, length(inputData.positionWS - cameraPositionWS), color.rgb); // color.rgb = MixFog(color.rgb, inputData.fogCoord); return color;
Cutoffの数値でAlphaTest処理を分岐させる
ShaderGraphのAlphaTest仕様
PBRMasterNodeはビルド時にAlphaClipThreasholdのSlotを参照して0以上又は何かしらの接続がされている場合常にAlphaTestが実行される仕様になっています
これはPBRSubShader.csのGetActiveFieldsFromMasterNodeメソッドで以下のように実装されています
if (masterNode.IsSlotConnected(PBRMasterNode.AlphaThresholdSlotId) || masterNode.GetInputSlots<Vector1MaterialSlot>().First(x => x.id == PBRMasterNode.AlphaThresholdSlotId) .value > 0.0f) { baseActiveFields.Add("AlphaClip"); }
ここでAlphaClipが定義されるとシェーダ生成時にShaderGraphが参照しているPassMesh.template内の$AlphaClipがアクティブになり以下の1行が埋め込まれる
#define _AlphaClip 1
これによりfrag関数内ではこのようにAlphaTestを実現することができる
#if _AlphaClip clip(surfaceDescription.Alpha - surfaceDescription.AlphaClipThreshold); #endif
なんか微妙な仕様なのでそのうち仕様変更されそうな気がしますが、意図的に0を代入しない限り常にAlphaTestが行われていることが分かると思います
従来の処理に合わせて_ALPHATEST_ONマクロを定義する
Lit.shaderやその他Unity製のシェーダは_ALPHATEST_ONを使用して処理を切り替えているので、ShaderGraphの出力シェーダも同じになるように変更します
まずはFog処理の差し替えと同じようにKeywordDescriptorを追加します
AlphaTestはForwardPass以外のパスでも使用するため、以下の全Passに追加する必要があるので注意です
m_ForwardPass, m_DepthOnlyPass, m_ShadowCasterPass, m_LitMetaPass
new KeywordDescriptor() { displayName = "Cutout", referenceName = "_ALPHATEST_ON", type = KeywordType.Boolean, definition = KeywordDefinition.ShaderFeature, scope = KeywordScope.Global, },
Fog差し替え時に作成したhlslファイルのfrag関数内に_AlphaClipで切り替えている箇所があるので_ALPHATEST_ONに差し替える
#if _ALPHATEST_ON clip(surfaceDescription.Alpha - surfaceDescription.AlphaClipThreshold); #endif
あとはshader_featureを切り替えるだけなのでいつもどおりShaderGUIでKeywordを切り替えるだけです
var propCutout = FindProperty("_Cutoff", properties); if (propCutout != null) { if (propCutout.floatValue > 0.001f) { material.EnableKeyword("_ALPHATEST_ON"); } else { material.DisableKeyword("_ALPHATEST_ON"); } }
Universal Render Pipeline/LitシェーダではCutoffパラメータとAlphaTestの2つに分かれていますが、今回は_Cutoffが0なら適用しない実装にしました
おわり
ノリで書いたら説明不足のよくわかんない記事になった気がしますが今ならMasterNodeは割と簡単に作れるよって事が言いたかったです(昔は複雑で訳わかんなかった)