這里分享的是Unreal在Siggraph 2015上關(guān)于SDF的相關(guān)技術(shù),原始PPT在參考文獻(xiàn)[1]中給出。
分享的作者是UE的圖形程序Daniel Wright,到2015年為止,他在Epic待了9年,專注于lighting & rendering相關(guān)的技術(shù),此前參與過(guò)Gears of War系列。
先來(lái)提出問(wèn)題,在一個(gè)可以動(dòng)態(tài)變化的場(chǎng)景而言,在如下的一些要求下,目前并沒(méi)有一套比較好的Occlusion(遮擋光效,比如AO/Shadow)方案:
- 精準(zhǔn)、柔軟
- 支持area shadows,sky occlusion, reflection shadowing
一個(gè)有效的解決策略是使用Ray Marching Signed Distance Fields。
受Inigo Quilez使用SDF+Raymarching生成漂亮效果(如下圖)以及Alex Evans 2006的Fast Approximations for Global Illumination on Dynamic Scenes文章激發(fā),這里嘗試使用SDF來(lái)解決上述的問(wèn)題。

在開(kāi)始計(jì)數(shù)方案介紹之前,我們先來(lái)看下此前SDF的應(yīng)用方式。

如上圖所示,UE3中層嘗試將SDF用在反射陰影(地面反射效果上的陰影)的計(jì)算上面,這里沒(méi)有介紹其中的實(shí)現(xiàn)細(xì)節(jié),后面有機(jī)會(huì)再補(bǔ)上。
這個(gè)方案對(duì)場(chǎng)景是有要求的:
- 場(chǎng)景需要是static的,需要單個(gè)volume texture(整個(gè)場(chǎng)景一張還是每個(gè)物件一張?)來(lái)存儲(chǔ)distance field數(shù)據(jù)
- 性能消耗較高,只有高端硬件才支持。

SDF的另一項(xiàng)應(yīng)用是用于計(jì)算天光遮罩(Occlusion)效果,同樣沒(méi)有給出實(shí)現(xiàn)細(xì)節(jié),后續(xù)有機(jī)會(huì)再補(bǔ)充。
大概的實(shí)現(xiàn)方式是,為屏幕空間的每個(gè)像素朝著某個(gè)方向(比如bent normal之類)以某個(gè)cone angle(bent normal計(jì)算時(shí)附帶輸出的未圍擋區(qū)域的范圍計(jì)算得來(lái))進(jìn)行cone march,以cone march的結(jié)果作為天光的illumination效果,這種方案的弊端在于:
- 需要27個(gè)cones(每個(gè)像素?)
- 即使在非常小的場(chǎng)景中依然只能得到2fps的幀率……

GDC 2015 Kite Tech Demo給出了SDF的另一項(xiàng)應(yīng)用,計(jì)算植被的Occlusion效果,同樣只支持高端硬件。

相對(duì)于上述的一些應(yīng)用場(chǎng)景的高昂的性能消耗,Epic目前在Fortnite上使用的方案則更為親民:
- 支持動(dòng)態(tài)TOD
- 支持高動(dòng)態(tài)場(chǎng)景光照效果
- 在PS4級(jí)別的硬件上都能有流暢的體驗(yàn)
接下來(lái)進(jìn)入正文,整個(gè)技術(shù)介紹分成五個(gè)部分,下面一一介紹。
1. SDF簡(jiǎn)介

SDF的中文翻譯叫做有向距離場(chǎng),這里的場(chǎng)描述的是3D空間中的數(shù)值分布,數(shù)值是距離,是到物件/場(chǎng)景的最小距離,而有向則指的是距離是帶符號(hào)的,當(dāng)某個(gè)點(diǎn)處于物件內(nèi)部,這時(shí)的距離是小于0的,否則是大于等于0的。
SDF的數(shù)據(jù)有很多存儲(chǔ)方式,一個(gè)常用的方式是使用volume texture,即3D貼圖,貼圖中的每個(gè)像素對(duì)應(yīng)于所覆蓋范圍中的一個(gè)點(diǎn)的SDF數(shù)值,其他未落在像素中心的點(diǎn)則可以通過(guò)對(duì)其他像素?cái)?shù)據(jù)進(jìn)行插值得到。

