Deferred Decal(延遲貼花)

Decal渲染是一個引擎中重要的一部分,記憶中印象最深刻的就是以前CS中的彈痕與爆炸痕跡了。目前來說,Decal的實現方法也比較多,而且感覺還跟遊戲類型有關,比如子彈亂飛的射擊類FPS遊戲中對貼花系統的要求就比較高,因爲本來Decal的變化就比較豐富。一般來說貼花渲染主要有兩種實現方法:

  • Texture projection關於投影紋理的原理網上也有很多文章,基本就是需要對地形渲染兩遍,一遍正常渲染,一遍用投影的紋理渲染,然後把這兩個混合的結果融合。顯然,如果場景中的貼花較多,且場景規模複雜的話,這種方法就會比較費。
  • Mesh projection 這種方法應該來說使用比較多,包括早期Source中的Decal等使用的應該就是這種方法,其主要原理就是將Decal處理爲附着於其所能貼到的幾何表面上的幾何網格,然後當做polygon list來渲染。當然這些網格也是經過修飾的(比如貼到一個桌子的邊角上時,就需要根據桌角幾何網格的分佈來裁剪Decal所對應的網格)。關於該方法這裏有一篇文章介紹得非常好:http://blog.wolfire.com/2009/06/how-to-project-decals/。另外,基於此種方法的改進類型也不少,比如Frosbite引擎中就利用Geometry Shader來生成類似該類型的Decal(Shadows and Decals in the Frostbite Engine)。

結合最近普遍應用的後處理渲染流程,於是自然而然地也就出現了Deferred Decal,這裏一個最基本的條件就是需要有一個基於延遲渲染的系統才能使用這種方法。關於Deferred decal的實現原理也比較簡單,過程如下:

  1. 在後處理渲染中完成正常的G-Buffer渲染後,即可得到對應的Normal-depth buffer,以及Diffuse Buffer,但是此時仍沒有進行光照計算,這時就需要將Decal給渲染到對應的Diffuse buffer中。爲了實現Decal,這裏需要在G-Buffer後再增加一個Pass,並使用Diffse buffer作爲Render target,並使用Decal shader進行渲染,計算並更新Decal所能夠影響到的那些Pixel的Diffuse。
  2. 爲了確定每個Decal所能夠影響到的範圍以便更新相應的Diffuse buffer,一般需要繪製出Decal所對應的一個包圍幾何體,這裏可以是Sphere或Box(例如Volume decal中使用的就是Sphere,CryEnigne中使用的就是Box),當然,如果不計代價還可以使用更復雜的形體。
  3. 最後,關鍵的一步就是計算需要Decal的那些Pixel所對應的Decal uv,這裏根據包圍體的不同方法也不同。Sphere的映射方法比較簡單,可以直接將Pixel的世界座標轉到局部球體座標即可,再根據對應的Volume texture或2D texture再做一次映射,然後訪問紋理即可。如果使用Box的話可能就稍麻煩些,這時需要知道Decal box所對應的一個局部座標系轉化。這個Decal座標系由以下幾個量來確定:Decal所貼的位置,Decal貼到處的Normal,Decal的水平方向,Decal的大小變換;然後由這三個基本量來構靠世界到Decal局部的變換矩陣:
    float3 gDecalScale;
    float3 gDecalPosition;
    float3 gDecalNormal;
    float3 gDecalLRVector;
    
    float3 zAxis = normalize(gDecalNormal);
    float3 xAxis = normalize(gDecalLRVector);
    float3 yAxis = normalize(cross(xAxis , zAxis));
    
    float4x4 decalMatrix = 
    {
    	{xAxis.x , yAxis.x , zAxis.x , 0.0f} ,
    	{xAxis.y , yAxis.y , zAxis.y , 0.0f} ,
    	{xAxis.z , yAxis.z , zAxis.z , 0.0f} ,
    	{-dot(xAxis , gDecalPosition) , -dot(yAxis , gDecalPosition) , -dot(zAxis , gDecalPosition) , 1.0f}
    };
    
    這裏的變換矩陣其實也相當於在Decal的位置上放置一個Camera,即從Decal的角度來觀察整個場景~_~
  4. 得到變換矩陣之後即可對Pixel變換,然後在局部座標中計算出對應的Decal UV就可以做相應的Decal紋理讀取了。

