淺談Virtual Texture

一、摘要

隨着更多開放大世界的遊戲的流行,遊戲引擎就會需要使用到更多的資源,這無論是對帶寬還是存儲都是一個挑戰。當然,這個問題需要很多的解決方案共同完成,本文介紹其中一個解決方案:Virtual Texture,用來解決巨量Texture的加載問題。


二、總覽

Virtual Texture是由id Software提出,用來解決Texture加載問題的一套解決方案,不僅解決了帶寬和內存問題,還帶來了其它的好處。隨後,在這個方案的基礎上,行業又相繼提出瞭如:Procedural Virtual Texture、Adaptive Procedural Virtual Texture、Hardware Virtual Texture等解決方案。本文會從id Software的方案出發,逐步介紹到最新的基於新一代圖形API的硬件Virtual Texture。

1. 在Virtual Texture之前

其實在Virtual Texture之前,已經出現過一些解決方案,這些方案中的思想直接促成了Virtual Texture的誕生。由於Mesh的LOD,產生了一種將Texture bake到不同LOD的解決方案,與之相類似的,在運行時動態決定要加載的Texture,給不同的LOD Mesh。一個名爲Jonathan Blow的程序員在2000年提出了基於後者的方案,1998年Tanner發表了一篇論文:《The Clipmap: A Virtual Mipmap》,這篇論文中提到的Clipmap的技術本來用於地圖渲染,後被id Software應用到遊戲中,提出了一個叫做MegaTexture的技術。

Clipmap的基本思想是:設置一個Mipmap大小的上限,超過這個上限的Mipmap會被clip掉,也就是不會加載到內存中。

當我們的視野發生變化時,我們需要修改被clip的區域,讓視野內Mipmap的部分加載:

這項技術被用在了Doom3的開發上,而這項技術背後的推動者是John Carmack。


2. Software Virtual Texture

在MegaTexture的基礎上,id Software進一步提出了Virtual Texture的概念,這個概念取自於Virtual Memory。與虛擬內存類似的是,一個很大的Texture將不會全部加載到內存中,而是根據實際需求將需要的部分加載;與虛擬內存不同的是,它不會阻塞執行,可以使用更高的Mipmap來暫時顯示,它對基於block的壓縮貼圖有很好的支持。

基本思路是:將紋理的Mipmap chain分割爲相同大小的Tile或Page,這裏的紋理是虛紋理,然後通過某種映射,映射到一張內存中存在的紋理,這裏的紋理是物理紋理,在遊戲視野發生變化的時候,一部分物理紋理會被替換出去,一部分物理紋理會被加載。

這樣的機制不僅僅減少了帶寬消耗和內存(顯存)消耗,也帶來了其它好處。比如有利於合批,而不用因爲使用不同的Texture而打斷合批,這樣可以根據需求來組織幾何使得更利於Culling。當然合批的好處是states change變少,LightMap也可以預計算到一張大的Virtual Texture上用來合批。

2.1 地址映射

地址映射在Virtual Texture中是一個很重要的環節,但是如何將一個Virtual Texture的Texel映射到Physical Texture的Texel上,需要適配到高分辨率的Page沒有加載的情況,並得到已經加載的對應低分辨率的Page地址。

2.1.1 四叉樹映射
使用四叉樹主要是爲了和Mipmap對應,也就是每個低MIP的Map會對應有四個高MIP的Map,四叉樹中只存儲加載的Mipmap信息。這裏的對應關係就是每個加載的Virtual Texture的Page對應一個四叉樹的節點,具體的計算如下:

這裏存在每個四叉樹的節點中的內容就是bias和scale,這樣就可以將虛擬紋理的地址轉換成物理紋理的地址。如果沒有找到,也可以用父節點的地址來得到低分辨率。但是這裏要找到對應的節點需要搜索這個四叉樹,搜索的速度取決於樹的高度,也就是Mipmap的層級,在差的低MIP的地址上效率會比較低。

