最近在整理UE的光照系統(tǒng)襟己,其中有一塊是間接光方案萎河,經過查閱資料了解到UE存在多種實現(xiàn)對間接光模擬的技術方案随抠,如Lightmap禁筏、Indirect Light Cache(ILC)、Volumetric Lightmap(VLM)、Light Propagation Volumes(LPV)以及各類后處理方案(SSGI第练、DFAO阔馋、SSAO,這部分由于效果十分有限复旬,通常用作輔助垦缅,本文就不做討論了)等,關于這些技術方案驹碍,很容易就想到如下的一些問題:
- 這些方案背后的技術原理是什么?
- 這個問題在后面的正文中有敘述凡恍,這里就不做回答了
- 為什么同樣一個問題志秃,會涌現(xiàn)出這么多不同的方案?
- 這是因為不同的方案適用于不同的應用場景嚼酝,不同的應用場景有不同的需求浮还。
- 這些方案之間的優(yōu)劣對比是如何的,各自具有如何的適用場景闽巩?
- lightmap方案通過額外的內存钧舌、包體消耗來避免運行時的計算消耗,對于靜態(tài)物體而言具有較好的效果涎跨,但是對于動態(tài)物體而言洼冻,就無法得到高質量的間接光與間接陰影效果
- ILC/VLM通過離線烘焙的方式得到一系列預生成的數(shù)據(jù),這些數(shù)據(jù)可以用于動態(tài)物體的間接光計算隅很,其不足在于精度不能太高撞牢,否則內存扛不住,其次則是probe的覆蓋范圍也不能過大叔营,否則內存消耗依然扛不住屋彪,因此在效果上會有局限。
- LPV方案則是完全運行時實時計算的間接光方案绒尊,通過較低的消耗實現(xiàn)任意位置的間接光效果畜挥,干掉了預處理的繁瑣流程,但是其不足在于運行時的計算消耗依然難以做到十分通用婴谱,尤其是對目前主流的移動平臺而言蟹但。
為了解答上述問題,這里單開一個篇章用做對相關技術學習后的總結與思考對比勘究。
UE的光源對場景的照亮主要是通過兩種方式矮湘,分別是直接光照明(實時動態(tài)計算)與Lightmass烘焙照明(離線烘焙,運行時使用)口糕。
Lightmass烘焙照明包含了如下的三種應用策略缅阳,分別是:
- 應用于靜態(tài)(Static)物體跟靜態(tài)或者非移動(Static or Stationary)光源的lightmap,由于Stationary光源在運行時會有可能發(fā)生光強與光照顏色的變化,而lightmap數(shù)據(jù)是離線烘焙出來的十办,因此無法在運行時變化秀撇,因此這里動態(tài)變化后的光源其lightmap效果可能會跟光源數(shù)據(jù)對不上
- 能夠cover住動態(tài)物體的ILC/VLM方案,由于lightmap是針對與靜態(tài)物體的向族,當物件變化(位置呵燕、朝向、縮放件相、形變等)后再扭,數(shù)據(jù)將不再可用,為了給動態(tài)物體(尤其是角色)加上間接光夜矗,需要額外的實現(xiàn)方案泛范,這里指的就是ILC/VLM。
UE的Lightmass對各個點間接光計算使用的算法是photon mapping紊撕,這個算法的原理可以參考此前的學習總結Photon Mapping算法學習總結
UE中的間接光方案可以分成靜態(tài)跟動態(tài)兩類罢荡,靜態(tài)間接光指的是作用于靜態(tài)物件與靜態(tài)光源的間接光效果,此類方案對于動態(tài)物體不生效对扶,主要有l(wèi)ightmap方案区赵;動態(tài)間接光方案指的是對動態(tài)物體也能起作用的間接光方案,有ILC浪南、VLM跟LPV等幾種方案笼才。
1. 靜態(tài)間接光方案
1.0 Lightmass間接光效果
Lightmass能夠提供眾多的間接光效果,下面逐一進行介紹逞泄。
1.0.1 Color Bleeding
也稱為Diffuse Interreflection患整,即打在相鄰物體上的光線反射到當前物體上會帶有相鄰物體的顏色的一種效果,這是間接光最常見的一種效果喷众。
1.0.2 character lighting
指的是動態(tài)物體身上感受到的間接光效果各谚,通過ILC或者VLM實現(xiàn)。但是目前有著一些不足之處:
- sample自動擺放的默認配置會導致一個大場景中擺放了大量的probe到千,從而導致插值時間消耗過高
- Lightmass Importance Volume之外的區(qū)域動態(tài)物件將感受不到GI效果
1.0.3 Ambient Occlusion
也叫Indirect Shadow昌渤,現(xiàn)在的效果有如下的一些問題:
- 需要較高分辨率才能出效果:因為邊角位置的AO變化是高頻的
- 因為AO需要較高密度的采樣,而preview模式的采樣點通常比較稀疏憔四,因此看到的AO效果跟實際效果相去甚遠
1.0.4 Masked shadows
實現(xiàn)Alpha Test物件的真實陰影膀息,如下圖所示:
1.0.5 Stationary Lights獨有的一些效果
1.0.5.1 Bent Normal sky occlusion
1.0.5.2 Distance field shadowmaps
1.0.6 Static Lights獨有的一些效果
1.0.6.1 Area lights and shadows
lightmass中的所有static光源都是被看成面光源的:
- 點光跟聚光燈的形狀是一個球,半徑通過Light Source Radius調整;
- 方向光的形狀是一個圓盤
影響陰影效果的兩個參數(shù)
- 光源尺寸
- 到occluder的距離
1.0.6.2 Translucent shadows
影響參數(shù)主要有兩個:
- 材質:不同的照明模式,計算邏輯不同:
Lit材質
混合模式
BLEND_Translucent and BLEND_Additive
Transmission = Lerp(White, BaseColor, Opacity)
BLEND_Modulate
Transmission = BaseColor
Unlit材質
混合模式
BLEND_Translucent and BLEND_Additive
Transmission = Lerp(White, Emissive, Opacity)
BLEND_Modulate
Transmission = Emissive
- 光源娄徊,參數(shù)為Light Source Angle,尺寸會影響陰影的銳利程度冗酿,下面給出不同的數(shù)值下的效果:
現(xiàn)有效果有如下幾點不足:
- Translucent Materials由于不參與light scatter,因此不會對周邊物體產生color bleeding
- 不支持折射(焦散)
- 半透材質不會對間接光計算中的第一次反彈光線造成影響
1.0.7 使用tips
tips主要涵蓋降低構建時間與提升構建質量兩項展開:
1.0.7.1 降低構建時間
Lighting Build Info彈窗面板可以看到各個物件的構建時長,主要有如下幾點使用策略:
- 只為高頻光影區(qū)域分配高分辨率lightmap裁替,不可見區(qū)域项玛、未暴露在直接光照的區(qū)域應該分配低分辨率lightmap
- 減少需要烘焙的物件數(shù)目,Lightmass Importance Volume將重點區(qū)域括起來
- 降低光照環(huán)境復雜度弱判,減少光源數(shù)目襟沮,縮小光照影響范圍
- 調整lightmap分辨率以使得場景中每個物件的build時間是相近的,并行構建昌腰,木桶原理开伏?
- 結構比較復雜的物件(比如帶有較多自遮擋)的構建時間會更長
1.0.7.2 提升構建質量
提升質量有兩方面的使用策略:
- 突出光照效果
1.1 對于diffuse貼圖而言,通過調整basecolor遭商,使之在光照下有較大的變化硅则,比如下面給出了高低對比度下的構建表現(xiàn),明顯高對比度質量更佳:
可以看到株婴,只有地板位置的高對比效果明顯。通過unlit模式可以很好的分析diffuse是否滿足條件暑认,unlit模式看起來比較平(flat)且色彩單調的困介,通常比較好,因為所有的顏色變化都是由光影控制蘸际。
1.2 對于光照配置而言
- 避免對全場景進行統(tǒng)一調整的環(huán)境光座哩,因為環(huán)境光會降低對比
- 增強直接光跟間接光區(qū)域的對比度
- 避免過曝跟過暗
- 增強光照質量
- 提升lightmap分辨率,這個會增加烘焙時間與內存消耗粮彤,可以考慮將lightmap分辨率分配給最需要的地方
- 調整solver根穷,跟lighting quality綁定,production level效果最好
1.1 Lightmap方案
什么是lightmap导坟?
Lightmap技術最早是由John Carmack在Quake(雷神之錘)中提出的屿良,目的是為了通過軟光柵解決此前頂點光照 Gouraud shading方案中光照效果過于粗糙的問題,后來隨著硬件進步惫周,逐漸能夠支持多貼圖采樣時尘惧,Lightmap則演變成了在離線的時候將光照結果緩存下來,運行時直接取用的方式來避免間接光照計算消耗過高的問題递递,從而可以實現(xiàn)實時性能下的間接光效果喷橙。
Lightmap方案的技術細節(jié)是什么樣的呢?
我們知道物件的顏色通常是通過頂點色以及texture來表示登舞,其中texture表示的顏色是通過從頂點上取得uv坐標經過插值之后傳遞到PS贰逾,在PS中使用插值后的UV對此貼圖進行采樣而得到。既然貼圖可以存儲顏色菠秒,而光源打在物件上某點反射出來的光照同樣是可以通過RGB來存儲的疙剑,那么是否可以考慮將這個結果在離線的時候計算完成,并將之存儲在貼圖中,運行時直接通過對應的uv對貼圖進行采樣實現(xiàn)相應的光照效果呢核芽?實際上囚戚,這就是Lightmap的大致原理,不過在實際實現(xiàn)的時候可能會因為各種細節(jié)的不同轧简,使得最終的Lightmap的解釋方式也有所不同驰坊。
最簡單的Lightmap中存儲的就是光照打在某個物件上經過物件交互后的反射光強,在運行時只需要在PS中通過uv對Lightmap進行采樣即可哮独。這里有些細節(jié)需要做一下定性的解釋:
- 首先拳芙,存儲在Lightmap中的反射光強由于是在離線的時候計算的,因此可以采用各種高端科技進行計算皮璧,以實現(xiàn)光照效果的最優(yōu)舟扎,這里的結果可以是直接光也可以包含間接光,通常來說悴务,不管光照計算方式多復雜睹限,反射次數(shù)有多高,其存儲的空間尺寸與運行時采樣的消耗是不變的讯檐,因此出于性價比考慮羡疗,這里通常會同時將直接光照與間接光照都考慮進去。
- 其次别洪,這里存儲的光照是針對靜態(tài)物體的叨恨,且輸入光源也理應是維持不變的,不然光照效果將會需要重新計算
- 第三挖垛,這里對Lightmap采樣所使用的UV跟物件顏色貼圖采樣所使用的UV是不同的痒钝,這也就是我們所說的2UV;2UV可以通過軟件自動生成(雖然效果可能不太好)痢毒,也可以通過其他建模軟件(比如3ds Max)實現(xiàn)手動配置送矩。
- 最后,同一個場景中擺放在不同位置的同一個模型闸准,因為位置不同益愈,受到的光照表現(xiàn)也是不同的,因此其Lightmap數(shù)據(jù)自然也是不同的夷家,但是同一個物件中存儲的2UV是相同的蒸其,這也就意味著,如果將這兩個物件實例的Lightmap數(shù)據(jù)存儲在一張Lightmap上库快,為了保證兩者采樣到的數(shù)據(jù)都是正確的摸袁,就需要一個額外的機制來將兩者的采樣區(qū)域錯開,最常見的方式就是為每個物件實例增加一個Lightmap Struct义屏,這個Struct會包含此實例對應的Lightmap的Index靠汁,同一個Lightmap中通常會存儲多個不同的物件實例的數(shù)據(jù)蜂大,因此這個Struct中還需要指定對應物件實例的Lightmap數(shù)據(jù)在對應Lightmap上的Texture Offset以及Texture Scale,如此才能為不同的物件實例映射到不同的Lightmap區(qū)域上蝶怔。
UE中的Lightmap是如何編碼的奶浦?
關于UE中的Lightmap編碼部分,可以參考此前整理的【UE】Unreal Lightmap編碼方案踢星,這里就不贅述了澳叉。
Lightmap有如下的一些優(yōu)缺點:
優(yōu)點:
- 可以以較低的計算消耗得到場景中絕大部分物件的間接光效果
缺點:
- 間接光表現(xiàn)取決于Lightmap的分辨率,而Lightmap分辨率過高會導致內存占用上升沐悦、包體增加成洗、帶寬消耗增加等問題;Lightmap分辨率過低則會導致效果存在異常反而導致光照質量的下降藏否。
- 增加了一次額外的采樣消耗瓶殃,Lightmap的使用也會導致內存包體帶寬的消耗增加等。
- 只能用于靜態(tài)物體+靜態(tài)光源的應用場景副签,光源與物件其中任一發(fā)生了變化(位移遥椿、形變、變色等)都會導致烘焙緩存的光照效果失效
此外需要注意的是淆储,Lightmap的生成范圍也是受LightmassImportanceVolume影響的修壕,只有處于這個范圍內的物件才會生成對應的lightmap(這是因為Lightmass的計算邏輯是不變的,而Lightmap跟后面ILC/VLM都是同一套算法計算得到的遏考,因此并無二致)
2. 動態(tài)間接光方案
動態(tài)間接光方案分為離線烘焙的方案與實時計算的方案,離線烘焙方案包含ILC跟VLM兩種蓝谨,前者是UE4.18及之前版本所使用的方案灌具,后者則是4.18之后版本使用的方案(在4.18以后的版本中ILC也是存在的,只是默認關閉的)譬巫;實時計算方案則主要是LPV方案咖楣。
離線烘焙方案中,主要是在場景中合適的位置放置若干個probe(這些probe同樣也是Lightmass用于烘焙Lightmap所使用的probe芦昔,只不過將數(shù)據(jù)保存下來以實現(xiàn)動態(tài)物體在運行時的光照計算诱贿,理論上這些probe存儲的數(shù)據(jù)靜態(tài)物體也是可以使用的,不過lightmap存儲的數(shù)據(jù)精度更高咕缎,計算消耗更低珠十,因此通常靜態(tài)物件不走這個光照計算邏輯),每個probe存儲的是使用SH編碼保存的光照信息凭豪,這些數(shù)據(jù)是在離線計算通過PRT算法采集各個probe位置處的光照信息得到的焙蹭,之后在運行時通過插值得到對應物件乃至對應像素位置的光照信息(依然使用SH編碼表達),通過SH系數(shù)的點乘求得對應法線位置的輸出Radiance結果嫂伞。
離線烘焙方案的效果給出如下:
2.1 Indirect Light Cache(ILC)方案
什么是ILC孔厉?這個術語包含兩重意思拯钻,分別是indirect light跟cache,前者表明這個方案是用于計算間接光的撰豺,每個probe中存儲的是對應位置向著各個方向輸出的Radiance信息粪般。Cache則意味著數(shù)據(jù)是帶有緩存的,通常計算場景中各個位置的probe的Radiance數(shù)據(jù)需要較高的消耗污桦,但是如果將這些結算得到的數(shù)據(jù)緩存下來亩歹,只有在失效(比如物件發(fā)生變化或者光照發(fā)生變化的時候)時才進行重新計算,就可以降低烘焙的時間消耗寡润,提升開發(fā)效率捆憎,這就是Cache的意義。
2.1.1 烘焙過程
具體而言梭纹,ILC只在Static物體上方(只要朝上即可躲惰,而非要求法線與Z軸平行)surface擺放高密度Probe(之所以設定為表面上方,是因為UE這邊考慮為角色可行走區(qū)域提供一些更為優(yōu)秀的光照效果变抽,當然不排除角色有攀爬可能础拨,但是這些都是小眾需求了,在表現(xiàn)與消耗之間做的一個不那么十全十美的平衡)绍载,其他surface地方擺放低密度Probe诡宗,這個過程是自動完成的,擺放范圍可以通過LightmassImportanceVolume控制击儡,在編輯器中可以通過Show->Visualize->Volume Lighting Samples查看場景中擺放的probe數(shù)據(jù)塔沃。
關于probe生成位置(可以參考FStaticLightingSystem::BeginCalculateVolumeSamples函數(shù)),還有一些優(yōu)化邏輯[11]:
對于退化的三角形或Lightmap精度小于給定閾值的表面阳谍,則該三角形不生成ILC
對于給定的采樣點蛀柴,如果它周圍(采樣方向為球體而不只是正半球)有超過30%的采樣點是物體的背面,則該采樣點不生成ILC
在UE4.25之前矫夯,ILC數(shù)據(jù)生成在PersistentLevel里鸽疾,這樣ILC在關卡被加載之初,就一直常駐于內存中训貌。這對于小型地圖來說無傷大雅制肮,但當游戲需要更大的世界,更大的地圖的時候递沪,它會同時帶來內存的巨大增長和ILC查詢插值性能的降低豺鼻。所以4.25之后ILC數(shù)據(jù)被分割到各個StreamingLevel中,可以配合WorldComposition和LevelStreaming對ILC數(shù)據(jù)進行動態(tài)加載和卸載款慨。
- 對于物體表面附近那些ILC來說拘领,它們會被放置到該物體所在的StreamingLevel中
- 對于CharacterImportanceVolume所產生的ILC來說,Lightmass會從采樣點世界空間向下打一條射線樱调,返回第一個被接觸到的物體所在的StreamingLevel中
- 稀疏的ILC放置點會存在于當前的PersistentLevel中
在一些情況下约素,自動擺放的probe可能無法提供需要的光照精度届良,比如在一個房間中靠近天花板的區(qū)域(因為默認只在房間地面上方一段距離內放置了probe),這個時候可以手動擺放一個LightmassCharacterIndirectDetailVolume來提升對應區(qū)域的probe密度圣猎,其實就是相當于在volume中添加額外的probe士葫,如下圖所示:
LightmassImportanceVolume除了控制著probe的擺放范圍之外,還有其他作用:在photon mapping算法中送悔,發(fā)射的光子在LightmassImportanceVolume內部會進行多次反射慢显,在其外部則只會進行一次反射,而如果不放置LightmassImportanceVolume欠啤,會對全場景進行間接光采樣荚藻,從而導致效率低下。
probe放置完成后洁段,就需要計算每個probe上通過3階SH系數(shù)存儲的Radiance數(shù)據(jù)应狱,這個部分可以在FStaticLightingSystem::CalculateVolumeSampleIncidentRadiance函數(shù)中找到,基本流程給出如下[11]:
計算每個probe上下半球潛在的貢獻光子的方向祠丝,存儲以用于真正的光照計算
計算probe可以接受到的直接光能量的SH參數(shù)
計算probe上半球間接光照入射光能量的SH參數(shù)(FinalGather,AdaptiveSample加速)
計算probe下半球間接光照入射光能量的SH參數(shù)(FinalGather,AdaptiveSample加速)
疊加2疾呻,3,4步的SH參數(shù)写半,生成最終的高質量和低質量ILC(高質量ILC包含的光照數(shù)據(jù)更少岸蜗,低質量ILC包含的光照數(shù)據(jù)更多,因為光質量ILC沒有包含的部分都是實時計算的叠蝇,這是高質量一詞的注解)
在計算潛在有貢獻的光子時璃岳,使用的是Volumetric PhotonSegementMapping算法,這個算法中的PhotonSegment跟附著在物體表面(photon與漫反射碰撞點)的傳統(tǒng)photon不同——它們是傳統(tǒng)photon在碰撞后的反射路徑上均勻分布的點悔捶,包含了當前點的方向和光照值所生成的能量記錄矾睦,在Lightmass中被叫做PhotonSegement。想象一下光線在空間以直線傳播炎功,被PhotonSegement均分為一段一段的小線段,所圖所示:
烘焙完成后缓溅,屬于某個物件的probe最終會存儲在一張全局的volume texture中蛇损,這個volume texture是一個atlas,每個物件分配的probe數(shù)目是可以調整的:
通過SurfaceLightSampleSpacing可以控制上表面的probe之間的間距坛怪,通過FirstSurfaceSampleLayerHeight可以調整上表面第一層的probe之間的間距淤齐,而SurfaceSampleLayerHeightSpacing可以調整每層probe之間的間距,NumSurfaceSampleLayers控制上表面probe層數(shù)袜匿,DetailVolumeSampleSpacing控制Character Indirect Detail Volume內的ILC采樣點距離更啄,而VolumeLightSampleSpacing控制既不是物體表面,又不是Character Indirect Detial Volume內的其它地方ILC采樣距離居灯,這個參數(shù)是對VLM生效的祭务,ILC沒有這類的probe内狗。(距離單位為cm)
每個物件移動或者輸入光照變化,Cache就失效了义锥,需要對物件所對應的probes進行更新柳沙。
烘焙數(shù)據(jù)是存儲在Level/StreamingLevel的MapBuildData中的,這個文件除了ILC/VLM數(shù)據(jù)之外拌倍,還包含2D的Lightmap數(shù)據(jù)赂鲤、IBL數(shù)據(jù)、NavMesh數(shù)據(jù)以及Shadow Mask數(shù)據(jù)等柱恤。
probe數(shù)據(jù)在場景中是按照八叉樹組織的数初,這樣有利于實現(xiàn)快速的查找,單個probe的存儲結構為FRPrecomputedLightVolumeData梗顺。這個結構的加載時機跟ULevel是一致的泡孩,即當Level加載時,就會同步完成對應的probe數(shù)據(jù)的加載(合理)荚守。
2.1.2 運行時采樣
運行時珍德,每個被設置為movable的物體上的光照會在直接光照上疊加上對probe進行插值得來的SH系數(shù)求取的間接光,這個插值是針對整個(movable)物體而言的矗漾,即每個物體只進行一次插值(per-instance)锈候,插值的輸入是物件周邊的probe,插值的結果會傳入到PS中用于計算光照敞贡。下面這個torus周邊就有5x5x5個插值點:
通常來說泵琳,插值過程不是每幀都進行的,而是只有當物體移動幅度達到一定程度誊役,使得之前的插值數(shù)據(jù)失效获列,才會重新進行一輪插值,這也是這個方案叫做Caching的另一重原因蛔垢。ILC更新邏輯給出如下:
其中ILC Octree的遍歷與插值是一個高頻操作击孩,因此UE也提供了性能更優(yōu)的多線程版本。此外鹏漆,SH表達常見的一個問題就是巩梢,輸入光是非負數(shù)值,但是經過SH投影與重建后會得到負值艺玲,這個表現(xiàn)叫做Ringing異常(詳情可以參考【GDC2008】Stupid Spherical Harmonics (SH) Tricks)括蝠,也就是上圖所說的振鈴現(xiàn)象,這是因為高頻信息缺失導致的震蕩饭聚,常用的做法是在頻域中使用窗口函數(shù)過濾掉高頻信息來得到較好的結果忌警,UE中沒有采用這種做法,而是在SH上添加上一個額外的數(shù)值來抬高重建后信號的下限秒梳,這個數(shù)值是在OppsiteLighting的基礎上添加一個小幅的(5%)額外振幅求得的法绵。
ILC方案的問題有:物件周邊才存在間接光效果箕速,在距離物件較遠區(qū)域,動態(tài)物體將失去此效果礼烈;因為不同物件上的probe密度是不同的弧满,因此多個物件銜接處效果可能會不夠平滑。
除此之外此熬,還拷貝了jiff[11]中關于ILC的相關陳述庭呜,僅做參考:
ILC的不足之處與優(yōu)化方案:
- 最嚴重的問題是漏光/漏陰影
- 針對漏光問題,可以考慮在會產生漏光的區(qū)域(如薄壁處)增加額外的probe并使用陰影檢測手段(通過判斷此處是否是屬于陰影中并添加一個clamp)來規(guī)避犀忱,也可以在離線生成ILC采樣點的時候做文章(可參考FarCry3)募谎,對檢測到光照斷層的區(qū)域降低ILC probe的作用半徑。
- 不支持場景的動態(tài)破壞阴汇,不支持TOD
- 對立體空間支持不佳数冬,尤其是對于空中飛行的游戲或跳傘類游戲來說
- 對室外遠離地面的空間,可以直接使用全局的SKY SH全類似于PostProcess Volume方式計算GI搀庶,只有在近地面或光線變化敏感的區(qū)域生成較密集的ILC采樣點
- 對于基于靜態(tài)實例(ISM)和分層的靜態(tài)實例(HISM拐纱,如植被)的支持不好,因為ILC是基于Component綁定哥倔,這就意味著所有的Instance會共享一份ILC SH秸架,對于一定較大范圍內合并的ISM或HISM,其結果會不盡人意
- 對于畫質要求高的游戲來說咆蒿,ISM/HISM可以考慮逐Instance生成ILC而不是全部共用(或者使用后面的VLM逐像素插值SH)
- 對使用ILC的物體體積有較嚴格要求东抹,比如一個大房子肯定是不能使用一個ILC來表達GI的,甚至一輛小卡車使用單ILC來表達也不足以表現(xiàn)其GI變化的頻率
- 對于畫質要求高的游戲來說沃测,中型物體需要使用ILC缭黔,也可以為一個物體生成多個ILC布點(類似COD)
其他優(yōu)化方案:
- 對內存緊張的情形來說,在稍微損失一些效果的前提下蒂破,ILC的SH參數(shù)總是可以壓縮為2階的馏谨,從32位浮點數(shù)壓縮為16位半精度
- 注意到ILC SH光照是在逐像素計算的,而對于沒有Normalmap的模型來說附迷,完全等價于在VS里計算ILC光照惧互。
2.2 Volumetric Lightmap(VLM)方案
跟ILC不同,VLM除了在Static物體上方會擺放probe之外挟秤,在空間中也會擺放Probe,這樣就避免了使用LightmassCharacterIndirectDetailVolume來處理無靜態(tài)物件區(qū)域中動態(tài)物件的間接光效果異常的問題抄伍,相當于擴大了probe的范圍艘刚,從而增加了動態(tài)物體被間接光覆蓋的范圍,此外需要補充一下截珍,相對于一些實時方案中光照使用2階球諧來表示(LPV)攀甚,ILC跟VLM中存儲的SH系數(shù)是三階的共9個參數(shù)箩朴。
在放置了LightmassImportanceVolume的地方,會生成均勻分布的4x4x4個probe(這個數(shù)值可以通過 Volumetric Lightmap Detail Cell Size 進行調整)秋度,而在static物件周圍會提高probe的密度(具體的密度提升邏輯估計跟前面ILC中設置Probe之間的間距邏輯實行同的炸庞,這里的一個問題是,靜態(tài)物件為什么不能使用probe的數(shù)據(jù)荚斯,而需要lightmap呢埠居?實際上,在UE4.25之后事期,已經可以通過將靜態(tài)物體的Lightmap Type設為Force Volumetric來使用ILC/VLM數(shù)據(jù)滥壕,從而避免為其生成占據(jù)包體以及內存的lightmap數(shù)據(jù)):
此外,光照插值基于渲染物體的像素(即每個像素的光照SH系數(shù)是不同的)進行兽泣,跟ILC基于probe插值(即每個物件會插值出一個對應于物件所在位置的probe绎橘,之后使用這個probe的數(shù)據(jù)進行所有像素的著色)不同,這就是為什么VLM可以實現(xiàn)fog光照的原因唠倦,也可以用于解決當物件較大的時候称鳞,ILC得到的光照效果會周邊lightmap計算得到的光照效果存在較大跳變的問題。
由于VLM是逐像素插值的稠鼻,且在靜態(tài)物體周邊的probe數(shù)目更為密集(確定嗎冈止?),因此其最終的效果也更為優(yōu)秀:
相對于ILC而言枷餐,VLM有如下的一些不足:
- 內存消耗增加了
ILC跟VLM的問題有:
- 動態(tài)角色站在墻體后面靶瘸,如果墻體較薄,可能會存在漏光毛肋,這是因為采樣到了墻外的probe數(shù)據(jù)導致怨咪,解決方案是,提升Probe密度或者增加墻體厚度
2.3 Light Propagation Volumes(LPV)方案
LPV是一種用于實時渲染場景中的間接光計算方案润匙,這個方案中:
- 首先會將光源打在場景中的光照數(shù)據(jù)用RSM(Reflection Shadow Map)來表示
- 之后將RSM的每個像素看成是一個新的光源VPL(Virtual Point Light)诗眨,并將每個VPL根據(jù)其法線轉換成二階的SH系數(shù)求得其Radiance數(shù)據(jù),并將這個數(shù)據(jù)塞入到覆蓋全場景的Light Propagation Volume的對應Cell中
- 通過相鄰Cell的光照傳播擴散孕讳,完成整個Light Propagation Volume Cell的數(shù)據(jù)填充匠楚,這個過程也是間接光二次反射的過程
- 將計算好的LPV用于光照,得到間接光效果厂财。
具體實現(xiàn)細節(jié)可以參考【Siggraph2009】Light Propagation Volumes in CryEngine 3
3. Actor移動類型(Mobility)
移動類型主要作用于兩種Actor芋簿,分別是StaticMesh Actor跟Light Actor,目的是用于在運行時使用不同的策略來完成光照的計算璃饱,類型主要有三種与斤,分別是Static,Stationary以及Movable:
Static表示的是Actor是完全不變的,包括位置以及其他撩穿;
Stationary表示的是位置是不變的磷支,其他數(shù)據(jù)可能會發(fā)生變化,對于光源而言,其表示的是光源的位置不會發(fā)生變化,但是其顏色與光強在運行時卻是可能發(fā)生變化的仑性;
Movable表示的是Actor是可能會發(fā)生變化的队橙,包括位置以及光源的光強等參數(shù)。
3.1 StaticMesh Mobility
對于StaticMesh Actor而言,不同的移動類型的光照計算方式的區(qū)別如下:
- Static:啟動lightmass光照構建時,在滿足條件的情況下(處于LightmassImportanceVolume中),會為這類Mesh構建其Lightmap數(shù)據(jù)(ILC/VLM同樣也會有)锭弊,且這種物件本身也會參與到Lightmass計算中,產生對其他物件的投影擂错。此外味滞,需要注意的是,這類物件的材質是可以在運行時發(fā)生變化的钮呀。
- Stationary:雖然這種類型的Mesh數(shù)據(jù)不會移動剑鞍,但是可能會發(fā)生其他的變化(不會導致陰影變化的參數(shù)變更等),因此lightmass不會為這類物件構建對應的Lightmap爽醋,也不會參與到其他物體的lightmap的計算中蚁署,其對場景的光影的影響是動態(tài)計算的,跟Movable物件一樣蚂四,不同的是光戈,這類物件生成的投影是可以在Shadow Cache中繪制到靜態(tài)陰影貼圖中的,而不需要每幀更新遂赠;
- Movable:同上久妆,這種位置都會發(fā)生變化的物件,在Lightmass中更加不會參與Lightmap構建跷睦,不過跟Stationary物體不同的是筷弦,這類物件的陰影不能Cache住,需要每幀更新抑诸。雖然物件是動態(tài)的烂琴,但是依然可以使用Static光源烘焙的光場數(shù)據(jù)(ILC/VLM)來計算間接光,而如果是Stationary或者Movable光源蜕乡,就只能動態(tài)計算直接光照了奸绷。
3.2 LightSource Mobility
對于光源Actor而言,不同的移動類型對光照的影響給出如下:
- Static:這類光源會參與到Lightmass的計算之中层玲,會對Lightmap以及ILC/VLM等數(shù)據(jù)產生影響号醉。生成的數(shù)據(jù)會對Static模型(Lightmap绒瘦、ILC/VLM)、Stationary以及Movable(ILC/VLM)等產生照明效果扣癣。
- Stationary:這類光源的位置不會動態(tài)變化,但是其光源強度與顏色卻是可能會變化的憨降,雖然如此父虑,這類光源依然可以用于Lightmass光照烘焙,以計算Static Mesh的陰影效果授药,需要注意的是士嚎,烘焙的間接光效果使用的數(shù)據(jù)是烘焙時那一刻的數(shù)據(jù),在運行時動態(tài)變化后悔叽,其間接光效果因為是預計算的莱衩,因此并不會跟著變化,此外這類光源對Movable/Stationary物件的陰影計算是動態(tài)完成的娇澎。Stationary光源的直接光照是運行時動態(tài)計算的笨蚁,因此可以隨著光強與顏色的變化而變化,而其直接投影則是在離線的時候通過Distance Field Shadow計算的到趟庄,會為Static物件生成Distance Field的Shadowmap括细,這種Shadowmap即使在較低分辨率情況下,也可以得到較為精確的陰影效果戚啥,且由于不再需要進行投影計算奋单,運行時消耗非常低。
Stationary的Shadowmap跟Lightmap是分開存儲的猫十,如下圖所示:
可以看到Shadowmap這里是將四個通道合并到一起使用的览濒,前面兩個通道存儲的是6個shadowmap(cubemap,對應點光陰影)拖云,第三個通道存儲的是方向光的Shadowmap贷笛,最后一個通道空置,效果如下圖所示:
Distance Field Shadowmap是怎么存儲的呢江兢,跟普通的Shadowmap有什么不一樣昨忆,具體采樣邏輯是怎么樣的?經過截幀分析發(fā)現(xiàn)杉允,Distance Field Shadowmap是已經計算好的Shadow Factor的map而非從光源角度出發(fā)的物件像素深度map邑贴,也就是說是一張普通的2D貼圖,之所以叫Distance Field Shadowmap大概是因為通過Distance Field完成計算的叔磷,其采樣邏輯給出如下:
float2 ShadowMapCoordinate;
uint LightmapDataIndex;
GetShadowMapCoordinate(Interpolants, ShadowMapCoordinate, LightmapDataIndex);
float4 DistanceField = Texture2DSample(LightmapResourceCluster_StaticShadowTexture, LightmapResourceCluster_LightMapSampler , ShadowMapCoordinate);
float4 InvUniformPenumbraSizes = GetLightmapData(LightmapDataIndex).InvUniformPenumbraSizes;
float4 DistanceFieldBias = -.5f * InvUniformPenumbraSizes + .5f;
float4 ShadowFactors = saturate(DistanceField * InvUniformPenumbraSizes + DistanceFieldBias);
Stationary Light經過Lightmass烘焙的間接光以及間接光陰影是存儲在Lightmap中的拢驾,而烘焙的直接光陰影則是存儲在Shadowmap中的,為啥不像Static Light烘焙結果一樣改基,將Shadowmap直接整合到Lightmap中呢繁疤?關于這一點,目前沒有太多信息,推測是因為Lightmap分辨率較低稠腊,無法保證較高的陰影效果
Static光源烘焙的數(shù)據(jù)是不包含Shadowmap的躁染,其陰影效果是直接烘焙到Lightmap中,因此在分辨率較低的時候質量較差架忌,如下圖所示:
Shadowmap是怎么存儲的呢吞彤,其讀取方式應該跟Lightmap不同嗎?這段邏輯是放在shader中的GetPrecomputedShadowMasks函數(shù)中的叹放,從讀取方式可以看到饰恕,Shadowmap的讀取uv跟Lightmap是完全相同的,且其存儲的就是可以直接拿來用的陰影效果井仰,在運行時做了一些處理以得到更好的效果埋嵌,這里的一個新的疑問是,這個處理為什么不直接在離線完成并編碼到Shadowmap中俱恶?推測可能是為了保證存儲的shadowfactor具有較高的精度雹嗦,因為浮點數(shù)越靠近原點,精度越高合是。
Distance Field Shadowmap存儲的是ShadowFactor俐银,也就是說是跟物件表面綁定的,那么如何用到場景中無Lightmap烘焙的半透物體上呢端仰?這是因為除了為每個物件計算的與Lightmap等價的Shadowmap之外捶惜,還有一張全局的Static Shadowmap(也就是用于靜態(tài)無條件向動態(tài)物件投影的Per-Object Shadowmap,下面會有更多介紹)荔烧,這張Shadowmap存儲的是靜態(tài)光源下靜態(tài)物件的光源視角的Depth數(shù)據(jù)吱七,從而省去在運行時繪制陰影的消耗(雖然是運行時動態(tài)創(chuàng)建的,但是不必每幀更新)鹤竭,而場景中的動態(tài)物件(比如透明物體)只需要在運行時將像素坐標轉換到光源視角并與shadowmap上對應位置的深度值進行比較就能知道當前點是否處于陰影之中了踊餐,需要注意的是,這張shadowmap的分辨率較低臀稚,默認是一米一個像素吝岭,這個數(shù)值可以在配置文件中進行調整(Baselightmass.ini中的StaticShadowDepthMapTransitionSampleDistanceX, StaticShadowDepthMapTransitionSampleDistanceY參數(shù))吧寺。
下圖給出的是不透明物體使用Distance Field Shadowmap得到的直接陰影效果:
需要注意的是窜管,由于Shadowmap最多包含四個通道,因此理論上同一時刻最多只有四盞Stationay光源可以擁有靜態(tài)直接陰影(Static光源不需要直接陰影稚机,因為直接嵌入Lightmap中了)幕帆,而實際上太陽光需要占用一個通道(太陽光如果用作動態(tài)光也需要占用一個通道嗎?)赖条,因此只剩下三個通道可用失乾,當需要同一時刻需要產生投影的Stationary光源超出這個數(shù)值常熙,超出的光源將采用動態(tài)投影方式輸出陰影,這就可能會導致嚴重的性能問題碱茁。
Stationary光源的Shadowmap中只存儲了靜態(tài)物體產生的直接陰影裸卫,但是動態(tài)物體本身在光源下也會產生直接陰影,如果不做處理纽竣,就會導致穿幫彼城,而動態(tài)物體在Stationary光源下的直接陰影通常是通過Per-Object Shadow來實現(xiàn)的。
實際上退个,Per-Object Shadowmap并不是為每個物件分配一張Shadowmap,而是分配一張大的Shadowmap Atlas调炬,每個需要創(chuàng)建Per-Object Shadow的物件會分配一塊空間语盈,如下圖所示,裝載了三個Per-Object Shadow:
那么這些Per-Object Shadow要如何應用到場景里呢缰泡,總不能場景的每個像素都需要對每個Per-Object Shadow進行一次比對吧刀荒?實際上Per-Object Shadow的繪制Bounds是在光源視角下計算得到的物件的AABB,而這個AABB沿著光線傳播的方向繼續(xù)延伸一個長度棘钞,就得到一個AABB Volume缠借,這個Volume就是對應物體在光源照射下的陰影覆蓋范圍,只需要計算出處于這個Volume中的屏幕空間像素即可宜猜,這樣就可以極大的減少繪制的消耗泼返。
這種方式是不是看起來很眼熟?這就是延遲渲染中用于降低Deferred Lighting計算消耗常用的方法姨拥,繪制一個Light Volume Primitive绅喉,獲得其影響的屏幕空間像素,對這些像素進行Lighting叫乌。具體而言柴罐,就是通過Stencil操作進行標記,對Volume進行繪制的時候憨奸,開啟雙面繪制革屠,正面跟背面的Stencil處理邏輯不同,如下圖所示(注意UE使用的是Reversed Z排宰,因此靠近近平面的位置Depth數(shù)值更大似芝,正常剔除使用的是GreaterEqual):
這里的繪制分成兩個pass完成,第一個pass用于標記出需要進行深度計算的像素板甘,采用的是雙面繪制国觉,Stencil的判定函數(shù)是Always,也就是說只要通過Depth Test就進行寫Stencil操作:
- 那么Back face如果沒被遮擋就執(zhí)行-1虾啦,F(xiàn)ront face執(zhí)行+1麻诀,這種情況對應的是場景中的像素處于Volume之后痕寓,不需要進行計算處理,Stencil結果為0
- 如果Back face被遮擋則測試失敗蝇闭,不寫Stencil呻率,F(xiàn)ront face通過執(zhí)行+1,這種情況對應的是場景中的像素處于Volume之中呻引,需要進行計算處理礼仗,Stencil結果為1
- 如果Back face被遮擋則測試失敗,不寫Stencil逻悠,F(xiàn)ront face失敗不寫Stencil元践,這種情況對應的是場景中的像素處于Volume之前,不需要進行計算處理童谒,Stencil結果為0
- 如果Back face被遮擋則測試通過单旁,Stencil -1,F(xiàn)ront face失敗不寫Stencil饥伊,這種情況是不可能出現(xiàn)的象浑。
第二個pass則是正常的渲染,執(zhí)行背面剔除琅豆,在這個地方會進行Stencil判定愉豺,只有非0的像素才會通過檢測,從而實現(xiàn)響應像素的篩選茫因。
這里需要考慮的是相機位于Volume內部的情況蚪拦,由于Front face會被近平面裁剪掉,因此會是的Stencil結果存在異常冻押,比如Back face位于物件之后外盯,理論上對應的是此像素處于Volume內部,但是在這種情況下翼雀,Stencil結果為0饱苟,因此也就不會計算對應的陰影。那么要怎么解決這個問題呢狼渊?
同樣分成兩個pass箱熬,第一個pass也是兩面渲染,區(qū)別主要在于之前對Stencil的寫操作在于Stencil Test Pass的時候完成狈邑,而現(xiàn)在改成Depth Test Failed(即對應的Volume像素被場景像素遮擋時)的時候進行城须,因為相機是在Volume中的,因此通過這種方式可以正確判定某個像素是否處于當前volume的范圍之中米苹。
第二個Pass的處理跟前面一致糕伐,需要注意的是,這里的Stencil等配置是逐Volume配置的蘸嘶,因此對于不同的volume其配置可能是不一樣的良瞧,從而保證任何情況下陪汽,都能得到正確的結果。
對于一個動態(tài)物體而言褥蚯,會需要動態(tài)創(chuàng)建兩張shadowmap:
- 一張用于處理場景中烘焙好的靜態(tài)物體向動態(tài)物體投射的陰影挚冤,這張對應的應該是普通的shadowmap,從截幀數(shù)據(jù)看赞庶,這里投影使用的是一張離線生成的Shadowmap训挡,且形狀較為奇怪,只有01兩個數(shù)據(jù)歧强,如下圖所示澜薄,這是為什么呢?
經過測試發(fā)現(xiàn)摊册,每當一個物體從Movable變成Static肤京,就會觸發(fā)這個貼圖的更新,將其繪制到這張全局共享的且?guī)ache的Static Shadowmap中丧靡,而之所以上面圖片顯示的只有01兩個數(shù)值,大概率是Renderdoc本身在深度貼圖顯示上的問題籽暇。
那么要計算這個貼圖對動態(tài)物體的陰影factor要怎么做呢温治,只需要將動態(tài)物體上的像素反向投影到光源空間,之后根據(jù)位置使用PCF計算即可戒悠,這里為了提升效率熬荆,也可以先將動態(tài)物體進行一遍不寫color的繪制,只是通過stencil標記出需要繪制陰影的像素绸狐,之后通過一個全屏后處理完成對應像素的shadow計算卤恳。
- 另一張用于處理動態(tài)物體向場景(包括靜態(tài)跟動態(tài))投射的陰影,這張shadowmap可以跟烘焙好的Distance Field Shadowmap最終計算得到的factor合并到一起來為靜態(tài)物體提供陰影效果寒矿。這張shadowmap可以理解為前面的Per-Object Shadowmap突琳。
對于設置為Stationary的方向光來說,還有一些其他的特殊處理:
- Stationay方向光除了烘焙的Distance Field Shadowmap之外符相,還會在運行時增加一套CSM(目的何在拆融?應該是為了應對相機frustum近端的大量動態(tài)物件,相當于物件的直接光照陰影既需要考慮Distance Field Shadowmap的計算結果還需要考慮CSM的計算消耗啊终,雖然看起來過程是更繁瑣了镜豹,但是節(jié)省下大量靜態(tài)物件在運行時參與陰影生成的消耗),這種策略在角色周圍存在著大量的植被的時候會比較有用蓝牲,可以省掉大量動態(tài)物件需要創(chuàng)建的Cascade消耗(Cascade趟脂?不是Per-Object Shadow嗎?從實現(xiàn)邏輯上來講例衍,Per-Object Shadow就相當于一個Cascade)昔期,而CSM覆蓋的動態(tài)陰影會隨著視距的增加逐漸過渡到靜態(tài)陰影上已卸,可以通過Dynamic Shadow Distance StationaryLight參數(shù)來調節(jié)過渡距離。這個距離比較小的時候镇眷,為每個動態(tài)物件創(chuàng)建的Per-Object Shadow是會比較有用的(因為這個創(chuàng)建不會考慮當前動態(tài)物件是否處于CSM覆蓋范圍之內)咬最,但是如果距離設置的比較大,Per-Object Shadow的創(chuàng)建就比較浪費了欠动,在這個時候可以關閉光源上的Use Inset Shadows For Movable Objects開關來干掉這部分消耗永乌。
- Stationary方向光支持一種叫做Use Area Shadows for Stationary Lights特性,當開啟這項特性的時候具伍,預計算的Shadowmap存儲的會是將光源看成是面光源計算得到的數(shù)據(jù)翅雏,而面光源產生的陰影具有距離投影物越遠而變得越柔軟的效果,如下圖所示:
- Movable:這類光源不但位置會發(fā)生變化人芽,其光強也可能會變化望几,因此不會參與到烘焙計算過程中,由于所有的光影都是動態(tài)計算的萤厅,因此使用這類光源的時候要十分小心橄抹,當然如果不投射陰影,且采用的是延遲管線惕味,那么這類光源的計算消耗就不是特別高(理論上來說楼誓,需要投影的動態(tài)光源計算消耗是不需要投影的動態(tài)光源計算消耗的20倍[13])。
參考文獻
[1] Lightmap - Wiki
[2] Unwrapping UVs for Lightmaps
[3] Lightmap(光照貼圖)能實現(xiàn)動態(tài)光照嗎名挥?
[4] 【UE4 全局光照明】虛幻引擎學習之路:渲染模塊之全局光照明
[5] Indirect Lighting Cache
[6] UNREAL ENGINE 4 LIGHTING MASTERCLASS (SUMMARY)
[7] CPU Lightmass Global Illumination
[8] Light Propagation Volumes
[9] Unreal Summit 2016 Seoul Lighting the Planetary World of Project A1
[10] Volumetric Lightmaps
[11] 深入淺出UE4 ILC
[12] Actor Mobility
[13] Stationary Lights