1、原理
1.1、概述
由一個物體向其他物體投射陰影,以及一個物體如何接收其他物體的陰影,
實時渲染中經常使用 Shadow Map 技術,它會首先把攝像機的位置放在與光源重合的位置上, 那么場景中該光源的陰影區域就是那些攝像機看不到的地方,Unity 就是使用的這種技術,
在前向渣染路徑中, 如果場景中最重要的平行光開啟了陰影, Unity 就會為該光源計算它的陰影映射紋理( shadow map), 這張陰影映射紋理本質上也是一張深度圖, 它記錄了從該光源的位置出發、 能看到的場景中距離它最近的表面的深度資訊,
1.2、ShadowCaster Pass
Unity 選擇使用一個額外的 Pass 來專門更新光源的陰影映射紋理, 這個 Pass 就是 LightMode 標簽被設
置為 ShadowCaster 的 Pass,
這個 Pass 的渲染目標為深度紋理,
Unity 首先把攝像機放置到光源的位置上, 然后呼叫該 Pass, 通過對頂點變換后得到光源空間下的位置, 并據此來輸出深度資訊到陰影映射紋理中,
1.3、Screenspace Shadow Map
正常情況下,我們在渲染陰影時,會將每個頂點位置變換到光源空間下, 然后使用 xy 分量對陰影映射紋理進行采樣,得到陰影映射紋理中該位置的深度資訊, 如果該深度值小于該頂點的深度值( z 分量),那么說明該點位于陰影中,
Unity 提出了一種新的采樣技術:螢屏空間的陰影映射技術(Screenspace Shadow Map),
這項技術原本時延遲渲染中產生陰影的一種方法,應用這項技術要求顯卡支持 MRT ,
Unity 首先會通過呼叫 LightMode 為 ShadowCaster 的 Pass 來得到可投射陰影的光源的陰影映射紋理以及攝像機的深度紋理,
然后, 根據光源的陰影映射紋理和攝像機的深度紋理來得到螢屏空間的陰影圖, 如果攝像機的深度圖中記錄的表面深度大于轉換到陰影映射紋理中的深度值, 就說明該表面雖然是可見的, 但是卻處于該光源的陰影中,
如果我們想要一個物體接收來自其他物體的陰影, 只需要在 Shader 中把表面坐標從模型空間變換到螢屏空間, 然后使用這個坐標對陰影圖進行采樣即可,
1.4、總結
一個物體接收來自其他物體的陰影, 以及它向其他物體投射陰影是兩個程序,
- 如果我們想要一個物體接收來自其他物體的陰影, 就必須:
- 在 Shader 中對陰影映射紋理( 包括螢屏空間的陰影圖) 進行采樣, 把采樣結果和最后的光照結果相乘來產生陰影效果,
- 再打開模型的 Mesh Render 組件的 Receive Shadows 屬性,
- 如果我們想要一個物體向其他物體投射陰影, 就必須:
- 在 Shader 中添加標簽為 ShadowCaster 的 Pass ,
- 更改模型的 Mesh Render 組件的 Cast Shadows 屬性,
如果使用了螢屏空間的投影映射技術, Unity 還會使用這個 Pass 產生一張攝像機的深度紋理,
如果沒有手動添加標簽為 ShadowCaster 的 Pass ,Unity 可能會在 Fallback 中找到合適的 Pass ,
2、不透明物體的陰影
2.1、向物體投射陰影
可以直接利用 FallBack 中的代碼實作投射陰影,
2.2、讓物體接收陰影
2.2.1、計算程序
使用宏來計算陰影,使用 Base Pass 計算陰影,
- 添加
#include "AutoLight.cginc" - 在 v2f 中添加
SHADOW_COORDS(2) - 頂點著色器中添加
TRANSFER_SHADOW(o); - 片元著色器中添加
fixed shadow = SHADOW_ATTENUATION(i); - 片元著色器的輸出修改為:
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
2.2.2、解釋
在前向渲染中, 宏 SHADOW_COORDS 實際就是宣告了一個名為 _ShadowCoord 的陰影紋理坐標變數,
TRANSFER_SHADOW 的實作會根據平臺不同而有所差異,
- 如果當前平臺可以使用螢屏空間的陰影映射技術(判斷是否定義了 UNITY_NO_SCREENSPACE_SHADOWS 來得到),TRANSFER_SHADOW 會呼叫內置的 ComputeScreenPos 函式來計算 _ShadowCoord;
- 如果該平臺不支持螢屏空間的陰影映射技術, 就會使用傳統的陰影映射技術,
TRANSFER_SHADOW 會把頂點坐標從模型空間變換到光源空間后存盤到 _ShadowCoord 中,
SHADOW_ATTENUATION 負責使用 _ShadowCoord 中的紋理進行采樣,得到陰影資訊,
由于這些宏中會使用背景關系變數來進行相關計算, 例如 TRANSFER_SHADOW 會使用 v.vertex 或 a.pos 來計算坐標, 因此為了能夠讓這些宏正確作業, 我們需要保證自定義的變數名和這些宏中使用的變數名相匹配,
我們需要保證: a2f 結構體中的頂點坐標變數名必須是 vertex , 頂點著色器的輸出結構體 v2f 必須命名為 v, 且 v2f 中的頂點位置變數必須命名為 pos ,
2.3、代碼
Shader "Unity Shaders Book/Chapter 9/Shadow" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;//光照衰減
fixed shadow = SHADOW_ATTENUATION(i);
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
//判斷是否是平行光,獲得世界坐標下光線方向
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
//判斷是否是平行光,處理衰減
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
3、統一管理光照衰減與陰影
使用內置宏UNITY_LIGHT_ATTENUATION,實作同時計算光照衰減因子與陰影,
在上述代碼的基礎上,修改片元著色器,
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
//使用宏UNITY_LIGHT_ATTENUATION
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
宏UNITY_LIGHT_ATTENUATION會幫助我們宣告 atten 變數,
該宏輸入三個變數:輸出引數名、結構體 v2f 、世界空間坐標,
該宏使用結構體 v2f ,呼叫 14.4 中的 SHADOW_ATTENUATION 宏實作陰影計算,
該宏使用世界空間下的坐標,計算光源空間下的坐標,并對衰減紋理采樣得到光照衰減,
如果我們希望可以在 Additional Pass 中添加陰影效果,
使用#pragma multi_compile_fwdadd_fullshadows指令
代替 Additional Pass 中的#pragma multi_compile_fwdadd指令,
這樣一來, Unity 也會為這些額外的逐像素光源計算陰影, 并傳遞給 Shader,
4、透明度測驗-陰影
FallBack 使用FallBack "Transparent/Cutout/VertexLit"即可讓鏤空的部分不投射陰影,
Shader "Unity Shaders Book/Chapter 9/AlphaTestWithShadow"
{
Properties
{
_Color ("Main Tint", Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white" {}
_Cutoff("Alpha CutOff", Range(0, 1)) = 0.5//透明度測驗的判斷條件
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector"="True" "RenderType" = "TransparentCutout"}
Pass{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
//屬性
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;//紋理縮放
fixed _Cutoff;
//輸入輸出結構體
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
//我們已經占用了三個插值暫存器,因此將其存盤在TEXCOORD3上
SHADOW_COORDS(3)
};
v2f vert (a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
//Alpha Test
clip(texColor.a - _Cutoff);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + diffuse * atten, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
5、透明度混合-陰影
生成透明度混合物體的陰影,需要在每個光源空間下仍然嚴格按照從后往前的順序進行渲染,
這會非常消耗性能,因此 Unity 所有內置的透明度混合的檔案都沒有包含陰影投射的 Pass,
如果一定需要陰影,可以將其當作不透明物體來設定有 ShadowCaster 標簽的 Pass ,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/499650.html
標籤:其他
上一篇:選擇排序/插入排序/冒泡排序