SDF的一個(gè)常見(jiàn)應(yīng)用是陰影計(jì)算,某個(gè)點(diǎn)是否被陰影覆蓋,只需要從這個(gè)點(diǎn)向著光源方向發(fā)射射線,判斷射線是否會(huì)跟場(chǎng)景相交即可,而如果沒(méi)有SDF,在沿著射線進(jìn)行ray marching的時(shí)候,就需要以一個(gè)較小的step逐點(diǎn)步進(jìn)判定,這個(gè)消耗是非常高的。
有了SDF之后,由于我們可以知道任意點(diǎn)的SDF,那么相當(dāng)于以這個(gè)數(shù)值作為marching step長(zhǎng)度是完全可以保證不會(huì)穿到物體內(nèi)部的,也不會(huì)出現(xiàn)step過(guò)大某個(gè)遮擋體被跳過(guò)的可能(當(dāng)然,這要求場(chǎng)景的SDF表達(dá)的精度足夠高),因此可以對(duì)這個(gè)計(jì)算過(guò)程進(jìn)行加速,這種算法有個(gè)術(shù)語(yǔ)叫做sphere tracing(如上圖中右邊的小圖所示)。

通過(guò)前面的sphere tracing,我們可以很容易的直到某點(diǎn)是否處于陰影中,這個(gè)過(guò)程得到的是硬陰影,要想得到軟影效果,還需要一些額外的處理:
- 根據(jù)當(dāng)前像素到遮擋體的距離來(lái)對(duì)陰影進(jìn)行軟化(除以某個(gè)與距離(或距離的平方)成正比的數(shù)值)
- 在sphere tracing的過(guò)程中,當(dāng)前射線與場(chǎng)景相交的最小cone,如上圖中右邊小圖所示,以這個(gè)cone的角度與一個(gè)預(yù)設(shè)的全亮?xí)r的cone的角度(這個(gè)角度跟光源形狀有關(guān),可以用作面光源軟影的求取方式)作比,結(jié)果用作陰影的軟化程度。

跟直接用voxel來(lái)進(jìn)行場(chǎng)景或者物件表達(dá)相比,SDF有如下的一些優(yōu)勢(shì):
- 可以表示表面的朝向
- 可以根據(jù)數(shù)據(jù)進(jìn)行插值求得更為精確的表達(dá)結(jié)果
- 可以用于對(duì)ray marching等算法進(jìn)行加速
- 可以無(wú)需進(jìn)行prefiltering處理以實(shí)現(xiàn)cone intersection求取。
當(dāng)然,相對(duì)而言,SDF也有其不足之處,比如只支持position/normal等數(shù)據(jù)的存儲(chǔ)(其他數(shù)據(jù)就不支持了),又比如,要想得到可見(jiàn)性就只能通過(guò)trace的方式來(lái)計(jì)算。
2. 場(chǎng)景表達(dá)
下面來(lái)介紹一下如何使用SDF完成對(duì)場(chǎng)景的表達(dá)。

每個(gè)物件的SDF是在離線的時(shí)候通過(guò)暴力遍歷所有面片的方式求得的,SDF使用一張3D的volume貼圖表達(dá),貼圖數(shù)據(jù)格式為float point 16,平均而言,每個(gè)物件的3D貼圖分辨率只需要50x50x50即可。
除了CPU算法之外,還可以借助GPU的并行加速功能來(lái)完成這個(gè)計(jì)算。

由于3D貼圖分辨率有限,為了保證精度,就需要將其覆蓋范圍收縮得足夠小,而非直接覆蓋整個(gè)場(chǎng)景。
如上圖所示,在這種時(shí)候要計(jì)算某個(gè)點(diǎn)到場(chǎng)景的最小距離,則需要分成兩步:
- 第一步,是求取當(dāng)前點(diǎn)到覆蓋范圍bounds的交點(diǎn),計(jì)算當(dāng)前點(diǎn)到交點(diǎn)的距離
- 第二步,從上個(gè)交點(diǎn)處獲取到物件的最小距離
將上述兩步的距離相加用作當(dāng)前點(diǎn)到物件的最小距離的近似。

將SDF用trace steps表示出來(lái),大概如上圖所示。

這里的一個(gè)問(wèn)題是,對(duì)于開(kāi)口的物件而言,其SDF是如何表示的內(nèi),被半包圍的空間處的數(shù)值是正還是負(fù)?
如上圖所示,判斷某個(gè)點(diǎn)的SDF數(shù)值是正還是負(fù),主要看其與物件的碰撞面是frontface還是backface的,前者是正,后者是負(fù)。

