日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

visio任意区域填充斜线阴影_DX12渲染管线(6) - 级联阴影与距离场阴影

發布時間:2024/2/28 编程问答 67 豆豆
生活随笔 收集整理的這篇文章主要介紹了 visio任意区域填充斜线阴影_DX12渲染管线(6) - 级联阴影与距离场阴影 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

接上一期:

膜力鴨蘇蛙可:引擎搭建記錄(5) - 距離場 : 建場?zhuanlan.zhihu.com

項目地址:

MrySwk/GravityEngine?github.com

上一期,我們完成了距離場的構建,本文接下來分析并完整地實現一套類似于堡壘之夜中使用到的陰影系統,如下圖。

近處兩層級聯,遠處距離場

最遠的大桌子是距離場陰影,近處的兩個是級聯/pcss(因為沒有AO所以桌角還是有明顯的漂浮感,不過這不是這篇文章的討論范圍)。


CSM與PCSS

因為不是在unity這類的引擎里面實現,要考慮的東西就多了很多,事實上我現在這個小輪子里CPU端只有一個線程負責update和發dc,再加上系統里還沒有接入LOD的庫,導致一旦場景變復雜,效率會變得非常低,后期可能需要重構一次把渲染線程和更新線程分出來,以及加上LOD,場景的復雜度帶來的代價才能壓得下去,因此這里就不花時間討論這一塊,后面著手進行這一塊的優化的話可能再另作分享。

級聯和PCSS網上也有很多解釋了,在下就不拿來水文章湊數了,下面我們簡單提一下CSM/PCSS,再重點介紹距離場陰影的實現。

級聯

級聯的初衷是增加shadowmap的分辨率,一張貼圖覆蓋特別大的范圍會導致效果很糟糕,所以考慮離鏡頭越近的地方用分辨率越高的shadowmap,越遠的地方則分辨率越低,下圖是一個按視錐劃分的四層級聯。

view space alignment

而這樣做帶來的問題是,一旦鏡頭轉動,就會出現很嚴重的閃爍(想象一張網格紙在另一張網格紙上面轉動的效果),所以一般劃分級聯是在光源的空間中劃分:

light space alignment

這樣劃分的話,每一層級聯之間的重疊會更大,一般我們在重疊區域會取更精細的一層作為結果,在接近交縫處則考慮混合。

在渲染完shadowmap后,我們把陰影投回屏幕上,下圖是把各層級聯可視化的效果

R,G,B分別表示前兩層級聯和距離場的范圍

PCSS

Percentage Closer Soft Shadow是3A大作中常見的軟陰影解決方案,和卷積sm、esm、vsm這些技術相比,PCSS能更好的表現陰影的軟硬變化,其通過距離比計算出半影大小,再進行PCF濾波(也可以不選擇PCF而是和其他軟陰影技術結合使用)。PCSS主要解決的是,離陰影接觸點越近,陰影越硬的問題。

PCSS的半影大小(軟硬程度)是由下圖方式計算得到。

dBlocker的求法是,采樣周圍的像素,找有沒有哪個點對當前這個點造成了遮蔽,如果有,那么根據這個blocker來算半影的大小,然后再用比較大的核去采樣,如果不在半影范圍內的,則不需要使用大的采樣核,離blocker近的也使用小的采樣核,這樣就可以使得接觸點近處的陰影硬,遠的陰影軟。

因為代碼比較長所以就不列了,畢竟不是本文重點,此外,這一步可以考慮用比較低的采樣數+temporal過濾,不過這種方法也不適合特別大的采樣核(因為變化過大會導致被clip/clamp掉),帶temporal的效果我也在代碼中也實現了,但是效率似乎不如直接加大采樣數,因此最后這個效果中沒有開啟prefilter也沒有開啟temporal,采樣分布也非常naive地用了Hammersley,最終消耗加上后面的SDF March不會超過0.6ms。


實現距離場陰影

級聯是非常貴的,不作任何處理的話,每層級聯都意味著drawcall翻倍。其實,對于現代cpu來說,dc數量已經不是最大的問題了,相比之下,頂點、填充的開銷可能會更嚴重。而距離場絕對不僅僅是為了陰影更軟,還可以在保持動態的情況下降低級聯的開銷。

