HDRPでカスタムShader -レイヤーが一致するDirectionalLightの影響を受ける・レイヤーで指定したオブジェクトの影を落とす-

はじめに

以前HDRPでカスタムシェーダーを作成し、DirectionalLightの影を落とす・受け取る方法について書きました。

daiki-eguchi.hatenablog.com

今回はHDRPで複数のDirectionalLightがあった際にレイヤーを設定することでレイヤーが合っているものだけ影響を受ける方法についてです。

開発環境

  • Unity2022.3.4f1
  • HDRP14.08

注意点

まず、HDRPではカスケードシャドウを使用している時、複数のDirectionalLightの影を落とすことはできなさそうです。 なので一灯のDirectionalLightとは別角度の影を落としたいときはSpotLightを使用したりしています。(SpotLightでは複数方向の影を落とすことができる)

DirectionalLightを複数Shadowオンにすると出るエラー

Lightのレイヤー設定

DIrectionalLightにはLayerを設定することができ、そのライトの影響を及ぼすレイヤー分けを行うことができます。 このレイヤーを設定することでどのライトがどのオブジェクトに影響を及ぼすか。ということを設定することができます。

LightのLayerを設定する

このレイヤーはRendererのAdditionalSettings欄にあるRenderingLayerMaskと対応しています。

オブジェクト側のRenderingLayer

もし、Light側のLayer設定がない場合は以下のように「Show All Additional Properties」をクリックし、開いたPreferenceの「CoreRenderPipeline」からVisibilityをAllVisibleに設定することで確認できるかと思います。

実装

今回、以下のようにDirectionalLightを複数用意し、RenderingLayerをそれぞれ「0」と「1」に設定します。

前記事ではDirectionalLightは一つと考えていたので以下のように指定していました。DirectionalLightやスポットライトの情報はStructuredBufferとして登録されています。

DirectionalLightData directionalLightData = _DirectionalLightDatas[0];

DirectionalLightの数はDirectionalLightCountとして設定されているため、DirectionalLightDatasを順番にfor分で回し、レイヤーを判定します。

Shaderのフラグメントシェーダー部分を以下のようにします。

float4 frag (v2f i) : SV_Target
{
    float4 mainColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                
    for (int lightCount = 0; lightCount < _DirectionalLightCount; lightCount++)
    {
        if(IsMatchingLightLayer(_DirectionalLightDatas[lightCount].lightLayers, 1 << 1))
        {
            DirectionalLightData directionalLightData = _DirectionalLightDatas[lightCount];

            float NdotL = saturate(dot(i.normal, -directionalLightData.forward));

            HDShadowContext shadowContext = InitShadowContext();
            float3 shadow = GetDirectionalShadowAttenuation(
                        shadowContext,
                        i.positionSS.xy,
                        i.positionWS,
                        i.normal,
                        directionalLightData.shadowIndex,
                       -directionalLightData.forward);

             mainColor.rgb *= directionalLightData.color * NdotL * shadow;
         }
    }
                
    return mainColor;
}

まず、for文でDirectionalLightを回します。最大数は_DirectionalLightCountになります。

for (int lightCount = 0; lightCount < _DirectionalLightCount; lightCount++)

そして、IsMatchingLightLayerメソッドでLightレイヤーとオブジェクトのRenderingLayerとあっているかを判定します。

LightLayerは_DirectionalLightDatas[lightCount].lightLayersになり、複数を設定することが可能です。以下のように書くとレイヤーが1のもののみ影響を受けるようになります。

if(IsMatchingLightLayer(_DirectionalLightDatas[lightCount].lightLayers, 1 << 1))

この関数を使用するときは以下のインクルードを行う必要があることに注意してください。

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"

上記によってLayerが0に設定しているライトを緑、Layerを1に設定しているライトを黄色にしていますが、Layerを1の黄色の影響をうけています。

レイヤーが1の黄色の影響を受けている

しかし、影が落ちていません。

影が落ちていない

影を落とす対象オブジェクトのレイヤーを設定する

これはデフォルトではLightLayerと一致しているレイヤーの影を落とそうとするので影が落ちていないのが原因になります。

HDRPには影を落とす対象のオブジェクトのレイヤーを設定することができるのですが、これがなにもしないとLightLayerの設定値になるので(試してみた感じ)、今LightLayerを1に設定しているためにRenderingLayerが1の影を落とそうとしています。 現在、CubeのRenderingLayerは0なので、0の影を落とす必要があります。

そのため、以下のようにLightコンポーネントのShadow欄にある「CustomShadowLayer」をオンにして影を落としたいオブジェクトのレイヤーを指定します。 今回はCubeやPlaneのレイヤーは0なのでCustomShadowLayerは0にします。

この設定を行うと以下のようにしっかりとシャドウが描画されます。

無事に影が描画された

最後に

これによって複数のDirectionalLightがあったときにレイヤー指定したライトの影響を受けるように。そしてレイヤー分けで狙った影を描画することができました。

次はHDRPのカスタムシェーダーで記載するDepthPrepassについて記事を書こうかと思っています。