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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[工作积累] shadow map问题汇总

發(fā)布時間:2025/3/13 编程问答 67 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [工作积累] shadow map问题汇总 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1.基本問題和相關(guān)

Common Techniques to Improve Shadow Depth Maps:

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx

?

Cascaded Shadow Maps

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416307(v=vs.85).aspx

?

Soft shadow

PCSS: http://developer.download.nvidia.com/shaderlibrary/docs/shadow_PCSS.pdf

PCSS shader sample:?http://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf

?

Translucent shadow

http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/S2008-Filion-McNaughton-StarCraftII.pdf

http://www.crytek.com/download/Playing%20with%20Real-Time%20Shadows.pdf

Shadows & Transparency

Translucency map generation:

  • Depth testing using depth buffer from a regular opaque shadow map to avoid back projection/leaking
  • Transparency alpha is accumulated only for objects that are not in “opaque” shadows
  • Alpha blended shadow generation pass to accumulate translucency alpha (sorted back to front)
  • ?In case of cascaded shadow maps, generate translucency map for each cascade
  • Shadow terms from shadow map and translucency map are both combined during deferred shadow passes with max() operation S

?

2.實(shí)現(xiàn)細(xì)節(jié)和問題

?

Hardware shadow map:

D16/ D32 / D24S8 作為rendertarget.關(guān)閉color wirte以加速渲染.

?

Hardware PCF:

  • 紋理采樣開啟雙線性過濾
  • d3d9用tex2Dproj,?d3d11用SampleCmpLevelZero

這兩個指令跟普通雙線性采樣不同的是, 會先執(zhí)行比較(深度比較),然后基于比較后的結(jié)果(陰影強(qiáng)度)進(jìn)行雙線性插值

?

Depth Bias

常規(guī)的shadow acne通過depth bias就可以修復(fù), 如果是自陰影, slope scaled bias也很重要.

如果需要用RFloat來繪制深度, color buffer沒辦法用bias, 那么就需要模擬slope scaled bias. 根據(jù)?https://msdn.microsoft.com/en-us/library/windows/desktop/cc308048(v=vs.85).aspx

對于dx可以這樣模擬:?

?(shadow slope scaled depth pass, rendering to a RFloat)

float dx = ddx(depth); float dy = ddy(depth); const float bias = max(abs(dx), abs(dy)) * YOUR_SLOPE_SCALED_BIAS; return depth + bias;

OpenGL 也有類似的功能?https://www.opengl.org/sdk/docs/man/html/glPolygonOffset.xhtml

原理都一樣,spec沒有說具體公式,但是通常有個偏移就可以正確顯示了,不需要精確的一致

?

最小視錐(minimal frustum)

在shadow map貼圖大小固定的情況下, 視錐越小, shadow map上的內(nèi)容越少(有效內(nèi)容不變), 所以利用率和分辨率越高.

?

naive 實(shí)現(xiàn):

1.計(jì)算場景包圍盒

2.根據(jù)場景包圍盒計(jì)算最小視錐

3.渲染shadow map

?

更好的實(shí)現(xiàn):

1.計(jì)算場景包圍盒

2.用場景相機(jī)裁剪這個包圍盒

3.用裁剪后的凸包體,計(jì)算最小視錐

4.渲染shadow map

?

由于場景包圍盒被相機(jī)裁剪(求交), 所以包圍盒變小, 那么生成的視錐也變得更小.

因?yàn)橐曞F是不規(guī)則六面體, 視錐和AABB求交得到的是一個凸包.求交方法可以看Ogre, Ogre有convex實(shí)現(xiàn).

我用的另外一個方法: 用aabb 的12條邊和視錐的6個面求交, 視錐的12個邊和aabb的6個面求交, 得到的交點(diǎn)如果同時在aabb和視錐內(nèi)就是凸包體的頂點(diǎn).

如果要計(jì)算凸包在Light space的包圍盒, 那么應(yīng)該先將每個頂點(diǎn)變換到light space, 再求包圍盒.

如果先求包圍盒, 在把包圍盒變換到light space, 因?yàn)锳ABB的軸對齊特性, 變換以后通常會變大.

?

我嘗試過的其他的實(shí)現(xiàn):

將場景bounding (minz, maxz)渲染到小紋理(比如32x32), 再download到CPU, 最后得到硬件裁剪后的最后可見的bounding box.

特點(diǎn): shadow map利用率高, 對于自陰影, 拉得非常近時, 效果仍然非常清晰. 這種方式一般情況下沒有必要使用.