很多游戲里會用非常近的級聯,然后遠處用別的技術代替,比如far cry以前就用過高度陰影,虛幻里針對地形也做了個高度場,此外還有更直接的用static shadowmap的辦法,用靜態的壞處是,光源就不能動了,而tod在現在3A中是很常見的需求,此外,遠處的陰影也就只有靜態物體有了,其實對于大多數線性流程的游戲,烘焙的陰影也是完全夠用的,但是對于沙盤之類要求比較高的游戲,靜態可能會不太夠用,對于這種情境,SDF不能算是唯一的解決方案,但絕對是非常decent的一種。

距離場陰影效率遠遠高于普通shadowmap陰影,全屏1spp的ray march,在pc平臺上是完全可以接受的,實際上因為是遠處,降半分辨率也不會有太大影響,頑皮狗在美末中甚至用過1/4分辨率的cone trace來做軟陰影。

下圖給出的是1080p全分辨率全屏trace(不是只trace遠處),對1000個距離場進行march的結果,場景如下。

測試場景

首先需要提一下的一點是,用的是32x32x32分辨率的距離場(也是虛幻中默認的距離場大小),march的效率和網格多邊形數量無關,只和分辨率有關,所以不要在意都是box,復雜物體的效率也是一樣的。

結果剔除用了0.694ms,trace用到的耗時為0.669毫秒,因為沒有隨機,1spp畫面也是穩定的,不需要做任何高斯、雙邊、temporal。

剔除耗時

trace耗時(不要在意名字)

看上去是1.3ms~1.4ms的效率,但是分tile剔除本身是一個非常偏vgpr consuming的pass,這意味著,在主機和pc的A卡上,如果async安排的好,那么這個pass基本上是免費的,代價基本上就是后面的這個0.67ms的ray march,而實際使用的時候只有遠處,不是全屏,一般來講級聯還是會覆蓋屏幕上大部分區域,所以march的開銷會在0.3ms以內。

  • 分Tile剔除

上一期我們烘好了任意網格體的距離場:

烘出來的距離場(色帶是因為march步長較大)

對距離場進行march之前,我們要確定當前這個像素點會交到哪些距離場,因為march的方向是固定朝著光源方向進行,所以我們從光源的方向進行分塊剔除是一個比較合適的思路。

這一步我取的是64x64的分塊(瞎取的),沒有用距離場的AABB去求交,為了效率故直接用了sphere bound去求交,求交沒有投影到燈光的viewport里進行,而是直接在view space,用的最粗糙的剔除,代碼也很簡單:

for (int i = 0; i < gSceneObjectNum; i++){center = gSceneObjectSdfDescriptors[i].objLightSpaceCenter.xy;radius = gSceneObjectSdfDescriptors[i].objWorldRadius;xmin = tileXmin - radius;xmax = tileXmax + radius;ymin = tileYmin - radius;ymax = tileYmax + radius;if (center.x > xmin &&center.x < xmax &&center.y > ymin &&center.y < ymax){gSdfList[tileIndex].SdfObjIndices[gSdfList[tileIndex].NumSdf] = i;gSdfList[tileIndex].NumSdf++;}}

分塊的效果和剔除的結果可視化一下,為了看的清楚點,就用比較少的物體演示下,用灰色代表1個,白色代表2個,如圖所示:

  • Ray March

剔除完了之后,就可以開始cone trace了,trace的原理上一期也簡單提過,這里再解釋一次:

我們選定一個cone的角度,這個cone的角度越大,陰影越軟,選定好后,沿著cone的中心軸步進,采樣到的距離場值,就是其位置到最近點的距離,我們要找的是這跟中心軸上被遮蔽得最厲害的點,也就是(距離/圓錐半徑)的最小值:

float dist = gSdfTextures[sdfInd].Sample(basicSampler, pos).r; shadow = min(shadow, saturate(dist / (totalDis * CONE_TANGENT)));

march的距離我們知道,乘上tangent就是圓錐的半徑,而距離就是距離場里采樣的值,這樣取到的最小值,就是陰影的值。

追蹤的這一部分代碼我大半都是嫖虛幻的。這里把我的代碼貼一下,并解釋一下大致原理:

