UnityでLambert拡散反射シェーダーを作る

UnityでLambert拡散反射シェーダーを実装する方法について紹介します。

Lambert拡散反射モデルとは

Lambert(ランバート)拡散反射モデルとは、オブジェクトに落ちる陰(※影ではありません!)を疑似的に表現するシェーダー技法です。実装は非常にシンプルで、ライト方向とメッシュの法線方向の内積を計算することで陰の濃淡を表現します。

それほど難しくないシェーダーなのですが、数点ポイントがあるので注意しましょう。

法線をワールド座標系に変換する

頂点シェーダー内で、オブジェクト座標系になっている法線をワールド座標系に変換してください。変換にはmulを使い、unity_ObjectToWorldというマクロを掛け合わせてください。

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex); //頂点をMVP行列変換
    o.normal = mul(unity_ObjectToWorld, v.normal); //各頂点が持つ法線(オブジェクト座標系)をワールド座標系に変換
    return o;
}

ライト方向と法線の内積を計算する

フラグメントシェーダー内では、ライト方向と法線を使い、内積を計算します。この内積が、1に近いほど(=ライト方向と法線重なる)、光が当たっているとみなします。

なお、シーンのライト方向を取得するには_WorldSpaceLightPos0を利用しますが、こちらは #include “Lighting.cginc” の宣言が必要になるので注意してください。

計算した内積のうち、マイナスとなる部分は必要ないのでmax関数を使い0にクランプしています。こちらは、saturate(t)という関数でも代用できます。

※saturateは飽和の意。0 – 1にクランプします。

fixed4 frag (v2f i) : SV_Target
{
    float3 ligDirection = normalize(_WorldSpaceLightPos0.xyz); //シーンのディレクショナルライト方向を取得
    fixed3 ligColor = _LightColor0.xyz; //ディレクショナルライトのカラーを取得

    float t = dot(i.normal, ligDirection); //ライト方向と法線方向で内積を計算
    t = max(0, t); //計算した内積のうち、t < 0は必要ないのでクランプ

    float3 diffuseLig = ligColor * t; //ディフューズカラーを計算。内積が0に近いほど色が黒くなる

    float4 finalColor = float4(1, 1, 1, 1);

    finalColor.xyz *= diffuseLig;

    return finalColor;
}

シェーダー全体

これらのコードをすべてまとめたものが、以下の通りです。ランバート拡散反射モデルを最低限のコードで構成しました。

Shader "CAGraphicsAcademy/Lambert"
{
    Properties
    {

    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal   : NORMAL;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex); //頂点をMVP行列変換
                o.normal = mul(unity_ObjectToWorld, v.normal); //各頂点が持つ法線(オブジェクト座標系)をワールド座標系に変換
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 ligDirection = normalize(_WorldSpaceLightPos0.xyz); //シーンのディレクショナルライト方向を取得
                fixed3 ligColor = _LightColor0.xyz; //ディレクショナルライトのカラーを取得

                float t = dot(i.normal, ligDirection); //ライト方向と法線方向で内積を計算
                t = max(0, t); //計算した内積のうち、t < 0は必要ないのでクランプ

                float3 diffuseLig = ligColor * t; //ディフューズカラーを計算。内積が0に近いほど色が黒くなる

                float4 finalColor = float4(1, 1, 1, 1);

                finalColor.xyz *= diffuseLig;

                return finalColor;
            }
            ENDCG
        }
    }
}

返信を残す

メールアドレスが公開されることはありません。