這個就是類似 SDSM (Sample Distribution Shadow Maps) (一開始不知道叫這個名字,自己想的方法,后來在龔敏敏大大的文章里才知道), 這里有個例子,是將深度讀回并down sample得到minz和maxz:?https://github.com/TheRealMJP/Shadows

?

Depth bias based on frustum depth range

因?yàn)槭褂玫氖荋ardware shadowmap, 所以需要指定depth bias和slope scaled bias.

由于frustum根據(jù)根據(jù)convex的頂點(diǎn)計(jì)算來.?如果frustum的深度范圍(znear, zfar)不固定的話, 那么相同的bias值, 對應(yīng)的誤差會有浮動.

所以可以根據(jù)視錐的深度范圍來計(jì)算bias, 這樣的誤差值是固定的. 方法:

渲染shadow map depth的時候只指定slope scaled bias, 不指定depth bias.

渲染物體的時候, shadow matrix = M * light_projection * light_view , 也就是再乘以一個參數(shù)矩陣 M (DepthBias):

float offset = isD3D9 ? 0.5f / (float)ShadowMapSize : 0f; //half texel offsetfloat bias = -0.0015f / (DepthRange) * adjustScale;DepthBias[0] = Vector4(1, 0.0f, 0.0f, offset));DepthBias[1] = Vector4(0.0f, 1, 0.0f, offset));DepthBias[2] = Vector4(0.0f, 0.0f, 1.0f, bias));DepthBias[3] = Vector4(0.0f, 0.0f, 0.0f, 1.0f));

同時由于計(jì)算出的uv要采樣shadowmap, 對于dx9來說, 同時可以預(yù)先應(yīng)用half pixel offset.

?

PCSS:

PCSS的原理比較簡單, 也有很多變種. 目前用的是標(biāo)準(zhǔn)的實(shí)現(xiàn). 遇到的問題:

PCSS的半影采樣范圍是根據(jù)相似三角形計(jì)算出來的

float PenumbraSize(float zReceiver, float zBlocker) //Parallel plane estimation {return (zReceiver - zBlocker) / zBlocker; }

如果zblocker的深度太小(接近0), 那么半影采樣范圍就變得非常大, 難以接受.

這種情況在做Self shadow的時候會遇到, 因?yàn)橐曞F是很小的.?解決辦法:

1.用戶指定半影的大小范圍(shader constant, lightWidthMin, lightWidthMax), ?然后根據(jù)深度(距離)做線性插值.

或者 2.放大depth range, 這樣最小的depth也不會接近0

?

如果產(chǎn)生陰影的物體和接受陰影的物體靠得太近,?zReceiver - zBlocker 太小, 導(dǎo)致半影范圍接近于0, 導(dǎo)致鋸齒:

解決辦法可以用上面的線性插值, 因?yàn)榫€性插值最小值是lightWidthMin, 保證有最小的半影范圍.

或者: 在半影范圍上加一個常量值, 比如1.0/shadowMapSize
個人使用lightwidth_min和max線性插值, 這樣也方便美術(shù)調(diào)控參數(shù).

?

Translucent shadow(not alpha test):

上面已經(jīng)貼出的Crytek和StarCraftII的方法了, 方式比較類似.

我這里的簡單實(shí)現(xiàn):?

R8 + Depth16,?alpha和depth同時繪制, 一個color buffer保存alpha, 一個depth buffer保存深度.

opaque: Depth test - less, disable color write.

transparency: Depth test - less,?disable Z write, output alpha, enable color blending(addative)

這種方式比較簡單, 一次繪制沒有render target切換, 先畫不透明物體再畫半透明物體. shader中對于alpha的陰影判斷也比較hacky: 如果alpha值(R8.color.r)不為0, 則認(rèn)為有陰影, 不需要比較深度. 因?yàn)槟軐慳lpha值的時候, 說明深度測試less通過了.?對于PCSS需要深度的, 可以模擬一個深度值.

