<meta charset="utf-8">
一.陰影的實現(xiàn)原理
1.1. Shadow Map
在 Unity 的實時渲染中肮疗,我們采用的是 Shadow Map 技術(shù)。
原理:計算光源的陰影映射紋理扒接,記錄光源的位置出發(fā)伪货、能看到的場景中距離它最近的表面位置。簡單理解就是先把攝像機的位置與光源重合钾怔,然后攝像機看不到的區(qū)域就是陰影碱呼。
前向渲染中,如果平行光開啟了陰影(要注意需要手動開啟宗侦,創(chuàng)建了一個新光源愚臀,默認是沒有陰影的),Unity 就會為這個平行光計算陰影映射紋理矾利。這張陰影映射紋理實質(zhì)就是一張深度紋理姑裂,記錄著從光源出發(fā),距離光源最近的表面信息男旗。
unity選擇使用一個額外的pass來專門更新光源的陰影映射紋理舶斧,即LightMode標簽被設(shè)為ShadowCaster的pass。這個pass的渲染目標不是幀緩存察皇,而是陰影映射紋理(或者深度紋理)
1.2.屏幕空間的陰影映射技術(shù)(ScreenShadowMap)
原理:此技術(shù)根據(jù)光源的陰影映射紋理和攝像機的深度紋理來得到屏幕空間的陰影圖茴厉。如果攝像機的深度圖記錄的表面深度大于陰影映射紋理中的深度值,說明表面是可見的什荣。
方式:1.先調(diào)用LightMode為ShaderCaster的pass得到可投射陰影的光源的陰影映射紋理以及相機的深度紋理
2.然后根據(jù)光源的陰影映射紋理和相機的深度紋理得到屏幕空間的陰影圖矾缓。
限制:陰影映射紋理本質(zhì)上是一張深度圖,這個技術(shù)原本是延遲渲染產(chǎn)生陰影的方法稻爬,所以顯卡需要支持MRT嗜闻。
1.3. 總結(jié)
1.如果想要一個物體接收其它的物體的陰影,就要在 shader 中對陰影映射紋理進行采樣因篇,把采樣結(jié)果和光照結(jié)果相乘得到陰影效果泞辐。
2.如果想要一個物體向其它物體投射陰影,就要把該物體加入到陰影映射紋理之中竞滓,這一步驟是在 Shadow Pass 中實現(xiàn)的咐吼。
3.如果想要一個光源產(chǎn)生陰影效果,則需要手動選擇陰影類型:No Shadows , Hard Shadows , Soft Shadows商佑。Hard Shadows 相對于 Soft Shadows 計算量少一些锯茄,能滿足大部分場景,邊緣不平滑,鋸齒明顯肌幽。
二.不透明物體的陰影
2.1. 讓物體投射陰影
unity中晚碾,讓一個物體投射或者接收陰影,通過Mesh Render組件中的Cast Shadows和Receive Shadow屬性來實現(xiàn)喂急。
當 shader 中沒有 ShadowCaster 的 Pass 時會去它的 Fallback 里面找格嘁,我們之前的 Fallback 為 Specular,Specular 中也沒有這個 Pass, 但Specular 的回調(diào)Fallback 調(diào)用了VertexLit廊移。想看源碼的讀者糕簿,可以在 Unity 官方下載 內(nèi)置著色器 ,解壓之后狡孔,在 builtin-shaders-xxx->DefaultResourcesExtra -> Normal-VertexLit .shader 找到以下代碼:
// Pass to render object as a shadow caster
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_shadowcaster
#pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert( appdata_base v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag( v2f i ) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
默認情況下懂诗,計算光源的陰影映射紋理會剔除掉物體的背面∶缦ィ可以將Cast Shadows設(shè)置為two sided來允許對物體的所有面都進行計算殃恒。.
2.2. 讓物體接收陰影
步驟:
1.在頂點著色器的輸出結(jié)構(gòu)體添加內(nèi)置宏SHADOW_COORDS,作用是聲明一個用于對陰影紋理采樣的坐標,參數(shù)是下一個可用的插值寄存器的索引值辱揭。
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
2.在頂點著色器返回之前添加另一個內(nèi)置宏TRANSFER_SHADOW离唐,作用是在頂點著色器計算上一步聲明的陰影紋理坐標。
v2f vert(a2v v) {
v2f o;
...
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
3.在片元著色器計算陰影值界阁,使用另一個內(nèi)置宏SHADOW_ATTENUATION侯繁。
fixed shadow = SHADOW_ATTENUATION(i);
4.將得到的陰影值與漫反射顏色胖喳,高光反射顏色相乘泡躯。
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
注意點:這三個宏是天生的三個好基友,如果關(guān)閉了陰影丽焊,那么 SHADOW_COORDS较剃,TRANSFER_SHADOW 會不起作用,而 SHADOW_ATTENUATION 的值為 1 技健。那么漫反射顏色和高光反射顏色不受 shadow 影響写穴。而且這些宏會使用 v.vertex 和 a.pos 等變量來計算,所以 a2v 頂點坐標變量必須為 vertex雌贱,輸入結(jié)構(gòu)體 a2v 必須命名為 v 啊送,且 v2f 中頂點位置坐標為 pos。
2.3. 完善的的光照衰減和陰影管理
在Base Pass中欣孤,平行光的衰減因子總是等于1馋没,而在Additional Pass中,需要判斷該Pass處理的光源類型降传,再使用內(nèi)置變量和宏計算衰減因子篷朵。實際上,光照衰減和陰影對物體最終的渲染都是把光照衰減因子和陰影值以及光照結(jié)果相乘得到最終的渲染結(jié)果。
Unity 提供了一個內(nèi)置宏UNITY_LIGHT_ATTENUATION來同時得到光照衰減因子和陰影值声旺。
UNITY_LIGHT_ATTENUATION在AutoLight中的定義:
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
unityShadowCoord3 lightCoord = mul(unity_WorldToLight,unityShadowCoord4(worldPos, 1)).xyz; \
fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
#endif
完整代碼:
Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
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
// Need these files to get built-in macros
#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;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 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
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#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;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
如果想在Additional Pass添加陰影效果笔链,需要將#pragma multi_compile_fwdadd編譯指令替換成#pragma multi_compile_fwdadd_fullshadows,使得這些額外的逐像素光源進行陰影計算腮猖。
三.透明物體的陰影
3.1透明度測試
透明度測試結(jié)合之前Unity shader學(xué)習(xí)---透明效果代碼鉴扫,把 Fallback 改為 VertexLit,
陰影部分相當于整個正方體的陰影澈缺,但鏤空區(qū)域不應(yīng)該有陰影幔妨。這是因為 VertexLit 中處理陰影的 Pass 并沒有做透明度測試的計算。所以為了提供這樣的一個 Pass 谍椅,我們可以更改 Fallback 為 "Transparent/Cutout/VertexLit" 误堡。要注意的是,需要提供一個 _CutOff 的屬性和 SHADOW_COORDS 的索引值〕裕現(xiàn)在看一下效果:
3.2透明度混合
Shader "Unity Shaders Book/Chapter 9/Alpha Blend With Shadow" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
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);
// Pass shadow coordinates to pixel shader
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);
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 not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + diffuse * atten, texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
// Or force to apply shadow
// FallBack "VertexLit"
}
四.完整的光照 shader
包含了法線紋理锁施,多光源,光照衰減和陰影杖们,基于 Blinn-Phong 的高光發(fā)射 shader悉抵。
Shader "Unity Shaders Book/Common/Bumped Specular" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_Specular ("Specular Color", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
TANGENT_SPACE_ROTATION;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
作者:無職轉(zhuǎn)生者
鏈接:http://www.reibang.com/p/79329716d74b
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)摘完,非商業(yè)轉(zhuǎn)載請注明出處姥饰。