簡單陰影分析
ShadowMap實現方式
正常ShadowMap
實現的Shadowmap的方法就是,在光源位置放置一個相機,朝著光照方向,繪製一遍場景,得到的深度圖就是Shadowmap。而在真正繪製場景的時候則可以將場景的世界空間下的點P使用燈光的proj*view矩陣轉換到Shadowmap座標系下,取樣shadowmap,把得到的shadow值與場景點的深度值進行比較,如果當前點的深度值比shadow值更大,那麼該點就處於陰影中,從而不做對應點的光照計算。
螢幕空間ShadowMap
具體參考螢幕空間深度陰影貼圖(Screen Space Deep Shadow Maps)
正常繪製陰影肯定會有OverDraw,為了減少OverDraw,可以先把整個場景的深度圖繪製出來,可以使用EarlyZ。
- 繪製得到深度圖了
- 再回到光源位置,將光源位置處將會繪製ShadowMap的每個畫素位置轉換到世界空間,然後再轉換到真正的相機投影到的depth空間,取得深度值。
- 將得到的深度值轉換回光源的depth空間,並存到當前的ShadowMap對應的畫素位置上。
避免了OverDraw,場景以及每個光源都只需要渲染一次
潛在問題
本節參考
1、Shadow Acne
主要表現是在不該產生陰影的位置產生間隔的波浪形陰影,如圖所示,球面 出現了自陰影,
增大陰影紋理解析度並不能消除
生成的原因如圖所示(參考自https://www.zhihu.com/question/49090321)
光源處得到的shadowmap的一個畫素對應於場景中一片區域,如果光源與場景面的夾角很大,那麼一個shadowmap的畫素對應的區域會更大,導致的artifact會更明顯。
ShadowBias就是做如下操作
解決方法是使用ShadowBias
- Depth Bias:計算時把場景畫素超光源方向移動一定偏差距離
- Slope-Scaled Depth Bias: 跟Depth Bias一樣,但是移動的距離跟物體表面與光源的傾斜程度有關,表面傾斜程度越大,移動的偏差就越大
- Normal Offset:畫素朝著頂點表面法線方向移動,也要考慮表面傾角
- View Direction Offset:跟NormalOffset一樣,但是是朝著相機方向移動
- Receiver Plane Depth Bias:The receiver plane is calculated to analytically find the ideal bias.
軟陰影
本節參考知乎回答
由於陰影的邊緣是硬邊緣,所以需要軟邊緣來模擬軟陰影。方便分析,參考ESM的paper,定義一個Shadow Test Function
s
(
x
)
=
f
(
d
(
x
)
,
z
(
p
)
)
s(x)=f(d(x),z(p))
s(x)=f(d(x),z(p))
x
x
x 是攝像機看到的場景中某一點,
d
(
x
)
d(x)
d(x)是這一點到光源的距離,
p
p
p是在陰影紋理中遮擋物的位置,
z
z
z則是從shadowmap中讀取出來的值。
對於標準陰影貼圖技術
f
(
d
,
z
)
=
{
1
,
d
(
x
)
≥
z
(
p
)
0
,
d
(
x
)
<
z
(
p
)
f(d,z)=\left\{\begin{matrix} 1, & d(x) \ge z(p)\\ 0, & d(x) \lt z(p) \end{matrix}\right.
f(d,z)={1,0,d(x)≥z(p)d(x)<z(p)
PCF
PCF就是正常的計算ShadowMap,但是在計算場景表面一個點的陰影的時候會多重取樣,並將差值的結果作為 d ( x ) d(x) d(x)的值,從而得到軟陰影,想要要過更好就取樣更大的取樣核函式5x5等。Unity預設使用的就是PCF
- 優點:效果好
- 缺點:計算量大,需要在計算陰影的時候對每個計算陰影的點都做多重取樣。可以參考元神,通過計算出一個螢幕的陰影邊緣mask,只對半影區域(即陰影邊緣)做PCF,這樣既利用了PCF的優點,同時也避免了在陰影內和陰影外的無意義取樣。
參考
這張神奇的Mask貼圖是怎麼生成的呢?這張Mask貼圖的解析度是螢幕解析度的1/4×1/4,也就是說一個Mask值對應的是一個4×4的block。然後我們對4×4的block裡面的每一個畫素,來判斷它是不是在陰影中,最後彙總成一個陰影、半影和非陰影的三個狀態,儲存到Mask貼圖裡。這樣我們能夠得到一個準確的半影資訊,但是它不夠快,所以我們做了進一步的優化,只選擇4×4這個block裡面很少的幾個畫素,來判斷是不是在陰影當中。
這幾個畫素的判斷結果,就代表了整個block的資訊,顯然這樣會出現一些誤差,因為我們是拿幾個少數幾個畫素的結果來代表整個block,所以我們把這樣計算得到了Mask貼圖做了模糊處理,讓半影的區域稍微擴散出去。整個Mask貼圖的生成,包括模糊處理大概的開銷是在0.3毫秒左右。大家可以看一下對比圖。
通過計算Mask的方法確實非常值得借鑑。而且就使用經驗而言,近景軟陰影就只有PCF是完美的選擇
VSM
VSM的實現關鍵在於渲染到深度紋理的同時渲染深度的平方,並在shading pass計算期望和方差。這個深度貼圖會佔用比之前多一倍的記憶體
實現需要注意幾個地方:一是在shadow pass 的時候就利用mipmap,高斯模糊等技術做好預處理,減少鋸齒和突變。二是在shading 的時候,注意只有深度大於期望的時候才利用切比雪夫公式進行近似計算(這是由於計算的深度是一個上界)
ESM
PCF需要在計算每個點的時候都進行多重取樣,這也是它的最致命缺點,而ESM則是通過通過對ShadowMap進行預濾波,然後在計算的陰影的時候取樣ESM的計算方法。
ShadowTest可以被線性化為
f
(
d
,
z
)
=
∑
i
=
1
∞
a
i
(
d
)
B
i
(
z
)
f(d, z)=\sum_{i=1}^{\infty} a_{i}(d) B_{i}(z)
f(d,z)=i=1∑∞ai(d)Bi(z)
假設
d
≥
z
d \ge z
d≥z,即場景所有的點的深度值都比shadowmap的值更大,那麼把
f
f
f 重新定義成如下
f
(
d
,
z
)
=
lim
α
→
∞
e
−
α
(
d
−
z
)
f(d, z)=\lim _{\alpha \rightarrow \infty} e^{-\alpha(d-z)}
f(d,z)=α→∞lime−α(d−z)
則可以使用一個大的
c
c
c 來逼近
α
\alpha
α ,則指數形式可以寫成
f
(
d
,
z
)
=
e
−
c
(
d
−
z
)
=
e
−
c
d
e
c
z
\begin{aligned} f(d, z) &=e^{-c(d-z)} \\ &=e^{-c d} e^{c z} \end{aligned}
f(d,z)=e−c(d−z)=e−cdecz
接下來,我們可以對shadowmaping函式進行處理
s
f
(
x
)
=
[
w
∗
f
(
d
(
x
)
,
z
)
]
(
p
)
=
[
w
∗
(
e
−
c
d
(
x
)
e
c
z
)
]
(
p
)
=
e
−
c
d
(
x
)
[
w
∗
e
c
z
]
(
p
)
\begin{aligned} s_{f}(\mathbf{x}) &=[w * f(d(\mathbf{x}), z)](\mathbf{p}) \\ &=\left[w *\left(e^{-c d(\mathbf{x})} e^{c z}\right)\right](\mathbf{p}) \\ &=e^{-c d(\mathbf{x})}\left[w * e^{c z}\right](\mathbf{p}) \end{aligned}
sf(x)=[w∗f(d(x),z)](p)=[w∗(e−cd(x)ecz)](p)=e−cd(x)[w∗ecz](p)
我們可以看到,可以直接把預濾波放到shadowmap上做了。但是如圖所示,
c
c
c 必須要大,才能讓ESM的效果更好,但是
c
c
c 太大,又會導致超越深度圖值最大值。
簡單來說,它就是用 exp ( k ∗ ( z − d ) ) = exp ( k ∗ z ) ∗ exp ( − k ∗ d ) \exp^{(k*(z-d))} = \exp^{(k*z)} * \exp^{(-k*d)} exp(k∗(z−d))=exp(k∗z)∗exp(−k∗d)來近似(z-d>0)。生成了ESM之後,照樣可以和VSM一樣用gaussian blur或者box blur。
SSM | VSM | ESM | |
---|---|---|---|
生成 | return d; | float2 dxdy = float2(ddx(d), ddy(d)); return float2(d, d* d + 0.25f * dot(dxdy, dxdy)); | return exp(c * d); |
使用 | return z < d; | float p = (z < moments.x);float variance = moments.y – moments.x * moments.x; variance = max(variance, min_variance); float m_d = moments.x – z; float p_max = variance / (variance + m_d * m_d); p_max = linstep(bleeding_reduce, 1, p_max);return max(p, p_max); | return saturate(occluder * exp(-c * z)); |
引數 | 無 | min_variance和bleeding_reduce,場景相關 | c,場景無關,越大越好 |
優點 | 簡單 | - 可以blur - 無bias問題 | - 簡單 - 可以blur - 無bias問題 - blur kernel較小 - 1個32F通道 - 效果和深度複雜度無關 |
缺點 | - 無法blur - bias問題 | - 需要較大的blur kernel - 2個32F通道 - 效果和深度複雜度有關 | - 精度 - 非線性depth |
改進,使用 SIGGRAPH 2009的Advances in Real-Time Rendering in 3D Graphics and Games裡,Lighting Research at Bungie提到的logarithmic space filtering。這裡正是利用d-z遠小於d或z的原理,把取值範圍縮小了,精度也因此提高。filtering本身就是完成這個:
∑
i
=
0
N
w
i
e
c
d
i
\sum_{i=0}^N w_i e^{cd_i}
i=0∑Nwiecdi
其中w來自於gaussian filter的kernel。如果進一步推這個公式,就能得到:
∑
i
=
0
N
w
i
e
c
d
i
=
e
c
d
0
e
ln
(
w
0
+
∑
i
=
1
N
w
i
e
c
(
d
i
−
d
0
)
)
\sum_{i=0}^N w_ie^{cd_i} = e^{cd_0}e^{\ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right)}
i=0∑Nwiecdi=ecd0eln(w0+∑i=1Nwiec(di−d0))
這個被稱為log space filtering。最終filter的結果是個不會溢位的量。
d
=
c
d
0
+
ln
(
w
0
+
∑
i
=
1
N
w
i
e
c
(
d
i
−
d
0
)
)
d = cd_0 + \ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right)
d=cd0+ln(w0+i=1∑Nwiec(di−d0))
用這個修改過的公式去做gaussian blur,那麼即便是16F也可以承受高達300的c。
最後改進後的形式為
SSM | ESM | 改進的ESM | |
---|---|---|---|
生成 | return d; | return exp(c * d); | return d; |
使用 | return z < d; | return saturate(occluder * exp(-c * z)); | return saturate(exp(occluder – c * z)); |
引數 | 無 | c,場景無關,越大越好 | c,場景無關,越大越好 |
優點 | 簡單 | - 簡單 - 可以blur - 無bias問題 - blur kernel較小 - 1個32F通道 - 效果和深度複雜度無關 | - 簡單 - 可以blur - 無bias問題 - blur kernel較小 - 1個16F通道 - 效果和深度複雜度無關 - 很大的c - 線性depth |
缺點 | - 無法blur -bias問題 | - 精度 - 非線性depth | 必須使用修改過的blur |
從實際表現上來看,ESM最大的問題還是漏光
根據原理,預濾波的方法必然會導致漏光的現象。
因為在陰影邊緣,陰影的變化非常大,而做了指數為c的運算後,其值會變得非常的大。c值很大的時候,陰影會變得非常的硬,但是c值不大的時候又會因為做了指數運算,在陰影邊緣稍微有點差別都會被指數倍放大,做filter的時候回汙染陰影值,所以會導致漏光。所以其實並不好用。
但是如果是用在靜態軟陰影上,我們生成靜態軟陰影的時候其實是可以知道哪些地方是相交的,所以可以提前把相交的面上做的filter減弱,那麼最終得到的預濾波的shadowmap就不會有漏光的問題。
ESM和VSM是實現
我用Unity的SRP實現了VSM和ESM,有興趣的可以拉下來看看
VSM和ESM實現
引用
[2] https://zhuanlan.zhihu.com/p/26853641
[3] http://www.gamelook.com.cn/2020/11/404063
[5] 切換到esm
相關文章
- 簡單好用的陰影庫 ShadowLayout
- css圖片陰影、文字陰影CSS
- 【Avalonia】【跨平臺】關於控制元件陰影簡單用法控制元件
- boder 陰影
- 假陰影,低開銷的陰影實現方式
- 陰影進階,實現更加的立體的陰影效果!
- 【CSS】曲線陰影CSS
- view邊框陰影View
- CSS 陰影進階,實現更加的立體的陰影效果!CSS
- Shadow Map(陰影貼圖)跟Soft Shadows(軟陰影)
- canvas設定陰影效果Canvas
- WPF 實現陰影效果
- HTML中的盒子陰影HTML
- Qt 視窗強制禁用系統陰影(自定義選單)QT
- 遊戲中的陰影實現遊戲
- CardView改變陰影顏色View
- CSS陰影效果三劍客CSS
- CardView原始碼解析-View陰影View原始碼
- CSS jquery圓角帶陰影的導航選單程式碼分享CSSjQuery
- 《Morkredd》:使用陰影構建遊戲玩法遊戲
- CSS圖片邊框陰影效果CSS
- WebGL實踐之半透陰影Web
- CSS3筆記(二)陰影CSSS3筆記
- css實現邊框陰影效果CSS
- iOS清除輸入框內陰影iOS
- Unity移動端動態陰影Unity
- view的陰影效果shadowColorView
- 變形元素旋轉css陰影CSS
- WPS文件如何給標題加陰影?WPS文件給標題加陰影的方法教程
- ExplosionField簡單分析
- CSS 邊框陰影立體邊框CSS
- CSS:遮罩效果、陰影效果、毛玻璃效果CSS遮罩
- CSS3-陰影引數基礎CSSS3
- css邊框陰影程式碼例項CSS
- Android Material Design 陰影實現AndroidMaterial Design
- 網頁頂部陰影邊框效果網頁
- 超讚的 CSS 陰影技巧與細節CSS
- 實現給一個DIV加陰影效果!