問題: 不支持自陰影(doesn't support self shadow). 產(chǎn)生陰影的半透明物體本身, 如果要計(jì)算陰影, 根據(jù)alpha!=0這個判斷, 也是有陰影的...

?

改進(jìn)方式:

基于上面的方式, 給transparent objects再加上一個depth pass, 繪制陰影時采樣兩張深度圖. 或者將前面的R8改為RGBA, A保存透明度, RGB打包深度, 單獨(dú)混合alpha通道, 這樣不用切換render target.
兩個depth pass的話多一張D16的貼圖, 顯存占用要比RGBA小.

另外, alpha值越大, 光線穿透越少, shadow值應(yīng)該越小, 我用crytek的max()結(jié)果是不對的,我用的是min(sample(ShadowMap), 1-alpha), 懷疑crytek使用的是 1- max(1-sample(ShadowMap), alpha).

即crytek paper所說的shadow term是(1-sample(shadowMap)), 改值越大,陰影就越大。

?

問題: 自陰影錯誤

| ? ? ? ?| ? ? ? ? ?|

a1 ? ? ?a2 ? ? ? O

如上, O是不透明物體, a1和a2是透明物體.

當(dāng)出現(xiàn)多層透明物體的時候, O的陰影是對的, 因?yàn)閍1和a2的alpha 會混合.

基于上面的改進(jìn), 因?yàn)橛辛松疃刃畔? 再加上bias, 所以a1不會有陰影, 也是對的.

但是a2的陰影和O的陰影是一樣的, 都是基于同一個alpha計(jì)算出來的.由于a2是半透明物體, 陰影表現(xiàn)沒有那么明顯, 這里的問題可以忽略.

或者: 用深度來做線性插值進(jìn)行:

shadow(uv.xyz) = lerp(1-alpha_a1a2, 1, saturate( (depth_O ?- uv.z) / (deoth_O - ?depth_a1) ) );?

其中uv.z是shader中當(dāng)前物體的深度.?alpha_a1a2是alpha混合的結(jié)果. depth_O是opaque shadow map采樣出的深度. depth_a1是transparent shadow map采樣出來的深度.

這其實(shí)還是不對的, 因?yàn)閍2的陰影透明度是1-alpha_a1, 跟距離無關(guān), 但是可以解決透明物體和非透明物體靠的太近時的z fighting和shadow?acne.

?

上面是工作中主要遇到的問題. 另外簡單記錄一下其他東西.

CSM

如果所有陰影都產(chǎn)生在一張shadowmap上,那么近處的分辨率也會比較低.CSM主要是通過多個cascade來提高近處的陰影分辨率.

之前工作中也做過PSSM, 一般會把多個shadowmap合到一張上, 比如4張1024x1024的shadowmap, 可以用2048x2048的貼圖, 通過viewport來繪制四個區(qū)域. 上面Crytek也提到了.

還有CSM邊界分割處也需要blend處理, 不然會有縫隙.

?

PSM

perspect最大的特點(diǎn)是近大遠(yuǎn)小, 所以使用pserspective 投影, 來提高近處陰影的分辨率.

但一般方向光都是平行光, 需要用orthographic (parallel)投影, 但由于一般場景相機(jī)都是perspective, 視錐不是box, 但在投影以后(post perspective space)是一個box, D3D是z[0,1]的扁盒子, OGL是一個cube. 在這個空間下, 因?yàn)榉较驎星凶?比如世界空間下的視錐四條射線, 到了這個空間是4條平行線), 所以原本世界空間的方向光到了post perspective space就變成了點(diǎn)光源, 可以用perspective projection了.

原理大致是這樣, 實(shí)現(xiàn)的話會比較tricky.

另外一個Light space perspective shadow map (LiSPSM), 也是一種PSM, 還是利用perspective 投影來提高近處投影的精度. 主要的改變是不在用場景相機(jī)的post perspective space. 因?yàn)閜erspective投影下大部分方向都會有形變, 但是垂直于視方向(平行于xy平面)的方向不會有改變. LiSPSM就是利用這一點(diǎn), 使用一個垂直于光照方向的透視投影, 來渲染深度. 如果直接用這個視錐渲染的話, y值就是沿著光照方向的深度值. 所以先把光空間變換到垂直于光照方向, 應(yīng)用透視投影, 再變換回來, 得到的z就是深度了. 這個實(shí)現(xiàn)上要比PSM簡單得多. 我也嘗試了一下, 但是和一般的shadow map差別不大(比LiSPM的demo視頻和代碼效果差多了), 可能是實(shí)現(xiàn)上有點(diǎn)問題, 或者是只渲染了一個角色的自陰影的問題.

?


更新:關(guān)于自陰影的問題,如果要支持全方向的光源,遇到了一些問題。

1.一般陰影用depth bias效果已經(jīng)不算差, 自陰影需要slope scaled bias 才能更好的去掉acne

2.自陰影的projective aliasing(投影鋸齒,最前面第一個鏈接有)非常嚴(yán)重:

