Unity URP Shader 支持內(nèi)置陰影

環(huán)境

Unity 2019.4?

URP7.5.2

如何做

1.Unity?編輯器設(shè)置部分

支持Unity 內(nèi)置陰影編輯器設(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?文件中可以看到:

UnityURP腳本源碼對(duì)_LightDirection賦值

2.頂點(diǎn)著色器:

在Shadows.hlsl?文件中ApplyShadowBias?函數(shù)定義:

ApplyShadowBias

可以看到其原理就是分別根據(jù)法線方向和光照方向偏移頂點(diǎn),其中偏移量_ShadowBias?就是管線配置文件當(dāng)中的設(shè)置數(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)鍵字的聲明:

光照和陰影關(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?的行為:

渲染管線配置文件關(guān)鍵字控制入口

在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ù)):

使用shader_feature + 屬性 實(shí)現(xiàn)接收陰影關(guān)鍵字開(kāi)關(guān)

因此綜合來(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?中可以找到:

TransformWorldToShadowCoord?方法

源碼首先根據(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?文件中找到其定義:

GetMainLight?方法定義

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

MainLightRealtimeShadow 定義

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

SampleShadowmap 定義

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

根據(jù)不同平臺(tái)卷積采樣陰影圖
渲染管線配置中設(shè)置軟陰影

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

應(yīng)用陰影強(qiáng)度
Light 控件中設(shè)置陰影強(qiáng)度

以上就是主光源實(shí)時(shí)陰影計(jì)算過(guò)程。

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

AdditionalLightRealtimeShadow 方法

首先計(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)做相同的偏移處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容