閱讀本文前最好先看一下Unity custom shader中調(diào)用內(nèi)置Lightmap和Light Probes
我們知道自阱,unity中的Lighting Mode分為三種茅姜,baked indirect晰奖,subtractive和shadowmask三種。
baked indirect模式混合了實(shí)時(shí)直接光和烘焙間接光爽室,并提供了實(shí)時(shí)陰影。由于光照比較真實(shí),陰影精確度比較可信拒秘,所以非常適合中端硬件。
subtractive模式提供直接和間接光的烘焙臭猜,直接實(shí)時(shí)陰影只受一盞方向光的影響躺酒。由于它并不能提供非常真實(shí)的光照結(jié)果,所以風(fēng)格化的渲染或者低端硬件比較適合這種模式蔑歌。
shadowmask模式混合了實(shí)時(shí)直接光和烘焙間接光羹应,它能烘焙物體的陰影并能自動(dòng)混合實(shí)時(shí)陰影。這種模式看起來最為真實(shí)但也最消耗資源次屠,不過可以在Quality Settings進(jìn)行調(diào)整园匹。高中端設(shè)備適合這種模式。
有了初步的印象劫灶,我們來看看具體使用時(shí)的不同之處
從生成的文件來看裸违,shadowmask模式比subtractive模式多了一張貼圖,這張貼圖保存了靜態(tài)物體的陰影烘焙信息本昏。那么subtractive模式?jīng)]有這張貼圖是不是意味著沒有陰影信息呢供汛?其實(shí)不是的,subtractive模式中靜態(tài)物體的陰影也烘焙了涌穆,并且保存在了光照貼圖中紊馏,也就是說陰影信息其實(shí)是shadowmask模式額外多用了一張貼圖來保存,并不是說subtractive模式陰影信息沒了蒲犬。
那么在shadowmask模式下有了這張陰影貼圖后我們?cè)撊绾稳ナ褂媚刂旒啵靠聪旅娴拇a
float directAtten = SHADOW_ATTENUATION(i);
float viewZ = dot(_WorldSpaceCameraPos - i.worldPos, UNITY_MATRIX_V[2].xyz);
float shadowFadeDistance = UnityComputeShadowFadeDistance(i.worldPos, viewZ);
float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
float bakedAtten = UnitySampleBakedOcclusion(i.uvLM,i.worldPos);
float atten = UnityMixRealtimeAndBakedShadows(directAtten, bakedAtten, shadowFade);
col.rgb *= atten;
col.rgb += lm;
首先我們需要UnitySampleBakedOcclusion
這個(gè)方法來獲取烘焙陰影,它需要光照貼圖的uv和世界坐標(biāo)來進(jìn)行采樣原叮,采樣完畢所得到的陰影需要再扔進(jìn)UnityMixRealtimeAndBakedShadows
這個(gè)方法赫编,同時(shí)扔進(jìn)實(shí)時(shí)陰影和一個(gè)距離來進(jìn)行混合,使得烘焙和實(shí)時(shí)的陰影不至于涇渭分明奋隶。
現(xiàn)在我們來詳細(xì)看看這兩個(gè)方法在干什么擂送,UnitySampleBakedOcclusion
的源碼如下
fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos)
{
#if defined (SHADOWS_SHADOWMASK)
#if defined(LIGHTMAP_ON)
fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
#else
fixed4 rawOcclusionMask = fixed4(1.0, 1.0, 1.0, 1.0);
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
rawOcclusionMask = LPPV_SampleProbeOcclusion(worldPos);
else
rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
#else
rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
#endif
#endif
return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector));
#else
//In forward dynamic objects can only get baked occlusion from LPPV, light probe occlusion is done on the CPU by attenuating the light color.
fixed atten = 1.0f;
#if defined(UNITY_INSTANCING_ENABLED) && defined(UNITY_USE_SHCOEFFS_ARRAYS)
// ...unless we are doing instancing, and the attenuation is packed into SHC array's .w component.
atten = unity_SHC.w;
#endif
#if UNITY_LIGHT_PROBE_PROXY_VOLUME && !defined(LIGHTMAP_ON) && !UNITY_STANDARD_SIMPLE
fixed4 rawOcclusionMask = atten.xxxx;
if (unity_ProbeVolumeParams.x == 1.0)
rawOcclusionMask = LPPV_SampleProbeOcclusion(worldPos);
return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector));
#endif
return atten;
#endif
}
首先可以知道,我們要用UnitySampleBakedOcclusion
不得不聲明一下SHADOWS_SHADOWMASK
唯欣,否則進(jìn)入else邏輯就不讀取陰影貼圖了(這時(shí)的陰影就要去讀取光照探針中的數(shù)據(jù)了嘹吨,傳入的世界坐標(biāo)worldPos
這時(shí)就有用了)。境氢。蟀拷。 然后主要的代碼就涉及到如果有光照貼圖的話碰纬,去采樣unity_ShadowMask
貼圖。采樣出來的rawOcclusionMask
記錄的是該像素问芬,被燈光影響的情況悦析,比如采樣的是(1,1,0,1)那么表示這個(gè)像素,被0號(hào)和1此衅、3號(hào)燈照射到了强戴,但是2號(hào)燈,不能照射到挡鞍,這是原始的遮擋信息骑歹。 unity_OcclusionMaskSelector
就是說,unity會(huì)自動(dòng)的進(jìn)行篩選最強(qiáng)的燈(像是(0,1,0,0)墨微,不可能出現(xiàn)(1,1,0,0)這種有最強(qiáng)的兩盞燈)道媚,這個(gè)unity_OcclusionMaskSelector就記錄對(duì)應(yīng)的燈的編號(hào)而已。dot
就是過濾出來真正需要的信息欢嘿,比如說現(xiàn)在rawOcclusionMask
是(0.5,0.1,0,0.8)瞧预,unity_OcclusionMaskSelector
是(0,1,0,0)相味,那么過濾出來就是0.1色查,rawOcclusionMask
的g通道的值被保留下來了冀宴。
UnityMixRealtimeAndBakedShadows
的源碼如下
half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
// -- Static objects --
// FWD BASE PASS
// ShadowMask mode = LIGHTMAP_ON + SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
// Distance shadowmask mode = LIGHTMAP_ON + SHADOWS_SHADOWMASK
// Subtractive mode = LIGHTMAP_ON + LIGHTMAP_SHADOW_MIXING
// Pure realtime direct lit = LIGHTMAP_ON
// FWD ADD PASS
// ShadowMask mode = SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
// Distance shadowmask mode = SHADOWS_SHADOWMASK
// Pure realtime direct lit = LIGHTMAP_ON
// DEFERRED LIGHTING PASS
// ShadowMask mode = LIGHTMAP_ON + SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
// Distance shadowmask mode = LIGHTMAP_ON + SHADOWS_SHADOWMASK
// Pure realtime direct lit = LIGHTMAP_ON
// -- Dynamic objects --
// FWD BASE PASS + FWD ADD ASS
// ShadowMask mode = LIGHTMAP_SHADOW_MIXING
// Distance shadowmask mode = N/A
// Subtractive mode = LIGHTMAP_SHADOW_MIXING (only matter for LPPV. Light probes occlusion being done on CPU)
// Pure realtime direct lit = N/A
// DEFERRED LIGHTING PASS
// ShadowMask mode = SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
// Distance shadowmask mode = SHADOWS_SHADOWMASK
// Pure realtime direct lit = N/A
#if !defined(SHADOWS_DEPTH) && !defined(SHADOWS_SCREEN) && !defined(SHADOWS_CUBE)
#if defined(LIGHTMAP_ON) && defined (LIGHTMAP_SHADOW_MIXING) && !defined (SHADOWS_SHADOWMASK)
//In subtractive mode when there is no shadow we kill the light contribution as direct as been baked in the lightmap.
return 0.0;
#else
return bakedShadowAttenuation;
#endif
#endif
#if (SHADER_TARGET <= 20) || UNITY_STANDARD_SIMPLE
//no fading nor blending on SM 2.0 because of instruction count limit.
#if defined(SHADOWS_SHADOWMASK) || defined(LIGHTMAP_SHADOW_MIXING)
return min(realtimeShadowAttenuation, bakedShadowAttenuation);
#else
return realtimeShadowAttenuation;
#endif
#endif
#if defined(LIGHTMAP_SHADOW_MIXING)
//Subtractive or shadowmask mode
realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
return min(realtimeShadowAttenuation, bakedShadowAttenuation);
#endif
//In distance shadowmask or realtime shadow fadeout we lerp toward the baked shadows (bakedShadowAttenuation will be 1 if no baked shadows)
return lerp(realtimeShadowAttenuation, bakedShadowAttenuation, fade);
}
相對(duì)于上一個(gè)方法簇抵,這個(gè)理解起來比較簡(jiǎn)單怯屉,根據(jù)不同情況返回實(shí)時(shí)陰影或者烘焙陰影窥淆,或者通過距離(點(diǎn)到攝像頭的距離)來進(jìn)行烘焙陰影與實(shí)時(shí)陰影的混合驰怎。
現(xiàn)在來看這個(gè)方法里的fade
參數(shù)钞馁,通過代碼可以看出我們需要調(diào)用UnityComputeShadowFadeDistance
和UnityComputeShadowFade
來計(jì)算得到最終的fade
虑省,那么就來看看這兩個(gè)方法在干什么吧。
UnityComputeShadowFadeDistance
源碼如下
float UnityComputeShadowFadeDistance(float3 wpos, float z)
{
float sphereDist = distance(wpos, unity_ShadowFadeCenterAndType.xyz);
return lerp(z, sphereDist, unity_ShadowFadeCenterAndType.w);
}
這里unity_ShadowFadeCenterAndType
包含了陰影中心位置(xyz分量保存)和陰影的類型(w分量保存)僧凰,由世界坐標(biāo)wpos
計(jì)算得到與陰影中心位置的距離sphereDist
探颈,按照陰影類型在視線距離z
和sphereDist
之間做lerp操作來得到陰影fade的距離。不過我谷歌了一下训措,這個(gè)陰影類型w
分量不是0就是1伪节,也就是說最終結(jié)果返回的不是z
就是sphereDist
。
UnityComputeShadowFade
源碼如下
half UnityComputeShadowFade(float fadeDist)
{
return saturate(fadeDist * _LightShadowData.z + _LightShadowData.w);
}
上面計(jì)算得到的陰影fade的距離還需要進(jìn)一步的被矯正绩鸣,所以需要把計(jì)算得到的距離扔進(jìn)UnityComputeShadowFade
方法怀大,而_LightShadowData
的z
分量包含著scale信息,w
分量包含著offset信息呀闻,計(jì)算一下后將結(jié)果限制在0-1之間化借,就是最終陰影fade的距離了。
最后捡多,我們把UnityMixRealtimeAndBakedShadows
計(jì)算得出的陰影乘上輸出的顏色蓖康,再加上(shadowmask模式下烘焙出來的光照貼圖比較暗铐炫,加比較能還原真實(shí)結(jié)果。而subtractive模式下是乘上去比較能還原結(jié)果钓瞭,個(gè)人覺得這里是加還是乘以美術(shù)導(dǎo)向?yàn)闇?zhǔn))光照貼圖就好了驳遵。
這里要注意,shadowmask有兩種模式山涡,distance shadowmask和shadowmask堤结。Distance shadowmask模式使用時(shí)第一眼看上去和baked indirect模式一樣,陰影都是實(shí)時(shí)計(jì)算的鸭丛。然而竞穷,還是會(huì)有一張陰影貼圖生成,這張貼圖中的陰影是當(dāng)超出場(chǎng)景設(shè)置中的陰影距離時(shí)使用的鳞溉,而在這個(gè)距離內(nèi)都會(huì)使用實(shí)時(shí)陰影瘾带,所以這個(gè)模式會(huì)是最為昂貴的模式。我們之前代碼里已經(jīng)用上了UnityMixRealtimeAndBakedShadows
這個(gè)方法熟菲,所以可以提供實(shí)時(shí)和烘焙陰影的過渡看政。
PS. 只有直接光的情況下UNITY_LIGHT_ATTENUATION
就是UNITY_SHADOW_ATTENUATION
,而UNITY_SHADOW_ATTENUATION
就是SHADOW_ATTENUATION
抄罕。
# define UNITY_SHADOW_ATTENUATION(a, worldPos) SHADOW_ATTENUATION(a)
#ifdef DIRECTIONAL
# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
#endif
最后允蚣,我們來明確下各個(gè)模式下對(duì)動(dòng)靜態(tài)物體的影響。
distance shadowmask模式
- 動(dòng)態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光呆贿,通過光照探針
- 在陰影距離內(nèi)動(dòng)態(tài)物體投出的實(shí)時(shí)陰影嚷兔,陰影源自shadowmap技術(shù)
- 在陰影距離內(nèi)靜態(tài)物體投出的實(shí)時(shí)陰影,陰影源自shadowmap技術(shù)
- 超出陰影距離后使用了光照探針的靜態(tài)物體所產(chǎn)生的烘焙陰影
- 靜態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光做入,通過光照貼圖
- 在陰影距離內(nèi)動(dòng)態(tài)物體投出的實(shí)時(shí)陰影冒晰,陰影源自shadowmap技術(shù)
- 在陰影距離內(nèi)靜態(tài)物體投出的實(shí)時(shí)陰影,陰影源自shadowmap技術(shù)
- 超出陰影距離后靜態(tài)物體所產(chǎn)生的烘焙陰影竟块,通過shadowmask貼圖實(shí)現(xiàn)
shadowmask模式
- 動(dòng)態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光壶运,通過光照探針
- 在陰影距離內(nèi)動(dòng)態(tài)物體投出的實(shí)時(shí)陰影,陰影源自shadowmap技術(shù)
- 超出陰影距離后使用了光照探針的靜態(tài)物體所產(chǎn)生的烘焙陰影
- 靜態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光浪秘,通過光照貼圖
- 在陰影距離內(nèi)動(dòng)態(tài)物體投出的實(shí)時(shí)陰影蒋情,陰影源自shadowmap技術(shù)
- 超出陰影距離后使用了陰影貼圖的靜態(tài)物體所產(chǎn)生的烘焙陰影
subtractive模式
- 動(dòng)態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光,通過光照探針
- 在陰影距離內(nèi)動(dòng)態(tài)物體由主光源投出的實(shí)時(shí)陰影秫逝,陰影源自shadowmap技術(shù)
- 使用了光照探針的靜態(tài)物體所產(chǎn)生的陰影
- 靜態(tài)物體被混合光照到會(huì)接受到:
- 烘焙直接光恕出,通過光照貼圖
- 烘焙間接光,通過光照貼圖
- 靜態(tài)物體產(chǎn)生的烘焙陰影违帆,通過光照貼圖
- 在陰影距離內(nèi)動(dòng)態(tài)物體由主光源投出的實(shí)時(shí)陰影浙巫,陰影源自shadowmap技術(shù)
baked indirect模式
- 動(dòng)態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光,通過光照探針
- 在陰影距離內(nèi)動(dòng)態(tài)物體投出的陰影,陰影源自shadowmap技術(shù)
- 在陰影距離內(nèi)靜態(tài)物體投出的實(shí)時(shí)陰影的畴,陰影源自shadowmap技術(shù)
- 靜態(tài)物體被混合光照到會(huì)接受到:
- 實(shí)時(shí)直接光
- 烘焙間接光渊抄,通過光照貼圖
- 靜態(tài)物體產(chǎn)生的實(shí)時(shí)陰影,陰影源自shadowmap技術(shù)
- 在陰影距離內(nèi)動(dòng)態(tài)物體產(chǎn)生的實(shí)時(shí)陰影丧裁,陰影源自shadowmap技術(shù)
參考
unity中UnityGlobalIllumination.cginc的分析
Mixed Lighting Lightmap & Shader In Unity | Unity中混合光照Lightmap研究
Rendering 17 Mixed Lighting
Unity 陰影淡入淡出效果中Shader常量 unity_ShadowFadeCenterAndType和_LightShadowData的問題