【圖形學與遊戲編程】開發筆記-基礎篇6:緩衝區與混合

(本系列文章由pancy12138編寫,轉載請註明出處:http://blog.csdn.net/pancy12138)

上一次的教程大家瞭解了最簡單的着色方法,那麼這一節我們來講解一些與着色無關的渲染管線流程。雖然在上一次的教程中我們成功的展示瞭如何給一個球體的表面進行光照着色。但是並不是說我們以往所有的知識就足以完美的展示出一個3D場景了。之所以我們能夠看到那個球體而沒有產生任何不適的感覺,主要是因爲我們不知不覺的已經用到了一些緩衝區的知識。當然,這在基礎篇一開始的時候就有涉及了,只是當初我們並沒有過多的去關注這些知識。那麼今天我們就要來仔細的分析緩衝區這一在圖形管線中極爲重要的算法。

首先,神馬是緩衝區,這個名詞大家應該不會陌生,所謂緩衝區就是一個和屏幕大小相當的數組。這在我們入門篇講解雙緩衝抗閃屏的時候就已經講到了。當初我們在講解交換鏈的時候第一次提及到了後臺(顏色)緩衝區。那麼事實上,爲了保證繪製過程的完美,整個繪製過程中我們需要很多緩衝區來實現各種各樣的算法。那麼今天我們要講的緩衝區包括:深度緩衝區,模板緩衝區,顏色緩衝區。然後,還有一個配合顏色緩衝區的alpha混合。注意,這裏所有的緩衝區都只是一個和屏幕一樣大的數組,所以他們都是工作於光柵化之後的,並且他們只會對每個屏幕像素保留一個值。使用的時候也是如此。大家不要習慣性的認爲這些緩衝區能夠存貯整個3D場景的所有信息。

首先是深度緩衝區,這個緩衝區的作用非常的明顯,就是爲了判斷物體之間的“遮擋”關係。比如說,當你把一個蘋果放在一個桌子前面的時候,蘋果就會把桌子的一部分擋住。但是我們之前講的3D->2D的投影操作以及矢量->光柵的光柵化操作都不能得到這個效果,這就會在之後得到一些錯誤的圖像,比如如果先畫蘋果後畫桌子,就會發現桌子把蘋果給覆蓋了,這很明顯和我們當初的想法是不符的。於是,無論是directx還是opegl都默認提供了一個深度緩衝區,這個緩衝區將會在光柵化完畢之後對每一個點進行一次“深度測試”,看看這個點究竟是不是離攝像機最近的點,如果是,那麼久蓋住之前的點,如果不是,那麼就不允許它繪製。用最簡單的程序語言來描述就是:

float depth_buffer[wind_width][wind_height];
void clear_buffer
{
     for(int i = 0; i < wind_width; ++i)
     {
          for(int j = 0; j < wind_height; ++j)
         {
             depth_buffer[i][j] = 1.0f;
         }
     }
}
void depth_check(float depth,int width,int height)
{
     if(depth < depth_buffer[width][height])
    {
        draw_point(width,height);
        depth_buffer[width][height] = depth;
    }
}

也就是跟大家平常寫的找一個數組裏的最大值差不多。每一幀開始的時候把緩衝區的每個像素都清空成最遠(注意這裏使用投影之後的點的z座標作爲距離攝像機的距離,所以最遠是1.0,最近是0.0)。然後,每次繪製物體的時候就調用檢驗函數來進行深度測試。當然深度測試函數肯定不能像上面我寫的那樣,在圖形庫裏面基本上都是調用的GPU加速的算法,並且屬於內置算法之一,運行速度是非常快的。這裏也許有些人會有一些疑問,爲什麼物體距離攝像機的深度可以用它的z座標來表示?如果換了觀察視角怎麼辦。事實上這個問題我們在之前的教程中已經講解過了。無論是directx還是opengl都是不允許你更改觀察視角的,也就是說那個視角其實是固定的。如果我們需要觀察視角變更的話,只要把物體挪動就好了。也就是說你想看一個物體的背面,並不需要走到他後面去,只需要把他轉一下把背面露給你就好了。深度緩衝區的用法事實上是很簡單的。directx裏面對於深度緩衝區是使用ID3D11depthstencilview來進行管理和使用的,而由於深度緩衝區的本質是一個二維圖片數組。而二維圖片數組本身是屬於“紋理”範疇的。所以,他的基本資源是一個texture2D資源,而管理器資源是ID3D11depthstencilview。大笑講到這裏大家估計會比較暈。博主你到底在說啥呢?這裏我稍微提及一下directx的資源管理方案:

首先,我們要知道,所有的大型資源(幾何體,紋理圖片,緩衝區等等)都必須存儲在顯存當中。但是我們控制繪製調用是用的CPU來調用的。因此,爲了避免CPU和GPU的數據交換,directx爲每一種不同的資源提供了不同的訪問方法,比如說shaderresourceview(SRV)用於訪問紋理,unorderdaccessview(UAV)用於訪問GPU可讀寫資源,depthstencilview(DSV)用於訪問深度模板緩衝區資源,這些資源訪問類我們可以理解成定義在cpu的指針,指向GPU的指針。當然這是一種更高級的訪問方式。而這些指針只擁有訪問資源的權限,並不能創建資源。如果要創建資源的時候,我們對於不同的資源會有不同的類來進行,比如說Id3d11buffer,或者texture2D這些。這些類都是繼承了id3d11resource類,其功能就是創建以及修改顯存中存儲的各種大型資源。由於顯存和內存之間進行數據交換非常的緩慢,所以我們一般只有在程序初始化的時候用這些類來進行顯存的創建工作,一旦顯存創建完畢,我們就可以只使用前面的那些GPU訪問類來對顯存資源進行調用。這樣就可以讓程序變的非常的流暢。而openGL也是以類似的思想來進行資源管理的(opengl1.0除外),只不過opengl裏面並沒有所謂的訪問指針,創建指針等分得很細的東西,我們在cpu上用一個int變量就可以代表顯存上相應的資源,由於所有的操作都封裝在OpenGL的狀態機裏面,所有我們既可以用這個int進行顯存的創建,也可以用它進行顯存的訪問。這種方法的話有好處也有壞處,好處是使用起來簡單,壞處是資源交換以及綁定就不簡單以及不太好封裝起來。不過這都不是重點,大家程序寫多了自然就都習慣了,所以對於這種資源管理的知識,大家要多練習,這樣才能掌握的比較好。

D3D11_TEXTURE2D_DESC dsDesc;
	dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	dsDesc.Width = wind_width;
	dsDesc.Height = wind_height;
	dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dsDesc.MipLevels = 1;
 	dsDesc.CPUAccessFlags = 0;
	dsDesc.MiscFlags = 0;
	dsDesc.Usage = D3D11_USAGE_DEFAULT;
	dsDesc.SampleDesc.Count = 4;
	dsDesc.SampleDesc.Quality = 0;

	ID3D11Texture2D* depthStencilBuffer;
	device_pancy->CreateTexture2D(&dsDesc, 0, &depthStencilBuffer);
	device_pancy->CreateDepthStencilView(depthStencilBuffer, 0, &depthStencilView);
	depthStencilBuffer->Release();
上述就是一個深度緩衝區的創建過程,這裏要注意的是深度模板緩衝區的msaa抗鋸齒倍數一定要和渲染目標,也就是後臺緩衝區的渲染倍數相同。不能出現比如後臺緩衝區4倍抗鋸齒,深度緩衝區8倍或者不開的這種情況。

這裏我們發現,深度緩衝區並不是一個單獨的緩衝區,他還附帶了一個模板緩衝區,一般一個32位的緩衝區會被分爲兩部分,其中24位作爲深度緩衝區,8位作爲模板緩衝區。那麼神馬又是模板緩衝區呢,這個緩衝區又有什麼作用呢?接下來我們就要來講解這一部分的知識。
由於深度緩衝區在directx與opengl中屬於功能不可更改的緩衝區(只能工作於深度比較)。但是其比對的思想又非常的好用。所以我們有時候會希望達到類似的像素測試效果,但是不希望用深度作爲標準,比如說我們希望x座標小於1的像素點不要顯示在屏幕上。那麼這個時候我們就需要一個自定義的緩衝區,這也就是模板緩衝區的作用,我們在使用模板緩衝區的時候,先要給緩衝區設定一個比較函數。然後再給模板緩衝區寫入一個用於比較的區域,然後再進行正式的渲染。這個時候就可以進行模板測試,凡是不能符合測試的像素點都不允許通過。這樣幹說理論可能大家比較暈。我舉一個最簡單的例子,比如說鏡面效果,就是通過鏡子能夠看到物體的鏡像這一效果:


上圖就展示了模板緩衝區的使用方法,首先我們先把鏡子渲染一遍,藉助這一過程把鏡子所在的像素點全部標記成可訪問的模式(白色區域),得到一張下面所示的模板緩衝區的數據。然後第二遍正式渲染的時候我們就可以開啓模板測試,只要最終渲染的像素座標對應的模板顏色不是白色的(也就是不可訪問),那就不能顯示,最終就可以使得我們渲染的模型的影子只出現在鏡面所在的位置。

當然,這只是最簡單的一個模板緩衝區的使用方式,模板緩衝區的應用還是比較多的,最經典的比如說shadow volume算法,這個會在之後講解全局陰影的時候來進行講解。現在可以大概的瞭解模板緩衝區的作用就好了,因爲這一緩衝區在大部分傳統算法中還是很少用到的,等到真正用到的時候再仔細學習也不算遲。


最後我們來講一個比較常用的東西,後臺(顏色)緩衝區以及alpha混合。顏色緩衝區以及其對應的顏色測試算法屬於最後一個測試算法。這個測試算法主要是用來屏蔽一些顏色輸出以及進行最終的顏色處理工作。我們在一開始講解後臺緩衝區的時候只是提到他是爲了防止閃屏而存在的。不過經過多年的優化,他也在進行圖像累計的過程中同時做了很多的顏色處理工作。首先是顏色屏蔽。如果我們的程序需要最終渲染的顏色的某個通道(RGBA)被屏蔽掉的話就可以開啓這個顏色屏蔽,這樣就可以達到一些比較特殊的效果,比如說我的博客譯文裏面的毛髮渲染,就是在pass1屏蔽了RGB通道來記錄最終的alpha效果。除了直接屏蔽通道以外,我們還可以根據自己的想法來進行顏色剔除工作,這個也很簡單,就是在pixel shader裏面,使用clip()函數來裁剪掉我們不想要的像素點。

然後是alpha混合,我們知道很多時候我們用於渲染的圖片並不是全部都是我們想要的,比如說一個角色的原畫,周圍的一些黑乎乎的東西我們就不希望他出現在渲染結果裏面:


比如說上面這張圖片,我們希望把角色顯示出來,但是不要顯示周圍的黑乎乎的背景。在ps中我們一般會選擇摳圖來解決。換算到shader裏面就是我們說的clip(color.r < 0.001f && color.g <=0.001f && color.b <=0.001f)這種類似的方式。但是這種硬生生的摳圖算法會使得圖像的邊緣非常的不平整,也就是不夠平滑。這是其一。其二就是對於一些“半透明”的物體,比如說水晶啊,水域啊這些的,我們希望透過它看到一些東西的時候就不能簡單地靠摳圖來進行解決。這個時候就需要alpha混合算法來進行。所謂的alpha混合就是指在進行顏色合成的時候,我們新渲染的物體的顏色會和之前渲染的物體的顏色進行合成。合成的公式有很多,最簡單的就是a.rgb*a.a + b.rgb*(1-a.a)這樣得到一個新的顏色效果,這種做法即可以平滑的處理“摳圖”的效果,也可以很好的處理“透過a看到b”這一現象。alpha混合在很多算法裏面都有涉及,其應用範圍可以說是非常的廣。不過目前來說我們基礎篇的程序尚且沒有用到這一功能。所以大家先了解這一概念就可以了。之後再在講解相關的渲染算法的時候我們會經常提到這一概念。

ok,那麼這一次的教程到這裏就算是結束了。這一次的教程我們並沒有講解任何程序相關的東西,只是給大家普及了緩衝區以及alpha混合的概念。這些東西暫時還是用處比較小的,但是他們是確實存在於渲染管線當中,並且在以後的算法設計過程中會體現出非常巨大的效果。所以大家可以先了解了解這些知識。這樣之後在瞭解更爲複雜的算法的時候就會更加的得心應手。

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