#if !DEBUG_CASCADE_RANGE[branch]if (!cascaded){float3 lightDir = -normalize(gMainDirectionalLightDir.xyz);float3 opaqueWorldPos = worldPos;float3 rayStart = opaqueWorldPos + lightDir * RAY_START_OFFSET;float3 rayEnd = opaqueWorldPos + lightDir * MAX_DISTANCE;float2 tilePos = mul(float4(opaqueWorldPos, 1.0f), gSdfTileTransform).xy;int2 tileID = int2(floor(tilePos.x * SDF_GRID_NUM), floor((1.0f - tilePos.y) * SDF_GRID_NUM));int tileIndex = tileID.y * SDF_GRID_NUM + tileID.x;if (tileID.x < 0 || tileID.x >= SDF_GRID_NUM ||tileID.y < 0 || tileID.y >= SDF_GRID_NUM)return 1.0f;int objectNum = gSdfList[tileIndex].NumSdf;float minConeVisibility = 1.0f;[loop]for (int i = 0; i < objectNum; i++){uint objIndex = gSdfList[tileIndex].SdfObjIndices[i];int sdfInd = gSceneObjectSdfDescriptors[objIndex].SdfIndex;float3 volumeRayStart = mul(float4(rayStart, 1.0f), gSceneObjectSdfDescriptors[objIndex].objInvWorld).xyz;float3 volumeRayEnd = mul(float4(rayEnd, 1.0f), gSceneObjectSdfDescriptors[objIndex].objInvWorld).xyz;float3 volumeRayDirection = volumeRayEnd - volumeRayStart;float volumeRayLength = length(volumeRayDirection);volumeRayDirection /= volumeRayLength;float halfExtent = gMeshSdfDescriptors[sdfInd].HalfExtent;float rcpHalfExtent = rcp(halfExtent); #if USE_FIXED_POINT_SDF_TEXTUREfloat SdfScale = halfExtent * 2.0f * SDF_DISTANCE_RANGE_SCALE; #endiffloat3 localPositionExtent = float3(halfExtent, halfExtent, halfExtent);float3 outOfBoxRange = float3(SDF_OUT_OF_BOX_RANGE, SDF_OUT_OF_BOX_RANGE, SDF_OUT_OF_BOX_RANGE);float2 intersectionTimes = LineBoxIntersect(volumeRayStart, volumeRayEnd, -localPositionExtent - outOfBoxRange, localPositionExtent + outOfBoxRange);[branch]if (intersectionTimes.x < intersectionTimes.y){float sampleRayTime = intersectionTimes.x * volumeRayLength;uint stepIndex = 0;[loop]for (; stepIndex < MAX_STEP; stepIndex++){float3 sampleVolumePosition = volumeRayStart + volumeRayDirection * sampleRayTime;float3 clampedSamplePosition = clamp(sampleVolumePosition, -localPositionExtent, localPositionExtent);float distanceToClamped = length(clampedSamplePosition - sampleVolumePosition);float3 volumeUV = (clampedSamplePosition * rcpHalfExtent) * 0.5f + 0.5f;float distanceField; #if USE_FIXED_POINT_SDF_TEXTUREdistanceField = SampleMeshDistanceField(sdfInd, SdfScale, volumeUV); #elsedistanceField = SampleMeshDistanceField(sdfInd, volumeUV); #endifdistanceField += distanceToClamped;// Don't allow occlusion within an object's self shadow distance//float selfShadowVisibility = 1 - saturate(sampleRayTime * selfShadowScale);//float sphereRadius = clamp(TanLightAngle * sampleRayTime, VolumeMinSphereRadius, VolumeMaxSphereRadius);//float stepVisibility = max(saturate(distanceField / sphereRadius), selfShadowVisibility);float sphereRadius = CONE_TANGENT * sampleRayTime;float stepVisibility = saturate(distanceField / sphereRadius);minConeVisibility = min(minConeVisibility, stepVisibility);float stepDistance = max(abs(distanceField), MIN_STEP_LENGTH);sampleRayTime += stepDistance;// Terminate the trace if we are fully occluded or went past the end of the rayif (minConeVisibility < .01f ||sampleRayTime > intersectionTimes.y * volumeRayLength){break;}}// Force to shadowed as we approach max stepsminConeVisibility = min(minConeVisibility, (1 - stepIndex / (float)MAX_STEP));}if (minConeVisibility < .01f){minConeVisibility = 0.0f;break;}}shadow = minConeVisibility;#if DEBUG_TILE_CULLINGshadow = gSdfList[tileIndex].NumSdf / 2.0f;//shadow = (float)tileIndex / (SDF_GRID_NUM * SDF_GRID_NUM); #endif} #endif