樹(shù)木等物件的SDF是將樹(shù)葉當(dāng)成雙面物體來(lái)計(jì)算的,不過(guò)這種物件在ray marching的時(shí)候消耗會(huì)十分的高,因?yàn)槊嫫^(guò)于復(fù)雜,光線在其中穿梭的時(shí)候,SDF比較小,因此step也就跟著變小,導(dǎo)致采樣數(shù)目劇增。

SDF是支持同一物件的不同實(shí)例的,使用同一套SDF數(shù)據(jù),只是變換不一樣。

因?yàn)榉直媛瘦^低的原因,一些物件的邊緣區(qū)域可能就被四舍五入掉了,而一些薄片可能就僅僅只保留了內(nèi)部的一個(gè)負(fù)值像素(為了避免射線marching過(guò)程中的遺漏,這個(gè)保留是必要的。)

在實(shí)際使用中,通常不會(huì)對(duì)單個(gè)物體進(jìn)行ray marching,而是會(huì)將場(chǎng)景的所有物件的SDF組合成一個(gè)全局的SDF貼圖,對(duì)于一個(gè)長(zhǎng)寬高都為512m的場(chǎng)景而言,這個(gè)貼圖的內(nèi)存消耗大約在300M左右。
這個(gè)全局SDF貼圖的更新是在GPU上完成的,CPU將需要更新的數(shù)據(jù)(比如要移動(dòng)某個(gè)物件,只需要將物件的變換矩陣上傳即可)上傳到GPU,GPU根據(jù)數(shù)據(jù)對(duì)各個(gè)物件的SDF進(jìn)行讀取,并將之寫(xiě)入到全局SDF中,這個(gè)更新是增量完成的。

將SDF數(shù)據(jù)放到GPU,可以加速剔除,這里沒(méi)有給出剔除的實(shí)現(xiàn)細(xì)節(jié),推測(cè)一下,大概是從屏幕空間每個(gè)像素發(fā)射一條射線,計(jì)算交點(diǎn)即可判斷哪些物件是可見(jiàn)的了,而各個(gè)射線的求交是并行的。

這一頁(yè)從PPT給的關(guān)鍵字沒(méi)推測(cè)出描述的是什么,大概是使用SDF用作地形的heightfield?后面有機(jī)會(huì)再補(bǔ)上相關(guān)描述。

當(dāng)前使用volume texture的表達(dá)方式可以表達(dá)絕大部分場(chǎng)景的數(shù)據(jù),只是在如下的一些數(shù)據(jù)表達(dá)上還存在問(wèn)題:
- 非均勻拉伸的物件
- 蒙皮等動(dòng)態(tài)deform的數(shù)據(jù)
- 大型有機(jī)物或者volumetric terrain數(shù)據(jù)。
除了這種方式之外,當(dāng)然也還有其他的一些表達(dá)方法: - 分析式的Distance Function
- sparse volumetric SDF等
至于SDF的應(yīng)用,則凡是會(huì)有用到cone tracing的相關(guān)算法,應(yīng)該都能實(shí)現(xiàn)一定的加速。
3. 直接陰影

SDF的一個(gè)作用是可以很方便的計(jì)算直接光照的陰影,且得到的陰影分辨率會(huì)十分之高。

這張PPT給出的信息并不是十分直觀,在沒(méi)有對(duì)應(yīng)的演講視頻的情況下,只能根據(jù)個(gè)人理解推測(cè)其表達(dá)意思。
具有球狀外形的徑向光源(方向光、點(diǎn)光、聚光燈應(yīng)該都能應(yīng)用這種處理方式吧?)的處理邏輯:
- 根據(jù)光源的覆蓋范圍計(jì)算出受影響的物體
- 將屏幕分成tile,每個(gè)tile記錄一個(gè)光源列表
這個(gè)列表是通過(guò)對(duì)光源的cone與物件bounds相交來(lái)計(jì)算得到的?(沒(méi)有具體細(xì)節(jié))

