🌈

そろそろShaderをやるパート37 影を落とす、受ける

2021/07/25に公開

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

デモ

前回までのShaderは影に関する処理を行っていませんでした。
【参考リンク】:そろそろShaderをやるパート36 Directional Light、環境光を反映する

下記はオブジェクトが影を落とし、逆に他のオブジェクトから
影を受ける処理を加えたものです。

Shaderサンプル

Shader "Custom/Shade"
{
    Properties
    {
        _MainColor ("Main Color", Color) = (0, 0, 0, 1)
        _DiffuseShade("Diffuse Shade",Range(0,1)) = 0.5
    }

    SubShader
    {

        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }


            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _MainColor;
            float _DiffuseShade;

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

            struct v2f
            {
                float4 pos:SV_POSITION;
                half3 worldNormal:TEXCOORD0;
                SHADOW_COORDS(1)
            };

            //頂点シェーダー
            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //法線方向のベクトル
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                TRANSFER_SHADOW(o)
                return o;
            }

            //フラグメントシェーダー
            fixed4 frag(v2f i) : SV_Target
            {
                //最終的に出力するピクセルの色
                fixed4 finalColor = fixed4(0, 0, 0, 1);

                //1つ目のライトのベクトルを正規化
                float3 L = normalize(_WorldSpaceLightPos0.xyz);
                //ワールド座標系の法線を正規化
                float3 N = normalize(i.worldNormal);
                //ライトベクトルと法線の内積からピクセルの明るさを計算 ランバートの調整もここで行う
                fixed4 diffuseColor = max(0, dot(N, L) * _DiffuseShade + (1 - _DiffuseShade));
                //ライトの色を乗算
                finalColor = _MainColor * diffuseColor * _LightColor0;
                // 影を計算
                finalColor *= SHADOW_ATTENUATION(i);
                return finalColor;
            }
            ENDCG
        }

        //影を落とす処理を行うPass
        Pass
        {
            Tags
            {
                "LightMode"="ShadowCaster"
            }

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

            struct v2f
            {
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}

影を落とす

まずは、影を落とす処理です。
影を落とすだけの処理を担うPassを追加しています。

//影を落とす処理を行うPass
Pass
{
    Tags
    {
        "LightMode"="ShadowCaster"
    }

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

    struct v2f
    {
        V2F_SHADOW_CASTER;
    };

    v2f vert(appdata_base v)
    {
        v2f o;
        TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
        return o;
    }

    float4 frag(v2f i) : SV_Target
    {
        SHADOW_CASTER_FRAGMENT(i)
    }
    ENDCG
}

#pragma multi_compile_shadowcasterを記述する必要があります。

公式のドキュメントには下記のように記述があります。

We’ve used the #pragma multi_compile_shadowcaster directive. This causes the shader to be compiled into several variants with different preprocessor macros defined for each (see multiple shader variants page for details). When rendering into the shadowmap, the cases of point lights vs other light types need slightly different shader code, that’s why this directive is needed.

【参考リンク】:Vertex and fragment shader examples

#pragma multi_compile_shadowcasterが担っていることを端的に表現すると
影の落とし方(ライトの種類など)に応じて部分的に異なるシェーダーを
コンパイル時に生成しています。文字通りのマルチコンパイルです。

.cgincファイルの中でプリプロセッサディレクティブ(コンパイル前に判定する条件分岐)を
使用しているので、実行時にはマクロを呼び出すだけであとはよしなにやってくれます。

影を受ける

1パス目の#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlightが先ほど同様マルチコンパイルを担ってくれています。

あとはAutoLight.cgincの中の組み込み関数である
SHADOW_COORDSTRANSFER_SHADOWSHADOW_ATTENUATION
それぞれ適切な箇所で使用します。

頂点シェーダーに渡す構造体の中で頂点を受け取る変数として定義している
float4 pos:SV_POSITION;はマクロ内で決め打ちされている名前に合わせて
posにする必要があるようです。

クオリティーの問題

2022/04/04 追記
この記事の影の描画の仕方だと影のクオリティーがいまいちでした。
そのため、別のアプローチについて記事を書きました。
そろそろShaderをやるパート69 影を落とす処理、受ける処理を自作Shaderに追加する

参考リンク

UnityでForwardのライトに対応したLambert反射モデルのシェーダを作成する
【Unity】【Shader】影を描画する、影を受け取る
Shader Variantについて調べてみた
#if (C# リファレンス)

Discussion