2.1.2 單像素對應虛紋理的一個Page的映射
爲了減少索引,首先容易想到的就是,爲每個虛紋理的Page都存儲一份信息,這樣就能直接轉換了。這個方案就是創建一個帶Mipmap的Texture,一個Texel對應虛紋理的一個Page,Texel的內容就是四叉樹映射裏面的bias和scale。

假如對應的MIP沒有加載,存儲的就是高MIP的轉換信息,這樣顯然就提高了地址轉換的效率。但是這會帶來內存增加,因爲我們需要每個虛紋理的Page都對應一個Texel。其中bias和scale都是二維的向量,即使設計虛紋理和物理紋理的比例一致,我們也需要至少scale、SBias、TBias三個量,而且這三個量的精度要求很高,至少需要16bit的浮點數精度。如果要達到這樣的精度就需要F32*4的紋理格式,那麼必然會產生一個巨大的映射紋理,因此需要減小映射紋理的大小。

2.1.3 雙紋理映射
這個方案仍然有一個對應每個虛紋理Page的Texture,但是不同的是,紋理的內容存儲的是物理紋理Page的座標,用這個座標再去索引另外一張Texture。另外一張貼圖的內容纔是bias和scale,但不是每個虛擬紋理,而是每個物理紋理Page一個Texel。下圖是虛擬紋理對應的Texture:

這樣就減少了映射紋理的大小,但是同時多了一次紋理查詢。

2.1.4 Page和MIP level映射
總結上面兩個基於映射紋理的方案,要麼是紋理需要很大的存儲,要麼是需要多次查詢。如果從映射紋理比較大的角度考慮優化,可以考慮適當減少每個像素的大小,這個方案就是從這個角度出發的。在這個方案中,仍然是每個虛擬紋理的Page對應一個texel,但是存儲的內容是物理紋理Page的Offset和虛擬紋理所在的MIP level。

 

這樣存儲的好處就是,Page Offset對精度的要求沒有那麼大,用32bit的Texture即可。當然也可以壓縮到更小格式的紋理中,如RGB565。這種方案的使用最廣泛,基本各家引擎的實現都使用了這種方案。

2.1.5 HashTable映射
這是最直接的方法,好處是節省內存,查詢速度快,但是當遇到沒有加載的virtual Page的時候,需要多次查詢。這個和四叉樹還有一個問題,即如何設計一個GPU友好的數據結構。

2.2 Texture Filtering

由於虛擬紋理並沒有完整加載,所以各種採樣過濾在Page的邊界會有問題,我們需要自己設計解決這些問題的方法,適當的使用軟實現的採樣。

2.2.1 Bi-linear Filtering
這個解決方案比較簡單,就是給Physical Page加上一個像素的border。

2.2.2 Anisotropic Filtering
Anisotropic Filtering可能需要更多的相鄰像素,假如我們需要支持8倍的Anisotropic Filtering,那我們需要採樣步長爲4的相鄰像素,也就是我們的border要增加到4個像素。增加4個像素的border會增加Physical Texture的大小,但是帶來了一個好處,就是適配了block compression。

具體實現可以分爲軟實現和硬實現,硬實現放到下文的Tri-linear說,這裏說軟實現。軟實現其實就是在Shader中實現Anisotropic Filtering的算法,在決定採樣的MIP level的時候,需要把虛紋理相對於物理紋理的比例考慮進去,剩下的就是正常的Anisotropic Filtering。

 

2.2.3 Tri-linear Filtering
Tri-linear Filtering的實現方案可以分爲兩種:一種是軟實現,一種是硬實現。

所謂的軟實現與Anisotropic Filtering一樣,在Shader中實現Tri-linear Filtering。也就是說,需要在Shader中計算MIP level,然後進行兩次地址的轉換,採樣兩個物理紋理的Page後進行插值。