這里給出了陰影計(jì)算的算法步驟:
對(duì)于每個(gè)處于光源cone覆蓋范圍之內(nèi)的物體而言:
繪制在屏幕空間中的每個(gè)像素發(fā)射一條朝向光源的射線
對(duì)于射線上的每個(gè)采樣點(diǎn):
采樣SDF,得到當(dāng)前點(diǎn)到場(chǎng)景平面的最小距離。
計(jì)算DistanceToSurface/ConeRadius,這個(gè)數(shù)值實(shí)際上是cone的半角的tangent,判斷與此前紀(jì)錄的最小的比值的大小
每個(gè)采樣點(diǎn)前進(jìn)步長(zhǎng)為abs(DistanceToSurface)
當(dāng)射線與場(chǎng)景相交或者達(dá)到了最大采樣數(shù),則終止raymarching
最終的陰影值就為1減去記錄下的最小比值

這里給出了一個(gè)解釋,使用DistanceToSurface/ConeRadius得到的結(jié)果與(DistanceToOccluder/SphereRadius)^X的結(jié)果是一致的。
DistanceToOccluder指的是像素發(fā)出的射線到相交點(diǎn)處的距離,而SphereRadius則是遮擋體的半徑, 倒過(guò)來(lái)
可以看成是遮擋體占據(jù)的cone的比例,也就是陰影的濃度,那么倒過(guò)來(lái)之前就是光透過(guò)遮擋體進(jìn)入當(dāng)前像素的比例。
DistanceToSurface/ConeRadius中DistanceToSurface表示射線上當(dāng)前采樣點(diǎn)到最近的遮擋體的距離,ConeRadius則是當(dāng)前采樣點(diǎn)對(duì)應(yīng)的cone的半徑,同樣這個(gè)公式可以用來(lái)表達(dá)光源經(jīng)過(guò)遮擋體之后進(jìn)入當(dāng)前像素的光照比例,因此這兩個(gè)式子在直觀上應(yīng)該是一致的。

平行光的處理邏輯跟前面的徑向光源相似,不同的地方在于:
- 不再需要為光源查找對(duì)應(yīng)的屏幕空間tiles,而是全屏幕都參與
- 每個(gè)像素沿著光源方向的射線長(zhǎng)度不再是光源的覆蓋球的半徑,而是max view distance。


由于SDF無(wú)法處理形變物體,因此帶有頂點(diǎn)動(dòng)畫(huà)的植被等物件就不太適合使用這種方式來(lái)求取陰影,不過(guò)這也分情況,當(dāng)距離比較遠(yuǎn)的時(shí)候,完全可以停止頂點(diǎn)動(dòng)畫(huà),此時(shí)還是可以使用SDF求取陰影,否則只能考慮使用CSM來(lái)計(jì)算陰影了,上圖給出的效果可以看到,CSM的陰影精度相對(duì)于SDF的陰影精度有比較大的差距。

此外,遠(yuǎn)景植被如果退化成公告板,那么也是不能使用SDF來(lái)計(jì)算陰影的,這種時(shí)候可考慮使用conservative depth write(搜了一圈,沒(méi)找到相關(guān)技術(shù)文檔,不過(guò)這么遠(yuǎn)的距離,可以考慮直接將陰影烘焙到公告板上吧?)來(lái)解決這個(gè)問(wèn)題。

相對(duì)于傳統(tǒng)Shadow Map方案,使用SDF計(jì)算陰影有如下的一些好處:
- 可以實(shí)現(xiàn)帶有比較明顯輪廓的面陰影(如上圖中的下方小圖所示,效果更為真實(shí))
- 不會(huì)出現(xiàn)此前shadow map精度不足導(dǎo)致的毛刺或者peterpan問(wèn)題
- 陰影消耗與場(chǎng)景復(fù)雜度無(wú)關(guān),只取決于raymarching過(guò)程中的采樣點(diǎn)數(shù)目(與場(chǎng)景密度有關(guān))與屏幕分辨率,當(dāng)然可以使用半分辨率進(jìn)行計(jì)算來(lái)降低消耗,且支持一個(gè)較大距離的投影,還沒(méi)有CPU的消耗。
- 相對(duì)于傳統(tǒng)的陰影方案如cubemap/CSM而言,會(huì)快30%~50%。
4. Sky Occlusion 問(wèn)題與解決方案

SSAO只能給出小尺寸的遮擋效果,而我們經(jīng)常需要一些較大范圍(10m)的遮擋效果。