比如將下面這個很有追求的Decal貼到Sponza場景中的效果如下

 

Enhanced effect with Bump:

此外,爲了增強Decal的渲染效果可以使用帶有Bump的Decal,不過這裏要加入到整個渲染流程中的話可能會稍稍有些麻煩。爲了使用Decal的Normal並在此上進行光照着色,就需要把Decal的Bump給附加到G-Buffer中的Normal上,這一步雖然與Decal texture的附加類似,但通常情況下Normal與Depth會在一個Buffer裏邊存儲,而在進行Diffuse的decal pass時由於需要反求像素的世界位置,因而就要使用ND buffer,這樣就不能在該pass裏完成decal bump normal的合併。不過可以再建立一個跟Diffuse類似的中間Buffer,其只用來存儲渲染Decal時生成的Normal值;然後在當前的Decal pass結束以後,再使用另外一個Pass,使用Normal Depth作爲目標RT來合併原始Normal與Decal的Normal。

但是,這其中又涉及到另外一個變換:Decal normal由局部座標到世界座標的變換,這其實是相當於對應於decal space的一次tangent變換,需要對於上述的decalMatrix求逆轉矩陣來做變換,而這一步的代價還是比較費的,因而此功能就需要謹慎使用。

Some Tips:
  • 處理不同類型物體時的操作。在使用Defferd Decal的方法時,這個問題其實最爲常見,也最需要解決。其實這裏的不同類型較爲廣泛,比如說下述幾何上不屬於同一類物體(見下圖),本來是想只將Decal貼在旗子上,但是其卻也出現在了後面的牆上;另外,還在就是比如地面上的decal,但如是主角走過該處時,decal可能就同樣會出現在角色身上。這個問題一般有兩種解決方法:Stencil testing,Object index。前一種方法使用模板測試來標識不同的物體,相對來說其實現較爲方便些,而第於二種方法可能需要改變G-Buffer的內容,在使用上可能還不如第一種方法,雖然其通用性更強。                              
  •  
  • 關於Corner Wrap問題,這種情況的表現就像下圖所示。該情況的產生是由於通常情況下只考慮Decal水平方向上的座標投影變換來計算UV,而並沒有考慮Decal normal方向上的影響。知道原因後這個問題比較容易解決,可以利用法向量的變化來計算Decal垂直方向上的UV分佈就可以了。                    
  • Decal texture採樣尋址方式需要設置成CLAMP,否則就會出邊重複的情況,視覺上的不正確。另外,也要注意Alpha的混合狀態設置。
  • 關於Camera進行Decal Box或Sphere內部的問題。由Defferred decal的實現原理可知,畫出包圍Decal的Box或Sphere其實是用來標記其所影響的Pixel以便更改diffuse,但這樣就會有一個問題,那就是當Camera進入Box或Sphere內部後可能就會導致在視點處直接看到原始mesh,而不是box的mesh,這樣肯定就不會進行Decal所對應的shader了,於是也就沒法畫上decal。這個問題也好解決,既然想增加對原始Mesh的遮擋,那麼就可以將原始的Box或Sphere細分,即在其內部增加繪製的triangle以增加遮擋的機會,這樣即使Camera進入其內部,仍然可以有內部的子部件來遮擋原始Mesh。不過簡單起見可以只在Box內部再多畫幾個對角線上的triangle就可以應付大多數情況了。
  • 對於Decal數量較多的情況,加入Decal Box或Sphere的實例化渲染以及對應Camera的可見性檢測也是很有必要的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章