硬實現的方法是直接給物理紋理生成一個一層的Mipmap,然後利用硬件去直接採樣。同樣的,對於Anisotropic Filtering,也打開Anisotropic Filtering直接進行採樣。這樣的好處當然是由於硬件的加速,採樣的效率會提升,但是這樣同時會導致增加25%的紋理大小,而且由於Mipmap的邊界會變成兩個像素,對於block compression和超過4倍的Anisotropic Filtering來說,在遇到Page的邊界時都會出現問題。

2.3 Feedback Rendering

在Virtual Texture中一個很重要的事情是要有一個可以決定加載哪些Page的策略,這個策略要有一個Feedback Rendering的過程。這個可以設計爲一個單獨的pass,或者在渲染Pre-Z Buffer,GBuffer的時候同時渲染。渲染生成的這張Texture裏面存的就是虛紋理的Page座標,MIP level和可能的虛紋理ID(用來應對多虛紋理的情況)。

可以看到上圖,由於Page的變化在屏幕空間是比較低的,所以Feedback的RenderTexture不需要全分辨率,低分辨率渲染就可以。對於半透明物體或者Alpha Test的物體,在Feedback Rendering的過程中只能當作是不透明物體來渲染,那樣就會在屏幕像素上隨機產生當前像素的可能結果。

與之類似的,如果一個屏幕像素用到了兩個Page,也會是隨機出現一種在最後的結果RenderTexture上。這樣雖然可以讓所有需要的Page都加載,但是可能會遇到另外一個問題,即可能會發生這一幀加載的Page,下一幀的時候被卸載掉,然後再下一幀又要加載,這樣會導致物理紋理一直在置換,即便屏幕像素並未改變,物理紋理的Page也無法穩定下來。

爲了解決這個問題,需要設計一個調色板,對於半透明物體,間隔出現全透明或者不透明;對於多Page的情況,則需要設計爲間隔出現不同Page的結果,這樣就能同時加載所有Page,並且保持穩定。但是,如果出現了多層半透明物體的疊加或者多個Page的情況,如何設計一個合理的調色板就變成了一個問題。這裏可以考慮爲每個像素匹配一個linked list,但需要額外的硬件支持:structured append and consume buffers。

接着就是對這個Feedback的結果進行分析,分析需要將Feedback的結果讀回CPU,這裏可以使用Compute Shader解析這個結果,然後輸出到一個更簡單的buffer上去:

這樣可以使回讀操作更快,處理Page更新也能更快。對於如何更新Page,也需要策略,我們需要儘量不阻塞執行,異步地加載Page,但是對於完全不存在任何一個MIP的Page,我們還是需要同步加載防止顯示出錯。在異步的過程中,我們要對需要加載Page設置優先級,比如需要加載的MIP level和已經存在的MIP level相差越大的優先級越高,被越多像素要求加載的Page的優先級越高,這裏需要設計一個完整的加載策略。

2.4 Texture Poping

由於Page是異步加載的,所以會有延時,當加載的MIP比當前顯示的相差很遠時,我們渲染會使用新加載的更清晰的MIP,這樣我們會看到非常明顯的跳變。假如我們用了軟實現的Tri-linear Filtering,那麼當加載的MIP level跟當前顯示的MIP level相差很大的時候,需要做一個delay,等待中間的MIP Page的加載,然後再去更新。對於沒有Tri-linear Filtering的實現,就得逐漸更新Page,使得過度平滑。一個可能的方法是,Upsample低分辨率的MIP,直到高分辨率的MIP加載。但是,因爲採樣的位置其實發生了突變,仍然會出現跳變。

 

上圖可以看到,當分辨率增加2倍之後,結果會發生很大的不同。解決的方案是,先把Upsample的低分辨率Page加載到一個物理紋理的Page,當高分辨率的Page加載好了,插值過度物理紋理的Page,這樣採樣的位置沒有發生改變,只是每個像素的顏色在漸變,就不會有跳變出現了。

2.5 存儲和流式加載