首先我們根據深度還原世界坐標,然后投到light space里找到對應的tile,查看tile里面存的距離場,找到之后開始march,先發送一條光線和距離場的AABB求交,確定march的起點和終點,這里這個AABB可以稍微擴大一圈,以免陰影特別軟的時候參與march的范圍不夠。

接下來規定一個最大步長,我這里取了64步,然后對于距離場外的位置,我們直接clamp到距離場內,并且加上clamp的距離作為距離場采樣值,在距離場內的就直接采樣,然后就是按上面所說的步進、采樣,找最小的遮蔽系數作為陰影的值。

遇到負數,說明是本影,就可以early out,遇到超出邊界,也可以直接停止march,這樣,我們就得到了軟陰影的效果:

  • 作為近處使用的主要陰影系統的可能性?

距離場也有一些非常麻煩的問題,降低了距離場的實用性,首先就是烘焙慢,一萬面左右的模型,一個32分辨率的場大概要烘十來秒到一分鐘不等,128分辨率的經常五分鐘起步,對于比較大的項目而言,這會是一個比較大的開支,有的時候可以考慮只烘焙比較大的建筑物等。此外,顯存占用也是一個問題,比如在UE4里開啟距離場會吃掉300M的顯存。

烘焙速度慢,也就注定了距離場不能支持頂點動畫,只能烘死了不做動畫,物體級別的移動、旋轉、縮放等是支持的,我們只需要對每個距離場維護一個變換矩陣就可以了,但是如果不能做動畫,使用就非常受限了,所以,我們可以考慮用膠囊體等簡單形狀來做動畫。

膠囊體陰影

虛幻里很早就支持了膠囊體軟陰影,膠囊體做陰影的效果會遠遠軟于PCSS,發個文檔鏈接:

膠囊體陰影?docs.unrealengine.com

虛幻給的圖里效果不是很好,實際上用起來效果是相當不錯的,頑皮狗在美末里以前也有過這個思路,那就是用簡單的膠囊等幾何體代替人來做復雜的trace計算:

出來的效果非常軟非常好看,簡直吊打shadowmap:

膠囊體軟陰影

膠囊體、box、圓柱、圓環等大多數簡單的幾何形體,其距離場都可以用公式表達,這個網頁里收錄了很多距離場公式:

fractals, computer graphics, mathematics, shaders, demoscene and more?www.iquilezles.org

膠囊體的距離場函數:

float SampleMeshDistanceField(int sdfInd, float3 uv) {float dist = gSdfTextures[sdfInd].SampleLevel(basicSampler, uv, 0).r;return dist; }float SampleMeshDistanceField(int sdfInd, float SdfScale, float3 uv) {float dist = gSdfTextures[sdfInd].SampleLevel(basicSampler, uv, 0).r;dist = (dist - 0.5f) * SdfScale;return dist; }

march的過程和上面一樣,可以寫個簡單的看下效果:

這個trace是只沿光源方向,不包括環境光,所以可以看到陰影內還是非常flat,這種trace的方式也和上面圖中的不同,是存在比較大的本影區域的。

虛幻里有個專門給膠囊體用的CapsuleShadow,是CapsuleShadowShaders.usf這個文件,感興趣的話各位可以去看,實現思路也基本上是先分tile剔除,再march距離場。

總結

距離場陰影和shadowmap相比,沒有drawcall,代價非常小,效果遠遠軟于PCSS,可以在沒有額外代價的情況下調整陰影的軟硬程度(可以特別特別軟),配合膠囊體可以做動畫,代價是烘焙需要時間,吃顯存,總的來說是一項非常實用的陰影技術。

再次附上代碼實現地址:

MrySwk/GravityEngine?github.com

Reference

[1] fractals, computer graphics, mathematics, shaders, demoscene and more

[2] http://miciwan.com/SIGGRAPH2013/Lighting%20Technology%20of%20The%20Last%20Of%20Us.pdf

[3] Cascaded Shadow Maps - Win32 apps

[4] https://www.realtimeshadows.com/sites/default/files/Playing%20with%20Real-Time%20Shadows_0.pdf

[5] https://developer.download.nvidia.cn/shaderlibrary/docs/shadow_PCSS.pdf

[6] https://github.com/chenjd/Unity-Signed-Distance-Field-Shadow

[7] https://github.com/EpicGames/UnrealEngine

總結

以上是生活随笔為你收集整理的visio任意区域填充斜线阴影_DX12渲染管线(6) - 级联阴影与距离场阴影的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。