【Unity】【シェーダ】Unityシェーダチートシート

自分用メモ。随時更新。

全体の前提

  • v.vertexは頂点シェーダの入力としての頂点座標とする
  • v.normalは頂点シェーダの入力としての法線とする
  • v.tangentは頂点シェーダの入力としての接線とする

視点(カメラ)方向のベクトルを求める

// ローカル座標系(ObjectSpaceViewDir()の戻り値は正規化されていないので注意)
half3 eyeDir = normalize(ObjSpaceViewDir(v.vertex));

// ワールド座標系(WorldSpaceViewDir()の戻り値は正規化されていないので注意)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
half3 eyeDir = UnityWorldSpaceViewDir(worldPos);

// ちなみにワールド座標系の方はローカルの頂点座標から変換する関数も用意されてる
// がコメントにLegacyと書かれているので使わないほうがよさそう
half3 eyeDir = normalize(WorldSpaceViewDir(v.vertex));

ちなみに上記の関数はUnityCG.cginc内で次のようになっている。(Unity2017.4.1f1)

inline float3 ObjSpaceViewDir( in float4 v )
{
    float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
    return objSpaceCameraPos - v.xyz;
}

inline float3 UnityWorldSpaceViewDir( in float3 worldPos )
{
    return _WorldSpaceCameraPos.xyz - worldPos;
}

// *Legacy* Please use UnityWorldSpaceViewDir instead
inline float3 WorldSpaceViewDir( in float4 localPos )
{
    float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
    return UnityWorldSpaceViewDir(worldPos);
}

最適化を考えるとき以外は関数を使っておけばよさそう。

ライト方向のベクトルを求める

シェーダで取得できるライト情報はLightModeにより変わるのでまずこれを指定する。
たとえばフォワードレンダリングでディレクショナルライトの情報を得たい場合は次のようにする。

Tags { "LightMode"="ForwardBase"}

そのうえで下記のように求める。

// ローカル座標系(ObjSpaceLightDir()の戻り値は正規化されていないので注意)
half3 lightDir = normalize(ObjSpaceLightDir(v.vertex));

// ワールド座標系(UnityWorldSpaceLightDir()の戻り値は正規化されていないので注意)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
half3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));

// ちなみにワールド座標系の方はローカルの頂点座標から変換する関数も用意されてる
// がコメントにLegacyと書かれているので使わないほうがよさそう
half3 lightDir = normalize(WorldSpaceLightDir(v.vertex));

ちなみに上記の関数はUnityCG.cginc内で次のようになっている。(Unity2017.4.1f1)

inline float3 ObjSpaceLightDir( in float4 v )
{
    float3 objSpaceLightPos = mul(unity_WorldToObject, _WorldSpaceLightPos0).xyz;
    #ifndef USING_LIGHT_MULTI_COMPILE
        return objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;
    #else
        #ifndef USING_DIRECTIONAL_LIGHT
        return objSpaceLightPos.xyz - v.xyz;
        #else
        return objSpaceLightPos.xyz;
        #endif
    #endif
}

inline float3 UnityWorldSpaceLightDir( in float3 worldPos )
{
    #ifndef USING_LIGHT_MULTI_COMPILE
        return _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;
    #else
        #ifndef USING_DIRECTIONAL_LIGHT
        return _WorldSpaceLightPos0.xyz - worldPos;
        #else
        return _WorldSpaceLightPos0.xyz;
        #endif
    #endif
}

// *Legacy* Please use UnityWorldSpaceLightDir instead
inline float3 WorldSpaceLightDir( in float4 localPos )
{
    float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
    return UnityWorldSpaceLightDir(worldPos);
}

接空間への変換行列を求める

// ワールド空間のnormal, tangent, binormalを求める
// オブジェクト空間と接空間の変換行列を求めたい場合はここでオブジェクト空間のnormal, tangent, binormalを求めておく
half3 normal = UnityObjectToWorldNormal(v.normal);
half3 tangent = mul(unity_ObjectToWorld, v.tangent.xyz);
half3 binormal = normalize(cross(v.normal, v.tangent) * v.tangent.w);
binormal = mul(unity_ObjectToWorld, binormal);

// 上記の情報をつかって行列を作成する
half3x3 tangentToWorld = transpose(half3x3(tangent.xyz, binormal, normal));

// この行列をかけて座標変換する
mul(tangentToWorld, tangentNormal);

ワールド空間の法線を求める

half3 normal = UnityObjectToWorldNormal(v.normal);

ちなみに上記の関数はUnityCG.cginc内で次のようになっている。(Unity2017.4.1f1)

inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
    return UnityObjectToWorldDir(norm);
#else
    // mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}
    return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}

ローカル空間とワールド空間の変換行列

