Unity
ShaderLab

【Unity】Surfaceシェーダーとフラグメントシェーダーの違いを軽くまとめてみた

More than 1 year has passed since last update.

はじめに

Unityのシェーダーへの理解を深めるため、Surfaceシェーダーとフラグメントシェーダーの違いを軽くまとめてみました。

Surfaceシェーダーとフラグメントシェーダー

Surfaceシェーダー

Surfaceシェーダーを使うとライティングやシャドウを考慮したシェーダーを簡単に書くことができます。

頂点シェーダー/フラグメントシェーダー

複雑なことをやりたい場合はこちらのシェーダー。

こちらはSurfaceシェーダーと比べてコードが複雑になりやすいです。

UnlitシェーダーやImageEffectシェーダーはこちらに含まれます。

両者の書き方の違い

筆者はこれらのシェーダーのどこが違うのかよくわからなかったので、書き方の違いを自分なりに軽くまとめてみました。

主な違い

具体的には以下のような違いがあります。 他にもあるかもしれません。
・コンパイルディレクティブの書き方の違い
・Passブロックの有無
・構造体データの書き方の違い
・vertexシェーダー関数の有無
・vertexシェーダー関数の書き方の違い
・フラグメントシェーダー関数の書き方の違い
・ライティングの記述方法の違い

コンパイルディレクティブの書き方の違い

Surfaceシェーダーの場合

以下のような書き方をします。

コンパイルディレクティブの書き方その1
#pragma surface surf Standard fullforwardshadows vertex vert
コンパイルディレクティブの書き方その2
#pragma surface surf Standard fullforwardshadows
#pragma vertex vert

Surfaceシェーダーでは、シェーダー関数指定時にライティングモデルを指定する必要があります。

サンプルではStandardがライティングモデルになっています。

参考: https://docs.unity3d.com/jp/540/Manual/SL-SurfaceShaders.html

フラグメントシェーダーの場合

以下のような書き方をします。

コンパイルディレクティブの書き方
#pragma vertex vert
#pragma fragment frag

参考: https://docs.unity3d.com/jp/540/Manual/SL-ShaderPrograms.html

Passブロックの有無

Surfaceシェーダーの場合

・Passを書けない

フラグメントシェーダーの場合

・Passを書く必要がある
・複数のPassが記述可能

参考: https://docs.unity3d.com/jp/540/Manual/SL-Pass.html

構造体データの書き方の違い

Surfaceシェーダーの場合

以下のような書き方をします

構造体データの記述
struct Input {
  float2 uv_MainTex;
  float2 uv_BumpMap;
};

参考: https://docs.unity3d.com/jp/540/Manual/SL-SurfaceShaders.html

フラグメントシェーダーの場合

以下のような書き方をします

構造体データの記述
struct v2f {
    half3 worldRefl : TEXCOORD0;
    float4 pos : SV_POSITION;
};

TEXCOORD0 のようなセマンティクスを書く必要があります。

参考: https://docs.unity3d.com/jp/540/Manual/SL-VertexFragmentShaderExamples.html

vertexシェーダー関数の有無

Surfaceシェーダーの場合

vertexシェーダー関数は書かなくてもいい

フラグメントシェーダーの場合

vertexシェーダー関数を書く必要がある

vertexシェーダー関数の書き方の違い

Surfaceシェーダーの場合

以下のような書き方をします

vertexシェーダー関数の書き方その1
void vert (inout appdata_full v) {
    v.vertex.xyz += v.normal * _Amount;
}
vertexシェーダー関数の書き方その2
void myvert (inout appdata_full v, out Input data)
{
    UNITY_INITIALIZE_OUTPUT(Input,data);
    float4 hpos = UnityObjectToClipPos(v.vertex);
    hpos.xy/=hpos.w;
    data.fog = min (1, dot (hpos.xy, hpos.xy)*0.5);
}

計算結果を構造体データへ格納しているのが大きな特徴です。

参考:https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

フラグメントシェーダーの場合

以下のような書き方をします。

vertexシェーダー関数の書き方その1
v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}
vertexシェーダー関数の書き方その2
v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
{
    v2f o;
    o.pos = UnityObjectToClipPos(vertex);
    o.worldPos = mul(_Object2World, vertex).xyz;
    o.worldNormal = UnityObjectToWorldNormal(normal);
    return o;
}

計算結果を構造体データとしてreturnしているのが大きな特徴です。

参考: https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html

サーフェースシェーダー関数(フラグメントシェーダー関数)の書き方の違い

Surfaceシェーダーの場合

サーフェースシェーダー関数の書き方その1
void surf (Input IN, inout SurfaceOutput o) {
    o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
サーフェースシェーダー関数の書き方その2
void surf (Input IN, inout SurfaceOutput o) {
    clip (frac((IN.worldPos.y+IN.worldPos.z*0.1) * 5) - 0.5);
    o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
    o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}

計算結果を構造体データの中へ格納しているのが特徴です。

参考: https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

フラグメントシェーダーの場合

フラグメントシェーダー関数の書き方その1
fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    col *= i.diff;
    return col;
}
フラグメントシェーダー関数の書き方その2
fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    fixed shadow = SHADOW_ATTENUATION(i);
    fixed3 lighting = i.diff * shadow + i.ambient;
    col.rgb *= lighting;
    return col;
}

計算結果をカラー値としてreturnしているのが特徴です。

参考: https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html

ライティングの記述方法の違い

Surfaceシェーダーの場合

以下のようにパラメータを指定するだけでライティングを記述することができます。

Surfaceシェーダーのライティング例
#pragma surface surf SimpleLambert

half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {
    half NdotL = dot (s.Normal, lightDir);
    half4 c;
    c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
    c.a = s.Alpha;
    return c;
}

参考: https://docs.unity3d.com/jp/540/Manual/SL-SurfaceShaderLightingExamples.html

フラグメントシェーダーの場合

ライティング処理を自分で書く必要があります。

参考: https://docs.unity3d.com/ja/540/Manual/SL-VertexFragmentShaderExamples.html

その他

appdataについて

appdataの正式な名前は Application to Vertex Shader Structure らしいです。

参考: http://wiki.unity3d.com/index.php?title=Shader_Code#Application_to_Vertex_Shader_Structure_.28appdata.29

v2fについて

v2fの正式な名前はVertex Shader to Fragment Shader Structureらしいです。

参考: http://wiki.unity3d.com/index.php?title=Shader_Code#Vertex_Shader_to_Fragment_Shader_Structure_.28v2f.29

さいごに

筆者はシェーダーに関しての知識が浅いので、間違っているところがあるかもしれません。
間違いを見つけた場合は教えていただけると嬉しいです。

参考URL

Unity のシェーダの基礎を勉強してみたのでやる気出してまとめてみた
http://tips.hecomi.com/entry/2014/03/16/233943

Shader Code - Unify Community Wiki
http://wiki.unity3d.com/index.php?title=Shader_Code#Application_to_Vertex_Shader_Structure_.28appdata.29

[Unity] Vertex/Fragment shaderで通常のライティングとシャドウを適用するサンプル
http://qiita.com/edo_m18/items/ee4b9018e860c11199b0