projective aliasing是因?yàn)楣庹辗较蚝捅砻媲芯€平行(與法線垂直)的時候,這個面上的的所有點(diǎn)只共享了一個投影點(diǎn)造成的陰影精度不夠。shadowmap分辨率和利用率提高,以及好的sampling比如NxN的PCF可能會緩解這種問題。
我目前的處理方式,是根據(jù)它產(chǎn)生的特點(diǎn),做一個糾正

#define SELFSHADOW_COS_MAX 0.00872653549837393496488821397358 //cos 89.5 degree shadow = sampleSahdowMap(...); shdaow = min( saturate(NdotL-SELFSHADOW_COS_MAX ), shadow);

即在接近垂直的時候,clamp為0,省得各種黑白交錯。這么做的結(jié)果是,0.5 degree precision loss,但是效果好了很多。

注意在計(jì)算spot/point light 的NdotL時, L = normlalize(lightPos.xyz - worldPos.xyz * lightPos.w); 是光源位置到當(dāng)前點(diǎn)位置的向量, 這跟繪制shadowmap的direction并不一樣,需要使用實(shí)際的光源方向。

為了方便使用,可以封裝為一個簡單的宏或者函數(shù):

//anti projective alias #define SHADOW_APA(worldNormal, shadow) min(saturate(saturate(dot(worldNormal.xyz,Uniform_SelfShadow_Direction[Uniform_LightIndex].xyz)) - SELFSHADOW_COS_MAX), shadow)

這里Uniform_SelfShadow_Direction就是方向光的方向或者spotlight的方向,是渲染shadowmap時使用的方向。

?


更新:

對于toon shading,由于非真實(shí)光照的緣故,大多不能使用NdotL,所以上邊方法不可行,有兩個方案:
1.使用HW shadowmap,HW PCF fliter能夠很大程度減少projective alias

2. 使用VSM。經(jīng)過測試,VSM能過很大程度減少與projective alias,比HW shadowmap要好很多

?


更新筆記2:

前面提到的LiSPSM,效果不好的原因找到了,因?yàn)長iSPSM在計(jì)算光空間包圍盒的時候有點(diǎn)tricky,用了視錐的點(diǎn)往場景做raycast,得到更緊湊的包圍盒。簡單點(diǎn)用ascii圖示。。:

E F +-----------------------+ |\ /| | \ / | | \ / | | \ / | | \ / | | \ / | | \ / | | \ / | +--------+-----+--------| A B C D

上圖為光空間的,垂直于光方向的截面,BCEF是相機(jī)視錐投影,BC是近平面的點(diǎn),EF是遠(yuǎn)平面的點(diǎn)。LiSPSM需要用BCEF(實(shí)際上在3D空間有8個點(diǎn))作為原點(diǎn),光的(反)方向作為方向,做raycast,于場景包圍盒相交,得到的點(diǎn)用于計(jì)算土凸包和最小包圍盒,這么做是為了擴(kuò)展包圍盒,將場景不可見的,但是影子可見的物體加入包圍盒。

我之前用的是足夠大的光的正交視錐,和場景包圍盒相交,其實(shí)就是ADEF,這樣會將近平面BC放大到AD。很明顯用正交視錐來算的話,包圍盒和標(biāo)準(zhǔn)shadow map一樣大,主要是近平面被放大,會導(dǎo)致LiSPSM的透視投影變很大。。。

其實(shí)LiSPSM的raycast方法,在極端情況下也會有問題:

R F' Q +-----------+-------------------+| \ |G +H \ || \ ||C' \ ||\ \ || \ \ || \ \ || \ \ |+----+---------------+----------+O C F P

?

比如上圖,OPQR是場景包圍盒,CF是視錐的近平面和遠(yuǎn)平面上的點(diǎn),FF'是光的反方先, raycast得到的點(diǎn)是C‘和F’。 raycast場景包圍盒,相交的凸包點(diǎn)為CFF‘C’,得到的光空間包圍盒為CFF’G,F‘G垂直于光方向并與場景包圍盒相交于H。 那么F’ R H, 這個區(qū)域內(nèi)的物體是可能沒有投影的。 這種情況比較極端,在大場景下基本看不到,在查看模型的時候,模型的內(nèi)部邊緣看(比如sponza場景的墻里面),就會出現(xiàn)。

?

轉(zhuǎn)載于:https://www.cnblogs.com/crazii/p/5443534.html

總結(jié)

以上是生活随笔為你收集整理的[工作积累] shadow map问题汇总的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。