前置き
本記事ではlilToonのカスタムシェーダー(あるいはlilToon拡張)の作成にあたってのTipsを紹介する.
ただし,公式ドキュメントに記載されている情報は記載せず,筆者がlilToonのカスタムシェーダーを作成する中で得た知見を記載する.
基本的なカスタムシェーダーの情報は下記公式ドキュメントを参照すること.
ドキュメント中にある下記のファイルがテンプレートであり,公式ドキュメント同様,この中の数ファイルを編集する形で述べていく.
なお,本記事で紹介する事項を盛り込んだテンプレートジェネレータを作成している.
※lilToon本体に更新があれば,本記事中のリンクも最新のソースへのリンクへと更新している(現在 2.3.4 へのリンク).
そもそもlilToonのカスタムシェーダーとは
lilToonのカスタムシェーダーとは,lilToon本体のシェーダージェネレーターを利用して,lilToon本体を変更することなく,非破壊的に処理を追加・既存処理を変更して作成されたlilToon本体とは別の派生シェーダーを指す.
実態としては,ScriptedImporter を用い,拡張子 .lilcontainer のテンプレートファイルからShaderLabソースアセットを動的に生成 している.
本体側ではプリプロセッサマクロによる処理の差し込み可能な箇所があるため,カスタムシェーダー側にてそれらのマクロを定義することにより,処理の追加・既存処理の変更が実現できる.
なお,lilToon本体は ScriptedImporter を用いているわけではなく,拡張子 .lilinternal のテンプレートファイル から /Packages/jp.lilxyzw.liltoon/Shader (/Assets/liltoon/Shader) 配下に,直接Unityプロジェクトのレンダーパイプラインに応じた ShaderLabソースを生成 するようになっている.
カスタムシェーダーの利点としては下記のようなものがあると思う.
自然とBRP, URP, HDRPなどの各種レンダーパイプラインに対応したシェーダーとなる
本体側の処理はそのままにできるため,本体とライティング等の差がなく,見た目の親和性を保てる
特殊な処理を記述していない限り,本体側をバージョンアップしても動作する
反対に本体のバージョンを 1.4.1 あたりに戻したとしても動作する
シェーダー作成者は追加したい処理の一部を記述するだけでよく,簡単に作成できる
ある主のフレームワークというべき機構に則ってシェーダー処理およびインスペクタを書く形になるので,特有の難しさはあるが...
目次
custom.hlsl, custom_insert.hlsl について
知っておくべき文法
カスタムシェーダーはプリプロセッサを用いて,本体側の特定位置へカスタムシェーダーで記述した処理を差し込むことができる.
そのため,プリプロセスについてC/C++の文献等をあたり,基本的な文法は知っておいた方がよい.
プリプロセッサとともによく併用されるのが行末の \ である.
これは次の行もその行にあるように扱う「行継続」の意味を持っている.
換言するならば改行の打ち消しである.
#define ,#if 等のプリプロセッサ指令は1行のみ有効であるため,本来は1行で記述しなければならない.
しかし長い1行というのは視認性が悪い.
行継続を用いることで,視認性のために改行を行いつつも,コンパイラには1行として扱うように指示することができる.
カスタムシェーダーの全体図
カスタムシェーダーは下記のような順で各ファイルのインクルードが行われる.
#include "custom.hlsl"
LIL_FEATURE_XXX マクロの定義
#include "Includes/lil_pipeline_xxx.hlsl"
#include "Includes/lil_common.hlsl"
#include "custom_insert.hlsl"
#include "Includes/lil_pass_xx.hlsl"
custom_insert_post.hlsl ( lilSubShaderInsertPost を記述している場合)
上記からわかる通り, BEFORE_xx, OVERRIDE_xx 系のマクロは必ずしも custom.hlsl で定義する必要はなく, custom_insert.hlsl で定義してもよい.
特に LIL_LITE などのマクロの定義有無で定義を分けたい場合( #if を用いて定義を分けたい場合)は,むしろ custom_insert.hlsl で定義しなくてはならない.
各レンダーパイプライン,パスでどのようなシェーダーとなるかは Assets/lilToon/CustomShaderResources 以下のファイルを参照.
例えば,BRP用のデフォルトのシェーダーは Assets/lilToon/CustomShaderResources/BRP/Default.lilblock を参照するとよい.
また,具体的に生成されるシェーダーは .lilcontainerファイル 選択時に表示される「Export Shader」ボタンから保存 することができる.
保存したシェーダーの内容を眺めると処理順を掴みやすいと思われる.
lilToonカスタムシェーダのExport
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 >
</ 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.LogErrorFormat("Failed to open Library/ShaderCache.db [{0}]" , result);
return ;
}
try
{
result = NativeMethods.Execute(dbHandle, "DELETE FROM shadererrors" );
if (result != 0 )
{
Debug.LogErrorFormat("SQL failed [{0}]" , result);
return ;
}
}
finally
{
result = NativeMethods.Close(dbHandle);
if (result != 0 )
{
Debug.LogErrorFormat("Failed to close database [{0}]" , result);
}
}
AssetDatabase.ImportAsset("Assets/TemplateFull/Shaders" , ImportAssetOptions.ImportRecursive);
}
< summary >
</ summary >
internal static class NativeMethods
{
#if UNITY_EDITOR && !UNITY_EDITOR_WIN
< summary >
</ summary >
private const string LibraryName = "sqlite3" ;
< summary >
</ summary >
private const CallingConvention CallConv = CallingConvention.Cdecl;
#else
< summary >
</ summary >
private const string LibraryName = "winsqlite3" ;
< summary >
</ summary >
private const CallingConvention CallConv = CallingConvention.StdCall;
#endif
< summary >
</ summary >
< param name ="filePath" > </ param >
< param name ="pDb" > </ param >
< returns > </ returns >
< remarks >
< seealso href ="https://www.sqlite.org/c3ref/open.html" />
</ remarks >
[DllImport(LibraryName, EntryPoint = "sqlite3_open16" , CallingConvention = CallConv, CharSet = CharSet.Unicode)]
public static extern int Open([In] string filePath, out IntPtr pDb);
< summary >
</ summary >
< param name ="pDb" > </ param >
< returns > </ 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 pDb);
< summary >
</ summary >
< param name ="pDb" > </ param >
< param name ="sql" > </ param >
< param name ="pCallback" > </ param >
< param name ="pCallbackArg" > </ param >
< param name ="pErrMsg" > </ param >
< returns > </ 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 pDb, [In] string sql, IntPtr pCallback = default (IntPtr), IntPtr pCallbackArg = default (IntPtr), IntPtr pErrMsg = default (IntPtr));
}
ただ単にDELETEを実行するだけなので最小限の処理にしている.
真面目にやるのであれば下記のような点も考慮するといい感じになる.
sqlite3_exec() の第5引数でエラーメッセージを受け取る
SQLite3のハンドルは SafeHandle の継承クラスで扱う
戻り値の enum を定義する
まず sqlite3.dll のロードを試みて,見つからなければ winsqlite3.dll のロードを試みる
ユーザにdllを用意してもらうことで使用できるようにする想定
x86を考慮して呼び出し規約の切り分け
winsqlite3.dll なら stdcall
sqlite3.dll なら cdecl
SQLite3 のライブラリが見つからない場合はメニューに追加しない
Reimport対象のアセットパス直書きではなくGUID経由で取得する
Unrecognized sampler 'sampler_maintex' ...というコンパイルエラーが発生する
例えば, OVERRIDE_MAIN マクロを
#define OVERRIDE_MAIN \
fd.col = float4 ( 1.0 , 1.0 , 0.5 , 1.0 );
のように定義したときに,下記のシェーダーコンパイルエラーが発生する.
Fragment program 'frag': Unrecognized sampler 'sampler_maintex' - does not match any texture and is not a recognized inline name (should contain filter and wrap modes).
lilToonでは _MainTex のテクスチャサンプラ sampler_MainTex を使い回すようになっている.
そのため, _MainTex のサンプリングを無くすと,HLSLの最適化処理により sampler_MainTex は削除されてしまい,前述のコンパイルエラーが発生する.
対策1: サンプラを使用していることを明示する
下記のように コンパイル時定数ではない 0を掛けることにより,最適化によるサンプリング処理の除去を阻害することができる.
LIL_TIME は _Time.y ,すなわちワールドに入室してからの経過秒数のマクロ であるが,uniform変数である以上,コンパイラは min(0.0, LIL_TIME) が0になることをコンパイル時に判定できないことを利用している.
#define OVERRIDE_MAIN \
LIL_GET_MAIN_TEX \
fd.col = min ( 0.0 , LIL_TIME) * fd.col + float4 ( 1.0 , 1.0 , 0.5 , 1.0 );
下記のように単純に0.0を掛けると,コンパイル時最適化によりサンプリング部分のコードも除去される.
それによって _MainTex の参照無しとなり, _MainTex のテクスチャサンプラも削除され,コンパイルエラーとなってしまう.
#define OVERRIDE_MAIN \
LIL_GET_MAIN_TEX \
fd.col = 0.0 * fd.col + float4 ( 1.0 , 1.0 , 0.5 , 1.0 );
対策2: インラインサンプラに置換するマクロを定義する
テクスチャサンプラはテクスチャに付随するものの他に,特殊な命名規則で宣言することで,テクスチャとは独立して用意 することができる.
lilToon本体では2種類のインラインサンプラが宣言されている ので,マクロ定義によって sampler_MainTex をこれらのサンプラに置き換える.
ただし,アウトライン用のパスでは同様の置換マクロ定義が行われている ので,アウトライン以外のパスでの置換を試みる必要がある.
具体的には custom_insert.hlsl で下記の置換定義を行う.
#if !defined(LIL_OUTLINE)
# define sampler_MainTex lil_sampler_linear_clamp
#endif
テクスチャフィルタリングモードやテクスチャラップモードを別のものにしたい場合は自前でサンプラを定義すること.
公式ドキュメントに記載 の通り,uniform変数の宣言は LIL_CUSTOM_PROPERTIES と LIL_CUSTOM_TEXTURES で行う.
このとき,知っておくべき文法 で述べたように,マクロの行継続 \ が抜けるとそれ以降の変数はマクロとして展開されないので注意.
誤例1
_CustomVertexWaveSpeed はマクロ定義外となる.
#define LIL_CUSTOM_PROPERTIES \
float4 _CustomVertexWaveScale; \
float4 _CustomVertexWaveStrength;
float _CustomVertexWaveSpeed;
正しくは _CustomVertexWaveStrength の行末に行継続が必要である.
#define LIL_CUSTOM_PROPERTIES \
float4 _CustomVertexWaveScale; \
float4 _CustomVertexWaveStrength; \
float _CustomVertexWaveSpeed;
誤例2
空行を入れようとした例.
_CustomVertexWaveStrength と _CustomVertexWaveSpeed がマクロ定義外となる.
#define LIL_CUSTOM_PROPERTIES \
float4 _CustomVertexWaveScale; \
float4 _CustomVertexWaveStrength; \
float _CustomVertexWaveSpeed;
正しくは空行にも行継続を入れなければならない.
#define LIL_CUSTOM_PROPERTIES \
float4 _CustomVertexWaveScale; \
\
float4 _CustomVertexWaveStrength; \
float _CustomVertexWaveSpeed;
LIL_CUSTOM_PROPERTIES マクロの展開箇所は UnityPerMaterial という名前のConstant Bufferの定義内 である.
これは URP や HDPR 等で利用できる SRP Batcher のためのものである(BRPではSRP Batcherには関係のない機能).
そのため LIL_CUSTOM_PROPERTIES の定義外となったとしても, lil_custom.hlsl 内でuniform変数が定義されるようになるため,コンパイルエラーになるわけではない(なので気がつきにくい).
しかし,
SRP Batcher に対応しておいた方がお行儀が良いこと
1つのConstant Bufferにまとめておいた方がおそらくデータ転送効率がよい
ことから, LIL_CUSTOM_PROPERTIES マクロにunifrom変数定義をまとめておくべきだと思われる.
(Constant Bufferの指定なしのuniform変数は $Global というConstant Bufferに格納される)
頂点シェーダー→フラグメントシェーダーの受け渡しメンバを定義する
メンバの追加は LIL_CUSTOM_V2F_MEMBER を,値の設定処理は LIL_CUSTOM_VERT_COPY を利用する.
#define LIL_CUSTOM_V2F_MEMBER (id0,id1,id2,id3,id4,id5,id6,id7) \
float emissionWavePos : TEXCOORD ## id0;
#define LIL_CUSTOM_VERT_COPY \
output.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となるため,## を用いて字句結合を行う.
頂点シェーダー内での出力構造体変数は output を指定,フラグメントシェーダー内での入力構造体変数は 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 の使用を避けても良いと思う.
#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;
#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
lilToon 1.4.1で直った .
ジオメトリシェーダーを利用する
公式のジオメトリシェーダーを用いた作例 に習い,下記21ファイルに
lilSubShaderInsertPost "lilCustomShaderInsertPost.lilblock"
という記述を加える.
ltsmulti.lilcontainer
ltsmulti_fur.lilcontainer
ltsmulti_gem.lilcontainer
ltsmulti_o.lilcontainer
ltsmulti_ref.lilcontainer
ltspass_cutout.lilcontainer
ltspass_lite_cutout.lilcontainer
ltspass_lite_opaque.lilcontainer
ltspass_lite_transparent.lilcontainer
ltspass_opaque.lilcontainer
ltspass_tess_cutout.lilcontainer
ltspass_tess_opaque.lilcontainer
ltspass_tess_transparent.lilcontainer
ltspass_transparent.lilcontainer
lts_fakeshadow.lilcontainer
lts_fur.lilcontainer
lts_fur_cutout.lilcontainer
lts_fur_two.lilcontainer
lts_gem.lilcontainer
lts_ref.lilcontainer
lts_ref_blur.lilcontainer
lilSubShaderInsertPost という記述があれば,指定ファイルの内容 に置換 されるので,下記2ファイルを用意する.
lilCustomShaderInsertPost.lilblock
#include "custom_insert_post.hlsl"
そして, lilCustomShaderDatas.lilblock に下記の記述を追加する.
Replace "\r\n" "\n"
Replace "\r" "\n"
Replace "// Build Option\n #pragma vertex vert\n #pragma fragment frag\n" "// Build Option\n #pragma vertex vert\n #pragma geometry geomCustom\n #pragma fragment frag\n #pragma require geometry\n"
Replace " #pragma vertex vertTess\n #pragma fragment frag\n #pragma hull hull\n #pragma domain domain\n #pragma require tesshw tessellation\n" " #pragma vertex vertTess\n #pragma fragment frag\n #pragma hull hull\n #pragma domain domain\n #pragma geometry geomCustom\n #pragma require tesshw tessellation geometry\n"
上記の置換定義 により
#pragma vert
#pragma frag
という行が,
#pragma vert
#pragma geomCustom
#pragma frag
となり,
#pragma vertex vertTess
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma require tesshw tessellation
という行が,
#pragma vertex vertTess
#pragma fragment frag
#pragma hull hull
#pragma domain domain
#pragma geomCustom
#pragma require tesshw tessellation
という行に置換 される.
公式の置換定義との差異について
※公式の作例だと下記のようになっている.
Replace " #pragma vertex vert\r\n #pragma fragment frag\r\n" " #pragma vertex vertCustom\r\n #pragma geometry geomCustom\r\n #pragma fragment frag\r\n #pragma require geometry\r\n"
Replace " #pragma vertex vertTess\r\n #pragma fragment frag\r\n #pragma hull hull\r\n #pragma domain domain\r\n #pragma require tesshw tessellation\r\n" " #pragma vertex vertTess\r\n #pragma fragment frag\r\n #pragma hull hull\r\n #pragma domain domainCustom\r\n #pragma geometry geomCustom\r\n #pragma require tesshw tessellation geometry\r\n"
.lilcontainerファイルからshaderlabソースの構築にはStringBufferが用いられて いる.
1行のテキストの追加にはAppendLineメソッド を利用しているため,構築の途中段階ではOSデフォルトの改行コード(WindowsであればCR+LF(\r\n)であり,LinuxであればLF(\n))のシェーダーコード文字列が生成される.
そして,置換の適用は改行コードをCR+LFに正規化する前 であるため,lilToon 2.3.2 以前において,上記の置換定義はWindowsでしかうまく動作しなかった.
しかし, lilToon 2.3.3 で置換定義の改行コードが \r\n, \r, \n のいずれであってもOSのデフォルトの改行コードとして解釈するように修正 したため,Windows以外の環境においてもうまく動作するようになった.
lilToon 2.3.2 以前でこの問題を回避するためには,カスタムシェーダー側の置換定義で
Replace "\r\n" "\n"
Replace "\r" "\n"
を一番初めに記述し,改行コードを \n に正規化しておく(改行コードがCRである環境は今日ではほぼ存在しないが念のため).
そして,他の置換定義における改行コードを \n とし,WindowsでもLinuxでも適切に置換が行われるようにした方がよい.
このような記述をしたとしても, lilToon 2.3.3 以降での置換動作に問題はない.
また,
により, LightMode = "Never" のパスでも置換が行われてしまうようになり,コンパイルエラーが発生するようになった.
ダミーのパスであるため実害はないと思われるが,気持ち悪い.
コンパイルエラーの原因である LightMode = "Never" において置換が行われないようにするために前行の // Build Option というコメントを含めて置換するのがよいと思われる.
GrabPassを利用する
GrabPassを利用するには3種類の方法がある.
最初に各方法を比較した表を置いておく.
手法
Replaceタグ
InsertPassPre
GrabPass入りのlilblockを作成
手軽さ
〇
〇
△
パフォーマンス
×
〇
〇
柔軟性
×
×
〇
Replaceタグ
最も単純な対応方法であるが,推奨しない方法である.
lilCustomShaderDatas.lilblock のReplaceタグを用いることでGrabPassの挿入を行うことが可能である.
どのシェーダーでもForwardBaseが先頭にあり,かつ // Forward というコメントが記載されている.
先頭にGrabPassを挿入するなら下記の定義で可能である.
Replace "\r\n" "\n"
Replace "\r" "\n"
Replace " // Forward\n" " GrabPass {}\n\n // Forward\n"
ただし,置換処理は極めて単純 なものであり,下記のようにバックスラッシュでのエスケープを用いて置換定義内にダブルクオートそのものを含めるようなことはできない.
すなわち,取り込み先のテクスチャ名の指定はできないため,複数回使用する場合のパフォーマンスは悪い.
Replace " // Forward\n" " GrabPass { \"_MyGrabTexture\" }\n\n // Forward\n"
また,lilToon本体側で(未使用ではあるが) _GrabTexture の宣言がされている ため,カスタムシェーダー側での宣言が不要である.
InsertPassPre
lilCustomShaderDatas.lilblock のInsertPassPreタグを用いることで任意の文字列をどのパスよりも先立ってGrabPassを挿入することが可能である.
下記の合計5つのGem系シェーダーとRefraction系シェーダーには既に GrabPass {"_lilBackgroundTexture"} という形でGrabPassが定義されている.
DefaultGem.lilblock
DefaultMultiGem.lilblock
DefaultMultiRefraction.lilblock
DefaultRefraction.lilblock
DefaultRefractionBlur.lilblock
なので,これら5つのシェーダーではGrabPassを定義しないようにしつつ,それ以外のシェーダーでは定義するようにする.
公式サイトに記載されている ように,
指定ファイルの末尾がBRPであればBRP
引数を2つ指定する形式では第一引数が対象とするシェーダーバリエーションの部分文字列
PrePassは後に記述されているものが優先
であるので,これを踏まえて対応を行う.
まず, lilCustomShaderDatas.lilblock では下記を記述する.
InsertPassPre "PrePass.lilblock"
InsertPassPre "Gem" "PrePassNone.lilblock"
InsertPassPre "Refraction" "PrePassNone.lilblock"
GrabPassはBRPでしか使用できないため,URPやHDRPに影響を及ぼさないように,下記の PrePassBRP.lilblock を作成する.
// GrabPass
GrabPass {"_lilBackgroundTexture"}
PrePassNoneBRP.lilblock は空ファイルで作成する.
これで全てのシェーダーバリエーションで _lilBackgroundTexture にレンダリング結果が入る.
lilToonの本体では
_lilBackgroundTexture
_lilBackgroundTexture_TexelSize
の2つが定義されている ため,カスタムシェーダー側での宣言は不要である.
また,
というサンプリング用のマクロも定義されている ため,活用するとよい.
このマクロはURPやHDRP用にも定義されており,使用することでBRP以外のレンダーパイプラインに対応したことになる.
GrabPass入りのlilblockを作成
各シェーダーの大部分のテンプレートは lilSubShaderXXX で指定されているものである.
このテンプレートは本体側に同梱されているが,自作のものを指定することが可能である.
本体側の処理を見てわかるように,指定されているものがDefaultという文字列を含み,かつlilblockという拡張子を含んでいなければ,カスタムシェーダーのShaderディレクトリをベースデイレクトリとして,指定されたファイルを展開する ようになっている.
下記17個のlilblockファイルをlilToon本体の CustomShaderResources/BRP ディレクトリ からカスタムシェーダーの lilCustomShaderDatas.lilblock ファイルがあるディレクトリに,UsePass系列を除いてコピーし,編集を行う.
直下に全てコピーすると散らかるので,本体に習って BRP という名前のサブディレクトリを作成し,その中にまとめるのがオススメである.
ただし,「GrabPass {"_lilBackgroundTexture"} 有無」欄に○が付いた5個のlilblockについては, GrabPass {"_lilBackgroundTexture"} というGrabPassが先頭に記述されている.
他12個でも同名のテクスチャで宣言するのであれば作成は不要である.
No.
lilblockファイル名
GrabPass {"_lilBackgroundTexture"} 有無
1
Default.lilblock
2
DefaultFakeShadow.lilblock
3
DefaultFur.lilblock
4
DefaultFurTwoPass.lilblock
5
DefaultGem.lilblock
○
6
DefaultLite.lilblock
7
DefaultLiteTwoSide.lilblock
8
DefaultMulti.lilblock
9
DefaultMultiFur.lilblock
10
DefaultMultiGem.lilblock
○
11
DefaultMultiOutline.lilblock
12
DefaultMultiRefraction.lilblock
○
13
DefaultRefraction.lilblock
○
14
DefaultRefractionBlur.lilblock
○
15
DefaultTessellation.lilblock
16
DefaultTessellationTwoSide.lilblock
17
DefaultTwoSide.lilblock
次に前述のlilblockファイルを読み込むようにlilcontainerファイルを編集する.
lilSubShaderBRPタグの引数を下記表に記載されているものに変更すること.
「Gem or Refraction」に○が付いているものは,デフォルトで GrabPass {"_lilBackgroundTexture"} が記述されているlilblockが指定されているものである.
No.
lilcontainerファイル名
lilSubShaderBRP変更前
lilSubShaderBRP変更後
Gem or Refraction
1
lts_fakeshadow.lilcontainer
DefaultFakeShadow
BRP/CustomFakeShadow.lilblock
2
lts_fur.lilcontainer
DefaultFur
BRP/CustomFur.lilblock
3
lts_fur_cutout.lilcontainer
DefaultFur
BRP/CustomFur.lilblock
4
lts_fur_two.lilcontainer
DefaultFurTwoPass
BRP/CustomFurTwoPass.lilblock
5
lts_gem.lilcontainer
DefaultGem
BRP/CustomGem.lilblock
○
6
lts_ref.lilcontainer
DefaultRefraction
BRP/CustomRefraction.lilblock
○
7
lts_ref_blur.lilcontainer
DefaultRefractionBlur
BRP/CustomRefractionBlur.lilblock
○
8
ltsmulti.lilcontainer
DefaultMulti
BRP/CustomMulti.lilblock
9
ltsmulti_fur.lilcontainer
DefaultMultiFur
BRP/CustomMultiFur.ilblock
10
ltsmulti_gem.lilcontainer
DefaultMultiGem
BRP/CustomMultiGem.lilblock
○
11
ltsmulti_o.lilcontainer
DefaultMultiOutline
BRP/CustomMultiOutline.lilblock
12
ltsmulti_ref.lilcontainer
DefaultMultiRefraction
BRP/CustomMultiRefraction.lilblock
○
13
ltspass_cutout.lilcontainer
Default
BRP/Custom.lilblock
14
ltspass_lite_cutout.lilcontainer
DefaultLite
BRP/CustomLite.lilblock
15
ltspass_lite_opaque.lilcontainer
DefaultLite
BRP/CustomLite.lilblock
16
ltspass_lite_transparent.lilcontainer
DefaultLiteTwoSide
BRP/CustomLiteTwoSide.lilblock
17
ltspass_opaque.lilcontainer
Default
BRP/Custom.lilblock
18
ltspass_tess_cutout.lilcontainer
DefaultTessellation
BRP/CustomTessellation.lilblock
19
ltspass_tess_opaque.lilcontainer
DefaultTessellation
BRP/CustomTessellation.lilblock
20
ltspass_tess_transparent.lilcontainer
DefaultTessellationTwoSide
BRP/CustomTessellationTwoSide.lilblock
21
ltspass_transparent.lilcontainer
DefaultTwoSide
BRP/CustomTwoSide.lilblock
エミッションの実装
lilFargData には emissionColor というメンバがあり,発光の色と記載されている.
本体バージョン1.3.6以前では,各処理工程で fd.emissionColor にエミッション色を足し込んでおくことで,最後に fd.col に fd.emissionColor が加算 される仕組みとなっていた.
しかし,このメンバは本体バージョン1.3.7以降では本体側の処理では使用されない メンバとなった.
( fd.emissionColor に値が足し込まれてはいるが,使用されない)
1.3.7以降で単純な加算合成のエミッションを実現するためには, fd.color にエミッション色を足し込む実装にしなければならない.
fd.col.rgb += yourEmissionColor.rgb ;
本体と同様に,4種類:
Normal(通常)
Add(加算)
Screen(スクリーン)
Multiply(乗算)
のエミッションを実現する場合は,本体側の実装と同様に lilBlendColor() を用いるとよい.
float yourEmissionBlend = _YourEmissionBlend * yourEmissionColor.a ;
#if LIL_RENDER == 2 && !defined(LIL_REFRACTION)
yourEmissionBlend *= fd.col.a ;
#endif
fd.col.rgb = lilBlendColor (fd.col.rgb , yourEmissionColor.rgb , yourEmissionBlend, _YourEmissionBlendMode);
本体側のバージョンに応じてカスタムシェーダーの条件コンパイルを行う
前述の特定のバージョン向けのワークアラウンド等で本体のバージョンに応じてカスタムシェーダーの処理を変更したいことがある.
本体のシェーダーのマクロ定義にはバージョンに関するものがないが,C#側には存在する .
これを利用して,下記のようにしてバージョン値定義マクロを定義したファイルを生成する.
GuidShaderDir はカスタムシェーダーの Shaders ディレクトリのGUIDにすること.
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using lilToon;
namespace lilToon
{
< summary >
</ summary >
internal sealed class Startup : AssetPostprocessor
{
< summary >
</ summary >
private const int DefaultBufferSize = 1024 ;
< summary >
</ summary >
TODO
private const string GuidShaderDir = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ; TODO
#if UNITY_2021_2_OR_NEWER
< summary >
</ summary >
< param name ="importedAssets" > </ param >
< param name ="deletedAssets" > </ param >
< param name ="movedAssets" > </ param >
< param name ="movedFromAssetPaths" > </ param >
< param name ="didDomainReload" > </ param >
< remarks >
< seealso href ="https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetPostprocessor.OnPostprocessAllAssets.html" />
</ remarks >
private static void OnPostprocessAllAssets(string [] importedAssets, string [] deletedAssets, string [] movedAssets, string [] movedFromAssetPaths, bool didDomainReload)
#else
< summary >
</ summary >
< param name ="importedAssets" > </ param >
< param name ="deletedAssets" > </ param >
< param name ="movedAssets" > </ param >
< param name ="movedFromAssetPaths" > </ param >
< remarks >
< seealso href ="https://docs.unity3d.com/2019.4/Documentation/ScriptReference/AssetPostprocessor.OnPostprocessAllAssets.html" />
</ remarks >
private static void OnPostprocessAllAssets(string [] importedAssets, string [] deletedAssets, string [] movedAssets, string [] movedFromAssetPaths)
#endif
{
UpdateVersionDefFile();
}
< summary >
</ summary >
private static void UpdateVersionDefFile()
{
var dstDirPath = AssetDatabase.GUIDToAssetPath(GuidShaderDir);
if (dstDirPath.Length == 0 )
{
Debug.LogWarning("Cannot find file or directory corresponding to GUID: " + GuidShaderDir);
return ;
}
if (! Directory.Exists(dstDirPath))
{
Debug.LogWarningFormat("Directory not found: {0} ({1})" , dstDirPath, GuidShaderDir);
return ;
}
UpdateVersionDefFile(Path.Combine(dstDirPath, "lil_current_version.hlsl" ));
}
< summary >
</ summary >
< param name ="filePath" > </ param >
< param name ="bufferSize" > < see cref ="FileStream" />
< see cref ="MemoryStream" /> </ param >
public static void UpdateVersionDefFile(string filePath, int bufferSize = DefaultBufferSize)
{
using (var ms = new MemoryStream(bufferSize))
{
WriteVersionFileBytes(ms);
var buffer = ms.GetBuffer();
var length = (int )ms.Length;
if (CompareFileBytes(filePath, buffer, 0 , length, bufferSize))
{
return ;
}
using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize, FileOptions.SequentialScan))
{
fs.Write(buffer, 0 , length);
}
Debug.LogFormat("Update {0}" , filePath);
}
}
< summary >
< see cref ="s" />
</ summary >
< param name ="s" > </ param >
< param name ="bufferSize" > < see cref ="StreamWriter" /> </ param >
private static void WriteVersionFileBytes(Stream s, int bufferSize = DefaultBufferSize)
{
using (var writer = new StreamWriter(s, Encoding.ASCII, bufferSize, true )
{
NewLine = " \n "
})
{
writer.WriteLine("#ifndef LIL_CURRENT_VERSION_INCLUDED" );
writer.WriteLine("#define LIL_CURRENT_VERSION_INCLUDED" );
writer.WriteLine();
writer.WriteLine("#define LIL_CURRENT_VERSION_VALUE {0}" , lilConstants.currentVersionValue);
var match = new Regex(@"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" ).Match(lilConstants.currentVersionName);
if (match.Success)
{
var groups = match.Groups;
writer.WriteLine("#define LIL_CURRENT_VERSION_MAJOR {0}" , groups[1 ].Value);
writer.WriteLine("#define LIL_CURRENT_VERSION_MINOR {0}" , groups[2 ].Value);
writer.WriteLine("#define LIL_CURRENT_VERSION_PATCH {0}" , groups[3 ].Value);
}
writer.WriteLine();
writer.WriteLine("#endif // LIL_CURRENT_VERSION_INCLUDED" );
}
}
< summary >
</ summary >
< param name ="filePath" > </ param >
< param name ="contentData" > </ param >
< param name ="offset" > < paramref name ="contentData" /> </ param >
< param name ="count" > < paramref name ="contentData" /> </ param >
< param name ="bufferSize" > < see cref ="FileStream" /> </ param >
< returns > < see cref ="contentData" /> </ returns >
private static bool CompareFileBytes(string filePath, byte [] contentData, int offset, int count, int bufferSize = DefaultBufferSize)
{
if (! File.Exists(filePath))
{
return false ;
}
if (new FileInfo(filePath).Length != count)
{
return false ;
}
var minBufferSize = Math.Min(count, bufferSize);
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, minBufferSize, FileOptions.SequentialScan))
{
var buffer = new byte[minBufferSize];
int nRead;
while ((nRead = fs.Read(buffer, 0 , buffer.Length)) > 0 )
{
if (! CompareMemory(buffer, 0 , contentData, offset, nRead))
{
return false ;
}
offset += nRead;
}
}
return true ;
}
< summary >
</ summary >
< param name ="data1" > </ param >
< param name ="offset1" > </ param >
< param name ="data2" > </ param >
< param name ="offset2" > </ param >
< param name ="count" > < paramref name ="data1" /> < paramref name ="data2" /> </ param >
< returns > </ returns >
private static bool CompareMemory(byte [] data1, int offset1, byte [] data2, int offset2, int count)
{
for (int i = 0 ; i < count; i++ )
{
if (data1[offset1 + i] != data2[offset2 + i])
{
return false ;
}
}
return true ;
}
}
}
上記のコードで生成されるのは lil_current_version.hlsl というファイルで,下記のようなたった数行のファイルである.
ファイルサイズが十分に小さいので,NTFSなWindowsにおいてMFTに格納されるため,ディスクサイズとしてもやさしい.
#ifndef LIL_CURRENT_VERSION_INCLUDED
#define LIL_CURRENT_VERSION_INCLUDED
#define LIL_CURRENT_VERSION_VALUE 45
#define LIL_CURRENT_VERSION_MAJOR 2
#define LIL_CURRENT_VERSION_MINOR 3
#define LIL_CURRENT_VERSION_PATCH 4
#endif
lil_current_version.hlsl を custom.hlsl の先頭でインクルードする.
#include "lil_current_version.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
バージョン文字列(currentVersionName)とバージョン値(currentVersionValue, 上記の例だと45)の対応関係は以下の表の通り.
これは Assets/lilToon/Editor/lilConstants.cs の変更履歴を見ればわかる.
ただし,バージョン文字列lilToon 2.1.1 より package.json のバージョンを参照するようになって いる.
シェーダーキーワードを使用する
下記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ファイルの代わりに下記1ファイルにのみキーワードの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 >
</ summary >
private List<string > _shaderKeywords = new List<string >();
< summary >
</ summary >
< param name ="me" > < see cref ="MaterialEditor" /> < see cref ="OnGUI(MaterialEditor, MaterialProperty[])" /> </ param >
< param name ="mps" > </ param >
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
{
base .OnGUI(materialEditor, props);
var material = (Material)materialEditor.target;
foreach (var keyword in _shaderKeywords)
{
material.EnableKeyword(keyword);
}
_shaderKeywords.Clear();
}
< summary >
</ summary >
< param name ="material" > </ param >
protected override void DrawCustomProperties(Material material)
{
_shaderKeywords.Add($"_TOGGLEPROP_ { (prop.floatValue >= 0.5f ? "ON" : "OFF" )} " );
}
lilToonMultiで使用されるキーワード を参考に,雑にやるなら下記のようにしてもよいと思う.
< summary >
</ summary >
private List<string > _shaderKeywords = new List<string >();
< summary >
</ summary >
< param name ="me" > < see cref ="MaterialEditor" /> < see cref ="OnGUI(MaterialEditor, MaterialProperty[])" /> </ param >
< param name ="mps" > </ param >
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
{
base .OnGUI(materialEditor, props);
var material = (Material)materialEditor.target;
foreach (var keyword in _shaderKeywords)
{
material.EnableKeyword(keyword);
}
_shaderKeywords.Clear();
}
< summary >
</ summary >
< param name ="material" > </ param >
protected override void DrawCustomProperties(Material material)
{
foreach (var keyword in material.shaderKeywords)
{
if (! IsMultiKeyword(keyword))
{
_shaderKeywords.Add(keyword);
}
}
}
< summary >
</ summary >
< param name ="keyword" > </ param >
< returns > </ 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 内で下記のように宣言すること.
#if !defined(LIL_FEATURE_AUDIOLINK) && !defined(AUDIOLINK_CGINC_INCLUDED)
TEXTURE2D_FLOAT (_AudioTexture);
float4 _AudioTexture_TexelSize;
#endif
uniform変数の宣言は custom.hlsl 内の LIL_CUSTOM_PROPERTIES や LIL_CUSTOM_TEXTURES マクロで行うべきと思うかもしれないが, custom.hlsl の段階では LIL_FEATURE_AUDIOLINK マクロが定義されていないため不可能である.
下記9つの変数はVRChat側から提供されているuniform変数 であり,どのワールドでも使用することができる.
(最初は3つだけだった ので,今後さらにuniform変数が追加される可能性もある)
_VRChatCameraMode
_VRChatCameraMask
_VRChatMirrorMode
_VRChatFaceMirrorMode
_VRChatMirrorCameraPos
_VRChatScreenCameraPos
_VRChatScreenCameraRot
_VRChatPhotoCameraPos
_VRChatPhotoCameraRot
_VRChatTimeUTCUnixSeconds
_VRChatTimeNetworkMs
_VRChatTimeEncoded1
_VRChatTimeEncoded2
lilToon 2.3.4 現在ではいずれも本体側シェーダーで宣言されていないため,custom.hlsl の LIL_CUSTOM_PROPERTIES を用いて宣言して問題ない.
#define LIL_CUSTOM_PROPERTIES \
float _VRChatCameraMode; \
uint _VRChatCameraMask; \
float _VRChatMirrorMode; \
float _VRChatFaceMirrorMode; \
float3 _VRChatMirrorCameraPos; \
float3 _VRChatScreenCameraPos; \
float4 _VRChatScreenCameraRot; \
float3 _VRChatPhotoCameraPos; \
float4 _VRChatPhotoCameraRot; \
uint _VRChatTimeUTCUnixSeconds; \
uint _VRChatTimeNetworkMs; \
uint _VRChatTimeEncoded1; \
uint _VRChatTimeEncoded2;
なお,VRChat用の変数を用いるということは,VRChat用のシェーダーである,すなわちBRP用シェーダーであることを意味するため,どのConstant Bufferに含まれるかはあまり意識しなくてよい.
そのため, LIL_CUSTOM_PROPERTIES マクロ外でuniform変数を宣言しても問題ない.
今後,もし本体側で宣言された場合,
featureマクロが定義されるならAudioLinkと同じ対応
どんな場合でも定義されるならバージョン判定で定義するか否かの対応
を取るとよい.
本体のissueにミラー表示制御を実装してほしいという要望がある ため,将来的に追加される可能性がある.
シェーダー側でmultiシェーダーかどうかを判定する
マクロ LIL_MULTI が定義されているかどうかを調べる.
ただし,このマクロは custom.hlsl の段階では定義されておらず, custom_insert.hlsl の段階でないと使用できないことに注意.
#ifdef LIL_MULTI
#endif
非multiとmulti版である程度コードを共有する
非multi版でif文を用い,multi版で条件コンパイルを用いると,同じコードを2度書くことになる.
lilCustomShaderProperties.lilblock
[Toggle] _ToggleProp ("Toggle Property" , Int) = 0
[KeywordEnum (Foo, Bar, Baz)] _KeywordEnumProp ("Keyword enum property" , Int) = 0
#define LIL_CUSTOM_PROPERTIES \
bool _ToggleProp; \
int _KeywordEnumProp;
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変数を定義しない,ということは諦める.
#ifdef LIL_MULTI
# ifdef _TOGGLEPROP_ON
# define _ToggleProp true
# else
# define _ToggleProp false
# endif
# if defined(_KEYWORDENUMPROP_FOO)
# define _KeywordEnumProp 0
# elif defined(_KEYWORDENUMPROP_BAR)
# define _KeywordEnumProp 1
# elif defined(_KEYWORDENUMPROP_BAZ)
# define _KeywordEnumProp 2
# endif
#endif
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 マクロの呼び出しを入れておくことで,コンパイラの最適化処理(※)により,頂点シェーダーの先頭あたりに上記のコードを記述したのと同一のコードが生成される.
すなわち,より早い段階でearly returnを行うことができる.
LIL_V2F_OUT_BASE.positionCS は float4 であるが, 0.0 / 0.0 は全要素 0.0 / 0.0 の float4 に暗黙的に変換されるのを利用している.
後続の処理は不要なので,returnしておく.
※下記のコードにおいて,(1) ~ (2), (2) ~ (3)間に x を参照する処理がなければ,(2)の代入処理自体は削除される.これと似た最適化処理を期待.
float x = 0.0 ;
x = 1.0 ;
x = 0.0 ;
参考: LIL_INITIALIZE_STRUCT なし版のDXD11アセンブリコード(クリックで開く 閉じる ).
Keywords: DIRECTIONAL
-- Hardware tier variant: Tier 1
-- Vertex shader for "d3d11":
// Stats: 207 math, 13 temp registers, 27 branches
// -- 略 --
0: if_nz cb0[168].w
1: mov o0.xyzw, l(0,0,0,0)
2: mov o1.xyzw, l(0,0,0,0)
3: mov o2.xyzw, l(0,0,0,0)
4: mov o3.xyzw, l(0,0,0,0)
5: mov o4.xyzw, l(0,0,0,0)
6: mov o6.xyzw, l(0,0,0,0)
7: mov o5.xyz, l(0,0,0,0)
8: mov o7.xyz, l(0,0,0,0)
9: mov o8.xyz, l(0,0,0,0)
10: mov o9.xyz, l(0,0,0,0)
11: ret
12: endif
13: ine r0.x, cb0[183].y, l(0)
14: ine r0.y, cb0[184].z, l(0)
15: and r0.x, r0.y, r0.x
16: if_nz r0.x
17: ieq r0.xy, cb0[167].zzzz, l(1, 2, 0, 0)
18: add r1.xyz, v0.xyzx, -cb0[99].xyzx
19: dp3 r0.z, r1.xyzx, r1.xyzx
20: sqrt r0.z, r0.z
21: mad r0.z, r0.z, cb0[98].x, cb0[98].y
22: movc r0.x, r0.x, r0.z, cb0[98].y
23: sincos r1.x, r2.x, cb0[98].z
24: add r0.zw, v1.xxxy, l(0.000000, 0.000000, -0.500000, -0.500000)
25: mul r0.w, r1.x, r0.w
26: mad r0.z, r0.z, r2.x, -r0.w
27: add r0.z, r0.z, l(0.500000)
28: mad r0.z, r0.z, cb0[98].x, cb0[98].y
29: movc r0.x, r0.y, r0.z, r0.x
30: if_nz cb0[184].w
31: mul r0.z, cb0[101].x, -cb1[0].y
32: mul r0.z, r0.z, cb0[101].y
33: mul r0.z, r0.z, l(0.016667)
34: frc r0.z, r0.z
35: add r0.z, r0.z, cb0[101].z
36: add r1.x, r0.z, r0.x
37: mov r1.y, cb0[98].w
38: sample_l r1.xyzw, r1.xyxx, t0.xyzw, s0, l(0.000000)
39: else
40: resinfo r2.xyzw, l(0), t1.xyzw
41: ftoi r0.z, r2.x
42: ilt r0.z, l(16), r0.z
43: if_nz r0.z
44: mul r0.y, cb0[98].w, l(0.062500)
45: sample_l r1.xyzw, r0.xyxx, t1.xyzw, s1, l(0.000000)
46: mov_sat r1.x, r1.x
47: else
48: mad r0.x, cb1[0].y, cb0[95].z, -r0.x
49: frc r0.x, r0.x
50: add_sat r0.x, r0.x, cb0[95].w
51: mul r0.x, r0.x, cb0[95].y
52: mad_sat r1.x, -r0.x, cb0[95].x, cb0[95].x
53: endif
54: endif
55: mad r0.xyz, v9.xyzx, cb0[100].wwww, cb0[100].xyzx
56: mad r0.xyz, r0.xyzx, r1.xxxx, v0.xyzx
57: else
58: mov r0.xyz, v0.xyzx
59: endif
60: mul r1.xyz, r0.yyyy, cb3[1].xyzx
61: mad r1.xyz, cb3[0].xyzx, r0.xxxx, r1.xyzx
62: mad r1.xyz, cb3[2].xyzx, r0.zzzz, r1.xyzx
63: add r1.xyz, r1.xyzx, cb3[3].xyzx
64: dp3 r2.x, v9.xyzx, cb3[4].xyzx
65: dp3 r2.y, v9.xyzx, cb3[5].xyzx
66: dp3 r2.z, v9.xyzx, cb3[6].xyzx
67: dp3 r1.w, r2.xyzx, r2.xyzx
68: rsq r1.w, r1.w
69: mul r2.xyz, r1.wwww, r2.xyzx
70: mul r3.xyz, v10.yyyy, cb3[1].xyzx
71: mad r3.xyz, cb3[0].xyzx, v10.xxxx, r3.xyzx
72: mad r3.xyz, cb3[2].xyzx, v10.zzzz, r3.xyzx
73: dp3 r1.w, r3.xyzx, r3.xyzx
74: rsq r1.w, r1.w
75: mul r3.xyz, r1.wwww, r3.xyzx
76: ne r1.w, cb0[0].x, l(0.000000)
77: if_nz r1.w
78: mov o0.xyzw, l(0xffc00000,0xffc00000,0xffc00000,0xffc00000)
79: mov o1.xy, v1.xyxx
80: mov o1.zw, v2.xxxy
81: mov o2.xy, v3.xyxx
82: mov o2.zw, v4.xxxy
83: mov o3.xyz, r0.xyzx
84: mov o3.w, l(0)
85: mov o4.xyz, r1.xyzx
86: mov o4.w, l(0)
87: mov o6.w, v10.w
88: mov o6.xyz, r3.xyzx
89: mov o5.xyz, r2.xyzx
90: mov o7.xyz, l(0,0,0,0)
91: mov o8.xyz, l(0,0,0,0)
92: mov o9.xyz, l(0,0,0,0)
93: ret
94: else
95: mul r5.xyzw, r1.yyyy, cb4[18].xyzw
96: mad r5.xyzw, cb4[17].xyzw, r1.xxxx, r5.xyzw
97: mad r5.xyzw, cb4[19].xyzw, r1.zzzz, r5.xyzw
98: add r4.xyzw, r5.zxyw, cb4[20].zxyw
99: endif
参考: LIL_INITIALIZE_STRUCT あり版のDXD11アセンブリコード(クリックで開く 閉じる ).
Keywords: DIRECTIONAL
-- Hardware tier variant: Tier 1
-- Vertex shader for "d3d11":
// Stats: 207 math, 12 temp registers, 27 branches
// -- 略 --
0: if_nz cb0[168].w
1: mov o0.xyzw, l(0,0,0,0)
2: mov o1.xyzw, l(0,0,0,0)
3: mov o2.xyzw, l(0,0,0,0)
4: mov o3.xyzw, l(0,0,0,0)
5: mov o4.xyzw, l(0,0,0,0)
6: mov o6.xyzw, l(0,0,0,0)
7: mov o5.xyz, l(0,0,0,0)
8: mov o7.xyz, l(0,0,0,0)
9: mov o8.xyz, l(0,0,0,0)
10: mov o9.xyz, l(0,0,0,0)
11: ret
12: endif
13: ine r0.x, cb0[183].y, l(0)
14: ine r0.y, cb0[184].z, l(0)
15: and r0.x, r0.y, r0.x
16: if_nz r0.x
17: ieq r0.xy, cb0[167].zzzz, l(1, 2, 0, 0)
18: add r1.xyz, v0.xyzx, -cb0[99].xyzx
19: dp3 r0.z, r1.xyzx, r1.xyzx
20: sqrt r0.z, r0.z
21: mad r0.z, r0.z, cb0[98].x, cb0[98].y
22: movc r0.x, r0.x, r0.z, cb0[98].y
23: sincos r1.x, r2.x, cb0[98].z
24: add r0.zw, v1.xxxy, l(0.000000, 0.000000, -0.500000, -0.500000)
25: mul r0.w, r1.x, r0.w
26: mad r0.z, r0.z, r2.x, -r0.w
27: add r0.z, r0.z, l(0.500000)
28: mad r0.z, r0.z, cb0[98].x, cb0[98].y
29: movc r0.x, r0.y, r0.z, r0.x
30: if_nz cb0[184].w
31: mul r0.z, cb0[101].x, -cb1[0].y
32: mul r0.z, r0.z, cb0[101].y
33: mul r0.z, r0.z, l(0.016667)
34: frc r0.z, r0.z
35: add r0.z, r0.z, cb0[101].z
36: add r1.x, r0.z, r0.x
37: mov r1.y, cb0[98].w
38: sample_l r1.xyzw, r1.xyxx, t0.xyzw, s0, l(0.000000)
39: else
40: resinfo r2.xyzw, l(0), t1.xyzw
41: ftoi r0.z, r2.x
42: ilt r0.z, l(16), r0.z
43: if_nz r0.z
44: mul r0.y, cb0[98].w, l(0.062500)
45: sample_l r1.xyzw, r0.xyxx, t1.xyzw, s1, l(0.000000)
46: mov_sat r1.x, r1.x
47: else
48: mad r0.x, cb1[0].y, cb0[95].z, -r0.x
49: frc r0.x, r0.x
50: add_sat r0.x, r0.x, cb0[95].w
51: mul r0.x, r0.x, cb0[95].y
52: mad_sat r1.x, -r0.x, cb0[95].x, cb0[95].x
53: endif
54: endif
55: mad r0.xyz, v9.xyzx, cb0[100].wwww, cb0[100].xyzx
56: mad r0.xyz, r0.xyzx, r1.xxxx, v0.xyzx
57: else
58: mov r0.xyz, v0.xyzx
59: endif
60: ne r0.w, cb0[0].x, l(0.000000)
61: if_nz r0.w
62: mov o0.xyzw, l(0xffc00000,0xffc00000,0xffc00000,0xffc00000)
63: mov o1.xyzw, l(0,0,0,0)
64: mov o2.xyzw, l(0,0,0,0)
65: mov o3.xyzw, l(0,0,0,0)
66: mov o4.xyzw, l(0,0,0,0)
67: mov o6.xyzw, l(0,0,0,0)
68: mov o5.xyz, l(0,0,0,0)
69: mov o7.xyz, l(0,0,0,0)
70: mov o8.xyz, l(0,0,0,0)
71: mov o9.xyz, l(0,0,0,0)
72: ret
73: else
74: mul r8.xyz, r0.yyyy, cb3[1].xyzx
75: mad r8.xyz, cb3[0].xyzx, r0.xxxx, r8.xyzx
76: mad r8.xyz, cb3[2].xyzx, r0.zzzz, r8.xyzx
77: add r5.xyz, r8.xyzx, cb3[3].xyzx
78: mul r8.xyzw, r5.yyyy, cb4[18].xyzw
79: mad r8.xyzw, cb4[17].xyzw, r5.xxxx, r8.xyzw
80: mad r8.xyzw, cb4[19].xyzw, r5.zzzz, r8.xyzw
81: add r7.xyzw, r8.zxyw, cb4[20].zxyw
82: dp3 r8.x, v9.xyzx, cb3[4].xyzx
83: dp3 r8.y, v9.xyzx, cb3[5].xyzx
84: dp3 r8.z, v9.xyzx, cb3[6].xyzx
85: dp3 r0.w, r8.xyzx, r8.xyzx
86: rsq r0.w, r0.w
87: mul r8.xyz, r0.wwww, r8.xyzx
88: mul r9.xyz, v10.yyyy, cb3[1].xyzx
89: mad r9.xyz, cb3[0].xyzx, v10.xxxx, r9.xyzx
90: mad r9.xyz, cb3[2].xyzx, v10.zzzz, r9.xyzx
91: dp3 r0.w, r9.xyzx, r9.xyzx
92: rsq r0.w, r0.w
93: mul r9.xyz, r0.wwww, r9.xyzx
94: mov r10.xy, v1.xyxx
95: mov r10.zw, v2.xxxy
96: mov r11.xy, v3.xyxx
97: mov r11.zw, v4.xxxy
98: mov r9.w, v10.w
99: mov o1.xyzw, r10.xyzw
100: mov o2.xyzw, r11.xyzw
101: mov o6.xyzw, r9.xyzw
102: mov o3.xyz, r0.xyzx
103: mov o5.xyz, r8.xyzx
104: endif
NaNの警告回避
本体側で4008番の警告を無効化するpragma定義がされている ので不要.
0.0 / 0.0 と書きたくないなら asfloat(0x7fc00000) と書いてもよい.
シェーダー名の制約
シェーダー名に "Multi" が含まれる場合はmulti版のシェーダー
シェーダー名に "Lite" が含まれる場合はlite版のシェーダー
と判定する処理が本体にいくつかあるため,
lilCustomShaderDatas.lilblock
カスタムシェーダーのインスペクタ
に記述するシェーダー名にMultiやLiteを含めると,意図しないエラーが発生することがあるので用いてはならない.
例えば "koturn/lilToonMultiRimLight" という名前にすると,Lite版のシェーダーに切り替えた際にMulti版の処理を通り,特定プロパティが存在しないことにより処理エラーが発生する.
ただし,v2.3.0 で改善した ので,恣意的に誤検知される名前にしない限りはエラーは発生しなくなった.
マテリアルエディタ
キーワードについて
マルチシェーダーでない限りキーワードは削除される ので注意.
マルチシェーダーかどうかの判定
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列目以降が各言語に応じた文言である.
lilToon本体や lilToonGeometryFX では,キー名にsプレフィックスが用いられている.
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 側面を生成 측면 생성 生成侧面 生成側面
TSVとは言っても,CSVにおけるRFC4180のようなデータ自体に改行やセパレータが含まれることを考慮したパースの仕方ではなく,単純に1行読み取ってタブ文字で分割している処理 となっていることに注意.
また,1行目のヘッダ行は単純に読み飛ばしされているため,ヘッダ順序を変えたからといって言語の記述順が変わるわけではない.
先頭フィールドから順に,下記のように解釈される.
キー名
英語名
日本語名
韓国語
中国語(簡体字)
中国語(繁体字)
lilToon 2.1.8 以前は設定している言語に対応するフィールド項目が存在しない場合,IndexOutOfRangeExceptionが発生し,インスペクタが表示できない問題があったが,lilToon 2.1.9で修正 した.
ファイル名は何でもよい(GUIDで参照するため).
作例に習うなら lang_custom.txt .
ただし,拡張子 .tsv にしておくと,GitHubでプレビュー表示したときに表形式で表示される利点がある.
GUIDは lang_custom.txt.meta を参照すること.
C# 側では LoadCustomLanguage() メソッドでファイルを読み込み, GetLoc() メソッドでキーを指定してローカライズされた文言を取得する.
もし,定義されていないキーであった場合.GetLoc() は本体側の言語ファイルのローカライズ結果を返す .
本体側の言語ファイルでも定義されていない場合は,英語 のフォールバック結果 を返そうとするが,基本的にそこで引っ掛かることはなく,結局キー名がそのまま返る.
protected override void LoadCustomProperties(MaterialProperty[] props, Material material)
{
LoadCustomLanguage("a5875813c34e16a49ae1c8e1a846ea75" );
}
protected override void DrawCustomProperties(Material material)
{
var label = GetLoc("sCustomGeometryAnimation" );
}
.poファイル
lilToon本体では2.0.0からTSV形式の言語ファイル が廃止され,.poファイルによる多言語対応 となった.
現状 L10nクラスはinternal であるが,そのうちカスタムシェーダー側で利用可能なように仕組みが整うかもしれない.
言語TSVファイルの読み込み回数を減らす
テンプレートに習って言語TSVファイルの読み込みを行うと, OnGUI() が呼び出される度に言語ファイルの読み込みが行われる.
そこまで気になるほどの動作の重さを感じることはないが,無駄なファイルIOは避けたいものである.
カスタムシェーダーの言語ファイルの読み込みが必要なのは下記3場面である.
カスタムシェーダーの言語ファイルが読み込まれていないとき
言語を切り替えたとき
別のカスタムシェーダーのインスペクタから当該カスタムシェーダーのインスペクタに表示を切り替えたとき
これを実現するためには,
OnGUI() をオーバーライドし,言語ファイルの読み込みが必要であるかどうかを判定
LoadCustomProperties() にて前述の判定結果を基に言語ファイルを読み込み
という手段を取るとよい.
具体的には下記のようなコードにするとよい.
< summary >
< see cref ="ShaderGUI" />
</ summary >
public sealed class LilToonPluralRimLightInspector : lilToonInspector
{
< summary >
</ summary >
public const string ShaderName = "koturn/lilToonPluralRimLight" ;
< summary >
</ summary >
private static bool isShowCustomProperties;
< summary >
</ summary >
private static string prevLanguageName;
< summary >
</ summary >
< param name ="materialEditor" > < see cref ="MaterialEditor" /> < see cref ="OnGUI(MaterialEditor, MaterialProperty[])" /> </ param >
< param name ="props" > </ param >
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
{
_shouldLoadLanguage = lts == null || lts.name != ShaderName + "/lilToon" || prevLanguageName != lilLanguageManager.langSet.languageName;
base .OnGUI(materialEditor, props);
}
< summary >
</ summary >
< param name ="props" > </ param >
< param name ="material" > </ param >
protected override void LoadCustomProperties(MaterialProperty[] props, Material material)
{
isCustomShader = true ;
ReplaceToCustomShaders();
isShowRenderMode = ! material.shader.name.Contains("/[Optional] " );
if (_shouldLoadLanguage)
{
LoadCustomLanguage(AssetGuid.LangCustom);
prevLanguageName = lilLanguageManager.langSet.languageName;
}
}
}
項目の検索に対応する
lilToon本体に備わっている機能として,インスペクタの下記検索窓に項目名やプロパティ名を入力することで,表示項目を絞り込むことができる.
lilToonインスペクタの検索窓
しかし,単純にカスタムシェーダーのインスペクタを作成した場合は,どのようなキーワードを用いても,カスタムシェーダーで追加したプロパティが表示されてしまう.
検索キーワードに応じて描画しないようにするためには, lilEditorGUI.ChecPropertyToDraw() を用いて判定を行う必要がある.
if (lilEditorGUI.CheckPropertyToDraw(_fooProp))
{
m_MaterialEditor.ShaderProperty(_fooProp, "Label for toggle property" );
}
項目の多言語を考慮するなら下記のとおり.
var label = GetLoc("sCustomGeometryAnimation" );
if (lilEditorGUI.CheckPropertyToDraw(_fooProp) || lilEditorGUI.CheckPropertyToDraw(label))
{
m_MaterialEditor.ShaderProperty(_fooProp, "Label for toggle property" );
}
ただし,これは displayName が言語ファイルのキー名と同一になっていない場合のみ.
lilCustomShaderProperties.lilblock のプロパティ定義でキー名が含まれているなら,最初の1つの判定のみで済む.
_FooProp ("sFooProp" , Int) = 0
また,このように定義している場合は,いちいち GetLoc() と lilEditorGUI.CheckPropertyToDraw() の呼び出しを行う必要はなく, lilEditorGUI.LocalizedProperty() の呼び出しだけで済む.
lilEditorGUI.LocalizedProperty(m_MaterialEditor, _fooProp);
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 System.Reflection;
< summary >
< see cref ="MaterialProperty" />
</ summary >
< param name ="shader" > < see cref ="Shader" /> </ param >
< param name ="prop" > < see cref ="MaterialProperty" /> </ param >
private static void SetToggleKeyword(Shader shader, MaterialProperty prop)
{
SetToggleKeyword(shader, prop, prop.floatValue >= 0.5f );
}
< summary >
< see cref ="MaterialProperty" />
</ summary >
< param name ="shader" > < see cref ="Shader" /> </ param >
< param name ="prop" > < see cref ="MaterialProperty" /> </ param >
private static void SetToggleKeyword(Shader shader, MaterialProperty prop, bool isOn)
{
var asm = Assembly.GetAssembly(typeof (UnityEditor.MaterialPropertyDrawer ));
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" );
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" );
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 System.Linq.Expression;
using System.Reflection;
< summary >
</ summary >
< remarks >< seealso cref ="CreateToggleKeywordDelegate" /></ remarks >
private static Action<Shader, MaterialProperty, bool > _toggleKeyword;
< summary >
< see cref ="MaterialProperty" />
</ summary >
< param name ="shader" > < see cref ="Shader" /> </ param >
< param name ="prop" > < see cref ="MaterialProperty" /> </ param >
private static void SetToggleKeyword(Shader shader, MaterialProperty prop)
{
SetToggleKeyword(shader, prop, prop.floatValue >= 0.5f );
}
< summary >
< see cref ="MaterialProperty" />
</ summary >
< param name ="shader" > < see cref ="Shader" /> </ param >
< param name ="prop" > < see cref ="MaterialProperty" /> </ param >
< param name ="isOn" > </ 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 > </ para >
< code >
=
=
=
</ code >
</ summary >
private static Action<Shader, MaterialProperty, bool > CreateSetKeywordDelegate()
{
var asm = Assembly.GetAssembly(typeof (UnityEditor.MaterialPropertyDrawer ));
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 >
</ summary >
< param name ="material" > < see cref ="Material" /> </ param >
< param name ="prop" > < see cref ="MaterialProperty" /> </ param >
< param name ="label" > </ 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]);
}
}
}
ただし,上記コードは
Undo (Ctrl-Z) が考慮されていない
C# のコードとしてイマイチ
inspector.ConvertMaterialToCustomShader の処理が大袈裟である(lilToon本体とカスタムシェーダーの全てのバリエーションについて Shader.Find() を呼び出す)
Material Variantを除外していない
ので,下記のようにするのがオススメである.
< summary >
</ summary >
[MenuItem("Assets/TemplateFull/Convert material to custom shader" , false , 1100 )]
private static void ConvertMaterialToCustomShaderMenu()
{
foreach (var material in Selection.GetFiltered<Material>(SelectionMode.Assets))
{
#if UNITY_2022_1_OR_NEWER
if (material.parent != null )
{
Debug.LogWarningFormat("Ignore {0} because it is Material Variant" , AssetDatabase.GetAssetPath(material));
continue ;
}
#endif
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 >
</ summary >
< param name ="originalShader" > </ param >
< returns > </ returns >
private static Shader GetCorrespondingCustomShader(Shader originalShader)
{
var customShaderName = GetCorrespondingCustomShaderName(originalShader.name);
return customShaderName == null ? null : Shader.Find(customShaderName);
}
< summary >
</ summary >
< param name ="originalShaderName" > </ param >
< returns > </ 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] lilToonOutlineOnlyCutout" : return shaderName + "/[Optional] OutlineOnly/Cutout" ;
case "_lil/[Optional] lilToonOutlineOnlyTransparent" : 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] lilToonFurOnlyTransparent" : 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/[Optional] 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 >
</ summary >
[MenuItem("Assets/TemplateFull/Convert material to original shader" , false , 1101 )]
private static void ConvertMaterialToOriginalShaderMenu()
{
foreach (var material in Selection.GetFiltered<Material>(SelectionMode.Assets))
{
#if UNITY_2022_1_OR_NEWER
if (material.parent != null )
{
Debug.LogWarningFormat("Ignore {0} because it is Material Variant" , AssetDatabase.GetAssetPath(material));
continue ;
}
#endif
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 >
</ summary >
< param name ="customShader" > </ param >
< returns > </ returns >
private static Shader GetCorrespondingOriginalShader(Shader customShader)
{
var customShaderName = GetCorrespondingOriginalShaderName(customShader.name);
return customShaderName == null ? null : Shader.Find(customShaderName);
}
< summary >
</ summary >
< param name ="customShaderName" > </ param >
< returns > </ 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] lilToonOutlineOnlyCutout" ;
case shaderName + "/[Optional] OutlineOnly/Transparent" : return "_lil/[Optional] lilToonOutlineOnlyTransparent" ;
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] lilToonFurOnlyTransparent" ;
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/[Optional] 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のラベルとして使用できる.
オリジナルシェーダーとカスタムシェーダーの対応表
オリジナルシェーダーの名前とカスタムシェーダー名の対応表は下記の通り(横長なので折り畳みにした).
*LIL_SHADER_NAME* は lilCustomShaderDatas.lilblock で指定するシェーダー名である.
(カスタムシェーダーは .lilcontainer を編集すれば自由にシェーダー名は変更可能であるが,テンプレートにならった場合の例を記載した)
シェーダー名対応表(クリックで開く 閉じる ).
No.
lilShaderManager メンバ名
ファイル名
シェーダー名
シェーダー名(カスタム)
1
lts
lts.shader
lilToon
*LIL_SHADER_NAME*/lilToon
2
ltsc
lts_cutout.shader
Hidden/lilToonCutout
Hidden/*LIL_SHADER_NAME*/Cutout
3
ltst
lts_trans.shader
Hidden/lilToonTransparent
Hidden/*LIL_SHADER_NAME*/Transparent
4
ltsot
lts_onetrans.shader
Hidden/lilToonOnePassTransparent
Hidden/*LIL_SHADER_NAME*/OnePassTransparent
5
ltstt
lts_twotrans.shader
Hidden/lilToonTwoPassTransparent
Hidden/*LIL_SHADER_NAME*/TwoPassTransparent
6
ltso
lts_o.shader
Hidden/lilToonOutline
Hidden/*LIL_SHADER_NAME*/OpaqueOutline
7
ltsco
lts_cutout_o.shader
Hidden/lilToonCutoutOutline
Hidden/*LIL_SHADER_NAME*/CutoutOutline
8
ltsto
lts_trans_o.shader
Hidden/lilToonTransparentOutline
Hidden/*LIL_SHADER_NAME*/TransparentOutline
9
ltsoto
lts_onetrans_o.shader
Hidden/lilToonOnePassTransparentOutline
Hidden/*LIL_SHADER_NAME*/OnePassTransparentOutline
10
ltstto
lts_twotrans_o.shader
Hidden/lilToonTwoPassTransparentOutline
Hidden/*LIL_SHADER_NAME*/TwoPassTransparentOutline
11
ltsoo
lts_oo.shader
_lil/[Optional] lilToonOutlineOnly
*LIL_SHADER_NAME*/[Optional] OutlineOnly/Opaque
12
ltscoo
lts_cutout_oo.shader
_lil/[Optional] lilToonOutlineOnlyCutout
*LIL_SHADER_NAME*/[Optional] OutlineOnly/Cutout
13
ltstoo
lts_trans_oo.shader
_lil/[Optional] lilToonOutlineOnlyTransparent
*LIL_SHADER_NAME*/[Optional] OutlineOnly/Transparent
14
ltstess
lts_tess.shader
Hidden/lilToonTessellation
Hidden/*LIL_SHADER_NAME*/Tessellation/Opaque
15
ltstessc
lts_tess_cutout.shader
Hidden/lilToonTessellationCutout
Hidden/*LIL_SHADER_NAME*/Tessellation/Cutout
16
ltstesst
lts_tess_trans.shader
Hidden/lilToonTessellationTransparent
Hidden/*LIL_SHADER_NAME*/Tessellation/Transparent
17
ltstessot
lts_tess_onetrans.shader
Hidden/lilToonTessellationOnePassTransparent
Hidden/*LIL_SHADER_NAME*/Tessellation/OnePassTransparent
18
ltstesstt
lts_tess_twotrans.shader
Hidden/lilToonTessellationTwoPassTransparent
Hidden/*LIL_SHADER_NAME*/Tessellation/TwoPassTransparent
19
ltstesso
lts_tess_o.shader
Hidden/lilToonTessellationOutline
Hidden/*LIL_SHADER_NAME*/Tessellation/OpaqueOutline
20
ltstessco
lts_tess_cutout_o.shader
Hidden/lilToonTessellationCutoutOutline
Hidden/*LIL_SHADER_NAME*/Tessellation/CutoutOutline
21
ltstessto
lts_tess_trans_o.shader
Hidden/lilToonTessellationTransparentOutline
Hidden/*LIL_SHADER_NAME*/Tessellation/TransparentOutline
22
ltstessoto
lts_tess_onetrans_o.shader
Hidden/lilToonTessellationOnePassTransparentOutline
Hidden/*LIL_SHADER_NAME*/Tessellation/OnePassTransparentOutline
23
ltstesstto
lts_tess_twotrans_o.shader
Hidden/lilToonTessellationTwoPassTransparentOutline
Hidden/*LIL_SHADER_NAME*/Tessellation/TwoPassTransparentOutline
24
ltsl
ltsl.shader
Hidden/lilToonLite
*LIL_SHADER_NAME*/lilToonLite
25
ltslc
ltsl_cutout.shader
Hidden/lilToonLiteCutout
Hidden/*LIL_SHADER_NAME*/Lite/Cutout
26
ltslt
ltsl_trans.shader
Hidden/lilToonLiteTransparent
Hidden/*LIL_SHADER_NAME*/Lite/Transparent
27
ltslot
ltsl_onetrans.shader
Hidden/lilToonLiteOnePassTransparent
Hidden/*LIL_SHADER_NAME*/Lite/OnePassTransparent
28
ltsltt
ltsl_twotrans.shader
Hidden/lilToonLiteTwoPassTransparent
Hidden/*LIL_SHADER_NAME*/Lite/TwoPassTransparent
29
ltslo
ltsl_o.shader
Hidden/lilToonLiteOutline
Hidden/*LIL_SHADER_NAME*/Lite/OpaqueOutline
30
ltslco
ltsl_cutout_o.shader
Hidden/lilToonLiteCutoutOutline
Hidden/*LIL_SHADER_NAME*/Lite/CutoutOutline
31
ltslto
ltsl_trans_o.shader
Hidden/lilToonLiteTransparentOutline
Hidden/*LIL_SHADER_NAME*/Lite/TransparentOutline
32
ltsloto
ltsl_onetrans_o.shader
Hidden/lilToonLiteOnePassTransparentOutline
Hidden/*LIL_SHADER_NAME*/Lite/OnePassTransparentOutline
33
ltsltto
ltsl_twotrans_o.shader
Hidden/lilToonLiteTwoPassTransparentOutline
Hidden/*LIL_SHADER_NAME*/Lite/TwoPassTransparentOutline
34
ltsref
lts_ref.shader
Hidden/lilToonRefraction
Hidden/*LIL_SHADER_NAME*/Refraction
35
ltsrefb
lts_ref_blur.shader
Hidden/lilToonRefractionBlur
Hidden/*LIL_SHADER_NAME*/RefractionBlur
36
ltsfur
lts_fur.shader
Hidden/lilToonFur
Hidden/*LIL_SHADER_NAME*/Fur
37
ltsfurc
lts_fur_cutout.shader
Hidden/lilToonFurCutout
Hidden/*LIL_SHADER_NAME*/FurCutout
38
ltsfurtwo
lts_fur_two.shader
Hidden/lilToonFurTwoPass
Hidden/*LIL_SHADER_NAME*/FurTwoPass
39
ltsfuro
lts_furonly.shader
_lil/[Optional] lilToonFurOnlyTransparent
*LIL_SHADER_NAME*/[Optional] FurOnly/Transparent
40
ltsfuroc
lts_furonly_cutout.shader
_lil/[Optional] lilToonFurOnlyCutout
*LIL_SHADER_NAME*/[Optional] FurOnly/Cutout
41
ltsfurotwo
lts_furonly_two.shader
_lil/[Optional] lilToonFurOnlyTwoPass
*LIL_SHADER_NAME*/[Optional] FurOnly/TwoPass
42
ltsgem
lts_gem.shader
Hidden/lilToonGem
Hidden/*LIL_SHADER_NAME*/Gem
43
ltsfs
lts_fakeshadow.shader
_lil/[Optional] lilToonFakeShadow
*LIL_SHADER_NAME*/[Optional] FakeShadow
44
ltsover
lts_overlay.shader
_lil/[Optional] lilToonOverlay
*LIL_SHADER_NAME*/[Optional] Overlay
45
ltsoover
lts_overlay_one.shader
_lil/[Optional] lilToonOverlayOnePass
*LIL_SHADER_NAME*/[Optional] OverlayOnePass
46
ltslover
ltsl_overlay.shader
_lil/[Optional] lilToonLiteOverlay
*LIL_SHADER_NAME*/[Optional] LiteOverlay
47
ltsloover
ltsl_overlay_one.shader
_lil/[Optional] lilToonLiteOverlayOnePass
*LIL_SHADER_NAME*/[Optional] LiteOverlayOnePass
48
ltsm
ltsmulti.shader
_lil/lilToonMulti
*LIL_SHADER_NAME*/lilToonMulti
49
ltsmo
ltsmulti_o.shader
Hidden/lilToonMultiOutline
Hidden/*LIL_SHADER_NAME*/MultiOutline
50
ltsmref
ltsmulti_ref.shader
Hidden/lilToonMultiRefraction
Hidden/*LIL_SHADER_NAME*/MultiRefraction
51
ltsmfur
ltsmulti_fur.shader
Hidden/lilToonMultiFur
Hidden/*LIL_SHADER_NAME*/MultiFur
52
ltsmgem
ltsmulti_gem.shader
Hidden/lilToonMultiGem
Hidden/*LIL_SHADER_NAME*/MultiGem
53
ltsbaker
ltspass_baker.shader
Hidden/ltsother_baker
54
ltspo
ltspass_opaque.shader
Hidden/ltspass_opaque
Hidden/*LIL_SHADER_NAME*/ltspass_opaque
55
ltspc
ltspass_cutout.shader
Hidden/ltspass_cutout
Hidden/*LIL_SHADER_NAME*/ltspass_cutout
56
ltspt
ltspass_transparent.shader
Hidden/ltspass_transparent
Hidden/*LIL_SHADER_NAME*/ltspass_transparent
57
ltsptesso
ltspass_tess_opaque.shader
Hidden/ltspass_tess_opaque
Hidden/*LIL_SHADER_NAME*/ltspass_tess_opaque
58
ltsptessc
ltspass_tess_cutout.shader
Hidden/ltspass_tess_cutout
Hidden/*LIL_SHADER_NAME*/ltspass_tess_cutout
59
ltsptesst
ltspass_tess_transparent.shader
Hidden/ltspass_tess_transparent
Hidden/*LIL_SHADER_NAME*/ltspass_tess_transparent
60
ltspass_bakeramp.shader
Hidden/ltsother_bakeramp
61
ltspass_dummy.shader
Hidden/ltspass_dummy
62
ltspass_lite_cutout.shader
Hidden/ltspass_lite_cutout
Hidden/*LIL_SHADER_NAME*/ltspass_lite_cutout
63
ltspass_lite_opaque.shader
Hidden/ltspass_lite_opaque
Hidden/*LIL_SHADER_NAME*/ltspass_lite_opaque
64
ltspass_lite_transparent.shader
Hidden/ltspass_lite_transparent
Hidden/*LIL_SHADER_NAME*/ltspass_lite_transparent
65
ltspass_proponly.shader
Hidden/ltspass_proponly
その他
本体のソースコードリーディングを楽にする
Vim等のctagsに対応しているエディタを使っている人向け.
lilToon本体のソースコードをGitHubからクローンし,タグファイルを作成しておくと,本体の関数やマクロへの定義ジャンプが可能になり,色々と楽になる.
以下は ~/github/lilToon に本体のソースコードをcloneしたものとしたタグファイル(フルパスのタグファイル)の生成方法である.
$ ctags -f shaderlab.lilToon.tags --languages=c --langmap=c:+.shader,c:+.hlsl -R ~/lilToon/Assets/lilToon/Shader/
$ 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ファイルを用意する必要がある.
別途プラグインを導入して判別できている場合はしなくてよい.
au BufNewFile ,BufRead *.hlsl setfiletype shaderlab
~/.vim/ftdetect/shaderlab.vim
au BufNewFile ,BufRead *.shaderlab setfiletype shaderlab
記事内容全部入りのインスペクタテンプレート
記事内容+自分好みに修正したインスペクタテンプレート.
全部入りのテンプレートのunitypackageは ここから ダウンロードできる.
また,GitHubリポジトリとしても置いてある .
長いので折り畳み(クリックで開く 閉じる ).
using System;
using System.IO;
using System.Runtime.InteropServices;
using UnityEditor;
using UnityEngine;
namespace lilToon
{
< summary >
< see cref ="ShaderGUI" />
</ summary >
public class TemplateAllInspector : lilToonInspector
{
< summary >
</ summary >
TODO
public const string GuidShaderDir = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ;
< summary >
</ summary >
private const string ShaderName = "TemplateAll" ;
< summary >
</ summary >
private static bool isShowCustomProperties;
< summary >
</ summary >
private static string prevLanguageName;
< summary >
</ summary >
< param name ="materialEditor" > < see cref ="MaterialEditor" /> < see cref ="OnGUI(MaterialEditor, MaterialProperty[])" /> </ param >
< param name ="props" > </ param >
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
{
_shouldLoadLanguage = lts == null || lts.name != ShaderName + "/lilToon" || prevLanguageName != lilLanguageManager.langSet.languageName;
base .OnGUI(materialEditor, props);
}
< summary >
</ summary >
< param name ="props" > </ param >
< param name ="material" > </ param >
protected override void LoadCustomProperties(MaterialProperty[] props, Material material)
{
isCustomShader = true ;
ReplaceToCustomShaders();
isShowRenderMode = ! material.shader.name.Contains("/[Optional] " );
if (_shouldLoadLanguage)
{
prevLanguageName = lilLanguageManager.langSet.languageName;
}
}
< summary >
</ summary >
< param name ="material" > </ param >
protected override void DrawCustomProperties(Material material)
{
var titleLoc = GetLoc("sCustomShaderTitle" );
isShowCustomProperties = Foldout(titleLoc, titleLoc, isShowCustomProperties);
if (! isShowCustomProperties)
{
return ;
}
using (new EditorGUILayout.VerticalScope(boxOuter))
{
EditorGUILayout.LabelField(GetLoc("sCustomPropertyCategory" ), customToggleFont);
using (new EditorGUILayout.VerticalScope(boxInnerHalf))
{
}
}
}
< summary >
</ 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 >
</ summary >
[MenuItem("Assets/" + ShaderName + "/Convert material to custom shader" , false , 1100 )]
#pragma warning disable IDE0052
private static void ConvertMaterialToCustomShaderMenu()
#pragma warning restore IDE0052
{
foreach (var material in Selection.GetFiltered<Material>(SelectionMode.Assets))
{
#if UNITY_2022_1_OR_NEWER
if (material.parent != null )
{
Debug.LogWarningFormat("Ignore {0} because it is Material Variant" , AssetDatabase.GetAssetPath(material));
continue ;
}
#endif
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, ShaderName + "/ConvertMaterialToCustomShaderMenu" );
var renderQueue = lilMaterialUtils.GetTrueRenderQueue(material);
material.shader = shader;
material.renderQueue = renderQueue;
}
}
< summary >
< see cref ="ConvertMaterialToCustomShaderMenu" />
</ summary >
< returns > < see cref ="ConvertMaterialToCustomShaderMenu" /> </ returns >
[MenuItem("Assets/" + ShaderName + "/Convert material to custom shader" , true )]
#pragma warning disable IDE0051
private static bool ValidateConvertMaterialToCustomShaderMenu()
#pragma warning restore IDE0051
{
foreach (var material in Selection.GetFiltered<Material>(SelectionMode.Assets))
{
#if UNITY_2022_1_OR_NEWER
if (material.parent != null )
{
Debug.LogWarningFormat("Ignore {0} because it is Material Variant" , AssetDatabase.GetAssetPath(material));
continue ;
}
#endif
if (GetCorrespondingCustomShaderName(material.shader.name) != null )
{
return true ;
}
}
return false ;
}
< summary >
</ summary >
[MenuItem("Assets/" + ShaderName + "/Convert material to original shader" , false , 1101 )]
#pragma warning disable IDE0051
private static void ConvertMaterialToOriginalShaderMenu()
#pragma warning restore IDE0051
{
foreach (var material in Selection.GetFiltered<Material>(SelectionMode.Assets))
{
#if UNITY_2022_1_OR_NEWER
if (material.parent != null )
{
Debug.LogWarningFormat("Ignore {0} because it is Material Variant" , AssetDatabase.GetAssetPath(material));
continue ;
}
#endif
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, ShaderName + "/ConvertMaterialToOriginalShaderMenu" );
var renderQueue = lilMaterialUtils.GetTrueRenderQueue(material);
material.shader = shader;
material.renderQueue = renderQueue;
}
}
< summary >
< see cref ="ValidateConvertMaterialToOriginalShaderMenu" />
</ summary >
< returns > < see cref ="ValidateConvertMaterialToOriginalShaderMenu" /> </ returns >
[MenuItem("Assets/" + ShaderName + "/Convert material to original shader" , true )]
#pragma warning disable IDE0051
private static bool ValidateConvertMaterialToOriginalShader()
#pragma warning restore IDE0051
{
foreach (var material in Selection.GetFiltered<Material>(SelectionMode.Assets))
{
#if UNITY_2022_1_OR_NEWER
if (material.parent != null )
{
Debug.LogWarningFormat("Ignore {0} because it is Material Variant" , AssetDatabase.GetAssetPath(material));
continue ;
}
#endif
if (GetCorrespondingOriginalShaderName(material.shader.name) != null )
{
return true ;
}
}
return false ;
}
< summary >
</ summary >
[MenuItem("Assets/" + ShaderName + "/Refresh shader cache" , false , 2000 )]
#pragma warning disable IDE0052
private static void RefreshShaderCacheMenu()
#pragma warning restore IDE0052
{
var result = NativeMethods.Open("Library/ShaderCache.db" , out var pDb);
if (result != 0 )
{
Debug.LogErrorFormat("Failed to open Library/ShaderCache.db [{0}]" , result);
return ;
}
try
{
result = NativeMethods.Execute(pDb, "DELETE FROM shadererrors" );
if (result != 0 )
{
Debug.LogErrorFormat("SQL failed [{0}]" , result);
return ;
}
}
finally
{
result = NativeMethods.Close(pDb);
if (result != 0 )
{
Debug.LogErrorFormat("Failed to close database [{0}]" , result);
}
}
var shaderDirPath = AssetDatabase.GUIDToAssetPath(GuidShaderDir);
if (shaderDirPath.Length == 0 )
{
Debug.LogWarning("Cannot find file or directory corresponding to GUID: " + GuidShaderDir);
return ;
}
if (! Directory.Exists(shaderDirPath))
{
Debug.LogWarningFormat("Directory not found: {0} ({1})" , shaderDirPath, GuidShaderDir);
return ;
}
AssetDatabase.ImportAsset(shaderDirPath, ImportAssetOptions.ImportRecursive);
}
< summary >
< see cref ="RefreshShaderCacheMenu" />
</ summary >
< returns > < see cref ="RefreshShaderCacheMenu" /> </ returns >
[MenuItem("Assets/" + ShaderName + "/Refresh shader cache" , true )]
#pragma warning disable IDE0051
private static bool ValidateRefreshShaderCacheMenu()
#pragma warning restore IDE0051
{
try
{
NativeMethods.Close(IntPtr.Zero);
return true ;
}
catch (DllNotFoundException)
{
return false ;
}
}
< summary >
</ summary >
< param name ="originalShader" > </ param >
< returns > </ returns >
private static Shader GetCorrespondingCustomShader(Shader originalShader)
{
var customShaderName = GetCorrespondingCustomShaderName(originalShader.name);
return customShaderName == null ? null : Shader.Find(customShaderName);
}
< summary >
</ summary >
< param name ="originalShaderName" > </ param >
< returns > </ 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] lilToonOutlineOnlyCutout" : return ShaderName + "/[Optional] OutlineOnly/Cutout" ;
case "_lil/[Optional] lilToonOutlineOnlyTransparent" : 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] lilToonFurOnlyTransparent" : 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/[Optional] 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 >
</ summary >
< param name ="customShader" > </ param >
< returns > </ returns >
private static Shader GetCorrespondingOriginalShader(Shader customShader)
{
var customShaderName = GetCorrespondingOriginalShaderName(customShader.name);
return customShaderName == null ? null : Shader.Find(customShaderName);
}
< summary >
</ summary >
< param name ="customShaderName" > </ param >
< returns > </ 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] lilToonOutlineOnlyCutout" ;
case ShaderName + "/[Optional] OutlineOnly/Transparent" : return "_lil/[Optional] lilToonOutlineOnlyTransparent" ;
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] lilToonFurOnlyTransparent" ;
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/[Optional] 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 >
</ summary >
internal static class NativeMethods
{
#if UNITY_EDITOR && !UNITY_EDITOR_WIN
< summary >
</ summary >
private const string LibraryName = "sqlite3" ;
< summary >
</ summary >
private const CallingConvention CallConv = CallingConvention.Cdecl;
#else
< summary >
</ summary >
private const string LibraryName = "winsqlite3" ;
< summary >
</ summary >
private const CallingConvention CallConv = CallingConvention.StdCall;
#endif
< summary >
</ summary >
< param name ="filePath" > </ param >
< param name ="pDb" > </ param >
< returns > </ returns >
< remarks >
< seealso href ="https://www.sqlite.org/c3ref/open.html" />
</ remarks >
[DllImport(LibraryName, EntryPoint = "sqlite3_open16" , CallingConvention = CallConv, CharSet = CharSet.Unicode)]
public static extern int Open([In] string filePath, out IntPtr pDb);
< summary >
</ summary >
< param name ="pDb" > </ param >
< returns > </ 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 pDb);
< summary >
</ summary >
< param name ="pDb" > </ param >
< param name ="sql" > </ param >
< param name ="pCallback" > </ param >
< param name ="pCallbackArg" > </ param >
< param name ="pErrMsg" > </ param >
< returns > </ 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 pDb, [In] string sql, IntPtr pCallback = default (IntPtr), IntPtr pCallbackArg = default (IntPtr), IntPtr pErrMsg = default (IntPtr));
}
}
}
作例
Templateと作例を比較すると勉強になるはず.
Booth(無料)
Booth(有料)
GitHub
カスタムシェーダーではない作例
以下はlilToonのカスタムシェーダーに分類されない作例である.