環(huán)境
Unity 2019.4?
URP7.5.2
如何做
1.Unity?編輯器設(shè)置部分

2.Shader?代碼部分
分為兩個(gè)部分:投射和接收
投射部分:
需要在Shader?中額外添加一個(gè)名稱(chēng)為"ShadowCaster"?的Pass。
1.全局變量聲明:
float3 _LightDirection;
2.頂點(diǎn)著色器:
分別計(jì)算頂點(diǎn)的世界空間坐標(biāo)和世界空間法線:
float3 worldPos = TransformObjectToWorld(i.vertex.xyz);
float3 normal = TransformObjectToWorldNormal(i.normal);
使用內(nèi)置的Shadows.hlsl 文件中提供的方法計(jì)算陰影偏移(bias),然后將其轉(zhuǎn)換到裁剪空間中:
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
然后確保陰影偏移后的位置不會(huì)超出裁剪空間:
#if UNITY_REVERSED_Z
????positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
?#else
????positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
3.片元著色器
最后在片源著色器中,根據(jù)需要保留片元:如果投射陰影的網(wǎng)格(mesh)是不透明物體,直接返回0;否則可以根據(jù)透明度測(cè)試或其他規(guī)則來(lái)裁剪片元,這里直接返回0;
return 0;
接收部分:
1.關(guān)鍵字聲明:
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS //開(kāi)啟額外光源
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS //主光源陰影
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE //主光源層級(jí)陰影是否開(kāi)啟
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS //額外光源陰影
#pragma multi_compile _ _SHADOWS_SOFT //軟陰影
2.頂點(diǎn)輸出(片元輸入)結(jié)構(gòu)體:
添加陰影坐標(biāo)寄存器聲明:
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
????float4 shadowCoord : TEXCOORD1;
#endif
3.頂點(diǎn)著色器:
按需計(jì)算陰影坐標(biāo)
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
? ? o.shadowCoord = TransformWorldToShadowCoord(o.worldPos);
#endif
4.片元著色器:
獲取主光源陰影坐標(biāo):
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
????float4 shadowCoord = input.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
????float4 shadowCoord = TransformWorldToShadowCoord(inputData.positionWS);
#else
????float4 shadowCoord = float4(0, 0, 0, 0);
endif
獲取主光源結(jié)構(gòu)體(計(jì)算主光源的陰影衰減)然后混合陰影:
Light main = GetMainLight(shadowCoord);
o *= main.shadowAttenuation;
獲取額外光源結(jié)構(gòu)體(計(jì)算額外光源陰影衰減)然后混合額外光源陰影:
#ifdef _ADDITIONAL_LIGHTS
????uint pixelLightCount = GetAdditionalLightsCount();
????for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
????{
????????Light light = GetAdditionalLight(lightIndex, i.worldPos);//獲得額外光源結(jié)構(gòu)體(內(nèi)部包含陰影衰減)
? ? ? ? o *= light.shadowAttenuation;
????}
#endif
為什么
投射部分:
1.全局變量聲明:
聲明_LightDirection?是為了從Unity?中獲取當(dāng)前主光源的方向,在ShadowUtils.cs?文件中可以看到:

2.頂點(diǎn)著色器:
在Shadows.hlsl?文件中ApplyShadowBias?函數(shù)定義:

可以看到其原理就是分別根據(jù)法線方向和光照方向偏移頂點(diǎn),其中偏移量_ShadowBias?就是管線配置文件當(dāng)中的設(shè)置數(shù)值:

接收部分
1.頂點(diǎn)輸出(片元輸入)結(jié)構(gòu)體及頂點(diǎn)著色器:
根據(jù)是否實(shí)時(shí)計(jì)算主光源陰影和是否開(kāi)啟層級(jí)陰影(shadow cascade)來(lái)確定是否在頂點(diǎn)著色器中計(jì)算陰影坐標(biāo)然后輸出。
在Shadows.hlsl?文件中可以找到相關(guān)關(guān)鍵字的聲明:

這其中_MAIN_LIGHT_SHADOWS、_MAIN_LIGHT_SHADOWS_CASCADE 、_ADDITIONAL_LIGHT_SHADOWS?這三個(gè)關(guān)鍵字就是一開(kāi)始聲明的關(guān)鍵字,聲明了這些關(guān)鍵字后,就可以通過(guò)Unity?渲染管線配置文件來(lái)控制這些關(guān)鍵字是否開(kāi)啟,從而控制shader?的行為:

