Unityシェーダーの基礎知識。
ShaderVariantを使って複数の効果を1つのシェーダーに実装。
関連ページ
参考URL
UnityではShaderVariantという機能がある。
この機能を使えば、インスペクターからVariantを切り替えて、1つシェーダーに複数の効果を持たせることが出来る。
とても便利だが実質if文の亜流のような感じなので、使い方によっては処理時間が伸びる可能性もある。(Shaderではif文が推奨されていない)
ShaderVariantとは
まず今回テストに使うShaderVariantの効果を紹介。
インスペクターからVariantを切り替えることで、色相反転とシルエット化のシェーダー効果を切り替えている。
ShaderVariantは、端的に言うとC#のプリプロセッサに近い機能になる。
例えばC#ではプリプロセッサを使うことで、iOS端末とAndroid端末で処理を分けることが可能になる。
下はその一例。
UNITY_IOS
と
UNITY_ANDROID
は、Unity側で標準に用意されているシンボル名。
using
UnityEngine;
public
class
TestPreprocessor
:
MonoBehaviour
{
private
void
Start()
{
#if
UNITY_IOS
Debug.Log("iOSの処理");
#elif
UNITY_ANDROID
Debug.Log("Androirの処理");
#endif
}
}
C#はプロジェクトビルド時に、必要なプリプロセッサのコードのみビルドに含める。
なので上の例だと、BuildSettingsがiOSの場合は
private void Start()
{
Debug.Log("iOSの処理");
}
というコードのみがビルドに含まれ、Androidの場合は
private void Start()
{
Debug.Log("Androirの処理");
}
というコードのみがビルドに含まれる。
ShaderVariantの概念はこのプリプロセッサとほぼ同じになる。
下はShaderVariantの重要な処理部分のみ抜粋したもの。
fixed4
frag
(v2f
i)
:
SV_Target
{
fixed4
col
=
tex2D(_MainTex,
i.uv);
//_COLOR_REVERSALのシンボル名を選択している時は以下のコード
#ifdef
_COLOR_REVERSAL
col.r
=
1
-
col.r;
col.g
=
1
-
col.g;
col.b
=
1
-
col.b;
//_COLOR_SILHOUETTEのシンボル名を選択している時は以下のコード
#elif
_COLOR_SILHOUETTE
col.r
=
i.color.r;
col.g
=
i.color.g;
col.b
=
i.color.b;
#endif
return
col;
}
プロジェクト内で
_COLOR_REVERSAL
のVariantのみ使っている場合は、
_COLOR_REVERSAL
の範囲のみがビルドに含まれる。
反対に
_COLOR_SILHOUETTE
のVariantのみ使っている場合は、
_COLOR_SILHOUETTE
の範囲のみがビルドに含まれる。
問題はプロジェクト内で両方のVariantを使ってる場合で、この場合Unityは両方のコードをビルドに含める。
こうなると、実質if文を使ってるのと変わらないので、シェーダーの処理速度の劣化に繋がる。
コード全文と解説
下が今回のテストのコード全文。
Shader
"Test/TestVariant"
{
Properties
{
//インスペクターからVarinatを選択させるため設定
[KeywordEnum(Reversal,
Silhouette)]
_COLOR("Color",
Int)
=
0
}
SubShader
{
Tags
{
"Queue"
=
"Transparent"
}
Cull
Off
ZWrite
Off
Blend
SrcAlpha
OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma
vertex
vert
#pragma
fragment
frag
//Varinatのシンボル名を定義
#pragma
multi_compile
_COLOR_REVERSAL
_COLOR_SILHOUETTE
struct
appdata
{
fixed2
uv
:
TEXCOORD0;
fixed4
vertex
:
POSITION;
fixed4
color
:
COLOR;
};
struct
v2f
{
fixed2
uv
:
TEXCOORD0;
fixed4
vertex
:
SV_POSITION;
fixed4
color
:
COLOR;
};
sampler2D
_MainTex;
v2f
vert
(appdata
v)
{
v2f
o;
o.vertex
=
UnityObjectToClipPos(v.vertex);
o.uv
=
v.uv;
o.color
=
v.color;
return
o;
}
fixed4
frag
(v2f
i)
:
SV_Target
{
fixed4
col
=
tex2D(_MainTex,
i.uv);
//_COLOR_REVERSALのシンボル名を選択している時は以下のコード
#ifdef
_COLOR_REVERSAL
col.r
=
1
-
col.r;
col.g
=
1
-
col.g;
col.b
=
1
-
col.b;
//_COLOR_SILHOUETTEのシンボル名を選択している時は以下のコード
#elif
_COLOR_SILHOUETTE
col.r
=
i.color.r;
col.g
=
i.color.g;
col.b
=
i.color.b;
#endif
return
col;
}
ENDCG
}
}
}
Variantに関係する部分だけ解説。
インスペクター上にVariantの選択タブを表示させるために、まず下のコードを打ち込んでいる。
Properties
{
//インスペクターからVarinatを選択させるため設定
[KeywordEnum(Reversal,
Silhouette)]
_COLOR("Color",
Int)
=
0
}
ここの部分は結構重要なので3つに分割して解説。
[KeywordEnum(Reversal, Silhouette)]
ここに記述した文字列が実際に選択タブで表示される。
文字列の指定に制限はなく何でも良い。
この部分は制限がかなり厳しい。
まず一番最初に_(アンダーバー)を付ける必要がある。
更に、後ろに出てくるVarinatのシンボル名とリンクしている必要がある。
//Varinatのシンボル名を定義
#pragma
multi_compile
_COLOR_REVERSAL
_COLOR_SILHOUETTE
今回は
_COLOR_REVERSAL
と
_COLOR_SILHOUETTE
の2つのシンボル名を定義している。
なのでその共通する前半部分の
_COLOR
をプロパティにも入力している。
もしこれが
_COLOR
ではなく
_COLORS
にした場合、シンボル名と一致していないのでVariantが機能しない。
文字列が一致している必要があるものの、実はプロパティの方は小文字にするだけの変更なら問題ない。
つまり下のコードでもOK。
Properties
{
//インスペクターからVarinatを選択させるため設定
[KeywordEnum(Reversal,
Silhouette)]
_color("Color",
Int)
=
0
}
ただ問題なのは、実際のシンボル名の定義の方は小文字が許されておらず、必ず全て大文字にする必要がある。
下の例ではVariantが動かない。
//Varinatのシンボル名を定義
#pragma
multi_compile
_color_REVERSAL
_color_SILHOUETTE
わざわざプロパティだけ小文字にする意味もないので、今回のテストでは両方大文字を使っている。
"Color"
はインスペクターに表示される文字列、何を入力しても問題ない。
第二引数の
Int
に関しては、色々試したがFloatでも問題ない模様。
ただVariantのプロパティに0.4や0.6のfloat値を打ち込んでも結局0扱いされるので、Intで良いと思う。
= 0
がVariantの初期値の設定。
もしこれが
= 1
であった場合、MaterialからTest/TetstVariantを指定した時の初期設定はSilhouetteになる。
ShaderVarinatのシンボル名を定義する時、必ず
#pragma multi_compile
という記述が先に必要になる。
//Varinatのシンボル名を定義
#pragma
multi_compile
_COLOR_REVERSAL
_COLOR_SILHOUETTE
今回の場合2つしか定義していないが、別に3つでも4つでも良い。
増やした分は、ちゃんとプロパティのKeywordEnumにも追記する。
実際のシェーダー処理をしているフラグメントシェーダー部分は、今回の場合あまり特別なことをしていない。
プリプロセッサの知識があればすっと入ってくるコードだと思う。
インスペクターからReversalを選択していれば色相反転処理を、Silhoutteを選択していればシルエット化処理をしている。
fixed4
frag
(v2f
i)
:
SV_Target
{
fixed4
col
=
tex2D(_MainTex,
i.uv);
//_COLOR_REVERSALのシンボル名を選択している時は以下のコード
#ifdef
_COLOR_REVERSAL
col.r
=
1
-
col.r;
col.g
=
1
-
col.g;
col.b
=
1
-
col.b;
//_COLOR_SILHOUETTEのシンボル名を選択している時は以下のコード
#elif
_COLOR_SILHOUETTE
col.r
=
i.color.r;
col.g
=
i.color.g;
col.b
=
i.color.b;
#endif
return
col;
}
0
0