基本
基本的なカスタムシェーダーの情報は下記公式ドキュメントを参照すること.
ドキュメント中にある下記のファイルがテンプレートであり,この中の数ファイルを編集する.
目次
custom.hlsl
,custom_insert.hlsl
について- 知っておくべき文法
custom.hlsl
の注意点- フラグメントシェーダーにおけるデフォルトの各処理
- 各処理の挿入位置
custom.hlsl
,custom_insert.hlsl
の変更が反映されない- シェーダーのコンパイルエラー内容が古い内容のまま発生する
- 頂点シェーダー→フラグメントシェーダーの受け渡しメンバを定義する
- 本体側のバージョンに応じてカスタムシェーダーの条件コンパイルを行う
- シェーダーキーワードを使用する
- multi版以外でもどうしてもシェーダーキーワードを使用する
- AudioLinkの処理を書きたい
- VRChatのカメラ判定,ミラー判定のuniform変数
- シェーダー側でmultiシェーダーかどうかを判定する
- 非multiとmulti版である程度コードを共有する
- SV_POSITION に NaN
- マテリアルエディタ
- その他
- 作例
custom.hlsl
, custom_insert.hlsl
について
知っておくべき文法
カスタムシェーダーはプリプロセッサを用いて,本体側の特定位置にカスタムシェーダーで記述した処理を差し込んだシェーダーを作成するようになっている. そのため,プリプロセスについてC/C++の文献等をあたり,基本的な文法は知っておいた方がよい.
プリプロセッサとともによく併用されるのが行末の \
である.
これは次の行もその行にあるように扱う「行継続」の意味を持っている.
換言するならば改行の打ち消しである.
#define
,#if
等のプリプロセッサ指令は1行のみ有効であるため,本来は1行で記述しなければならない.
しかし長い1行というのは視認性が悪い.
行継続を用いることで,視認性のために改行を行いつつも,コンパイラには1行として扱うように指示することができる.
custom.hlsl
の注意点
基本的には処理置き換えのマクロにとどめておくこと.
関数定義もできるが,インクルード位置がuniform変数の宣言位置(custom.hlsl
内で記述している LIL_CUSTOM_PROPERTIES
のマクロの展開箇所も含む)よりも前なので,uniform変数に依存する処理は書けない.
uniform変数に依存する かどうかに関わらず,関数定義は custom_insert.hlsl
で行うように統一すると,問題は起こらないとも言える(好みの問題).
フラグメントシェーダーにおけるデフォルトの各処理
各処理の挿入位置
custom.hlsl
, custom_insert.hlsl
の変更が反映されない
Reimportを行うこと. 以前にコンパイルエラーがあった場合は,下記のコンパイルエラーがキャッシュされている件の解消方法を試した後にReimportを行うこと.
シェーダーのコンパイルエラー内容が古い内容のまま発生する
カスタムシェーダーは変にキャッシュが残ることがあり,custom.hlsl
, custom_insert.hlsl
を正しく修正してもコンパイルエラーが取れないことがある.
この現象が発生すると,インスペクタの値の変更がプレビューに反映されない,インスペクタでエラーとなっているバリエーションのシェーダーが表示されず選択できない,等の現象が発生する.
この現象を解決するためには Library/ShaderCache.db
を適当な sqlite3 クライアントで開き,下記のSQLを実行する.
DELETE FROM shadererrors;
sqlite3のコマンドラインツールなら下記のコマンドの実行でよい. (echoで '.exit' を出力するのはWindowsのため.空文字列を出力する方法がないため,受理されるコマンドを出力している.Linuxであれば空文字列でよい)
$ echo .exit | sqlite3 --cmd "DELETE FROM shadererrors;" ShaderCache.db
面倒であれば, Library/ShaderCache.db
のファイル削除でもよいのだが,Unity起動中は削除することができない.
winsqlite3.dllが含まれる最近のWindows10や sqlite3 の動的ライブラリにパスが通っているMac/Linux限定にはなるが,下記の処理を設けることで,SQLite3のライブラリの同梱なしに右クリックメニューから DELETE FROM shadererrors
を実行できるようにすることもできる.
/// <summary> /// Callback method for menu item which refreshes shader cache and reimport. /// </summary> [MenuItem("Assets/TemplateFull/Refresh shader cache", false, 2000)] private static void RefreshShaderCacheMenu() { var result = NativeMethods.Open("Library/ShaderCache.db", out var dbHandle); if (result != 0) { Debug.LogError($"Failed to open Library/ShaderCache.db [{result}]"); return; } try { result = NativeMethods.Execute(dbHandle, "DELETE FROM shadererrors", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if (result != 0) { Debug.LogError($"SQL failed [{result}]"); return; } } finally { result = NativeMethods.Close(dbHandle); if (result != 0) { Debug.LogError($"Failed to close database [{result}]"); } } AssetDatabase.ImportAsset("Assets/TemplateFull/Shaders", ImportAssetOptions.ImportRecursive); } /// <summary> /// Provides some native methods of SQLite3. /// </summary> internal static class NativeMethods { #if UNITY_EDITOR && !UNITY_EDITOR_WIN /// <summary> /// Native library name of SQLite3. /// </summary> private const string LibraryName = "sqlite3"; /// <summary> /// Calling convention of library functions. /// </summary> private const CallingConvention CallConv = CallingConvention.Cdecl; #else /// <summary> /// Native library name of SQLite3. /// </summary> private const string LibraryName = "winsqlite3"; /// <summary> /// Calling convention of library functions. /// </summary> private const CallingConvention CallConv = CallingConvention.StdCall; #endif /// <summary> /// Open database. /// </summary> /// <param name="filePath">SQLite3 database file path.</param> /// <param name="db">SQLite db handle.</param> /// <returns>Result code.</returns> /// <remarks> /// <seealso href="https://www.sqlite.org/c3ref/open.html"/> /// </remarks> [DllImport(LibraryName, EntryPoint = "sqlite3_open", CallingConvention = CallConv)] public static extern int Open(string filename, out IntPtr dbHandle); /// <summary> /// Close database. /// </summary> /// <param name="filePath">Database filename.</param> /// <param name="db">SQLite db handle.</param> /// <returns>Result code.</returns> /// <remarks> /// <seealso href="https://www.sqlite.org/c3ref/close.html"/> /// </remarks> [DllImport(LibraryName, EntryPoint = "sqlite3_close", CallingConvention = CallConv)] public static extern int Close(IntPtr db); /// <summary> /// Execute specified SQL. /// </summary> /// <param name="db">An open database.</param> /// <param name="sql">SQL to be evaluated.</param> /// <param name="callback">Callback function.</param> /// <param name="callbackArg">1st argument to callback.</param> /// <param name="pErrMsg">Error msg written here.</param> /// <returns>Result code.</returns> /// <remarks> /// <seealso href="https://www.sqlite.org/c3ref/exec.html"/> /// </remarks> [DllImport(LibraryName, EntryPoint = "sqlite3_exec", CallingConvention = CallConv)] public static extern int Execute(IntPtr dbHandle, string sql, IntPtr callback, IntPtr callbackArg, IntPtr pErrMsg); }
ただ単にDELETEを実行するだけなので最小限の処理にしている. 真面目にやるのであれば下記のような点も考慮するといい感じになる.
sqlite3_exec()
の第5引数でエラーメッセージを受け取るsqlite3_free()
での解放処理が必要
- SQLite3のハンドルは
SafeHandle
の継承クラスで扱う - 戻り値の enum を定義する
- まず sqlite3.dll のロードを試みて,見つからなければ winsqlite3.dll のロードを試みる
- ユーザにdllを用意してもらうことで使用できるようにする想定
- x86を考慮して呼び出し規約の切り分け
- winsqlite3.dll なら stdcall
- sqlite3.dll なら cdecl
- SQLite3 のライブラリが見つからない場合はメニューに追加しない
- Reimport対象のアセットパス直書きではなくGUID経由で取得する
- unitypackage/VPMの両対応のため
頂点シェーダー→フラグメントシェーダーの受け渡しメンバを定義する
メンバの追加は LIL_CUSTOM_V2F_MEMBER
を,値の設定処理は LIL_CUSTOM_VERT_COPY
を利用する.
custom.hlsl
#define LIL_CUSTOM_V2F_MEMBER(id0,id1,id2,id3,id4,id5,id6,id7) \ float emissionWavePos : TEXCOORD ## id0; // Add vertex copy #define LIL_CUSTOM_VERT_COPY \ LIL_V2F_OUT.emissionWavePos = pickupPosition(getEmissionPos(input.positionOS)) \ + (2.0 * rand(float2((float)input.vertexID, LIL_TIME)) - 1.0) * _EmissionWaveNoiseAmp; #define BEFORE_BLEND_EMISSION \ const float uDiff = frac(LIL_TIME * _EmissionWaveTimeScale + _EmissionWaveTimePhase) - remap01(_EmissionPosMin, _EmissionPosMax, input.emissionWavePos); \ const float sDiff = 2.0 * uDiff - 1.0; \ const float eFact = pow(0.5 * cos(clamp(sDiff * _EmissionWaveParam.x, -1.0, 1.0) * UNITY_PI) + 0.5, _EmissionWaveParam.y); \ fd.emissionColor += _EmissionWaveColor * eFact;
LIL_CUSTOM_V2F_MEMBER
の引数はTEXCOORDのIDとなるため,##
を用いて字句結合を行う.
頂点シェーダー内での出力構造体変数は LIL_V2F_OUT
を指定,フラグメントシェーダー内での入力構造体変数は input
を指定する.
LIL_CUSTOM_V2F_MEMBER
の展開箇所は例えば Assets/lilToon/Shader/Includes/lil_pass_forward_normal.hlsl
を参照するとよい.
頂点シェーダーは例えば Assets/lilToon/Shader/Includes/lil_common_vert.hlsl
等を,
フラグメントシェーダーは Assets/lilToon/Shader/Includes/lil_pass_forward_normal.hlsl
等を参照するとよい.
lilToon 1.4.0 でのバグとワークアラウンド
本体側で TEXCOORD
のIDと重複するIDが LIL_CUSTOM_V2F_MEMBER
に id0
として渡されているため,コンパイルエラーとなるパスが存在する.
ShadowCasterパスでは id0
の使用を避けるようにする.
面倒であればすべてのパスで id0
の使用を避けても良いと思う.
- NG
#define LIL_CUSTOM_V2F_MEMBER(id0,id1,id2,id3,id4,id5,id6,id7) \ float customMember01 : TEXCOORD ## id0; \ float4 customMember02 : TEXCOORD ## id1; \ float3 customMember03 : TEXCOORD ## id2;
- OK
#ifdef UNITY_PASS_SHADOWCASTER #define LIL_CUSTOM_V2F_MEMBER(id0,id1,id2,id3,id4,id5,id6,id7) \ float emissionWavePos : TEXCOORD ## id1; \ float4 customMember02 : TEXCOORD ## id2; \ float customMember03 : TEXCOORD ## id3; #else #define LIL_CUSTOM_V2F_MEMBER(id0,id1,id2,id3,id4,id5,id6,id7) \ float emissionWavePos : TEXCOORD ## id0; \ float4 customMember02 : TEXCOORD ## id1; \ float customMember03 : TEXCOORD ## id2; #endif
本体側のバージョンに応じてカスタムシェーダーの条件コンパイルを行う
前述の特定のバージョン向けのワークアラウンド等で本体のバージョンに応じてカスタムシェーダーの処理を変更したいことがある. 本体のシェーダーのマクロ定義にはバージョンに関するものがないが,C#側には存在する. これを利用して,下記のようにしてバージョン値定義マクロを定義したファイルを生成する.
GuidShaderDir
はカスタムシェーダーの Shaders ディレクトリのGUIDにすること.
using System.IO; using UnityEditor; using UnityEngine; using lilToon; namespace lilToon { /// <summary> /// Startup method provider. /// </summary> internal static class Startup { /// <summary> /// GUID of shader directory. /// </summary> private const string GuidShaderDir = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // TODO /// <summary> /// A method called at Unity startup. /// </summary> [InitializeOnLoadMethod] private static void OnStartup() { AssetDatabase.importPackageCompleted += Startup_ImportPackageCompleted; UpdateVersionDefFile(); } /// <summary> /// Update definition file of version value of lilToon, lil_current_version_value.hlsl /// </summary> private static void UpdateVersionDefFile() { var dstDirPath = AssetDatabase.GUIDToAssetPath(GuidShaderDir); var line = $"#define LIL_CURRENT_VERSION_VALUE {lilConstants.currentVersionValue}"; var dstFilePath = Path.Combine(dstDirPath, "lil_current_version_value.hlsl"); if (File.Exists(dstFilePath) && ReadFirstLine(dstFilePath) == line) { return; } using (var fs = new FileStream(dstFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var sw = new StreamWriter(fs)) { sw.Write(line); sw.Write('\n'); } Debug.Log($"Update {dstFilePath}"); } /// <summary> /// Read first line of the specified file. /// </summary> /// <param name="filePath">File to read.</param> /// <returns>First line of <paramref name="filePath"/>.</returns> private static string ReadFirstLine(string filePath) { using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var sr = new StreamReader(fs)) { return sr.ReadLine(); } } /// <summary> /// A callback method for <see cref="AssetDatabase.importPackageCompleted"/>. /// </summary> /// <param name="packageName">Imported package name.</param> private static void Startup_ImportPackageCompleted(string packageName) { if (!packageName.StartsWith("lilToon")) { return; } UpdateVersionDefFile(); } } }
上記のコードで生成されるのは lil_current_version_value.hlsl
というファイルで,下記のようなたった1行のファイルである.
#define LIL_CURRENT_VERSION_VALUE 34
lil_current_version_value.hlsl
を custom.hlsl
の先頭でインクルードする.
#include "lil_current_version_value.hlsl"
これを用いると前章の1.4.0向けのワークアラウンドは下記のように書き直せる.
#if LIL_CURRENT_VERSION_VALUE == 34 && defined(UNITY_PASS_SHADOWCASTER) #define LIL_CUSTOM_V2F_MEMBER(id0,id1,id2,id3,id4,id5,id6,id7) \ float emissionWavePos : TEXCOORD ## id1; \ float4 customMember02 : TEXCOORD ## id2; \ float customMember03 : TEXCOORD ## id3; #else #define LIL_CUSTOM_V2F_MEMBER(id0,id1,id2,id3,id4,id5,id6,id7) \ float emissionWavePos : TEXCOORD ## id0; \ float4 customMember02 : TEXCOORD ## id1; \ float customMember03 : TEXCOORD ## id2; #endif
バージョンとバージョン値(上記の例だと34)の対応関係は以下の表の通り. これは Assets/lilToon/Editor/lilConstants.cs の変更履歴を見ればわかる.
lilToonのバージョン(currentVersionName ) |
バージョン値(currentVersionValue ) |
---|---|
1.3.0 | 26 |
1.3.1 | 27 |
1.3.2 | 28 |
1.3.3 | 29 |
1.3.4 | 30 |
1.3.5 | 31 |
1.3.6 | 32 |
1.3.7 | 33 |
1.4.0 | 34 |
1.4.1 | 35 |
シェーダーキーワードを使用する
下記5ファイルに #pragma multi_compile
や #pragma shader_feature_local
を記述する.
multi版以外ではキーワードがインスペクタの処理で削除されるため,記述しても意味がない.
ltsmulti.lilcontainer
ltsmulti_fur.lilcontainer
ltsmulti_gem.lilcontainer
ltsmulti_o.lilcontainer
ltsmulti_ref.lilcontainer
HLSLINCLUDE #pragma shader_feature_local _ _TOGGLEPROP_ON #pragma shader_feature_local _ENUMKEYWORD_FOO _ENUMKEYWORD_BAR _ENUMKEYWORD_BAZ #include "custom.hlsl" ENDHLSL
multi版以外でもどうしてもシェーダーキーワードを使用する
lilToonの設計思想に真っ向から対立していると思うが....
まず,前述の5ファイルの代わりに下記ファイルにキーワードのpragmaを記述する.
lilCustomShaderInsert.lilblock
#pragma shader_feature_local _ _TOGGLEPROP_ON #pragma shader_feature_local _ENUMKEYWORD_FOO _ENUMKEYWORD_BAR _ENUMKEYWORD_BAZ #include "custom_insert.hlsl"
次にインスペクタのコードにて,OnGUI()
をオーバーライドし,親クラスの OnGUI()
を呼び出し後に,対象のマテリアルにキーワードを設定する処理を追加する.
キーワードは DrawCustomProperties()
で保存しておく.
/// <summary> /// Keywords to preserve. /// </summary> private List<string> _shaderKeywords = new List<string>(); /// <summary> /// Draw property items. /// </summary> /// <param name="me">The <see cref="MaterialEditor"/> that are calling this <see cref="OnGUI(MaterialEditor, MaterialProperty[])"/> (the 'owner').</param> /// <param name="mps">Material properties of the current selected shader.</param> public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) { base.OnGUI(materialEditor, props); foreach (var shaderKeyword in shaderKeywords) { material.EnableKeyword(keyword); } _shaderKeywords.Clear(); } /// <summary> /// Draw custom properties. /// </summary> /// <param name="material">Target material.</param> protected override void DrawCustomProperties(Material material) { // ... _shaderKeywords.Add($"_TOGGLEPROP_{(prop.floatValue >= 0.5f ? "ON" : "OFF")}"); // ... }
末検証であるが,lilToonMultiで使用されるキーワードを参考に,雑にやるなら下記のようにしてもよいと思う.
/// <summary> /// Keywords to preserve. /// </summary> private List<string> _shaderKeywords = new List<string>(); /// <summary> /// Draw property items. /// </summary> /// <param name="me">The <see cref="MaterialEditor"/> that are calling this <see cref="OnGUI(MaterialEditor, MaterialProperty[])"/> (the 'owner').</param> /// <param name="mps">Material properties of the current selected shader.</param> public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) { base.OnGUI(materialEditor, props); foreach (var shaderKeyword in shaderKeywords) { material.EnableKeyword(keyword); } _shaderKeywords.Clear(); } /// <summary> /// Draw custom properties. /// </summary> /// <param name="material">Target material.</param> protected override void DrawCustomProperties(Material material) { // ... // MUSE BE write the end of this method. foreach (var keyword in material.shaderKeywords) { if (!IsMultiKeyword(keyword)) { _shaderKeywords.Add(keyword); } } } /// <summary> /// Check keyword is used in lilToonMulti or not. /// </summary> /// <param name="keyword">Shader keyword.</param> /// <returns>True if the keyword is used in lilToonMulti, otherwise false.</returns> private static bool IsMultiKeyword(string keyword) { switch (keyword) { case "ANTI_FLICKER": case "EFFECT_BUMP": case "EFFECT_HUE_VARIATION": case "ETC1_EXTERNAL_ALPHA": case "GEOM_TYPE_BRANCH": case "GEOM_TYPE_BRANCH_DETAIL": case "GEOM_TYPE_FROND": case "GEOM_TYPE_LEAF": case "GEOM_TYPE_MESH": case "PIXELSNAP_ON": case "UNITY_UI_ALPHACLIP": case "UNITY_UI_CLIP_RECT": case "_COLORADDSUBDIFF_ON": case "_COLORCOLOR_ON": case "_COLOROVERLAY_ON": case "_DETAIL_MULX2": case "_EMISSION": case "_FADING_ON": case "_GLOSSYREFLECTIONS_OFF": case "_MAPPING_6_FRAMES_LAYOUT": case "_METALLICGLOSSMAP": case "_NORMALMAP": case "_PARALLAXMAP": case "_REQUIRE_UV2": case "_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A": case "_SPECGLOSSMAP": case "_SPECULARHIGHLIGHTS_OFF": case "_SUNDISK_HIGH_QUALITY": case "_SUNDISK_NONE": case "_SUNDISK_SIMPLE": return true; default: return false; } }
AudioLinkの処理を書きたい
lilToonの本体側での _AudioTexture
の宣言の有無はAudioLink機能が有効か無効であるかに依存する.
本体側のAudioLink機能が有効か無効であるかに左右されないようにするためには, custom_insert.hlsl
内で下記のように宣言すること.
custom_insert.hlsl
// _AudioTexture is declared in lil_common_input.hlsl. #ifndef LIL_FEATURE_AUDIOLINK TEXTURE2D_FLOAT(_AudioTexture); float4 _AudioTexture_TexelSize; #endif // LIL_FEATURE_AUDIOLINK
uniform変数の宣言は custom.hlsl
内の LIL_CUSTOM_PROPERTIES
や LIL_CUSTOM_TEXTURES
マクロで行うべきと思うかもしれないが,
custom.hlsl
の段階では LIL_FEATURE_AUDIOLINK
マクロが定義されていないため不可能である.
VRChatのカメラ判定,ミラー判定のuniform変数
下記3つの変数はVRChat側から提供されているuniform変数であり,どのワールドでも使用することができる.
_VRChatCameraMode
_VRChatMirrorMode
_VRChatMirrorCameraPos
lilToon 1.4.0現在では本体側シェーダーで宣言されていないため,custom.hlsl
の LIL_CUSTOM_PROPERTIES
を用いて宣言して問題ない.
#define LIL_CUSTOM_PROPERTIES \ float _VRChatCameraMode; \ float _VRChatMirrorMode; \ float3 _VRChatMirrorCameraPos;
もし本体側で宣言された場合はAudioLinkと同じ対応をとること. 本体のissueにミラー表示制御を実装してほしいという要望があるため,将来的に追加される可能性がある.
シェーダー側でmultiシェーダーかどうかを判定する
マクロ LIL_MULTI
が定義されているかどうかを調べる.
ただし,このマクロは custom.hlsl
の段階では定義されておらず, custom_insert.hlsl
の段階でないと使用できないことに注意.
#ifdef LIL_MULTI // マルチシェーダー用の処理 #endif
非multiとmulti版である程度コードを共有する
非multi版でif文を用い,multi版で条件コンパイルを用いると,同じコードを2度書くことになる.
lilCustomShaderProperties.lilblock
//---------------------------------------------------------------------------------------------------------------------- // Custom Properties [Toggle] _ToggleProp ("Toggle Property", Int) = 0 [KeywordEnum(Foo, Bar, Baz)] _KeywordEnumProp ("Keyword enum property", Int) = 0
custom.hlsl
#define LIL_CUSTOM_PROPERTIES \ bool _ToggleProp; \ int _KeywordEnumProp;
custom_insert.hlsl
float4 getColor() { #if !defined(LIL_MULTI) if (_ToggleProp) { return float4(1.0, 0.0, 0.0, 1.0); } else { return float4(0.0, 1.0, 0.0, 1.0); } #elif defined(_TOGGLEPROP_ON) return float4(1.0, 0.0, 0.0, 1.0); #else return float4(0.0, 1.0, 0.0, 1.0); #endif } float selectElement(float3 v) { #if !defined(LIL_MULTI) if (_KeywordEnumProp == 0) { return v.x; } else if (_KeywordEnumProp == 1) { return v.y; } else { return v.z; } #elif defined(_KEYWORDENUMPROP_FOO) return v.x; #elif defined(_KEYWORDENUMPROP_BAR) return v.y; #elif defined(_KEYWORDENUMPROP_BAZ) return v.z; #endif }
[Toggle]
や [KeywordEnum]
に対するuniform変数を用意し,if文で条件分岐を記述する.
マルチシェーダー,すなわち LIL_MULTI
が定義されている場合のみ,uniform変数をマクロによって定数に置換し,
if文の条件分岐がコンパイル時に確定するようにし,プリプロセス段階ではなくコンパイル段階での不要な処理の除去をコンパイラに任せる.
なお, custom.hlsl
の段階では LIL_MULTI
が定義されていないので,マルチシェーダーのときはuniform変数を定義しない,ということは諦める.
custom_insert.hlsl
#ifdef LIL_MULTI # ifdef _TOGGLEPROP_ON # define _ToggleProp true # else # define _ToggleProp false # endif // _TOGGLEPROP_ON # if defined(_KEYWORDENUMPROP_FOO) # define _KeywordEnumProp 0 # elif defined(_KEYWORDENUMPROP_BAR) # define _KeywordEnumProp 1 # elif defined(_KEYWORDENUMPROP_BAZ) # define _KeywordEnumProp 2 # endif #endif // LIL_MULTI float4 getColor() { if (_ToggleProp) { return float4(1.0, 0.0, 0.0, 1.0); } else { return float4(0.0, 1.0, 0.0, 1.0); } } float selectElement(float3 v) { if (_KeywordEnumProp == 0) { return v.x; } else if (_KeywordEnumProp == 1) { return v.y; } else { return v.z; } }
SV_POSITION に NaN
頂点シェーダーの出力構造体で SV_POSITION
に相当するメンバに NaN を代入することで,頂点に関連するポリゴンを消去するテクニック.
フラグメントシェーダーに渡ってから discard
するよりおそらくGPUにやさしい.
custom.hlsl
で下記のようにする(VRChatのカメラに写らなくするシェーダー例).
#define LIL_CUSTOM_VERT_COPY if (_VRChatCameraMode != 0.0) { \ LIL_INITIALIZE_STRUCT(v2f, LIL_V2F_OUT_BASE); \ LIL_V2F_OUT_BASE.positionCS = 0.0 / 0.0; \ return LIL_V2F_OUT; \ }
LIL_INITIALIZE_STRUCT
を入れておくことで,コンパイラの最適化処理により,頂点シェーダーの先頭あたりに上記のコードを記述したのと同一のコードが生成される.
LIL_V2F_OUT_BASE.positionCS
は float4
であるが, 0.0 / 0.0
は全要素 0.0 / 0.0
の float4
に暗黙的に変換されるのを利用している.
後続の処理は不要なので,returnしておく.
NaNの警告回避
本体側で4008番の警告を無効化するpragma定義がされているので不要.
0.0 / 0.0
と書きたくないなら asfloat(0x7fc00000)
と書いてもよい.
マテリアルエディタ
キーワードについて
マルチシェーダーかどうかの判定
lilToon.lilInspector
に定義されている静的メンバ isMulti
を参照する.
if (isMulti) { material.EnableKeyword("_TOGGLEPROP_ON"); }
シェーダー名に Multi
が含まれるかどうかで判定する手もある.
自前で定義したDrawer内では isMulti
は参照できないため,シェーダー名で判断するしかない?
protected readonly string _keyword; public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { var isOn = prop.floatValue >= 0.5f; var kw = string.IsNullOrEmpty(_keyword) ? prop.name.ToUpperInvariant() + "_ON" : _keyword; foreach (Material material in prop.targets.Where(material => material.shader.name.IndexOf("Multi", material.shader.name.LastIndexOf('/')) != -1)) { if (isOn) { material.EnableKeyword(kw); } else { material.DisableKeyword(kw); } } }
多言語対応
公式の作例の lilToonGeometryFX
を参照.
多言語ファイルは下記のような1行目がヘッダ行(言語名),2行目以降がデータ行のTSVファイル. データ行の1列名はキーで,2列目以降が各言語に応じた文言である.
ファイル名は何でもよい(GUIDで参照するため).
作例に習うなら lang_custom.txt
.
GUIDは lang_custom.txt.meta
を参照すること.
Language English Japanese Korean Chinese Simplified Chinese Traditional sCustomGeometryAnimation Geometry Animation ジオメトリアニメーション 지오메트리 애니메이션 Geometry Animation Geometry Animation sCustomBase Base Setting 基本設定 기본 설정 基本设置 基本設置 sCustomVector Vector 向き 방향 向量 向量 sCustomDelay Delay ディレイ 딜레이 延迟 延遲 sCustomSpeed Speed 速度 속도 速度 速度 sCustomRandomize Randomize ランダム化 임의화 随机化 隨機化 sCustomNormal Normal 法線 노멀 法线 法線 sCustomOffset Offset オフセット Offset Offset Offset sCustomNormalMap Normal Map ノーマルマップ 노멀 맵 法线贴图 法線貼圖 sCustomStrength Strength 強度 강도 强度 強度 sCustomShrink Shrink 縮小 축소 缩减 縮減 sCustomMotionNormal Motion Normal モーション法線 모션 법선 运动法线 運動法線 sCustomShadingNormal Shading Normal シェーディング法線 셰이딩 법선 着色法线 著色法線 sCustomGenerateSide Generate Side 側面を生成 측면 생성 生成侧面 生成側面
C# 側では LoadCustomLanguage()
メソッドでファイルを読み込み, GetLoc()
メソッドでキーを指定してローカライズされた文言を取得する.
もし,定義されていないキーであった場合.GetLoc()
はキー名をそのまま返す
protected override void LoadCustomProperties(MaterialProperty[] props, Material material) { // ... LoadCustomLanguage("a5875813c34e16a49ae1c8e1a846ea75"); // ... } protected override void DrawCustomProperties(Material material) { // ... var label = GetLoc("sCustomGeometryAnimation"); // ... }
ToggleLeftによる折り畳みとキーワード
シェーダー側で [Toggle]
を指定しているプロパティ(MaterialToggleDrawer
)について,lilToon本体の折り畳みに合わせ,なおかつキーワードを定義したい場合の解決法.
(当たり前のことではあるが,Drawerを定義して,そのDrawerを指定する方が良いとは思う.)
下記のように記載した場合, EditorGUI.ToggleLeft
が使用されないため不恰好になる.
m_MaterialEditor.ShaderProperty(_toggleProp, "Label for toggle property");
UnityEditor.MaterialEditor.ShaderProperty
UnityEditor.MaterialEditor.ShaderPropertyInternal
しかし,UnityEditor.MaterialEditor.ShaderProperty()
で行われている処理である
MaterialProperty
に設定されている Drawer
を取得し,その Drawer
の OnGUI()
を呼び出すのは,
使用されているクラス・メソッド類が外部からは private
となっているため,リフレクションを活用する必要がある.
// 下記のusing必要 using System.Reflection; // ... /// <summary> /// Enable or disable keyword of <see cref="MaterialProperty"/> which has MaterialToggleUIDrawer. /// </summary> /// <param name="shader">Target <see cref="Shader"/>.</param> /// <param name="prop">Target <see cref="MaterialProperty"/>.</param> private static void SetToggleKeyword(Shader shader, MaterialProperty prop) { SetToggleKeyword(shader, prop, prop.floatValue >= 0.5f); } /// <summary> /// Enable or disable keyword of <see cref="MaterialProperty"/> which has MaterialToggleUIDrawer. /// </summary> /// <param name="shader">Target <see cref="Shader"/>.</param> /// <param name="prop">Target <see cref="MaterialProperty"/>.</param> private static void SetToggleKeyword(Shader shader, MaterialProperty prop, bool isOn) { // Get assembly from public class. var asm = Assembly.GetAssembly(typeof(UnityEditor.MaterialPropertyDrawer)); // Get type of UnityEditor.MaterialPropertyHandler which is the internal class. var typeMph = asm.GetType("UnityEditor.MaterialPropertyHandler") ?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialPropertyHandler"); var miGetHandler = typeMph.GetMethod( "GetHandler", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialPropertyHandler.GetHandler"); // Instance of UnityEditor.MaterialPropertyHandler. var handler = miGetHandler.Invoke(null, new object[] { shader, prop.name }); var pi = typeMph.GetProperty( "propertyDrawer", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException("PropertyInfo not found: UnityEditor.MaterialPropertyHandler.propertyDrawer"); var drawer = pi.GetValue(handler) ?? throw new InvalidOperationException("Field not found: UnityEditor.MaterialPropertyHandler.propertyDrawer"); // Check if drawer is instance of UnityEditor.MaterialToggleUIDrawer or not. var typeMtd = asm.GetType("UnityEditor.MaterialToggleUIDrawer") ?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialToggleUIDrawer"); if (!drawer.IsSubClassOf(typeMtd)) { throw new ArgumentException($"{nameof(prop)} is not instance of UnityEditor.MaterialToggleUIDrawer."); } var miSetKeyword = typeMtd.GetMethod( "SetKeyword", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialToggleUIDrawer.SetKeyword"); miSetKeyword.Invoke(drawer, new object[] { prop, isOn }); }
リフレクション結果のキャッシュを作るのであれば下記のようにするとよい(クリックで開く閉じる).
// 下記のusing必要 using System.Linq.Expression; using System.Reflection; // ... /// <summary> /// Cache of reflection result of following lambda. /// </summary> /// <remarks><seealso cref="CreateToggleKeywordDelegate"/></remarks> private static Action<Shader, MaterialProperty, bool> _toggleKeyword; // ... /// <summary> /// Enable or disable keyword of <see cref="MaterialProperty"/> which has MaterialToggleUIDrawer. /// </summary> /// <param name="shader">Target <see cref="Shader"/>.</param> /// <param name="prop">Target <see cref="MaterialProperty"/>.</param> private static void SetToggleKeyword(Shader shader, MaterialProperty prop) { SetToggleKeyword(shader, prop, prop.floatValue >= 0.5f); } /// <summary> /// Enable or disable keyword of <see cref="MaterialProperty"/> which has MaterialToggleUIDrawer. /// </summary> /// <param name="shader">Target <see cref="Shader"/>.</param> /// <param name="prop">Target <see cref="MaterialProperty"/>.</param> /// <param name="isOn">True to enable (define) keyword, false to disable (undefine) keyword.</param> private static void SetToggleKeyword(Shader shader, MaterialProperty prop, bool isOn) { try { (_toggleKeyword ?? (_toggleKeyword = CreateSetKeywordDelegate()))(shader, prop, isOn); } catch (Exception ex) { Debug.LogError(ex.ToString()); } } /// <summary> /// <para>Create delegate of reflection results about UnityEditor.MaterialToggleUIDrawer.</para> /// <code> /// (Shader shader, MaterialProperty prop, bool isOn) => /// { /// MaterialPropertyHandler mph = UnityEditor.MaterialPropertyHandler.GetHandler(shader, prop.name); /// if (mph is null) /// { /// throw new ArgumentException("Specified MaterialProperty does not have UnityEditor.MaterialPropertyHandler"); /// } /// MaterialToggleUIDrawer mpud = mph.propertyDrawer as MaterialToggleUIDrawer; /// if (mpud is null) /// { /// throw new ArgumentException("Specified MaterialProperty does not have UnityEditor.MaterialToggleUIDrawer"); /// } /// mpud.SetKeyword(prop, isOn); /// } /// </code> /// </summary> private static Action<Shader, MaterialProperty, bool> CreateSetKeywordDelegate() { // Get assembly from public class. var asm = Assembly.GetAssembly(typeof(UnityEditor.MaterialPropertyDrawer)); // Get type of UnityEditor.MaterialPropertyHandler which is the internal class. var typeMph = asm.GetType("UnityEditor.MaterialPropertyHandler") ?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialPropertyHandler"); var typeMtud = asm.GetType("UnityEditor.MaterialToggleUIDrawer") ?? throw new InvalidOperationException("Type not found: UnityEditor.MaterialToggleUIDrawer"); var ciArgumentException = typeof(ArgumentException).GetConstructor(new[] {typeof(string)}); var pShader = Expression.Parameter(typeof(Shader), "shader"); var pMaterialPropertyHandler = Expression.Parameter(typeMph, "mph"); var pMaterialToggleUIDrawer = Expression.Parameter(typeMtud, "mtud"); var pMaterialProperty = Expression.Parameter(typeof(MaterialProperty), "mp"); var pBool = Expression.Parameter(typeof(bool), "isOn"); var cNull = Expression.Constant(null); return Expression.Lambda<Action<Shader, MaterialProperty, bool>>( Expression.Block( new[] { pMaterialPropertyHandler, pMaterialToggleUIDrawer }, Expression.Assign( pMaterialPropertyHandler, Expression.Call( typeMph.GetMethod( "GetHandler", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialPropertyHandler.GetHandler"), pShader, Expression.Property( pMaterialProperty, typeof(MaterialProperty).GetProperty( "name", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)))), Expression.IfThen( Expression.Equal( pMaterialPropertyHandler, cNull), Expression.Throw( Expression.New( ciArgumentException, Expression.Constant("Specified MaterialProperty does not have UnityEditor.MaterialPropertyHandler")))), Expression.Assign( pMaterialToggleUIDrawer, Expression.TypeAs( Expression.Property( pMaterialPropertyHandler, typeMph.GetProperty( "propertyDrawer", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException("PropertyInfo not found: UnityEditor.MaterialPropertyHandler.propertyDrawer")), typeMtud)), Expression.IfThen( Expression.Equal( pMaterialToggleUIDrawer, cNull), Expression.Throw( Expression.New( ciArgumentException, Expression.Constant("Specified MaterialProperty does not have UnityEditor.MaterialToggleUIDrawer")))), Expression.Call( pMaterialToggleUIDrawer, typeMtud.GetMethod( "SetKeyword", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("MethodInfo not found: UnityEditor.MaterialToggleUIDrawer.SetKeyword"), pMaterialProperty, pBool)), "SetKeyword", new [] { pShader, pMaterialProperty, pBool }).Compile(); }
上記の SetToggleKeyword()
メソッドを利用して下記のように記述する.
protected override void DrawCustomProperties(Material material) { // ... using (new EditorGUILayout.VerticalScope(boxOuter)) { DrawToggleLeft(material, _toggleProp, GetLoc("sToggleProp")); if (_enableWorldPos.floatValue >= 0.5f) { // 関連するプロパティの描画 } } // ... } /// <summary> /// Draw ToggleLeft property. /// </summary> /// <param name="material">Target <see cref="Material"/>.</param> /// <param name="prop">Target <see cref="MaterialProperty"/>.</param> /// <param name="label">Label for this toggle button.</param> private static void DrawToggleLeft(Material material, MaterialProperty prop, string label) { using (var ccScope = new EditorGUI.ChangeCheckScope()) { EditorGUI.showMixedValue = prop.hasMixedValue; var isChecked = EditorGUI.ToggleLeft( EditorGUILayout.GetControlRect(), label, prop.floatValue >= 0.5f, customToggleFont); EditorGUI.showMixedValue = false; if (ccScope.changed) { prop.floatValue = isChecked ? 1.0f : 0.0f; if (isMulti) { SetToggleKeyword(material.shader, prop); } } } }
オリジナルのlilToonからの移行を簡単にする
テンプレートのインスペクタのコード末尾のコメントアウト部分を解除すると,マテリアルの右クリックメニューが追加される. 名前は適切に置きかえること.
[MenuItem("Assets/TemplateFull/Convert material to custom shader", false, 1100)] private static void ConvertMaterialToCustomShaderMenu() { if(Selection.objects.Length == 0) return; TemplateFullInspector inspector = new TemplateFullInspector(); for(int i = 0; i < Selection.objects.Length; i++) { if(Selection.objects[i] is Material) { inspector.ConvertMaterialToCustomShader((Material)Selection.objects[i]); } } }
ただし,上記コードはCtrl-Zが考慮されていない,C# のコードとしてイマイチ,inspector.ConvertMaterialToCustomShader
の処理が大袈裟である(lilToon本体とカスタムシェーダーの全てのバリエーションについて Shader.Find()
を呼び出す)ので,下記のようにするのがオススメである.
/// <summary> /// Try to replace the shader of the selected material to custom lilToon shader. /// </summary> [MenuItem("Assets/TemplateFull/Convert material to custom shader", false, 1100)] private static void ConvertMaterialToCustomShaderMenu() { foreach (var obj in Selection.objects) { var material = obj as Material; if (material == null) { continue; } var shader = GetCorrespondingCustomShader(material.shader); if (shader == null) { Debug.LogWarningFormat($"Ignore {0}. \"{1}\" is not original lilToon shader.", AssetDatabase.GetAssetPath(material), material.shader.name); continue; } Undo.RecordObject(material, "TemplateFull/ConvertMaterialToCustomShaderMenu"); var renderQueue = lilMaterialUtils.GetTrueRenderQueue(material); material.shader = shader; material.renderQueue = renderQueue; } } /// <summary> /// Get a custom lilToon shader which is corresponding to specified original lilToon shader. /// </summary> /// <param name="originalShader">Original lilToon shader.</param> /// <returns>null if no custom lilToon shader is found, otherwise the one found.</returns> private static Shader GetCorrespondingCustomShader(Shader originalShader) { var customShaderName = GetCorrespondingCustomShaderName(originalShader.name); return customShaderName == null ? null : Shader.Find(customShaderName); } /// <summary> /// Get a custom lilToon shader name which is corresponding to specified original lilToon shader name. /// </summary> /// <param name="originalShaderName">Original lilToon shader name.</param> /// <returns>null if no custom lilToon shader name is found, otherwise the one found.</returns> private static string GetCorrespondingCustomShaderName(string originalShaderName) { switch (originalShaderName) { case "lilToon": return shaderName + "/lilToon"; case "Hidden/lilToonCutout": return "Hidden/" + shaderName + "/Cutout"; case "Hidden/lilToonTransparent": return "Hidden/" + shaderName + "/Transparent"; case "Hidden/lilToonOnePassTransparent": return "Hidden/" + shaderName + "/OnePassTransparent"; case "Hidden/lilToonTwoPassTransparent": return "Hidden/" + shaderName + "/TwoPassTransparent"; case "Hidden/lilToonOutline": return "Hidden/" + shaderName + "/OpaqueOutline"; case "Hidden/lilToonCutoutOutline": return "Hidden/" + shaderName + "/CutoutOutline"; case "Hidden/lilToonTransparentOutline": return "Hidden/" + shaderName + "/TransparentOutline"; case "Hidden/lilToonOnePassTransparentOutline": return "Hidden/" + shaderName + "/OnePassTransparentOutline"; case "Hidden/lilToonTwoPassTransparentOutline": return "Hidden/" + shaderName + "/TwoPassTransparentOutline"; case "_lil/[Optional] lilToonOutlineOnly": return shaderName + "/[Optional] OutlineOnly/Opaque"; case "_lil/[Optional] lilToonCutoutOutlineOnly": return shaderName + "/[Optional] OutlineOnly/Cutout"; case "_lil/[Optional] lilToonTransparentOutlineOnly": return shaderName + "/[Optional] OutlineOnly/Transparent"; case "Hidden/lilToonTessellation": return "Hidden/" + shaderName + "/Tessellation/Opaque"; case "Hidden/lilToonTessellationCutout": return "Hidden/" + shaderName + "/Tessellation/Cutout"; case "Hidden/lilToonTessellationTransparent": return "Hidden/" + shaderName + "/Tessellation/Transparent"; case "Hidden/lilToonTessellationOnePassTransparent": return "Hidden/" + shaderName + "/Tessellation/OnePassTransparent"; case "Hidden/lilToonTessellationTwoPassTransparent": return "Hidden/" + shaderName + "/Tessellation/TwoPassTransparent"; case "Hidden/lilToonTessellationOutline": return "Hidden/" + shaderName + "/Tessellation/OpaqueOutline"; case "Hidden/lilToonTessellationCutoutOutline": return "Hidden/" + shaderName + "/Tessellation/CutoutOutline"; case "Hidden/lilToonTessellationTransparentOutline": return "Hidden/" + shaderName + "/Tessellation/TransparentOutline"; case "Hidden/lilToonTessellationOnePassTransparentOutline": return "Hidden/" + shaderName + "/Tessellation/OnePassTransparentOutline"; case "Hidden/lilToonTessellationTwoPassTransparentOutline": return "Hidden/" + shaderName + "/Tessellation/TwoPassTransparentOutline"; case "Hidden/lilToonLite": return shaderName + "/lilToonLite"; case "Hidden/lilToonLiteCutout": return "Hidden/" + shaderName + "/Lite/Cutout"; case "Hidden/lilToonLiteTransparent": return "Hidden/" + shaderName + "/Lite/Transparent"; case "Hidden/lilToonLiteOnePassTransparent": return "Hidden/" + shaderName + "/Lite/OnePassTransparent"; case "Hidden/lilToonLiteTwoPassTransparent": return "Hidden/" + shaderName + "/Lite/TwoPassTransparent"; case "Hidden/lilToonLiteOutline": return "Hidden/" + shaderName + "/Lite/OpaqueOutline"; case "Hidden/lilToonLiteCutoutOutline": return "Hidden/" + shaderName + "/Lite/CutoutOutline"; case "Hidden/lilToonLiteTransparentOutline": return "Hidden/" + shaderName + "/Lite/TransparentOutline"; case "Hidden/lilToonLiteOnePassTransparentOutline": return "Hidden/" + shaderName + "/Lite/OnePassTransparentOutline"; case "Hidden/lilToonLiteTwoPassTransparentOutline": return "Hidden/" + shaderName + "/Lite/TwoPassTransparentOutline"; case "Hidden/lilToonRefraction": return "Hidden/" + shaderName + "/Refraction"; case "Hidden/lilToonRefractionBlur": return "Hidden/" + shaderName + "/RefractionBlur"; case "Hidden/lilToonFur": return "Hidden/" + shaderName + "/Fur"; case "Hidden/lilToonFurCutout": return "Hidden/" + shaderName + "/FurCutout"; case "Hidden/lilToonFurTwoPass": return "Hidden/" + shaderName + "/FurTwoPass"; case "_lil/[Optional] lilToonFurOnly": return shaderName + "/[Optional] FurOnly/Transparent"; case "_lil/[Optional] lilToonFurOnlyCutout": return shaderName + "/[Optional] FurOnly/Cutout"; case "_lil/[Optional] lilToonFurOnlyTwoPass": return shaderName + "/[Optional] FurOnly/TwoPass"; case "Hidden/lilToonGem": return "Hidden/" + shaderName + "/Gem"; case "_lil/lilToonFakeShadow": return shaderName + "/[Optional] FakeShadow"; case "_lil/[Optional] lilToonOverlay": return shaderName + "/[Optional] Overlay"; case "_lil/[Optional] lilToonOverlayOnePass": return shaderName + "/[Optional] OverlayOnePass"; case "_lil/[Optional] lilToonLiteOverlay": return shaderName + "/[Optional] LiteOverlay"; case "_lil/[Optional] lilToonLiteOverlayOnePass": return shaderName + "/[Optional] LiteOverlayOnePass"; case "_lil/lilToonMulti": return shaderName + "/lilToonMulti"; case "Hidden/lilToonMultiOutline": return "Hidden/" + shaderName + "/MultiOutline"; case "Hidden/lilToonMultiRefraction": return "Hidden/" + shaderName + "/MultiRefraction"; case "Hidden/lilToonMultiFur": return "Hidden/" + shaderName + "/MultiFur"; case "Hidden/lilToonMultiGem": return "Hidden/" + shaderName + "/MultiGem"; default: return null; } }
テンプレートに従っているならば, shaderName
はクラス内で下記のように宣言されているはずであり,これを用いるようにしている.
private const string shaderName = "TemplateFull";
カスタムシェーダーからオリジナルのlilToonへ簡単に戻せるようにする
前述のものと逆の動作を行うメソッドを用意し,右クリックメニューとして登録する.
/// <summary> /// Try to replace the shader of the material to original lilToon shader. /// </summary> [MenuItem("Assets/TemplateFull/Convert material to original shader", false, 1101)] private static void ConvertMaterialToOriginalShaderMenu() { foreach (var obj in Selection.objects) { var material = obj as Material; if (material == null) { continue; } var shader = GetCorrespondingOriginalShader(material.shader); if (shader == null) { Debug.LogWarningFormat($"Ignore {0}. \"{1}\" is not custom lilToon shader, \"" + shaderName + "\".", AssetDatabase.GetAssetPath(material), material.shader.name); continue; } Undo.RecordObject(material, "TemplateFull/ConvertMaterialToOriginalShaderMenu"); var renderQueue = lilMaterialUtils.GetTrueRenderQueue(material); material.shader = shader; material.renderQueue = renderQueue; } } /// <summary> /// Get a original lilToon shader which is corresponding to specified custom lilToon shader. /// </summary> /// <param name="customShader">Custom lilToon shader.</param> /// <returns>null if no original lilToon shader is found, otherwise the one found.</returns> private static Shader GetCorrespondingOriginalShader(Shader customShader) { var customShaderName = GetCorrespondingOriginalShaderName(customShader.name); return customShaderName == null ? null : Shader.Find(customShaderName); } /// <summary> /// Get a original lilToon shader name which is corresponding to specified custom lilToon shader name. /// </summary> /// <param name="customShaderName">Custom lilToon shader name.</param> /// <returns>null if no original lilToon shader name is found, otherwise the one found.</returns> private static string GetCorrespondingOriginalShaderName(string customShaderName) { switch (customShaderName) { case shaderName + "/lilToon": return "lilToon"; case "Hidden/" + shaderName + "/Cutout": return "Hidden/lilToonCutout"; case "Hidden/" + shaderName + "/Transparent": return "Hidden/lilToonTransparent"; case "Hidden/" + shaderName + "/OnePassTransparent": return "Hidden/lilToonOnePassTransparent"; case "Hidden/" + shaderName + "/TwoPassTransparent": return "Hidden/lilToonTwoPassTransparent"; case "Hidden/" + shaderName + "/OpaqueOutline": return "Hidden/lilToonOutline"; case "Hidden/" + shaderName + "/CutoutOutline": return "Hidden/lilToonCutoutOutline"; case "Hidden/" + shaderName + "/TransparentOutline": return "Hidden/lilToonTransparentOutline"; case "Hidden/" + shaderName + "/OnePassTransparentOutline": return "Hidden/lilToonOnePassTransparentOutline"; case "Hidden/" + shaderName + "/TwoPassTransparentOutline": return "Hidden/lilToonTwoPassTransparentOutline"; case shaderName + "/[Optional] OutlineOnly/Opaque": return "_lil/[Optional] lilToonOutlineOnly"; case shaderName + "/[Optional] OutlineOnly/Cutout": return "_lil/[Optional] lilToonCutoutOutlineOnly"; case shaderName + "/[Optional] OutlineOnly/Transparent": return "_lil/[Optional] lilToonTransparentOutlineOnly"; case "Hidden/" + shaderName + "/Tessellation/Opaque": return "Hidden/lilToonTessellation"; case "Hidden/" + shaderName + "/Tessellation/Cutout": return "Hidden/lilToonTessellationCutout"; case "Hidden/" + shaderName + "/Tessellation/Transparent": return "Hidden/lilToonTessellationTransparent"; case "Hidden/" + shaderName + "/Tessellation/OnePassTransparent": return "Hidden/lilToonTessellationOnePassTransparent"; case "Hidden/" + shaderName + "/Tessellation/TwoPassTransparent": return "Hidden/lilToonTessellationTwoPassTransparent"; case "Hidden/" + shaderName + "/Tessellation/OpaqueOutline": return "Hidden/lilToonTessellationOutline"; case "Hidden/" + shaderName + "/Tessellation/CutoutOutline": return "Hidden/lilToonTessellationCutoutOutline"; case "Hidden/" + shaderName + "/Tessellation/TransparentOutline": return "Hidden/lilToonTessellationTransparentOutline"; case "Hidden/" + shaderName + "/Tessellation/OnePassTransparentOutline": return "Hidden/lilToonTessellationOnePassTransparentOutline"; case "Hidden/" + shaderName + "/Tessellation/TwoPassTransparentOutline": return "Hidden/lilToonTessellationTwoPassTransparentOutline"; case shaderName + "/lilToonLite": return "Hidden/lilToonLite"; case "Hidden/" + shaderName + "/Lite/Cutout": return "Hidden/lilToonLiteCutout"; case "Hidden/" + shaderName + "/Lite/Transparent": return "Hidden/lilToonLiteTransparent"; case "Hidden/" + shaderName + "/Lite/OnePassTransparent": return "Hidden/lilToonLiteOnePassTransparent"; case "Hidden/" + shaderName + "/Lite/TwoPassTransparent": return "Hidden/lilToonLiteTwoPassTransparent"; case "Hidden/" + shaderName + "/Lite/OpaqueOutline": return "Hidden/lilToonLiteOutline"; case "Hidden/" + shaderName + "/Lite/CutoutOutline": return "Hidden/lilToonLiteCutoutOutline"; case "Hidden/" + shaderName + "/Lite/TransparentOutline": return "Hidden/lilToonLiteTransparentOutline"; case "Hidden/" + shaderName + "/Lite/OnePassTransparentOutline": return "Hidden/lilToonLiteOnePassTransparentOutline"; case "Hidden/" + shaderName + "/Lite/TwoPassTransparentOutline": return "Hidden/lilToonLiteTwoPassTransparentOutline"; case "Hidden/" + shaderName + "/Refraction": return "Hidden/lilToonRefraction"; case "Hidden/" + shaderName + "/RefractionBlur": return "Hidden/lilToonRefractionBlur"; case "Hidden/" + shaderName + "/Fur": return "Hidden/lilToonFur"; case "Hidden/" + shaderName + "/FurCutout": return "Hidden/lilToonFurCutout"; case "Hidden/" + shaderName + "/FurTwoPass": return "Hidden/lilToonFurTwoPass"; case shaderName + "/[Optional] FurOnly/Transparent": return "_lil/[Optional] lilToonFurOnly"; case shaderName + "/[Optional] FurOnly/Cutout": return "_lil/[Optional] lilToonFurOnlyCutout"; case shaderName + "/[Optional] FurOnly/TwoPass": return "_lil/[Optional] lilToonFurOnlyTwoPass"; case "Hidden/" + shaderName + "/Gem": return "Hidden/lilToonGem"; case shaderName + "/[Optional] FakeShadow": return "_lil/lilToonFakeShadow"; case shaderName + "/[Optional] Overlay": return "_lil/[Optional] lilToonOverlay"; case shaderName + "/[Optional] OverlayOnePass": return "_lil/[Optional] lilToonOverlayOnePass"; case shaderName + "/[Optional] LiteOverlay": return "_lil/[Optional] lilToonLiteOverlay"; case shaderName + "/[Optional] LiteOverlayOnePass": return "_lil/[Optional] lilToonLiteOverlayOnePass"; case shaderName + "/lilToonMulti": return "_lil/lilToonMulti"; case "Hidden/" + shaderName + "/MultiOutline": return "Hidden/lilToonMultiOutline"; case "Hidden/" + shaderName + "/MultiRefraction": return "Hidden/lilToonMultiRefraction"; case "Hidden/" + shaderName + "/MultiFur": return "Hidden/lilToonMultiFur"; case "Hidden/" + shaderName + "/MultiGem": return "Hidden/lilToonMultiGem"; default: return null; } }
shaderName
は const string
であるため,文字列リテラルとの結合結果もまたコンパイル時定数となり,caseのラベルとして使用できる.
その他
本体のソースコードリーディングを楽にする
Vim等のctagsに対応しているエディタを使っている人向け. lilToon本体のソースコードをGitHubからクローンし,タグファイルを作成しておくと,本体の関数やマクロへの定義ジャンプが可能になり,色々と楽になる.
以下は ~/github/lilToon
に本体のソースコードをcloneしたものとしたタグファイル(フルパスのタグファイル)の生成方法である.
- シェーダーファイル向け
$ ctags -f shaderlab.lilToon.tags --languages=c --langmap=c:+.shader,c:+.hlsl -R ~/lilToon/Assets/lilToon/Shader/
- C# 用
$ ctags -f cs.lilToon.tags --languages=c# -R ~/lilToon/Assets/lilToon/
上記のタグファイルを ~/.vim/tagfiles/
に配置し,~/.vimrc
に下記のように記述するとよい.
augroup MyCtags autocmd! autocmd FileType hlsl,shaderlab setlocal tags+=~/.vim/tagfiles/shaderlab.lilToon.tags autocmd FileType cs setlocal tags+=~/.vim/tagfiles/win32unix/cs.lilToon.tags augroup END
デフォルトだとファイルタイプがhlsl,shaderlabの判定がされないため,下記2ファイルを用意する必要がある. 別途プラグインを導入して判別できている場合はしなくてよい.
~/.vim/ftdetect/hlsl.vim
au BufNewFile,BufRead *.hlsl setfiletype shaderlab
~/.vim/ftdetect/shaderlab.vim
au BufNewFile,BufRead *.shaderlab setfiletype shaderlab
記事内容全部入りのインスペクタテンプレート
記事内容+自分好みに修正したインスペクタテンプレート.
長いので折り畳み(クリックで開く閉じる).
#if UNITY_EDITOR using UnityEditor; using UnityEngine; using System.Runtime.InteropServices; namespace lilToon { /// <summary> /// <see cref="ShaderGUI"/> for the custom shader variations of lilToon. /// </summary> public class TemplateFullInspector : lilToonInspector { // Custom properties //private MaterialProperty customVariable; /// <summary> /// A flag whether to fold custom properties or not. /// </summary> private static bool isShowCustomProperties; /// <summary> /// Name of this custom shader. /// </summary> private const string shaderName = "TemplateFull"; /// <summary> /// Load custom language file and make cache of shader properties. /// </summary> /// <param name="props">Properties of the material.</param> /// <param name="material">Target material.</param> protected override void LoadCustomProperties(MaterialProperty[] props, Material material) { isCustomShader = true; // If you want to change rendering modes in the editor, specify the shader here ReplaceToCustomShaders(); isShowRenderMode = !material.shader.name.Contains(shaderName + "/[Optional] "); // If not, set isShowRenderMode to false //isShowRenderMode = false; //LoadCustomLanguage(""); //customVariable = FindProperty("_CustomVariable", props); } /// <summary> /// Draw custom properties. /// </summary> /// <param name="material">Target material.</param> protected override void DrawCustomProperties(Material material) { // GUIStyles Name Description // ---------------- ------------------------------------ // boxOuter outer box // boxInnerHalf inner box // boxInner inner box without label // customBox box (similar to unity default box) // customToggleFont label for box isShowCustomProperties = Foldout("Custom Properties", "Custom Properties", isShowCustomProperties); if (!isShowCustomProperties) { return; } using (new EditorGUILayout.VerticalScope(boxOuter)) { EditorGUILayout.LabelField(GetLoc("Custom Properties"), customToggleFont); using (new EditorGUILayout.VerticalScope(boxInnerHalf)) { //m_MaterialEditor.ShaderProperty(customVariable, "Custom Variable"); } } } /// <summary> /// Replace shaders to custom shaders. /// </summary> protected override void ReplaceToCustomShaders() { lts = Shader.Find(shaderName + "/lilToon"); ltsc = Shader.Find("Hidden/" + shaderName + "/Cutout"); ltst = Shader.Find("Hidden/" + shaderName + "/Transparent"); ltsot = Shader.Find("Hidden/" + shaderName + "/OnePassTransparent"); ltstt = Shader.Find("Hidden/" + shaderName + "/TwoPassTransparent"); ltso = Shader.Find("Hidden/" + shaderName + "/OpaqueOutline"); ltsco = Shader.Find("Hidden/" + shaderName + "/CutoutOutline"); ltsto = Shader.Find("Hidden/" + shaderName + "/TransparentOutline"); ltsoto = Shader.Find("Hidden/" + shaderName + "/OnePassTransparentOutline"); ltstto = Shader.Find("Hidden/" + shaderName + "/TwoPassTransparentOutline"); ltsoo = Shader.Find(shaderName + "/[Optional] OutlineOnly/Opaque"); ltscoo = Shader.Find(shaderName + "/[Optional] OutlineOnly/Cutout"); ltstoo = Shader.Find(shaderName + "/[Optional] OutlineOnly/Transparent"); ltstess = Shader.Find("Hidden/" + shaderName + "/Tessellation/Opaque"); ltstessc = Shader.Find("Hidden/" + shaderName + "/Tessellation/Cutout"); ltstesst = Shader.Find("Hidden/" + shaderName + "/Tessellation/Transparent"); ltstessot = Shader.Find("Hidden/" + shaderName + "/Tessellation/OnePassTransparent"); ltstesstt = Shader.Find("Hidden/" + shaderName + "/Tessellation/TwoPassTransparent"); ltstesso = Shader.Find("Hidden/" + shaderName + "/Tessellation/OpaqueOutline"); ltstessco = Shader.Find("Hidden/" + shaderName + "/Tessellation/CutoutOutline"); ltstessto = Shader.Find("Hidden/" + shaderName + "/Tessellation/TransparentOutline"); ltstessoto = Shader.Find("Hidden/" + shaderName + "/Tessellation/OnePassTransparentOutline"); ltstesstto = Shader.Find("Hidden/" + shaderName + "/Tessellation/TwoPassTransparentOutline"); ltsl = Shader.Find(shaderName + "/lilToonLite"); ltslc = Shader.Find("Hidden/" + shaderName + "/Lite/Cutout"); ltslt = Shader.Find("Hidden/" + shaderName + "/Lite/Transparent"); ltslot = Shader.Find("Hidden/" + shaderName + "/Lite/OnePassTransparent"); ltsltt = Shader.Find("Hidden/" + shaderName + "/Lite/TwoPassTransparent"); ltslo = Shader.Find("Hidden/" + shaderName + "/Lite/OpaqueOutline"); ltslco = Shader.Find("Hidden/" + shaderName + "/Lite/CutoutOutline"); ltslto = Shader.Find("Hidden/" + shaderName + "/Lite/TransparentOutline"); ltsloto = Shader.Find("Hidden/" + shaderName + "/Lite/OnePassTransparentOutline"); ltsltto = Shader.Find("Hidden/" + shaderName + "/Lite/TwoPassTransparentOutline"); ltsref = Shader.Find("Hidden/" + shaderName + "/Refraction"); ltsrefb = Shader.Find("Hidden/" + shaderName + "/RefractionBlur"); ltsfur = Shader.Find("Hidden/" + shaderName + "/Fur"); ltsfurc = Shader.Find("Hidden/" + shaderName + "/FurCutout"); ltsfurtwo = Shader.Find("Hidden/" + shaderName + "/FurTwoPass"); ltsfuro = Shader.Find(shaderName + "/[Optional] FurOnly/Transparent"); ltsfuroc = Shader.Find(shaderName + "/[Optional] FurOnly/Cutout"); ltsfurotwo = Shader.Find(shaderName + "/[Optional] FurOnly/TwoPass"); ltsgem = Shader.Find("Hidden/" + shaderName + "/Gem"); ltsfs = Shader.Find(shaderName + "/[Optional] FakeShadow"); ltsover = Shader.Find(shaderName + "/[Optional] Overlay"); ltsoover = Shader.Find(shaderName + "/[Optional] OverlayOnePass"); ltslover = Shader.Find(shaderName + "/[Optional] LiteOverlay"); ltsloover = Shader.Find(shaderName + "/[Optional] LiteOverlayOnePass"); ltsm = Shader.Find(shaderName + "/lilToonMulti"); ltsmo = Shader.Find("Hidden/" + shaderName + "/MultiOutline"); ltsmref = Shader.Find("Hidden/" + shaderName + "/MultiRefraction"); ltsmfur = Shader.Find("Hidden/" + shaderName + "/MultiFur"); ltsmgem = Shader.Find("Hidden/" + shaderName + "/MultiGem"); } /// <summary> /// Try to replace the shader of the selected material to custom lilToon shader. /// </summary> [MenuItem("Assets/" + shaderName + "/Convert material to custom shader", false, 1100)] private static void ConvertMaterialToCustomShaderMenu() { foreach (var obj in Selection.objects) { var material = obj as Material; if (material == null) { continue; } var shader = GetCorrespondingCustomShader(material.shader); if (shader == null) { Debug.LogWarningFormat($"Ignore {0}. \"{1}\" is not original lilToon shader.", AssetDatabase.GetAssetPath(material), material.shader.name); continue; } Undo.RecordObject(material, "TemplateFull/ConvertMaterialToCustomShaderMenu"); var renderQueue = lilMaterialUtils.GetTrueRenderQueue(material); material.shader = shader; material.renderQueue = renderQueue; } } /// <summary> /// Menu validation method for <see cref="ConvertMaterialToCustomShaderMenu"/>. /// </summary> /// <returns>True if <see cref="ConvertMaterialToCustomShaderMenu"/> works, otherwise false.</returns> [MenuItem("Assets/" + shaderName + "/Convert material to custom shader", true)] private static bool ValidateConvertMaterialToCustomShaderMenu() { var count = 0; foreach (var obj in Selection.objects) { var material = obj as Material; if (material == null) { continue; } if (GetCorrespondingCustomShaderName(material.shader.name) != null) { count++; } } return count > 0; } /// <summary> /// Try to replace the shader of the material to original lilToon shader. /// </summary> [MenuItem("Assets/" + shaderName + "/Convert material to original shader", false, 1101)] private static void ConvertMaterialToOriginalShaderMenu() { foreach (var obj in Selection.objects) { var material = obj as Material; if (material == null) { continue; } var shader = GetCorrespondingOriginalShader(material.shader); if (shader == null) { Debug.LogWarningFormat($"Ignore {0}. \"{1}\" is not custom lilToon shader, \"" + shaderName + "\".", AssetDatabase.GetAssetPath(material), material.shader.name); continue; } Undo.RecordObject(material, "TemplateFull/ConvertMaterialToOriginalShaderMenu"); var renderQueue = lilMaterialUtils.GetTrueRenderQueue(material); material.shader = shader; material.renderQueue = renderQueue; } } /// <summary> /// Menu validation method for <see cref="ValidateConvertMaterialToOriginalShaderMenu"/>. /// </summary> /// <returns>True if <see cref="ValidateConvertMaterialToOriginalShaderMenu"/> works, otherwise false.</returns> [MenuItem("Assets/" + shaderName + "/Convert material to original shader", true)] private static bool ValidateConvertMaterialToOriginalShader(string customShaderCommonName) { var count = 0; foreach (var obj in Selection.objects) { var material = obj as Material; if (material == null) { continue; } if (GetCorrespondingOriginalShaderName(material.shader.name, customShaderCommonName) != null) { count++; } } return count > 0; } /// <summary> /// Callback method for menu item which refreshes shader cache and reimport. /// </summary> [MenuItem("Assets/" + shaderName + "/Refresh shader cache", false, 2000)] private static void RefreshShaderCacheMenu() { var result = NativeMethods.Open("Library/ShaderCache.db", out var dbHandle); if (result != 0) { Debug.LogError($"Failed to open Library/ShaderCache.db [{result}]"); return; } try { result = NativeMethods.Execute(dbHandle, "DELETE FROM shadererrors", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if (result != 0) { Debug.LogError($"SQL failed [{result}]"); return; } } finally { result = NativeMethods.Close(dbHandle); if (result != 0) { Debug.LogError($"Failed to close database [{result}]"); } } AssetDatabase.ImportAsset("Assets/TemplateFull/Shaders", ImportAssetOptions.ImportRecursive); } /// <summary> /// Menu validation method for <see cref="RefreshShaderCacheMenu"/>. /// </summary> /// <returns>True if <see cref="RefreshShaderCacheMenu"/> works, otherwise false.</returns> [MenuItem("Assets/" + shaderName + "Refresh shader cache", true)] private static bool ValidateRefreshShaderCacheMenu() { try { NativeMethods.Close(IntPtr.Zero); return true; } catch (DllNotFoundException) { return false; } } /// <summary> /// Get a custom lilToon shader which is corresponding to specified original lilToon shader. /// </summary> /// <param name="originalShader">Original lilToon shader.</param> /// <returns>null if no custom lilToon shader is found, otherwise the one found.</returns> private static Shader GetCorrespondingCustomShader(Shader originalShader) { var customShaderName = GetCorrespondingCustomShaderName(originalShader.name); return customShaderName == null ? null : Shader.Find(customShaderName); } /// <summary> /// Get a custom lilToon shader name which is corresponding to specified original lilToon shader name. /// </summary> /// <param name="originalShaderName">Original lilToon shader name.</param> /// <returns>null if no custom lilToon shader name is found, otherwise the one found.</returns> private static string GetCorrespondingCustomShaderName(string originalShaderName) { switch (originalShaderName) { case "lilToon": return shaderName + "/lilToon"; case "Hidden/lilToonCutout": return "Hidden/" + shaderName + "/Cutout"; case "Hidden/lilToonTransparent": return "Hidden/" + shaderName + "/Transparent"; case "Hidden/lilToonOnePassTransparent": return "Hidden/" + shaderName + "/OnePassTransparent"; case "Hidden/lilToonTwoPassTransparent": return "Hidden/" + shaderName + "/TwoPassTransparent"; case "Hidden/lilToonOutline": return "Hidden/" + shaderName + "/OpaqueOutline"; case "Hidden/lilToonCutoutOutline": return "Hidden/" + shaderName + "/CutoutOutline"; case "Hidden/lilToonTransparentOutline": return "Hidden/" + shaderName + "/TransparentOutline"; case "Hidden/lilToonOnePassTransparentOutline": return "Hidden/" + shaderName + "/OnePassTransparentOutline"; case "Hidden/lilToonTwoPassTransparentOutline": return "Hidden/" + shaderName + "/TwoPassTransparentOutline"; case "_lil/[Optional] lilToonOutlineOnly": return shaderName + "/[Optional] OutlineOnly/Opaque"; case "_lil/[Optional] lilToonCutoutOutlineOnly": return shaderName + "/[Optional] OutlineOnly/Cutout"; case "_lil/[Optional] lilToonTransparentOutlineOnly": return shaderName + "/[Optional] OutlineOnly/Transparent"; case "Hidden/lilToonTessellation": return "Hidden/" + shaderName + "/Tessellation/Opaque"; case "Hidden/lilToonTessellationCutout": return "Hidden/" + shaderName + "/Tessellation/Cutout"; case "Hidden/lilToonTessellationTransparent": return "Hidden/" + shaderName + "/Tessellation/Transparent"; case "Hidden/lilToonTessellationOnePassTransparent": return "Hidden/" + shaderName + "/Tessellation/OnePassTransparent"; case "Hidden/lilToonTessellationTwoPassTransparent": return "Hidden/" + shaderName + "/Tessellation/TwoPassTransparent"; case "Hidden/lilToonTessellationOutline": return "Hidden/" + shaderName + "/Tessellation/OpaqueOutline"; case "Hidden/lilToonTessellationCutoutOutline": return "Hidden/" + shaderName + "/Tessellation/CutoutOutline"; case "Hidden/lilToonTessellationTransparentOutline": return "Hidden/" + shaderName + "/Tessellation/TransparentOutline"; case "Hidden/lilToonTessellationOnePassTransparentOutline": return "Hidden/" + shaderName + "/Tessellation/OnePassTransparentOutline"; case "Hidden/lilToonTessellationTwoPassTransparentOutline": return "Hidden/" + shaderName + "/Tessellation/TwoPassTransparentOutline"; case "Hidden/lilToonLite": return shaderName + "/lilToonLite"; case "Hidden/lilToonLiteCutout": return "Hidden/" + shaderName + "/Lite/Cutout"; case "Hidden/lilToonLiteTransparent": return "Hidden/" + shaderName + "/Lite/Transparent"; case "Hidden/lilToonLiteOnePassTransparent": return "Hidden/" + shaderName + "/Lite/OnePassTransparent"; case "Hidden/lilToonLiteTwoPassTransparent": return "Hidden/" + shaderName + "/Lite/TwoPassTransparent"; case "Hidden/lilToonLiteOutline": return "Hidden/" + shaderName + "/Lite/OpaqueOutline"; case "Hidden/lilToonLiteCutoutOutline": return "Hidden/" + shaderName + "/Lite/CutoutOutline"; case "Hidden/lilToonLiteTransparentOutline": return "Hidden/" + shaderName + "/Lite/TransparentOutline"; case "Hidden/lilToonLiteOnePassTransparentOutline": return "Hidden/" + shaderName + "/Lite/OnePassTransparentOutline"; case "Hidden/lilToonLiteTwoPassTransparentOutline": return "Hidden/" + shaderName + "/Lite/TwoPassTransparentOutline"; case "Hidden/lilToonRefraction": return "Hidden/" + shaderName + "/Refraction"; case "Hidden/lilToonRefractionBlur": return "Hidden/" + shaderName + "/RefractionBlur"; case "Hidden/lilToonFur": return "Hidden/" + shaderName + "/Fur"; case "Hidden/lilToonFurCutout": return "Hidden/" + shaderName + "/FurCutout"; case "Hidden/lilToonFurTwoPass": return "Hidden/" + shaderName + "/FurTwoPass"; case "_lil/[Optional] lilToonFurOnly": return shaderName + "/[Optional] FurOnly/Transparent"; case "_lil/[Optional] lilToonFurOnlyCutout": return shaderName + "/[Optional] FurOnly/Cutout"; case "_lil/[Optional] lilToonFurOnlyTwoPass": return shaderName + "/[Optional] FurOnly/TwoPass"; case "Hidden/lilToonGem": return "Hidden/" + shaderName + "/Gem"; case "_lil/lilToonFakeShadow": return shaderName + "/[Optional] FakeShadow"; case "_lil/[Optional] lilToonOverlay": return shaderName + "/[Optional] Overlay"; case "_lil/[Optional] lilToonOverlayOnePass": return shaderName + "/[Optional] OverlayOnePass"; case "_lil/[Optional] lilToonLiteOverlay": return shaderName + "/[Optional] LiteOverlay"; case "_lil/[Optional] lilToonLiteOverlayOnePass": return shaderName + "/[Optional] LiteOverlayOnePass"; case "_lil/lilToonMulti": return shaderName + "/lilToonMulti"; case "Hidden/lilToonMultiOutline": return "Hidden/" + shaderName + "/MultiOutline"; case "Hidden/lilToonMultiRefraction": return "Hidden/" + shaderName + "/MultiRefraction"; case "Hidden/lilToonMultiFur": return "Hidden/" + shaderName + "/MultiFur"; case "Hidden/lilToonMultiGem": return "Hidden/" + shaderName + "/MultiGem"; default: return null; } } /// <summary> /// Get a original lilToon shader which is corresponding to specified custom lilToon shader. /// </summary> /// <param name="customShader">Custom lilToon shader.</param> /// <returns>null if no original lilToon shader is found, otherwise the one found.</returns> private static Shader GetCorrespondingOriginalShader(Shader customShader) { var customShaderName = GetCorrespondingOriginalShaderName(customShader.name); return customShaderName == null ? null : Shader.Find(customShaderName); } /// <summary> /// Get a original lilToon shader name which is corresponding to specified custom lilToon shader name. /// </summary> /// <param name="customShaderName">Custom lilToon shader name.</param> /// <returns>null if no original lilToon shader name is found, otherwise the one found.</returns> private static string GetCorrespondingOriginalShaderName(string customShaderName) { switch (customShaderName) { case shaderName + "/lilToon": return "lilToon"; case "Hidden/" + shaderName + "/Cutout": return "Hidden/lilToonCutout"; case "Hidden/" + shaderName + "/Transparent": return "Hidden/lilToonTransparent"; case "Hidden/" + shaderName + "/OnePassTransparent": return "Hidden/lilToonOnePassTransparent"; case "Hidden/" + shaderName + "/TwoPassTransparent": return "Hidden/lilToonTwoPassTransparent"; case "Hidden/" + shaderName + "/OpaqueOutline": return "Hidden/lilToonOutline"; case "Hidden/" + shaderName + "/CutoutOutline": return "Hidden/lilToonCutoutOutline"; case "Hidden/" + shaderName + "/TransparentOutline": return "Hidden/lilToonTransparentOutline"; case "Hidden/" + shaderName + "/OnePassTransparentOutline": return "Hidden/lilToonOnePassTransparentOutline"; case "Hidden/" + shaderName + "/TwoPassTransparentOutline": return "Hidden/lilToonTwoPassTransparentOutline"; case shaderName + "/[Optional] OutlineOnly/Opaque": return "_lil/[Optional] lilToonOutlineOnly"; case shaderName + "/[Optional] OutlineOnly/Cutout": return "_lil/[Optional] lilToonCutoutOutlineOnly"; case shaderName + "/[Optional] OutlineOnly/Transparent": return "_lil/[Optional] lilToonTransparentOutlineOnly"; case "Hidden/" + shaderName + "/Tessellation/Opaque": return "Hidden/lilToonTessellation"; case "Hidden/" + shaderName + "/Tessellation/Cutout": return "Hidden/lilToonTessellationCutout"; case "Hidden/" + shaderName + "/Tessellation/Transparent": return "Hidden/lilToonTessellationTransparent"; case "Hidden/" + shaderName + "/Tessellation/OnePassTransparent": return "Hidden/lilToonTessellationOnePassTransparent"; case "Hidden/" + shaderName + "/Tessellation/TwoPassTransparent": return "Hidden/lilToonTessellationTwoPassTransparent"; case "Hidden/" + shaderName + "/Tessellation/OpaqueOutline": return "Hidden/lilToonTessellationOutline"; case "Hidden/" + shaderName + "/Tessellation/CutoutOutline": return "Hidden/lilToonTessellationCutoutOutline"; case "Hidden/" + shaderName + "/Tessellation/TransparentOutline": return "Hidden/lilToonTessellationTransparentOutline"; case "Hidden/" + shaderName + "/Tessellation/OnePassTransparentOutline": return "Hidden/lilToonTessellationOnePassTransparentOutline"; case "Hidden/" + shaderName + "/Tessellation/TwoPassTransparentOutline": return "Hidden/lilToonTessellationTwoPassTransparentOutline"; case shaderName + "/lilToonLite": return "Hidden/lilToonLite"; case "Hidden/" + shaderName + "/Lite/Cutout": return "Hidden/lilToonLiteCutout"; case "Hidden/" + shaderName + "/Lite/Transparent": return "Hidden/lilToonLiteTransparent"; case "Hidden/" + shaderName + "/Lite/OnePassTransparent": return "Hidden/lilToonLiteOnePassTransparent"; case "Hidden/" + shaderName + "/Lite/TwoPassTransparent": return "Hidden/lilToonLiteTwoPassTransparent"; case "Hidden/" + shaderName + "/Lite/OpaqueOutline": return "Hidden/lilToonLiteOutline"; case "Hidden/" + shaderName + "/Lite/CutoutOutline": return "Hidden/lilToonLiteCutoutOutline"; case "Hidden/" + shaderName + "/Lite/TransparentOutline": return "Hidden/lilToonLiteTransparentOutline"; case "Hidden/" + shaderName + "/Lite/OnePassTransparentOutline": return "Hidden/lilToonLiteOnePassTransparentOutline"; case "Hidden/" + shaderName + "/Lite/TwoPassTransparentOutline": return "Hidden/lilToonLiteTwoPassTransparentOutline"; case "Hidden/" + shaderName + "/Refraction": return "Hidden/lilToonRefraction"; case "Hidden/" + shaderName + "/RefractionBlur": return "Hidden/lilToonRefractionBlur"; case "Hidden/" + shaderName + "/Fur": return "Hidden/lilToonFur"; case "Hidden/" + shaderName + "/FurCutout": return "Hidden/lilToonFurCutout"; case "Hidden/" + shaderName + "/FurTwoPass": return "Hidden/lilToonFurTwoPass"; case shaderName + "/[Optional] FurOnly/Transparent": return "_lil/[Optional] lilToonFurOnly"; case shaderName + "/[Optional] FurOnly/Cutout": return "_lil/[Optional] lilToonFurOnlyCutout"; case shaderName + "/[Optional] FurOnly/TwoPass": return "_lil/[Optional] lilToonFurOnlyTwoPass"; case "Hidden/" + shaderName + "/Gem": return "Hidden/lilToonGem"; case shaderName + "/[Optional] FakeShadow": return "_lil/lilToonFakeShadow"; case shaderName + "/[Optional] Overlay": return "_lil/[Optional] lilToonOverlay"; case shaderName + "/[Optional] OverlayOnePass": return "_lil/[Optional] lilToonOverlayOnePass"; case shaderName + "/[Optional] LiteOverlay": return "_lil/[Optional] lilToonLiteOverlay"; case shaderName + "/[Optional] LiteOverlayOnePass": return "_lil/[Optional] lilToonLiteOverlayOnePass"; case shaderName + "/lilToonMulti": return "_lil/lilToonMulti"; case "Hidden/" + shaderName + "/MultiOutline": return "Hidden/lilToonMultiOutline"; case "Hidden/" + shaderName + "/MultiRefraction": return "Hidden/lilToonMultiRefraction"; case "Hidden/" + shaderName + "/MultiFur": return "Hidden/lilToonMultiFur"; case "Hidden/" + shaderName + "/MultiGem": return "Hidden/lilToonMultiGem"; default: return null; } } /// <summary> /// Provides some native methods of SQLite3. /// </summary> internal static class NativeMethods { #if UNITY_EDITOR && !UNITY_EDITOR_WIN /// <summary> /// Native library name of SQLite3. /// </summary> private const string LibraryName = "sqlite3"; /// <summary> /// Calling convention of library functions. /// </summary> private const CallingConvention CallConv = CallingConvention.Cdecl; #else /// <summary> /// Native library name of SQLite3. /// </summary> private const string LibraryName = "winsqlite3"; /// <summary> /// Calling convention of library functions. /// </summary> private const CallingConvention CallConv = CallingConvention.StdCall; #endif /// <summary> /// Open database. /// </summary> /// <param name="filePath">SQLite3 database file path.</param> /// <param name="db">SQLite db handle.</param> /// <returns>Result code.</returns> /// <remarks> /// <seealso href="https://www.sqlite.org/c3ref/open.html"/> /// </remarks> [DllImport(LibraryName, EntryPoint = "sqlite3_open", CallingConvention = CallConv)] public static extern int Open(string filename, out IntPtr dbHandle); /// <summary> /// Close database. /// </summary> /// <param name="filePath">Database filename.</param> /// <param name="db">SQLite db handle.</param> /// <returns>Result code.</returns> /// <remarks> /// <seealso href="https://www.sqlite.org/c3ref/close.html"/> /// </remarks> [DllImport(LibraryName, EntryPoint = "sqlite3_close", CallingConvention = CallConv)] public static extern int Close(IntPtr db); /// <summary> /// Execute specified SQL. /// </summary> /// <param name="db">An open database.</param> /// <param name="sql">SQL to be evaluated.</param> /// <param name="callback">Callback function.</param> /// <param name="callbackArg">1st argument to callback.</param> /// <param name="pErrMsg">Error msg written here.</param> /// <returns>Result code.</returns> /// <remarks> /// <seealso href="https://www.sqlite.org/c3ref/exec.html"/> /// </remarks> [DllImport(LibraryName, EntryPoint = "sqlite3_exec", CallingConvention = CallConv)] public static extern int Execute(IntPtr dbHandle, string sql, IntPtr callback, IntPtr callbackArg, IntPtr pErrMsg); } } } #endif
作例
- ExEmissionAddon(lilToonカスタムシェーダー) - 求聞持 - BOOTH
- 誰でも触れる!もっちりシェーダー(Motchiri Shader) - 綿飴屋 Wataameya - BOOTH
- 【VRChat】リアル影システム (for Avatar) PCSS For VRC - Harukaの実験室 - BOOTH
- 【無料】カメラ/ミラー内で見え方が変わるカスタムliltoon - tofu研究室 - BOOTH
- 【無料】メッシュの裏を見せない!密着できるうねうねべたべたシェーダー【触手】 - yueのbooth - BOOTH
- Xユーザーのkb10uy🎮さん: 「lilToon_MsdfMask、アルファマスク 2nd の機能を実装した
- koturn/LilKustomShaders