在Shader?中添加屬性和關(guān)鍵字聲明:
[ToggleOff]_Receive_Shadows("接收陰影",?Float)?=?1.0
#pragma?shader_feature?_RECEIVE_SHADOWS_OFF?
就可以通過(guò)shader?來(lái)控制是否接收來(lái)自其他mesh?投射的陰影(實(shí)際上是否讀取陰影貼圖數(shù)據(jù)):

因此綜合來(lái)看,只有在接收陰影并且配置了主光源投射陰影且啟用了層級(jí)陰影時(shí),才會(huì)在頂點(diǎn)著色器中計(jì)算陰影坐標(biāo);否則如果沒(méi)有開(kāi)啟層級(jí)陰影但是主光源投射陰影時(shí),則直接在片元著色器中計(jì)算陰影坐標(biāo)。
獲取陰影坐標(biāo)的函數(shù)TransformWorldToShadowCoord?方法在Shadows.hlsl?中可以找到:

源碼首先根據(jù)是否開(kāi)啟了主光源層級(jí)陰影來(lái)獲取陰影層級(jí),然后將世界坐標(biāo)轉(zhuǎn)換到主光源的相應(yīng)層級(jí)的陰影圖空間下。
2.片元著色器
首先根據(jù)不同的設(shè)置來(lái)獲取主光源的陰影坐標(biāo)。
GetMainLight?方法可以在Unity?內(nèi)置的Lighting.hlsl?文件中找到其定義:

其中MainLightRealtimeShadow?給出了主光源實(shí)時(shí)陰影衰減,可以在Shadows.hlsl?文件中找到其定義:

通過(guò)前面計(jì)算的陰影坐標(biāo)來(lái)采樣陰影圖:

首先根據(jù)是否開(kāi)啟軟陰影來(lái)確定陰影圖采樣方式,如果開(kāi)啟了軟陰影(_SHADOWS_SOFT),那么內(nèi)部將會(huì)根據(jù)不同的平臺(tái)使用卷積的方式來(lái)采樣陰影:


然后根據(jù)Light?控件中設(shè)置的陰影強(qiáng)度來(lái)調(diào)整陰影:


以上就是主光源實(shí)時(shí)陰影計(jì)算過(guò)程。
對(duì)于額外光源陰影計(jì)算,在Lighting.hlsl?文件中的GetAdditionalLight?方法通過(guò)調(diào)用Shadows.hlsl?文件當(dāng)中AdditionalLightRealtimeShadow?方法來(lái)計(jì)算:

