原文:《A trip through the Graphics Pipeline 2011》
在上一篇關於紋理採樣器以後,咱們如今回到了3D前端。那執行完了頂點着色,如今就能夠實際的渲染東西了,對嗎?惋惜,還不行。你看,在咱們實際開始光柵化圖元以前,仍然還有不少事要作。因此在本篇裏咱們不會看到任何光柵化內容——還得等到下次講。
圖元組裝
當咱們離開頂點管線時,咱們從shader單元裏獲得了一塊着色過的頂點,這塊頂點中包含了一些完整的圖元——咱們不會讓三角形,直線或片被分割到多個塊裏。這很重要,由於這意味着咱們能夠真正的單獨處理每個塊,而且不須要緩衝多個shader輸出塊——雖然能夠緩衝,但不必這麼作。
下一步是組裝單個圖元的全部頂點。若是圖元碰巧是一個點,就只須要讀取確切的頂點並傳遞它。若是是直線,要讀取兩個頂點。若是是三角形,要三個頂點。並以此類推大量控制點的片。
簡而言之,這裏作的工做是收集頂點。咱們既能夠經過讀取原始的索引緩衝來收集頂點並存一份頂點索引的拷貝——緩存周圍的位置映射,或者咱們也能夠存儲隨同着色的頂點徹底展開的圖元的索引。這將花費一些空間用於存儲輸出緩衝,可是在這裏咱們就沒必要再讀取索引了。用哪一種方式均可以。
如今咱們已經展開了組成圖元的全部頂點。換言之,咱們如今有完整的三角形了,而不只僅是一堆頂點。那咱們已經能夠光柵化它們了嗎?還不行。
視口剔除與裁剪
我猜咱們應該先執行這個,對不?這是管線中,你應該最感興趣的部分。我不打算在這裏解釋三角形裁剪,你能夠在任何一本計算機圖形課本上查到,儘管一般都是一大堆內容看上去很可怕。若是你想詳細瞭解,就用Jim Blinn的(本書13章),雖然你可能會想經過傳[0,w]的裁剪空間來替代,若是沒別的方法就別搞混了。
裁剪簡而言之,即:在齊次裁剪空間裏,從vertex shader中返回頂點的位置。選用裁剪空間是爲了使方程描述視錐體儘量的簡單;在D3D中,是
![](http://static.javashuo.com/static/loading.gif)
,
![](http://static.javashuo.com/static/loading.gif)
,
![](http://static.javashuo.com/static/loading.gif)
以及
![](http://static.javashuo.com/static/loading.gif)
;注意全部最終方程實際上排除了齊次點(0,0,0,0),這是一種退化狀況。
咱們首先須要找出三角形是部分的,仍是徹底的在裁剪平面以外。這能夠用 Cohen-Sutherland直線裁剪算法高效執行。爲每一個頂點(例如,能夠在頂點着色的時候計算,並隨位置一塊兒存儲)計算輸出碼out-code(或裁剪碼clip-code)。而後,對於每一個圖元,裁剪碼clip-code的按位與運算將會告訴全部的視錐平面全部圖元頂點在錯誤的一側(意味着圖元徹底的在視錐以外,就能夠被拋棄了),而且裁剪碼clip-code的按位或運算將會告訴視錐平面須要再次裁剪圖元。裁剪碼只是硬件部分的很簡單的東西。
另外,shader還能夠生成一組「剔除距離(cull distances)」(若是全部頂點中任何一個剔除距離小於0,該三角形就會被丟棄),和一組「裁剪距離(clip distances)」(定義了額外的裁剪平面)。這些還用來參考圖元的rejection/clip testing。
在實際的裁剪過程當中,能夠採用兩種形式:咱們既可使用多邊形裁剪算法(會添加額外的頂點和三角形),也能夠添加額外的裁剪邊方程到光柵器裏(若是聽不懂不要緊,等到下個部分講光柵化,就理解了)。後者方式更好,徹底不須要實際的多邊形裁剪器,可是咱們得須要可以規範化32位浮點值來做爲有效的頂點座標;可能會有技巧構建快速的硬件光柵器來這樣作,可是彷佛很困難。因此我認爲有一個實際的裁剪器,包含了全部相關的東西(生成額外的三角形等)。這很麻煩,還很珍貴(比你想的要珍貴,我立刻就會講到),因此它不是個大問題。不肯定是否特殊的硬件也是,或者執行實際的裁剪;專用的裁剪單元的大小和須要多少,取決於在這個階段派發一個新的頂點着色負載是否合適。我不知道這些問題的答案,可是至少在性能方面,它不是很重要:實際上不會頻繁地「真的」裁剪。由於咱們會用到保護帶裁剪(guard-band clipping)。
保護帶裁剪(guard-band clipping)
這個名字不是很恰當;這不是一個神奇的裁剪方法。事實上,偏偏相反:直截了當的不作裁剪:)
底層思路很是簡單:在左,右,上和下裁剪面以外部分的大多數圖元徹底不須要裁剪。靠GPU來光柵化三角形,實際上的作法是掃描全屏區域(更準確的說,是裁剪區域scissor rect)並詢問每一個像素:「這個像素被當前三角形覆蓋了嗎?」(實際上這有點複雜,而且有更高效的方式,但這是常規思路)。而且這一樣適用於三角形徹底在視口內的狀況。只要咱們的三角形覆蓋測試(coverage test)是可靠的,咱們就徹底不須要裁剪靠近左,右,上和下平面的部分。
這個測試一般都是用固定精度的整數運算。最後,一步步的獲得一個三角形頂點,將會整數溢出而且獲得錯誤的結果。我以爲由光柵器生成像素而不是在三角形中生成,這點讓人感受很不爽很是,這應該是不合理的。硬件其實是違反了規範的。
針對這個問題有兩個解決辦法:首先是確保絕對不會進行三角形測試。若是真正作到了這點,那麼就不用裁剪四個平面了。這就是所謂的「無限保護帶」,保護帶其實是無限的。解決方案二是最後裁剪三角形,僅當他們在安全區域(光柵器計算不會溢出的區域)以外時。例如,光柵器有足夠的內部位來處理整數三角形座標:
![](http://static.javashuo.com/static/loading.gif)
,
![](http://static.javashuo.com/static/loading.gif)
(注意我這裏都用大寫的X和Y來表示屏幕空間的位置)。仍然用常規的視平面作視口裁剪測試,但實際上在投影和視口變換以後只是裁剪了指定的保護帶裁剪平面,結果的座標都在安全區域裏。如圖所示:
中間的小塊藍邊白色矩形表示咱們的視口,而大塊的橙色區域就是保護帶(guard band)。圖中的視口看起來貌似很小,但其實我還畫大了呢,好讓你能夠看到全部東西!在保護帶裁剪範圍-32768~32768裏,視口大約是5500個像素寬度,這裏能夠容納下一些很大的三角形。這些三角形表示了某些重要狀況。黃色三角形是最多見的——延伸到視口以外但沒出保護帶。這種能夠經過測試,不必進一步處理。綠色三角形在保護帶之內視口區域之外,因此它會被視口裁剪掉不會經過測試。藍色三角形延伸到了保護帶裁剪區域以外,須要被裁剪,但它徹底在視口區域以外,會被視口裁剪拒絕。最後的紫色三角形既延伸到了視口區域以內又延伸到了保護帶以外,就須要被裁剪了。
如你所見,這幾類三角形須要被四個側面裁剪都是比較極端的狀況。正如所說的,不要擔憂,這都是很罕見的狀況。
題外話:正確的裁剪
若是你熟悉算法,這塊就不是很難。但其中細節老是很神奇。三角形裁剪器實際上不得不遵照一些潛規則。若是破壞了這些規則,共用一個邊的鄰接三角形就會產生裂縫。這是不容許的。
- 視錐內的頂點位置必須被裁剪器保存爲比特率(bit-exact)。
- 裁剪一個平面中的邊AB必須與裁剪邊BA(方向相反)產生相同的結果(這能夠保證數學上徹底對稱,或者能夠保證老是裁剪相同方向上的邊)。
- 對多個平面裁剪的圖元必須按相同的順序對平面裁剪(或者一次對全部平面裁剪)。
- 若是用到了保護帶,必須對保護帶平面裁剪。若是真的須要剔除,就不能用保護帶了,得對原始的視口平面裁剪。不這麼作的話會產生裂縫。
討厭的遠近平面
好吧,雖然對於4側平面有很好解決方案,可是對於近和遠平面呢?尤爲近平面是很麻煩的,由於全部。那咱們該怎麼作呢?用z保護帶嗎?可是要怎麼工做呢——咱們實際上並無按z軸來光柵化!事實上只是在三角形上作插值!
另外,這只是三角形的插值。實際上插值Z的話,z-near test(Z<0)是很簡單的——只是 符號位。而z-far(Z>1)要額外比較(這裏我使用Z,而不是z,表示屏幕座標或投影以後的座標)。可是咱們還要進行逐像素的Z比較(Z test),因此這不是很大的開銷。視狀況而定,但這樣執行z裁剪是一個可選項。若是你想要支持像NVidias的「depth clamp」OpenGL擴展的話,就須要跳過z-near/z-far裁剪。實際上,這個擴展很好的暗示了他們是這樣作的,至少用過一段時間。
對於w>0的裁剪。也能擺脫它嗎?答案是固然,好比齊次座標的光柵化算法(
http://www.cs.unc.edu/~olano/papers/2dh-tri/)。 我不肯定硬件是是否這樣用的。這個方法不錯,不過很難符合D3D11的光柵化規則。也可能用一些我不瞭解的技巧。以上就是裁剪相關內容。
投影和視口變換
投影只須要將x,y和z座標除以w(除非你使用了齊次的光柵器,不然其實是並不投影,下面將忽略這種可能性),就獲得了在-1到1之間的NDC
(規範化的設備座標Normalized device coordinates)。而後用視口變換將投影的x和y映射到像素座標(將稱爲X和Y)以及投影的z映射到[0,1](將稱爲Z),這樣在z-near平面Z=0而且在z-far平面Z=1。
咱們還要對齊像素到子像素格上的小數座標。從D3D11開始,硬件須要精確的8位三角形座標的子像素精度。這個對齊會把一些很是窄的碎片(這些碎片會致使問題)變成退化三角形(不須要被渲染)。
背面和其它三角形剔除
當咱們擁有了全部頂點的X和Y,咱們就能夠叉乘邊向量來計算標記的三角形面積。若是面積是負值,三角形就是逆時針的(在這裏負面積對應逆時針,由於咱們正處於像素座標空間,在D3D的像素空間中y向下增長而不是向上增長,因此符號是相反的)。若是面積是正值,就是順時針。若是是0,就是退化三角形,不覆蓋任何像素,那麼它就能夠被安全的剔除了。咱們知道了三角形朝向就能夠進行背面裁剪了(開啓的狀況下)。
咱們如今快準備好光柵化了。實際上咱們還得先設置好三角形。但這塊還須要光柵化如何執行的知識,因此我會把放到下一篇再講。
結束語
我跳過並簡化了一部份內容,實際狀況要更復雜:好比,我假設你只是使用常規的齊次裁剪算法。一般是這樣——但你能夠用一些vertex shader屬性標記做爲使用屏幕空間線性插值來替代透視矯正插值。目前,常規齊次裁剪都是透視矯正插值;在使用屏幕空間線性屬性的時,你實際上須要執行一些額外的工做來不進行透視矯正:)
有不少光柵化算法(好比我提過的Olanos 2DH方法)可讓你跳過幾乎全部的裁剪,但如前所述,D3D11對於三角形光柵器需求很嚴格,全部沒有不少硬件實現的餘地;我不肯定那些方法是否符合規範(有不少細節下次會介紹)。我用的方法不是很先進,在光柵器中逐像素處理上用到少許的數學運算。若是你知道更好的解決方案,請在評論中告之。
最後,三角形剔除我這裏描述的是最基本狀況;例如,一類三角形在光柵化時會生成零個像素遠大於零面積的三角形,若是你能夠足夠快的查找到它,你就能夠當即丟棄掉這個三角形而且不須要通過三角形設置。最後說一點,在三角形設置以前以最低限度的光柵化進行剔除——找到其它方法來早期拒絕(early-reject)三角形是至關值得的。