這一部分本文會說的比較少,因爲這一部分內容甚至比Virtual Texture本身還要繁雜,所以只提一些Virtual Texture比較特殊的部分。

2.5.1 存儲和壓縮
對於存儲和壓縮,在光盤或者磁盤上存儲的,需要選擇一個適合的JPEG等的壓縮方式。重點是當加載到內存之後,提交給GPU,根據之前的內容,需要用一個block compression來適配各種Filtering,也可以使用不同顏色空間來進行進一步壓縮。

2.5.2 離線烘焙Feedback Data
就如同可見性烘培一樣,對於Feedback Data是可以離線烘培的,《Titanfall 2》中實現了這個方案。

2.6 Pipeline

總結一下Software Virtual Texture的流程:

 

首先執行一個Feedback Rendering,或者從烘培好的Feedback Data中取出當前數據,然後分析需要加載的Page,嘗試從內存中找到這個Page,如果沒有,就從磁盤或者光盤中加載。然後申請一個新的物理紋理Page,在使用這個紋理之前,假如是之前用過的Page,我們需要先與GPU解綁。確定完之後,把這個Texture轉換成一個GPU能用的壓縮的Texture內容,並將這塊Texture與GPU綁定。其中分析Feedback、Unmap、Transcode、Map過程是可以異步發生的,也就是需要一個Job System去驅動,他們的關係是生產者與消費者的關係。

在Software Virtual Texture中提到的一些內容,在其它的實現中也是需要的,比如Feedback Rendering在Hardware Virtual Texture中需要使用,地址影射在Hardware Virtual Texture和Procedural Virtual Texture中都要用到,接下來描述方案的時候,我就不一一再去說,只提一下他們的方案名字。


3. Hardware Virtual Texture

Software Virtual Texture的提出,對整個行業產生了重要的影響。但是由於需要適配各家顯卡和圖形接口,id Software的工程師們在戴着腳鐐跳舞。由於顯卡的虛擬內存是早就存在的東西,這裏更多的工作是需要各家圖形API去完成。這裏不會探究API廠商是如何使用GPU的虛擬內存實現的,而只討論如何使用和使用它所帶來的好處。

3.1 地址映射

虛擬內存系統爲我們做的就是,虛擬到物理的地址映射,從物理內存中讀取我們想要的Texel信息。假如Page不在內存中,返回一個錯誤告訴我們。這裏需要注意的是,GPU的虛擬內存的大小固定是64KB,也就是說每個Page存儲的像素的個數與定義的像素格式大小有關。

 

工作過程跟Software差不多,仍然需要Feedback Rendering,分析Feedback數據,不同的是如何更新和採樣的過程。

首先需要在內存中創建一個Page pool,這是用來存放從磁盤中加載的紋理內存,可以理解爲物理紋理。然後,我們再創建一個特殊格式的Texture,代表那張Virtual Texture,這個時候,圖形API會幫我們創建一個Page table用於映射。在更新的過程中,我們需要首先更新映射關係,也就是Page pool和Page table的關係,然後再將加載的Page pool中的內容更新到Virtual Texture中。

中間的Page table是與Virtual Texture Page一一對應的,對於維護這樣一個Page table要比Software簡單很多。當使用這樣的Texture的時候,我們跟使用普通Texture一樣就可以了,所有的採樣、內存的映射都是圖形API在底層爲我們實現,這大大降低了代碼複雜度,我們唯一需要處理的就是出現採樣錯誤的情況。

3.2 Hardware Virtual Texture益處

Hardware Virtual Texture對比Software有很多優勢,上面已經提到過,可以當一個普通Texture來採樣,而且支持所有硬件的採樣包括Bi-linear、Tri-linear、Anisotropic filtering。不需要爲物理Page增加border,不需要爲映射地址創建另外的Texture,減少了內存的佔用。地址的映射不需要再多餘的採樣其它的Texture,完全是硬件驅動,所以效率會更高。

 