首先計(jì)算了陰影坐標(biāo)然后使用主光源一樣的方式采樣額外光源的陰影貼圖。
以上就是額外光源的陰影計(jì)算過(guò)程。
源碼
Shader "Custom/Shadow"
{
? ? Properties
? ? {
_Color("基本顏色", color) = (1, 1, 1, 1)
? ? ? ? [ToggleOff]_Receive_Shadows("接收陰影", Float) = 1.0
? ? }
? ? SubShader
? ? {
? ? ? ? Tags
{
"RenderType"="Opaque"
"Queue"="Geometry"
"RenderPipeline"="UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
ENDHLSL
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
? ? ? ? ? ? #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
? ? ? ? ? ? #pragma shader_feature _RECEIVE_SHADOWS_OFF
struct a2v
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
? ? ? ? ? ? ? ? float3 worldPos : TEXCOORD0;
? ? ? ? ? ? ? ? #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
? ? ? ? ? ? ? ? ? ? float4 shadowCoord : TEXCOORD1;
? ? ? ? ? ? ? ? #endif
};
CBUFFER_START(UnityPerMaterial)
real4 _Color;
? ? ? ? ? ? CBUFFER_END
v2f vert(a2v i)
{
v2f o;
o.worldPos = TransformObjectToWorld(i.vertex.xyz);
o.vertex = TransformWorldToHClip(o.worldPos.xyz);
? ? ? ? ? ? ? ? #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
? ? o.shadowCoord = TransformWorldToShadowCoord(o.worldPos);
? ? ? ? ? ? ? ? #endif
return o;
}
real4 frag(v2f i) : SV_TARGET
{
real4 o = _Color;
? ? ? ? ? ? ? ? ////獲取主光源陰影坐標(biāo)
? ? ? ? ? ? ? ? #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
? ? ? ? ? ? ? ? ? ? float4 shadowCoord = i.shadowCoord;
? ? ? ? ? ? ? ? #elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
? ? ? ? ? ? ? ? ? ? float4 shadowCoord = TransformWorldToShadowCoord(i.worldPos);
? ? ? ? ? ? ? ? #else
? ? ? ? ? ? ? ? ? ? float4 shadowCoord = float4(0, 0, 0, 0);
? ? ? ? ? ? ? ? #endif
? ? ? ? ? ? ? ? Light main = GetMainLight(shadowCoord);//獲得主光源結(jié)構(gòu)體(內(nèi)部包含陰影衰減)
? ? ? ? ? ? ? ? o *= main.shadowAttenuation;
? ? ? ? ? ? ? ? #ifdef _ADDITIONAL_LIGHTS
? ? ? ? ? ? ? ? ? ? uint pixelLightCount = GetAdditionalLightsCount();
? ? ? ? ? ? ? ? ? ? for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Light light = GetAdditionalLight(lightIndex, i.worldPos);//獲得額外光源結(jié)構(gòu)體(內(nèi)部包含陰影衰減)
? ? ? ? ? ? ? ? ? ? ? ? o *= light.shadowAttenuation;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? #endif
return o;
}
ENDHLSL
}
Pass
? ? ? ? {
? ? ? ? ? ? Name "ShadowCaster"
? ? ? ? ? ? Tags{"LightMode" = "ShadowCaster"}
? ? ? ? ? ? ZWrite On
? ? ? ? ? ? ZTest LEqual
? ? ? ? ? ? ColorMask 0
? ? ? ? ? ? Cull[_Cull]
? ? ? ? ? ? HLSLPROGRAM
? ? ? ? ? ? #pragma vertex vert
? ? ? ? ? ? #pragma fragment frag
? ? ? ? ? ? struct a2v
? ? ? ? ? ? {
? ? ? ? ? ? ? ? float4 vertex : POSITION;
? ? ? ? ? ? ? ? float3 normal : NORMAL;
? ? ? ? ? ? };
? ? ? ? ? ? struct v2f
? ? ? ? ? ? {
? ? ? ? ? ? ? ? float4 vertex : SV_POSITION;
? ? ? ? ? ? };?
? ? ? ? ? ? float3 _LightDirection; //將會(huì)由Unity 完成賦值
? ? ? ? ? ? float4 GetShadowPositionHClip(float3 positionWS, float3 normalWS)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
? ? ? ? ? ? ? ? #if UNITY_REVERSED_Z
? ? ? ? ? ? ? ? ? ? positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
? ? ? ? ? ? ? ? #else
? ? ? ? ? ? ? ? ? ? positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
? ? ? ? ? ? ? ? #endif
? ? ? ? ? ? ? ? return positionCS;
? ? ? ? ? ? }
? ? ? ? ? ? v2f vert(a2v i)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? v2f o;
? ? ? ? ? ? ? ? float3 worldPos = TransformObjectToWorld(i.vertex.xyz);
? ? ? ? ? ? ? ? float3 normal = TransformObjectToWorldNormal(i.normal);
? ? ? ? ? ? ? ? o.vertex = GetShadowPositionHClip(worldPos, normal);
? ? ? ? ? ? ? ? return o;
? ? ? ? ? ? }
? ? ? ? ? ? half4 frag(v2f i) : SV_TARGET
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? }? ? ? ?
? ? ? ? ? ? ENDHLSL
? ? ? ? }
? ? }
}
注意事項(xiàng)
1.Unity URP 源碼Shadows.hlsl 文件中提供的計(jì)算主光源陰影函數(shù)默認(rèn)主光源是平行光(無(wú)透視)、提供的計(jì)算額外光源陰影函數(shù)默認(rèn)所有額外光源是有透視的,如果需要額外的平行光,可以自己寫(xiě)一個(gè)計(jì)算函數(shù)。
2.接收和投射并不一定都需要,即可以做只接收不投射或只投射不接收的效果。
3.如果著色器有頂點(diǎn)偏移的效果,注意頂點(diǎn)偏移后對(duì)陰影投射的影響,投射時(shí)應(yīng)該將投射Pass 中的頂點(diǎn)做相同的偏移處理。