ShadowMap基本的思想很簡單,首先從聚光燈角度對場景建立并儲存場景深度。然後在正常渲染場景中,比較每個渲染點到燈的距離值(或者說到燈的深度值)是否比對應的已經建立在場景深度中的值要大,也就是說要遠,如果遠證明從目前視點觀察的此點在燈角度中看不見,是以該渲染點處于陰影中,否則,不然。
首先分析一下ShadowMap的優劣:
優點:
ShadowMap隻适合較近距離的陰影投射,由于32位浮點數的精度有限,是以在較遠距離下,會将在陰影邊界附近出現錯誤現象。一種是隐隐約約的陰影(即陰影中帶有很多亮斑)。一種是陰影偏移并縮小。上述是比較嚴峻的劣勢,另外的一些就是常說的鋸齒,這些還算可以忍受。一般可以采用多重采樣要減輕這個狀況。再就是如果要使用點光源的話,需要都整個場景建立深度值,這樣就需要立方體紋理,也就是CubeMapping。
缺點:
雖然有如此多的缺點,但其優點确也是很值得關注的。在近景下十分不錯,而且ShadowMap的資源需求小,僅僅一張紋理。特别是其不會随着場景複雜而附加計算負擔這點。能給恒定幀率提供保證。
在ShadowMap中給我糾正的一個最大誤解就是,如何計算場景物體的點到燈的距離。不是我原本想想的那樣,在ps(Pixel Shader)中一個一個的求平方再開平方根求距離(其實這根本就是錯的,因為我們用的是深度值,并不是距離)。而是采用和建立的ShadowMap一樣的方法,就是将場景變換到燈的投影空間,然後用z處于w,得到範圍在0-1的z值也就是深度值。具體後面會提到。
ShadowMap案例中的大體思想知道後,欠缺的僅僅是一些程式設計接口的了解和使用。由于程式思路很清晰就不說程式的邏輯過程的!(不過看DX9對應的sample中代碼,覺得不夠條理,這個MS的員工寫代碼的時候可能在打盹)
還是主要談一下經驗和重點,基本上也是程式設計中遇到的需要注意的地方。
個人經驗:
ShadowMap中的陰影紋理啊
通過重新編寫sample,我獲得如下經驗:
在陰影紋理建立中特别需要注意的是,包含陰影紋理的表面不能采用mip鍊。因為我們隻需要一個表面,不需要那些mip鍊中的小表面。如果采用mip鍊後果将是很可怕的。因為采用後就會就導緻使用mip鍊進行過濾得出深度值。而經過過濾的深度值是沒有意義的。其實我查這個bug,查了5個小時,最後才發現是建立紋理時錯誤地把0傳遞給參數mipLeve,而0給mipLeve将會給表面建立mip鍊。而這是完全多餘和錯誤的。
添加判斷的偏移量是必須的,因為float的誤差,添加很小的量給建立的場景緩存是需要的,這樣最大程度減輕一個錯誤症狀,它就是把不在陰影區錯誤的弄成陰影區的症狀。
設定給燈的投影矩陣必須采用寬高比為1的矩陣,因為一般陰影紋理都采用正方形紋理。(此點後來證明不必要,隻要第四點滿足就可以)
兩次建立的燈投影矩陣必須一樣。這兩次分别是給場景建立深度值是采用的矩陣,另一個是在正常渲染中,為了得到渲染點相對于燈的深度值所采用的燈的投影矩陣。特别是建立兩次矩陣是采用的的近裁截面和遠裁截面必須一緻,否則兩次得到的深度值之間沒有可比性,因為投影空間中渲染點的深度值是通過用近裁截面和遠裁截面之間的距離壓縮得來的的。
投影空間中,x,y的範圍是(-1~1),需要将其投射都紋理空間(0~1)并且需要注意紋理空間和投影空間y的反向。
其實上面提到的投影矩陣應該說是燈的是視矩陣和燈的投影矩陣的乘積。單獨說投影矩陣隻是為了不累贅。
另外一些知識點:
建立ShadowMap紋理時,采用F32的浮點資料類型,對應的格式是D3DFMT_R32,用D3DFMT_R32是将4位元組解讀為單獨的紅色通道,并且是浮點數表示的。雖然在ps中需要傳回的顔色變量還是float4,但是隻需要指定其x分量即可。同樣在tex2D中我們也隻需要檢視其中的x分量就可以。
很開心!