在SDF表達(dá)的場(chǎng)景中,使用單個(gè)cone進(jìn)行trace可以得到較為柔軟的陰影效果,但是對(duì)于來(lái)自四面八方的天光而言,使用單個(gè)cone就不合適了。

這里考慮使用分布在以法線為中心方向的半球面上的多個(gè)cone trace來(lái)計(jì)算天光遮蔽,將多個(gè)cone trace的結(jié)果平均一下作為最終的天光遮擋效果。

每個(gè)cone的trace范圍限定在10m左右。

如果覺(jué)得cone的數(shù)目太多會(huì)導(dǎo)致消耗過(guò)高,可以考慮只使用一個(gè)cone,選擇bent normal作為cone的trace方向,trace的結(jié)果會(huì)被用在天光的SH diffuse lighting部分。

此外也可以使用視線的反射方向作為cone trace的方向來(lái)獲取specular。

這里給出天光遮蔽計(jì)算的一些優(yōu)化策略(信息有限,加入個(gè)人推測(cè)):
- 將屏幕空間劃分成大小均勻的tile
1.1 每個(gè)tile存儲(chǔ)兩個(gè)bounds,每個(gè)bounds的邊長(zhǎng)存儲(chǔ)在一張depth貼圖中,分別表示起始trace距離與終止trace距離?這個(gè)bounds怎么計(jì)算?下面有說(shuō)
1.2 每個(gè)tile還包含兩個(gè)culled list,什么作用? - 還可以借助SDF進(jìn)一步降低計(jì)算消耗,how?

這里說(shuō)到,在AMD的GCN架構(gòu)下,使用光柵化算法比直接使用CS效率要高。其實(shí)現(xiàn)算法給出如下:
- 從frustum culling處理后的結(jié)果中構(gòu)建Draw Buffer
- 每個(gè)物件的覆蓋范圍bounds作為渲染的數(shù)據(jù),使用DrawIndexedInstancedIndirect一次性完成所有bounds的繪制
- 在PS中完成各個(gè)像素的min/max bounds的輸出。

為了提升計(jì)算效率,這里cone trace是在1/8分辨率(單緯度)下計(jì)算的,但是這種情況下計(jì)算的結(jié)果會(huì)有很強(qiáng)的鋸齒感,需要添加一個(gè)1/2分辨率的bilateral filtering,這個(gè)filtering是geometry aware的。

還可以進(jìn)一步通過(guò)借助TAA的velocity數(shù)據(jù)來(lái)降噪。

另一個(gè)優(yōu)化策略是將物件的Distance Field合并成一個(gè)場(chǎng)景Distance Field,因?yàn)槿绻菍?duì)物件的SDF進(jìn)行采樣的話,由于物件數(shù)目眾多,在采樣的過(guò)程中需要頻繁切換采樣貼圖,可能還有一些其他消耗,而合并成一張貼圖之后流程就更為清晰簡(jiǎn)潔了。

場(chǎng)景SDF是存儲(chǔ)在以相機(jī)為中心的四級(jí)Clipmap(每級(jí)clipmap都以相機(jī)為中心,不過(guò)覆蓋范圍逐級(jí)遞增(比如翻倍))中的,每級(jí)Clipmap的分辨率都是128^3。
clipmap的覆蓋范圍會(huì)隨著相機(jī)的移動(dòng)而進(jìn)行逐行或者逐列更新。
覆蓋范圍大的clipmap的更新頻率會(huì)低一些。

場(chǎng)景SDF的問(wèn)題在于靠近物件時(shí)的精度會(huì)有所不足,這里可以考慮使用兩級(jí)策略,在cone開(kāi)始的時(shí)候使用物件SDF,其他地方使用場(chǎng)景SDF。

這種處理策略可以極大的降低SDF的堆疊復(fù)雜度,提升運(yùn)行效率。

這里給出了天光遮蔽算法各個(gè)步驟的執(zhí)行消耗。

這個(gè)算法目前還存在一些問(wèn)題,上面的描述比較抽象,似乎是說(shuō)室內(nèi)遮擋效果會(huì)存在問(wèn)題,具體后面有更多信息再來(lái)補(bǔ)充。

SDF除了前面的應(yīng)用之外,還可以用在gameplay中,比如用于實(shí)現(xiàn)粒子與場(chǎng)景的交互效果。

還可以用于實(shí)現(xiàn)軟體模擬、gameplay玩法以及漸進(jìn)式flow map生成等。