// ローカル > ワールド
unity_ObjectToWorld

// ワールド > ローカル
unity_WorldToObject

Toggleのプロパティからシェーダキーワードを切り替える

// プロパティを定義
[Toggle(SAMPLE)]_SampleToggle("Sample Toggle", Int) = 0  

// multi_compile or shader_feature
#pragma multi_compile _ SAMPLE

Enumのプロパティからシェーダキーワードを切り替える

// プロパティを定義
[KeywordEnum(ONE, TWO)]_SampleEnum("Sample Enum", Int) = 0  

// multi_compile or shader_feature
// キーワードは大文字になるので注意
#pragma multi_compile _SAMPLEENUM_ONE _SAMPLEENUM_TWO

数値の小数部分を取得

frac()を使用する。
いつも忘れる・・

frac | Microsoft Docs

最小コード

シェーダ最小コード。

Shader "Example"
{
    SubShader
    {      
        Pass
        {
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            fixed4 frag () : SV_Target
            {
                return 1;
            }

            ENDCG
        }
    }
}

半透明描画をするときのシェーダの設定

Tags { "Queue"="Transparent" "RenderType"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
Cull Off Lighting Off ZWrite Off

大事なのはQueueとRenderTypeをTranparentにしてZWriteをOffにすること。
ほかはケースバイケースだけど大体こんな感じが多そう、ってのを書いといた。

Diffuseライティング(ランバート、ハーフランバート

// ランバート
half3 col = max(0, dot(normal, lightDir));

// ハーフランバート
half3 col = dot(normal, lightDir) * 0.5 + 0.5;

// 適宜ライトや物体色を反映する
// col *= _Color.rgb * _LightColor0.rgb;

Specularライティング(ブリンフォン)

// ブリンフォン
half3 halfVec = normalize(lightDir + eyeDir);
col.rgb = pow(max(0, dot(normal, halfVec)), _Shininess);

// 適宜ライトや反射色を反映する
// col *= _SpecColor.rgb * _LightColor0.rgb;

キューブマッピング

Shader "Cubemapping"
{
    Properties{
        _Cube ("Cube", CUBE) = "" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
             
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 normal: NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
            };

            // キューブマップの定義はマクロを使う
            UNITY_DECLARE_TEXCUBE(_Cube);

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half3 worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
                half3 reflDir = reflect(-worldViewDir, i.worldNormal);
                // キューブマップのサンプリングはマクロを使う
                half4 refColor = UNITY_SAMPLE_TEXCUBE_LOD(_Cube, reflDir, 0);
                
                // UNITY_SAMPLE_TEXCUBE関数もあるが、Unity側で何かやってるようで
                // 環境反射をサンプリングしようとするとぼけたりするので
                // LODのほうを使っておいたほうが無難そう
                //half4 refColor = UNITY_SAMPLE_TEXCUBE(_Cube, reflDir);
                return refColor;
            }
            ENDCG
        }
    }
}

キューブマップ(ラフネス有り)

Shader "Cubemapping"
{
    Properties{
        _Cube ("Cube", CUBE) = "" {}
        _Roughness ("Roughness", Range(0.0, 1.0)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
             
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 normal: NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
            };

            UNITY_DECLARE_TEXCUBE(_Cube);
            half _Roughness;
            // UnityStandardConfig.cgincに定義されているのでインクルードしてもOK
           #ifndef UNITY_SPECCUBE_LOD_STEPS
               #define UNITY_SPECCUBE_LOD_STEPS (6)
           #endif

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half3 worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
                half3 reflDir = reflect(-worldViewDir, i.worldNormal);
                // キューブマップのサンプリングはマクロを使う
                half4 refColor = UNITY_SAMPLE_TEXCUBE_LOD(_Cube, reflDir, _Roughness * UNITY_SPECCUBE_LOD_STEPS);
                return refColor;
            }
            ENDCG
        }
    }
}

Surfaceシェーダで物理ベース(Standard)でライティングする

  1. LightModeにStandardを指定する
  2. アウトプット構造体をSurfaceOutputStandardにする
Shader "Custom/NewSurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        // lightModeにStandardを指定
       #pragma surface surf Standard
       #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // アウトプット構造体をSurfaceOutputStandardにする
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

ほかにもlightModeが定義されているが、基本的に変えるべき点は同じ。
詳細はマニュアルで。

docs.unity3d.com

Surfaceシェーダで半透明描画を行う

  1. RenderTypeとQueueをTransparentにする
  2. alphaオプションを指定する
Shader "Custom/NewSurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        // RenderTypeとQueueをTransparentにする
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        CGPROGRAM
        // alphaオプションを指定する
       #pragma surface surf Standard alpha
       #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}