【转载】URP 管线的自学之路 —— 主光源阴影投射和接收

728 阅读3分钟

原文链接

urp管线的自学hlsl之路 第十篇 主光源阴影投射和接收 | 一骑gens

出处:bilibili

正文

  • 主光源阴影透射和接收 image.png

阴影这块官方改了一些东西,和 Built-in 的实现方式不一样,翻了官方文档和网上的一些资料,研究出来了怎么实现阴影的接受和投射,这里分开讲一下,这篇只涉及 主光源 的阴影内容,额外多光源 的阴影下一篇再讲(还没研究出来)。


TransformWorldToShadowCoord

顶点着色器和之前的一样无变化,而片元着色器里,这里使用TransformWorldToShadowCoord(i.positionWS) 函数,该函数定义在 Shadow.hlsl

定义

float4 TransformWorldToShadowCoord(float3 positionWS)
{
#ifdef _MAIN_LIGHT_SHADOWS_CASCADE
    half cascadeIndex = ComputeCascadeIndex(positionWS);
#else
    half cascadeIndex = 0; 
#endif
    return mul(_MainLightWorldToShadow[cascadeIndex], float4(positionWs, 1.0));
}

作用

把模型的 世界空间 顶点坐标输入,得到 阴影坐标,用于在 ShadowMap 下进行比较。

注意

该函数里是要定义关键字 _MAIN_LIGHT_SHADOWS_CASCADE,这样才能得到正确的阴影坐标。


MainLightRealtimeShadow

第二个函数还是 GetMainLight(float4 shadowcoord),该函数是之前用的 GetMainLight 的重载形式,它会调用另外一个函数 half MainLightRealtimeShadow(float4 shadowCoord),该函数在 Shadow.hlsl ,它的定义如下。

定义

half MainLightRealtimeShadow(float4 shadowCoord)
{
#if !defined(MAIN_LIGHT_CALCULATE_SHADOWS)
    return 1.0h;
#endif
    ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
    half4 shadowParams = GetMainLightShadowParams();
    return SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture),
        shadowCoord, shadowSamplingData, shadowParams, false);          
}

作用

该函数是专门用来计算阴影衰减的

注意

使用它时,需声明关键字MAIN_LIGHT_CALCULATE_SHADOWS,但是这里直接定义该关键字是不会生效的,猜测还需要额外的关键字,找到了 _MAIN_LIGHT_SHADOWS,定义它时则定义了 calculate shadow,猜测它还定义了其他的内容,这里我们直接使用 _MAIN_LIGHT_SHADOWS 关键字即可,如有大佬明白其中原理希望评论指出。

  • _MAIN_LIGHT_SHADOWS 关键字
#if !defined(_RECEIVE_SHADOWS_OFF)
    #if defined(_MAIN_LIGHT_SHADOWS)
        #define MAIN_LIGHT_CALCULATE_SHADOWS

        #if !defined(_MAIN_LIGHT_SHADOWS_CASCADE)
            #define REOUIRES_VERTEX_SHADOW_COORD
        #endif

主光源阴影投射和接收 Shader 代码说明

ShadowCaster.png

以下为上面两个函数需要的关键字定义,还额外多了个 _SHADOWS_SOFT,该关键字是用来设置 软阴影 的(但是也会造成性能的降低),请酌情使用。

  • 关键字编译多个变体 shader
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT // 柔化阴影,得到软阴影

计算带 阴影衰减 的主光源。

  • 计算主光源
Light mylight = GetMainLight(TransformworldToShadowCoord(i.positionWS));

剩下的就常规的计算 高光半兰伯特

  • 片元的剩余部分
float3 Ws_L=normalize(mylight.direction);
float3 WS_N=normalize(i.normalws);
float3 Ws_V=normalize(_WorldSpaceCameraPos - i.positionWS);
float3 WS_H=normalize(WS_V + WS_L);
tex*=(dot(WS_L, WS_N) * 0.5 + 0.5) * mylight.shadowAttenuation * real4(mylight.color, 1);
float4 Specular = pow(max(dot(WS_N, WS_H), 0), _Gloss) * _SpecularColor * mylight.shadowAttenuation;
return tex + Specular;

这样,接受阴影就完成了,如果想实现阴影的投射,直接使用 URP 自带的 ShadowCaster 的 Pass 把模型的信息写到 ShadowMap 上去让其他模型接受本模型的阴影。

最后,附上 shader 源码:

Shader "URP/MainLightShadow"
{
    Properties
    {
        _MainTex("MainTex",2D)="white"{}
        _BaseColor("BaseColor",Color)=(1,1,1,1)
        _Gloss("gloss",Range(10,300))=20
        _SpecularColor("SpecularColor",Color  )=(1,1,1,1)
    }

    SubShader
    {
        Tags{
            "RenderPipeline"="UniversalRenderPipeline"
            "RenderType"="Opaque"
        }

HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

        CBUFFER_START(UnityPerMaterial)
        float4 _MainTex_ST;
        half4 _BaseColor;
        half _Gloss;
        real4 _SpecularColor;
        CBUFFER_END

        TEXTURE2D( _MainTex);
        SAMPLER(sampler_MainTex);
         struct a2v
         {
             float4 positionOS:POSITION;
             float4 normalOS:NORMAL;
             float2 texcoord:TEXCOORD;
         };

         struct v2f
         {
             float4 positionCS:SV_POSITION;
             float2 texcoord:TEXCOORD;
             float3 positionWS:TEXCOORD1; 
             float3 normalWS:NORMAL;
         };
ENDHLSL

        pass
        {
            Tags{
                "LightMode"="UniversalForward"
            }

HLSLPROGRAM

            #pragma vertex VERT
            #pragma fragment FRAG
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _SHADOWS_SOFT//柔化阴影,得到软阴影

            v2f VERT(a2v i)
            {
                v2f o;
                o.positionCS=TransformObjectToHClip(i.positionOS.xyz);
                o.texcoord=TRANSFORM_TEX(i.texcoord,_MainTex);
                o.positionWS=TransformObjectToWorld(i.positionOS.xyz);
                o.normalWS=TransformObjectToWorldNormal(i.normalOS);
                return o;
            }

            half4 FRAG(v2f i):SV_TARGET
            {
                half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord)*_BaseColor;
                Light mylight = GetMainLight(TransformWorldToShadowCoord(i.positionWS));
                float3 WS_L = normalize(mylight.direction);
                float3 WS_N = normalize(i.normalWS);
                float3 WS_V = normalize(_WorldSpaceCameraPos-i.positionWS);
                float3 WS_H = normalize(WS_V + WS_L);
                tex *= (dot(WS_L, WS_N) * 0.5 + 0.5) * mylight.shadowAttenuation * real4(mylight.color, 1);
                float4 Specular = pow(max(dot(WS_N, WS_H), 0), _Gloss) * _SpecularColor * mylight.shadowAttenuation;
                return tex+Specular  ;
            }
ENDHLSL
        }
        UsePass "Universal Render Pipeline/Lit/ShadowCaster"
    }
}