當然,這一切都是需要各家圖形API的支持,只有一些比較新的API版本才能支持,DirectX叫Tiled Resource,Vulkan叫Sparse Partially-Resident Images,OpenGL叫ARB_sparse_texture,Metal叫Sparse Texture,爲每個API做適配也是一個成本。


4. Procedural Virtual Texture

上面提到的兩種Virtual Texture中,Texture的內容來源都是離線的,也就是我們需要將它們從磁盤或者光盤中加載到內存中。現在介紹的這個方案的Texture是運行時產生的,所以叫Procedural,在Unreal裏稱爲Runtime Virtual Texture。這個方案首次提出是在Battlefield3中,另外一位華人工程師ChenKa在FarCry4中進行了改進,提出來叫Adaptive Virtual Texture的概念。與離線的Virtual Texture一樣,仍然需要去維護一個Virtual Texture到Physical Texture的映射,這樣的映射可以用Software實現,也可以用Hardware。

 

4.1 BattleFiled3方案

由於這項技術是用在地形渲染上的,對於地形渲染,以及對於大世界的地形渲染,如果將整張地形映射到一張Texture的座標上,而且要保持精度足夠,那虛擬紋理的座標就會很大。假如我們用Software來實現,就會帶來一個問題,就是Indirection Texture的大小會很大。爲了解決這個問題,BattleFiled3使用了上文提到的Clip Map的技術,讓Indirection Texture高精度的Mip只有部分存在。

 

4.2 Adaptive Procedural Texture

FarCry4中的方案則更加徹底,它的想法是突破最高的MIP精度,也就是在MIP0的上面讓虛紋理的Page更大,這樣就超過了MIP0的精度。虛紋理的Page的設計也被設計成非等大的Page,允許虛紋理出現各種不同大小的Page。

 

以下描述是我對Adaptive Procedural Texture的個人理解,由於在反覆看完PPT和GPU Pro中的文章後,發現ChenKa的書寫中存在一定的錯誤,按照其中的說法是無法完成實現的,所以我只能根據他書寫的內容,加上自己的推斷來儘量還原實現。

首先,需要理解的是,虛紋理是個不存在的紋理,主要是用來尋址和易於理解,所以一切對虛紋理Page大小的修改,落實到實現的時候,其實是在更改Indirect Texture,所以上圖顯示不同大小Page的虛紋理其實是Indirect Texture存在這樣不同大小的Page。

 

可以看這張圖的左邊。所以,我們根據相機的位置,其實改變大小的是Indirect Texture Page的大小。在之前的實現中,Indirect Texture的一個Page就是一個像素,這裏就不同了,一個Page對應的是多個像素,也就是說,其實一個Page對應了多個物理紋理的Page,上圖也體現了這一點。上圖的錯誤在於,當Page變大之後,應該是覆蓋更多的Page,而不是相同數量的Page。還有一個問題沒有解決:虛紋理如何映射到Indirect Texture的?或者說是根據當前的位置如何去決定Terrain用到的Indirect Texture?這裏引入另外一個概念:Sector,它用來描述地形塊。

 

整個地形被分割成相同大小的Sector,每個Sector上存的是Indirect Texture的信息,包括位置和大小。這樣,當採樣地形某個位置的時候,就可以確定到某個Indirect Texture的像素,也就確定到了物理紋理的Page上,多了一次轉換,即需要把Sector的座標轉換到Indirect Texture上。

以上是我根據ChenKa的描述來敘述的,其實換一種思路更好理解,把Sector理解成虛紋理,現在每個虛紋理的Page映射了多個物理紋理的Page,那麼就需要更改Indirect Texture的Page大小。在實現的過程中還有一些細節,比如在變大或者變小的過程中,需要更改Mipmap的內容,直接拷貝舊內容會比較快。

 

這裏的Virtual Image是Indirect Texture,另外在Feedback Rendering的時候除了要計算PageId和MIP level,還要加上一個Indirect Texture Page的大小。

4.3 Hardware Procedural Texture展望

