夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

ゼロから始めるUnityShader開発 第五章 ほかのオブジェクトの影を受けるシェーダーを書く

本日はシェーダー学習枠です。

前回まででShaderとは何か?どのように実行されているのか?Unityでの扱い方、URPでのShaderに関して紹介しUnityのライトを受けるシェーダーを書きました。

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

今回はUnityのライトによるほかのオブジェクトの影を受けるシェーダーを記述します。

〇Unityの影

Unityではリアルタイムシャドウという機能があり、UnityのDirectionalLightを使用することで影を落とすことができます。

docs.unity3d.com

影を落とすかどうかというのはMeshRendererCastShadowsによって設定できます。

例えば次の画像では左側のオブジェクトはCastShadowがOnになっており、右側のオブジェクトはChastShadowがOffになっています。

今回はこの影ようにほかのオブジェクトの影を受け取るシェーダーを書きます。

〇Shaderで影を描画する。

URPのShaderでUnityのライト情報による影を受け取るためには次の機能を使用します。

            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE

〇頂点シェーダーの処理

次に頂点シェーダーの処理を記述します。

今回は影を受け取るのが目的なのでフラグメントシェーダーでの処理がメインになりますが、モデル自体の情報をフラグメントシェーダーに渡す必要があり、処理を行います。

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

            struct v2f
            {
            float4 vertex : SV_POSITION;
            float3 normalWS : TEXCOORD1;
            float4 shadowCoord : TEXCOORD3;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                //面の法線を取得、ライトの当たる向きを計算
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);//追加
                o.shadowCoord = GetShadowCoord(vertexInput);//追加
                return o;
            }

今回はGetVertexPositionInputsに頂点情報を渡しています。

この関数は何をしているかというとTransformObjectToHClip()の上位互換的な関数でClipPos(カメラ位置)だけでなくワールドスペースなどすべての情報を取得しています。

ということで今回の本質ではありませんがTransformObjectToHClip()を置き換えます。

                VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
                o.vertex = vertexInput.positionCS;
                o.shadowCoord = GetShadowCoord(vertexInput);

positionCSのCSとはClipSpaceのようですね。

次にGetShadowCoord()に頂点を渡しています。

 Unityのシャドウはシャドウマップという動的な画像に焼きこまれているイメージで行われています。

o.shadowCoordはこの画像の座標空間を取得するために使用されています。

〇フラグメントシェーダーの処理

フラグメントシェーダーの処理は次のようになります。

            float4 frag (v2f i) : SV_Target
            {
                float4 col = _MainColor;
                //Light.hlslで提供されるUnityのライトを取得する関数
                Light lt = GetMainLight(i.shadowCoord);

                //ライトの向きを計算
                float strength = dot(lt.direction, i.normalWS);
                float4 lightColor = float4(lt.color, 1)*(lt.distanceAttenuation * lt.shadowAttenuation);
                return col* lightColor*strength;
            }

ここで注目点がGetMainLight()の引数に先ほど出力したshadowCoordを入れているところです。

これによってLight.shadowAttenuationの値が使用できるようになります。

これがシャドウマップの影響を受けた影になります。

貫通して影を受けていますが、このあたりはもともと影が付く想定なので黒に黒が塗りつぶされ最終的に自然になります。

この状態でライト情報と積算して出力することで影を受けるシェーダーができました。

本日は以上です。