上面說的是用Software的方式去實現,假如用Hardware來實現,一切似乎變得很簡單,直接給虛紋理設置到精度最高,由於沒有了Indirection Texture,只是直接的內存映射,所以不會出現內存佔用過多的問題,只要根據相機位置確定MIP level即可。但是,由於並未實踐過,需要去驗證。由NASA的Mars項目可以看到,能支持的Texture尺寸非常大。

之前說了,這項技術主要被用在地形的渲染裏面,當渲染地形的時候,可以將地形先渲染到類似於GBuffer的大貼圖上,這個過程是分幀完成的。然後,再使用極簡單的材質渲染地形,這樣就大大降低了地形渲染中多層blending的消耗,同時也可以支持巨量動態的Decals。


三、後記

由於Virtual Texture所涉及的並沒有很多高深的圖形技術,只是在原有的技術上的一個優化,更多考驗的是實現者的工程能力,本文只是粗淺介紹了Virtual Texture的實現原理,實現本身才是Virtual Texture的難點。下一篇準備以Unreal爲例,談談Virtual Texture的工程實現。


Reference

[1]Chajdas, Matthaus G., et al. "Virtual texture mapping 101." GPU Pro (2010).
[2]Mittring, Martin, and Crytek GmbH. "Advanced virtual texture topics." ACM SIGGRAPH 2008 Games. 2008. 23-51.
[3]Widmark, Mattias. "Terrain in Battlefield 3: A modern, complete and scalable system." GDC Presentation publications. dice. se/attachments/GDC12_Terrain_in_Battlefield3. pdf (2012-05-31) (2012).
[4]Chen, K. "Adaptive virtual texture rendering in far cry 4." Game Developers Conference. 2015.
[5]Chen, Ka. "Adaptive Virtual Textures." GPU Pro 7: Advanced Rendering Techniques 131 (2016).
[6]Obert, Juraj, J. M. P. van Waveren, and Graham Sellers. "Virtual texturing in software and hardware." ACM SIGGRAPH 2012 Courses. 2012. 1-29.
[7]Van Waveren, J. M. P. "Software Virtual Textures." (2012).
[8]Hollemeersch, Charles, et al. "Accelerating virtual texturing using cuda." GPU Pro: advanced rendering techniques 1 (2010): 623-641.
[9]Cebenoyan, Cem. "Real Virtual Texturing Taking Advantage of DirectX11. 2 Tiled Resources." Game Developer Conference. 2014.
[10]Barrett, Sean. "Sparse virtual textures." Talk at Game Developers Conference. 2008.
Blow, Jonathan. "Terrain rendering at high levels of detail." Proceedings of the 2000 Game Developers Conference. Vol. 3. sn, 2000.
[11]Tanner, Christopher C., Christopher J. Migdal, and Michael T. Jones. "The clipmap: a virtual mipmap." Proceedings of the 25th annual conference on Computer graphics and interactive techniques. 1998.
[12]https://channel9.msdn.com/Events/Build/2013/4-063
[13]http://twvideo01.ubm-us.net/o1/vault/gdc2017/Presentations/Barb_Chad_EfficientTextureStreaming.pdf
[14]http://renderingpipeline.com/2012/03/megatextures-in-rage/
[15]https://docs.microsoft.com/en-us/windows/win32/direct3d11/tiled-resources
[16]https://vulkan.lunarg.com/doc/view/1.0.37.0/linux/vkspec.chunked/ch28s04.html
[17]https://developer.apple.com/documentation/metal/textures/managing_texture_memory?language=objc
[18]https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_sparse_texture.txt


文末,再次感謝李兵的分享,如果您有任何獨到的見解或者發現也歡迎聯繫我們,一起探討。(QQ羣:793972859)

作者主頁:https://www.zhihu.com/people/li-bing-77-8,作者也是U Sparkle活動參與者,UWA歡迎更多開發朋友加入U Sparkle開發者計劃,這個舞臺有你更精彩!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章