前言:在之前的GPU大百科全書中,我們按照硬體流水線的順序完成了幾何部分以及光柵化部分的介紹,儘管上一期的大百科話題有些沉重,但整體上來講,我們已經遠離了光柵化這個凝固生命的過程。接下來,我們將跟隨光柵化之後嶄新誕生的圖元生命,開始一段有關像素的異彩紛呈的新旅程。
與幾何部分和光柵化部分若干年都沒什麼變化的情況不同,GPU內部發展最快的部分就是像素處理部分。如果你是一個像素,你一定會為幾年來自己周圍環境的巨變感到興奮,伴隨著像素處理部分的演進,像素不僅被處理得越來越快,而且顏色也日趨準確和符合效果的需求。究竟這幾年間像素處理部分發生了怎樣的變化,像素的處理過程又有著哪些特點呢?接下來就讓我們翻開GPU大百科全書新的章節來一探究竟吧。
● 五光十色的新世界
顏色和明暗,是讓我們這個世界變得美麗的一大原因,無論晨曦中朝露的璀璨,雨後彩虹的柔美,還是鋼筋水泥森林的質感,無不來自於顏色及明暗所傳達的強烈信息。想要在計算機世界中傳遞這些信息,我們需要一個最小的信息單位來最為載體,這個最小的信息單位,就是像素。
像素是構成圖像的基本單元
我們面前的屏幕能夠反映的最小的顏色點就是像素,大量像素按照正確的規律排列所形成的集合就成了圖像。由此可見,如要想要讓我們看到美麗真實,或者說正確的圖像,像素的顏色必須也是正確和自然的。
圖像的精確來自像素的正確
反應正確的顏色?這不是很容易麼?每種顏色都有一個RGB值,只要讓每一個像素都具有對應位置上正確的RGB值,顏色不就沒問題了?對於固定的反映式輸出,比如說照片來說確實如此,人們只需要通過感光元件採集自然顏色的RGB數值,然後將它們直接轉化成像素就行了。但對於電腦,或者說3D虛擬世界來說,這顯然是不可能的——我們所做的事情,是在沒有對象的基礎上憑空創造出一個數字的世界,這世界的每一點細節,每一滴顏色,全部都要由我們來決定,而我們所有的,只有腦海中無盡的想像,以及由現實中抽離出來的描述這些想像的數學關係。
用數學關係創造出來的世界,是什麼樣子的呢?
● 像素的精彩
對於大多數人來說,第一次明白「3D遊戲」這個概念,大概是從DOOM開始的,而第一次明白什麼是貼近真實的視覺效果,則是在Pixel Shader出現以後。下面這些,就是伴隨我們成長的那些像素們。
3Dmark2001的nature場景
3Dmark2003的nature場景
3Dmark06的極度深寒場景
3Dmark的測試場景精確的反映了這些年像素特效進化的過程。我們創造出來的像素世界,已經從最初的「美輪美奐」向著「栩栩如生」的方向堅定地前進了。
經由Pixel Shader處理的環境光照效果
經由Pixel Shader處理的real time HDR IBL
這日趨真實,甚至已經可以以假亂真的圖像顯然不是GPU出現第一天就被呈現出來的。那GPU究竟是怎樣具備的對象素的自由處理能力?這個過程又經歷了怎樣的曲折過程呢?
● 凡事總有第一次嘛
在可編程shader出現之前,人們對像素的操作實際上僅僅能夠稱之為染色。當時的我們可不像現在這麼幸福,那時候對特效的處理只能通過固定的單元來直接實現,每一代API下所能夠實現的特定的特效,都需要通過預先將其固化成固定指令的形式出現在硬體中,而對於像素的處理,也僅能局限於固化指令所能夠允許的範圍內,一旦像素進入管線,程式設計師就失去了對他的控制。因此,可編程shader尤其是Pixel Shader的出現,在當時是一件轟動的大事,Pixel Shader的出現,標誌著程式設計師在像素級層面上第一次具備了可以精確而且隨心所欲的控制自己想要實現特效的能力。
DirectX 7的不可編程像素流水線處理的畫面
其實說起來,想要在像素層面上精確控制並不是一件非常複雜的事情。顏色的表現來自構成它的三原色的混合度以及透明度,而三原色以及透明度在計算機的世界裡都是可以通過數字來精確度量和表達的。所以對於像素來說,只要能夠隨心所欲的處理構成它顏色以及透明度的RGBA這四組數字,就可以精確控制一個像素的顏色表現。這件事聽上去似乎很簡單,做起來就是另一回事了,儘管只要將固定管線替換成可以任意處理方程的可編程運算單元就能實現上述目的,但畢竟大家在固化單元裡生活裡很多年了,想邁出突破性的第一步是很艱難的。
DirectX一直都會在程式設計師最需要的時候及時出現
這世界上其實並不存在什麼無法克服的困難,關鍵在於人們有沒有動力去克服它。能夠控制像素這件事,不僅對程式設計師來說誘惑極大,對最終用戶也有著非常重要的意義,更好的圖形表現是行業發展下去的根本動力,而行業的蓬勃發展就意味著滾滾鈔票的到來,所以大商人微軟再次體現了它的無處不在,2001年微軟發布了全新的圖形API——DirectX 8,正式將可編程shader program引入到了桌面圖形界,並宣稱透過shader,人們可以實現電影工業中CG一般的真實特效。微軟這一推動的作用是極其明顯的,就算固化單元裡的生活再怎麼安逸寧靜,大商人放話要求可編程單元來執行新的指令,不出來幫襯一下顯然太不給面子了,於是,第一代Pixel shader單元也就應運而生了。
DirectX 8的出現讓很多波動的水面之類的特效得以實現
第一代完全符合DirectX 8要求的圖形構架均採用可編程單元來替代固定指令渲染管線,算術單元的引進成了這些構架共同的特徵,它們都通過支持INT16及FX12數據格式的combine單元來執行對像素RGBA數據的運算處理。使用可編程的具備直接執行能力的算術運算單元來代替固定指令處理像素,這讓程式設計師得以實現許多過去根本無法想像的特殊視覺效果,通過直接使用數學關係來對應圖形,真實的表面光照效果、界面半反射、散射以及折射等等視覺效果讓人們徹底擺脫了單純alpha貼圖的乏味。可編程shader program,尤其是Pixel Shader的出現,標誌著人類正式進入了遊戲應用向電影特效進軍的時代。
● run Forrest!run!
Pixel Shader的出現是一件讓所有人都歡欣鼓舞的事情,程式設計師們非常高興,他們就像摘掉了有色眼鏡的畫家一樣,終於擁有了可以正確控制所有像素顏色的可能,一般用戶也非常高興,他們如願以償的看到了光,看到了波光淋漓的水面,看到了各種各種各樣以前不可能見到的新奇的特效。有了令人「嘆為觀止」的特效,消費者就會願意為新遊戲買單,於是整個業界也變一片欣欣向榮了。
nature場景給很多人留下了極其深刻的印象
當你第一次看到3Dmark01的nature場景時,你是否為能夠生活在這個技術發達的美好世界而感到高興呢?是的,能夠在計算機的世界裡看到如此美妙且連貫流暢的畫面,而且還有人說今後一切特效都可以得到實現,自己掏的cash沒有白費,這是一件多麼美好的事情啊。包括筆者自己在內,大多數人在那個時代都有著這種幸福甚至是滿足的感覺。
Pixel Shader1.0實現的水面半反射效果
當然,不是所有人看到新的shader效果之後都會那麼的幸福和滿足,比如剛剛還很高興的程式設計師就開始犯愁了——奇怪,這圖像怎麼跟自己想像的不太一樣啊?
shader modle1.0的精度依舊不足
第一代Pixel Shader確實為程式設計師打開了控制所有像素的大門,但它本身還存在諸多問題,其中最大的問題便來自精度。第一代shader所採用的combine僅能處理整型數據,對於浮點數據則無能為力,這極大地限制了數據處理的精度,進而影響到了數據背後的像素顏色的表現。數據的精度決定顏色的精度,當特效對像素的要求達到一定高度之後,對顏色的正確性也會變得更加敏感,整型數據的運算精度顯然不能滿足最終效果的要求,連數字都沒算對,程式設計師當然看不到自己想像中的結果了。
由於精度不足,shader modle1.0的水面永遠都是「波瀾不驚」的
Pixel Shader1.0確實為人們帶來了比過去更高的對像素控制的自由度,但它距離真正的自由還有極大的距離,與其說它可以實現一切特效,不如說它只能實現「錯誤的特效」。這種現狀顯然不可能令一直都處在高速發展狀態下的圖形界滿足,當時圖形界剛剛看到了自由的曙光,就好像甩掉助行器的阿甘,才品嘗到奔跑帶來的快感,顯然不可能就此停下。所以大家一起通力合作,大商人制定路線計劃和規則,眾廠商奮力攻關,終於將Pixel Shader2.0的硬體帶到了人們的面前。
提高像素處理精度之後的效果
Pixel Shader2.0相對於前代最大的不同來自精度,在Pixel Shader2.0中微軟第一次引入了FP24/32浮點數據作為顏色處理的基本精度,而執行這些數據的硬體也從combine轉變成了功能更加強大的mini ALU。更加精確的浮點型數據讓RGBA數值具備了充足的準確性,這對於最終效果的準確表達起到了決定性的作用。另外,Pixel Shader2.0還提供了更大的指令數,讓程序有了比第一代Pixel Shader更好的執行效率。在Pixel Shader2.0的幫助下,程式設計師就好像治好了近視一樣,第一次達到了正確的控制像素以及控制正確的像素的程度,3D圖形終於完成了從「美妙」到「栩栩如生」的轉變。
● 打開真實世界的大門
Pixel Shader1.0讓圖形界從固定的條條框框中跳了出來,但它存在精度不足的問題。Pixel Shader2.0彌補了精度方面的缺失,那它就完美了麼?顯然不。儘管Pixel Shader2.0能夠提供足夠讓畫面達到人眼分辨上限的精度,但它卻存在不比精度不足好到哪裡去的問題——它太過笨拙了。
3Dmark03的nature場景已經足夠美麗了
3Dmark03的Pixel Shader2.0測試場景
與此同時,由於最大指令數本身的限制,程式設計師對每個像素所能夠進行的改變實際上仍然會受到限制。如果想要添加更多更真實的效果,程式設計師往往需要讓一個像素多次反覆進入Pixel Shader單元中進行處理,這讓本來就已經存在的效率問題變得更加雪上加霜。
好不容易能夠表達正確的結果了,效率卻跟不上,程式設計師們都覺得很不舒服。程式設計師不舒服,大商人的收入就會受到影響。於是注意到問題所在的微軟推出了號稱史上最完美的DirectX版本——DirectX 9.0C。
在DirectX 9.0C中,MS顯然想將之前版本的DirectX中所遺留的問題一次性徹底搞定,Shader Modle3.0的最大指令數提升至65535,增加了寄存器數量,引入了動態程序流控制,將分支和跳轉能力徹底開放給了程式設計師,同時通過多目標渲染(MRT)和延遲渲染(Deferred Shading)等創造性的技術保證了光柵化過程中整個流水線的整體效率,可以說DirectX 9.0C幾乎解決了ALU利用率和效率之外的一切問題,它成了歷史上第一個真正能夠「實現一切特效」的圖形API。
支持Shader Modle3.0特性的X-ray引擎畫面
得益於微軟英明的指揮和領導,支持Shader Modle3.0的圖形構架擁有了遠比之前構架更多的寄存器資源、接近無限長度的指令執行能力以及靈活的控制操作方式,因此也就具備了遠比之前構架更高的執行效率。程式設計師們獲得了比先前所有版本都多得多的自由度,他們終於可以在支持Shader Modle3.0的硬體中近乎於不受約束的盡情堆砌指令,來實現越來越接近於現實的色彩和效果了。
● 從像素向計算的跨越
如果要評價這世界上最悲情的職業,我覺得程式設計師一定會以高票數當選的。程式設計師永遠都得不到他們想要的理想環境,當他們遇到某些問題並最終等到了這些問題的解決時,往往會發現這些問題的解決其實只不過是引出了新的問題而已,像素的世界就是如此。Shader Modle3.0確實解決了先前版本對於指令長度的限制以及執行效率方面的缺陷問題,但急劇放大的指令長度以及其本身的出發點卻將另一個問題非常明顯的凸現了出來,那就是執行單元的總體效率問題。
彩虹六號:維加斯的執行效率已經受到了Shader Modle3.0的制約
傳統的Shader Modle中,Vertex Shader和Pixel Shader是完全分立的兩組programs,他們擁有不同的寄存器要求,不同的指令格式以及不同的運算器要求。因此傳統硬體只有使用專門的比例一定的Vertex Shader和Pixel Shader單元,將它們分開進行處理。這種舉措本身從最開始就註定了很多不可調和的矛盾——固定單元比例的硬體憑什麼就能達到程式設計師對Vertex Shader和Pixel Shader數量及分布的要求?Shader Modle3.0號稱是破除一切限制的史上最自由的Shader Modle,結果到頭來還是要程式設計師嚴格按照硬體構架的大致比例和節奏來分配自己的Vertex Shader programs和Pixel Shader programs,只要稍有出格,硬體馬上會以直線下降的執行效率來回報你。
傳統分立單元的效率很難得到保障
除此之外,這種固定比例還導致了非常嚴重的單元利用率低落的問題。假定一段shader programs中僅包含10%的Vertex Shader指令,剩下的90%都是Pixel Shader指令,那麼當重載的Pixel shader單元全力動作的時候Vertex shader單元實際上是處在欠載狀態的。這種情況反之亦然。一段實際的shader program是不可能完全做到50:50的指令平衡設計的,再加上指令的串行吞吐特性,程式設計師無論如何都不可能做到指令密度的平均化。因此我們不難發現,實際應用中根據傳統的API設計出來的硬體經常會出現大面積的負載不平衡的現象。
被MRT以及Shader拖累的彩虹六號
正當程式設計師為自己一次又一次的看到自由的曙光,卻又一次又一次的被打回到各種束縛中而黯然落淚時,一隻手輕輕地遞過來一張紙巾,程式設計師用紙巾擦拭著眼淚,卻發現紙巾下面蓋著一個金光燦燦的新API——DirectX 10。程式設計師驚訝的半張著嘴巴,正準備說些什麼的時候,微軟微笑著用它特有的渾厚嗓音說:「不用問了,我叫雷鋒!」
使用Shader Modle4.0全新GI效果的《狂野西部》
DirectX 10以及其所帶來的Shader Modle4.0可以說為圖形界翻開了全新的篇章,它創造性的引入了Unified Shader的概念,將傳統的Vertex Shader和Pixel Shader從軟體和硬體層面上予以統一,shader programs內部不再需要嚴格按照格式來區分Vertex/Geometry/Pixel。軟體變了,硬體也要跟著改,所以對於支持Shader Modle4.0的硬體來說,其執行shader programs的單元也從過去的分立式固定功能變成了更加強大完整且完全統一的通用ALU。因為ALU可以對全部shader進行無差別吞吐,整個硬體的執行效率第一次在理論上達到了100%。
Shader Modle3.0與Shader Modle4.0效果對比
Shader Modle4.0對像素來說有著極其重要的意義,它不僅通過統一ALU吞吐提升了硬體的執行效率,更讓程式設計師們可以更加隨心所欲的使用效率更高的指令。傳統的DirectX 9硬體中的shader格式是非常固定的,Vertex Shader指令天生就是4D(X,Y,Z,A),而Pixel Shader指令因為硬體單元設計通常都是對應RGBA的3D+1D 結構的緣故,一般情況下也會寫成4D。這導致了DirectX 9環境下的Pixel Shader指令無論屬於何種應用,哪怕僅包含一條Z-buffer或者一條texture load,也要在指令結構上找齊成4D格式。這種格式的刻板要求極大的限制了程式設計師對shader尤其是Pixel Shader的發揮。在Shader Modle4.0環境下,統一且無限制的指令格式要求以及直接面向底層ALU的特點讓程式設計師可以大膽的直接使用更加靈活的1D、2D指令以及各種算數函數,而不用擔心任何來自硬體方面的限制,這讓Shader Modle4.0的效率和靈活度提升到了一個新的高度。
大量採用1D指令及算數函數的極品飛車13
與此同時,由於對最底層運算單元的直接開放,人們忽然發現原來GPU裡竟然蘊藏著如此豐富的運算資源。常規GPU用來處理shader的大規模並行ALU,本身可以輕鬆的擁有數倍甚至數十倍於CPU的吞吐能力,通過Shader Modle4.0面向ALU的開放,現在程式設計師們可以通過編程手段將這些原本用於處理像素效果的運算單元拿出來進行數學計算。像素處理,終於開始了自己從圖形向計算的跨越。
●
從計算到圖形的回歸Shader Modle4.0是Shader Modle歷史上的一大飛躍,它不僅讓像素處理變得更加有效率,而且還讓過去單純處理像素的單元騰出手來處理圖形之外的計算工作,使得整個GPU的用途和應用範圍變得更加寬廣,從任何角度來說這個版本都應該是史上最完美的了。如此完美的版本,難道程式設計師們終於可以擺脫自己悲催的命運了麼?
苦中作樂的程式設計師
雷鋒同志的表現是極其穩定的,它為程式設計師們帶來的DirectX 10以及Shader Modle4.0一如既往的從根本上解決了前一個版本所暴露出的一堆嚴重的問題,然後將另一堆同樣嚴重的問題暴露給了程式設計師們,對程式設計師們來說,命運依舊是照舊輪迴的,這次暴露給他們的東西包括了並行度和幾何關聯性問題。
並行kernel相對於串行的優勢
在我們對像素進行處理時,每一個像素都會對應一個線程。隨著指令的日趨靈活和複雜,以及每代DirectX所帶來的效率提升等效化之後的更多視覺效果,分支、跳轉以及常規算術函數等等的日益增多,都讓線程隊列的執行效率問題變得越發重要起來,這種情況在Shader Modle4.0取消了對指令格式的機械限制之後變得更加明顯了。大量的線程聚集成CTA,進而成為一個kernel,若干kernel形成隊列,這些kernel由於待處理像素的不同以及內部線程的指令靈活度不同而變得不再同樣「豐滿」,如果kernel隊列以順序的方式一次一個的送入到GPU中,勢必無法做到讓整個GPU的所有ALU都滿負荷工作。換句話說,Shader Modle4.0對指令的解放,就如同Vertex Shader和Pixel Shader出現時一樣,在為我們解決問題的同時非常傳統的再次為我們帶來了一個新的麻煩,因為很不幸的,Shader Modle4.0對kernel的吞吐,剛好就是依順序方式一個一個來的……如何讓線程能夠儘可能的充盈所有的ALU,減少ALU的等待周期並提高他們的重複負載率,是近兩年GPU構架改進最重要的目的和任務。
Shader Modle4.0代碼頑固的幾何關聯性
除了kernel之外,Shader Modle4.0還存在計算性能難以釋放的問題。對,我沒說錯,你也沒有看錯,Shader Modle4.0雖然是第一個允許程式設計師直接使用ALU的運算能力做像素處理之外的事情的Shader Modle版本,但他也確實存在計算性能難以使用的問題。
想要使用Shader Modle4.0的通用計算能力,你可能還要去學學fortran
假如你是一個物理學家,想要用Shader Modle4.0帶來的運算能力來為你解決某個剛體碰撞問題,硬體會告訴你沒問題,你只需先學習圖形相關的GLSL或者HLSL,了解圖形處理過程以及整個圖形流水線的特點,甚至還可能要跑去學點fortran什麼的,然後將你所要運算的目標轉化成圖形線程,將之按照嚴格的幾何關聯性一一對應到圖形過程,比如Vertex、Texture或者Z-buffer中去,最後再把弄好的你也不知道到底應該算是圖形程序代碼還是科學運算代碼的東西打好包發給硬體。這樣算是方便釋放運算性能麼?起碼我不覺得是。
有矛盾就要被解決,有困難就要找雷鋒。於是在程式設計師們(這次還包括了大量非圖形界的程式設計師)悽楚的目光注視下,微軟再次拿出了解決上一個版本問題的全新版本——DirectX 11以及Shader Modle5.0。在Shader Modle5.0中,微軟引入了兩個重要的概念:並行kernel及Compute Shader,前者通過引入kernel並行執行的形式來解決ALU利用率的問題,而後者則通過打破幾何關聯性的方式將ALU的運算能力真正釋放了出來。
Compute Shader實現的1000光源場景
並行kernel的作用和意義非常直白,就如同字面意思一樣,並行kernel通過把不同的kernel以並行隊列的方式發送給GPU,達到提升其處理效率的目的。相對於並行kernel來說,Shader Modle5.0引入的Compute Shader要重要得多。取消了幾何關聯的Compute Shader是史上第一個完全開放的數學指令型shader,Compute Shader可以透過並行管理方便的實現數據共享,可以透過樹結構和延遲操作快速執行任意過程,雖然喪失了幾何關聯所帶來的各種自動功能讓Compute Shader看上去與大多數圖形過程絕緣了,但事實卻恰恰相反。Compute Shader的出現,不僅沒有進一步的將通用計算和圖形計算割裂開,反倒直接打破了傳統的界限和束縛,將圖形和通用計算徹底聯繫在了一起。有了Compute Shader,顯卡的通用計算能力不僅可以以最直接的數學形態被釋放出來以幫助需要計算量的領域,而且還能很方便的直接被用於特效的處理,使其成為圖形計算能力,ALU在經歷了Shader Modle4.0短暫的運算能力分離之後,終於在Shader Modle5.0完成了回歸和升華。
● 像素的沉重靈魂也許你會問,像素不就是顏色麼,處理像素就是刷個油漆而已,屏幕上幾個需要刷刷漆的點,何德何能讓包括微軟在內的整個圖形界圍著它們折騰了10年之久而且還要繼續折騰下去呢?
實際像素操作可不是刷刷漆那麼簡單
如果一個像素被擺放在靜止的空間內,周圍的環境完全沒有任何的變化,這個像素自然也就不會有任何變化。對於這樣的像素我們甚至不用處理,直接以烘焙材質+紋理貼圖的形式就可以完成表現了。但是,現實中的像素點肯定不會是這樣的,如果你想要表達真實自然的顏色效果,這些像素就必然的會與光和其他像素發生關係,並在發生關係之後表現出正確的符合物理規律的顏色。而與光以及其他像素發生關係,就勢必會導致複雜的處理過程,於是,也就有了這段長達10年而且可能會永遠沒完沒了的糾葛。
3Dmark Vantage的像素光照效果
以光照為例,在實際的應用中,對光照的操作有很多種方式,傳統的方式大多是將光照信息直接對應到Pixel Shader指令的執行過程,比如multi-pass render及multi-light single pass render等。在Pixel Shader2.0中,這些方式被用於處理不同場景的光源對物體的影響。如multi-pass render pipeline會為每個光源創建一個單獨的過程用來執行,多用於室內環境處理,而multi-light single pass render能夠在一個過程中同時處理3至4個光源,可被用於室外大範圍表現的環境。
不同的光照會導致極大地真實度差異
不論採用哪種方式,在處理過程中都會對光線與像素的數學關係,也就是光線對像素顏色的影響以及透明度的影響,最簡單的點光照改變像素顏色的公式可以寫成Color = Ambient + Shadow * Att * (N.L * DiffColor * DiffIntensity * LightColor + R.V^n * SpecColor * SpecIntensity * LightColor),這個最簡單的公式中最少有四個分量,即N.L、LightColor、R.V^n以及Attenuation需要處理,對每一個分量的處理都會導致最少一個單獨的指令,同時還要為這些處理過程搭配對應的buffer以便能夠緩衝和臨時存放中間結果,另外,由於顏色是三原色組成,因此LightColor還要被拆分成LightColor.r、LightColor.g、LightColor.b三個分量分開處理,最終再將它們合併在一起。
單點光源場景演示
折騰完這麼一大堆的方程、變量和指令之後,你終於完成了一個像素在一個點光源照射之下的顏色變化。如果是multi-pass render pipeline,這個像素也許就算是弄完了,要是趕上multi-light single pass render,後面還有長長的其他光源以及多光源符合效應等一大堆過程在等著呢。
全局光照場景演示
一個像素尚且如此,我們的屏幕中存在著2304000個像素(1920X1200),即便假設其中只有1/4需要處理,那也有576000個像素,即便所有這些像素都只被一個點光源照耀著,而且全部沒有其他的交互作用關係,我們也要把上面那些步驟整體重複576000次才算完,這其中的運算量到底有多大,諸位可以想像一下。而如果對效果的要求很高,比如說將漫散反射定義成新光源,那麼每個像素所要處理的運算量都將因為光源的激增而急劇加大,即便加入光照探針之類的手段,這巨大的運算量依舊會給現有的常規硬體帶來很大的壓力。而實際的遊戲應用中,我們面對的效果顯然不止漫散反射光源這麼點而已,大量的像素間的交互作用一樣要被處理,這種種處理需求加在一起,就構成了像素沉重的靈魂。
大量使用複雜光照效果的Crysis
現在,你應該明白為什麼微軟帶著大家忙活了十來年,也沒搞定刷漆這麼個簡單的活了吧。
● 像素到效果的橋梁
儘管恩怨糾葛持續了很久,硬體的結構也越來越複雜,但像素本身其實還是非常單純的。我們說過,對像素的處理,歸根結底是對構成顏色的三原色的處理,而對三原色的RGB值以及透明度Alpha的量化處理,實際上是非常單純的數學運算。這個單純的計算過程,為什麼會衍生出了如此紛繁複擾的處理過程的呢?像素到效果之間,究竟跨越了怎樣的階段呢?
Grid到Thread所對應的硬體
在前一頁中我們已經知道,如果我想將材質中某個原本黃色的像素點揉到光線效果中並令其因此改變顏色,需要將像素原本顏色的數值以及它與光線和其他通過數學的手段處理成一個或一組方程。這就是像素向效果邁進的第一步,從像素變成方程。不同的像素改變對應了不同的方程或者方程組,要處理這些方程/方程組,就必須將它們轉化成ALU能夠接受的形式,因此方程/方程組就變成了指令。
像素線程的分塊
接下來,硬體流水線如果要執行這些指令,就必須讓其擁有符合流水線特徵的身份,於是以1個像素為單位,這些指令被轉換成了Thread(線程)。要實現某種特效,顯然不太可能只改變一個像素,於是一堆像素的改變就變成了若干Thread的集團,為了方便硬體進行吞吐,若干Thread會合併成為CTA(線程塊),它對應硬體能夠執行的最小粒度,也就是NVIDIA的warp或者AMD的wavefront。若干CTA又可以組合成一個Block,這是方便程式設計師進行發放管理而設定的最小Thread單位。一個最小GPU執行單元比如SM可以面對若干個Block,當一個Block裡的所有CTA都被執行完之後,SM就會尋找其他未被執行的Block,被劃分在一起可以由同一個SM完成吞吐的Block,被稱作Grid。
對全屏幕像素的網格化(Grid)
最後,很多特效都是基於全屏基礎的,換句話說,屏幕內會出現大量需要更改的像素,這些像素更改所對應的全部過程就是一個kernel,這個kernel,就是由所有Grid組合成的集合。
從像素到畫面的轉變
你覺得有些頭暈?沒關係,我換個可以幫助理解的方式——因為我們要理解的是像素到畫面之間的遞進關係,所以我們只看像素即可。要實現特效,所要改變的最基本單位首先是像素,一個改變的最終結果對應一個像素任務(Thread);出於提高GPU的像素吞吐效率的考量,若干個像素會被組合成一個整體集團被ALU團簇執行(CTA);既然方便了硬體,程式設計師也要得到便利,所以我們又設定了一個程式設計師方便管理的最小單元,裡面包含了若干被組合在一起的像素集團(Block);一個ALU團簇一次能夠面對若干個這樣的像素集團,他們也可以被稱作一個更高級的集合體(Grid);最後,當屏幕內所有的這種最高級待改變像素集合體被執行完畢之後(kernel),特效就呈現在我們眼前了。
● 表面的把戲我們知道,在GPU的圖形流水線處理過程中,像素處理是排在光柵化過程後面的,當幾何模型完成3D到2D的坐標變換之後,留在流水線中的就只剩下原幾何模型中可以被攝像機鏡頭看到的那部分表面的投影了。因此,對這部分範圍內像素效果的處理,也可以被理解成是對表面顏色的操作。
光柵化之後的三角形
我們在前面對shader modle的回顧中提到過,在DirectX 10以前,這部分運算操作由專門的含有combine或者mini ALU的Pixel Shader單元來完成,而DirectX 10之後,則可以簡單的理解成由ALU直接完成。接下來,就讓我們輕鬆一下,來看看對表面的處理流程究竟是怎樣的吧。
DirectX 10圖形流水線
當經過光柵化的模型投影,或者說圖元出現在流水線之後,材質單元會根據程序的要求對圖元進行區域劃分定位,然後從材質庫中尋找對應表面區域的材質,將其拾取出來貼到已經2D化的模型平面對應的區域內。與此同時,ALU則要根據程序的要求,對不同的像素區域中的紋理進行對應的操作,比如光照探針偵測、光線關係判斷等等。需要注意的是這裡的所謂光線關係並非僅僅是明暗之類光照度以及光照角度那麼簡單,這其中還包括半漫反散射以及折射之類光線傳遞關係的效果。在完成上述關係的判斷之後,ALU會按照結果執行程序包含的描述這些關係的對應方程,並最終經過對方程的運算得到某個像素正確的顏色數值。最後,處理完的結果將被傳送至ROP單元,它會將處理好的像素與已經存在的基本材質進行混合,然後就可以輸出我們能夠看到的最終效果了。
標準的光柵化圖形處理流程
不難發現,對物體表面效果的處理並非shader/ALU一己之力完成,它實際上是與紋理單元共同完成的。儘管shader/ALU完成了其中最重要最複雜,同時也是最為考驗運算能力的環節,最終的表現結果都是由ALU決定的,但整個過程無法離開紋理單元以及材質的參與,這是為什麼呢?
● 像素離不開紋理既然像素處理過程就是處理像素,那我們為什麼還要現在物體表面蒙上一層預先烘焙好的材質作為基礎呢?反正這些材質上大部分的顏色都不是正確的,到頭來還是需要ALU對其進行運算並完成修改,那為何不直接讓像素處理單元直接在正確的位置上生成正確的像素呢?這樣既可以避免改錯這麼一個看上去似乎沒有必要的步驟,又可以用原本進行材質操作的單元的電晶體來進一步強化ALU部分,讓其擁有更強大的功能,何樂而不為呢。
shader與Texture是一對「好碰友」
答案很簡單——因為現在的ALU根本沒有那個本事面對直接生成像素所帶來的運算量。
我們在本文中反覆強調過,像素的處理過程從本質上來說並不複雜,其巨大的執行難度並不來自步驟的繁瑣,而是來源於對大量像素進行數學關係運算所導致的運算量,這10年來針對shader反覆的折騰其實也只是因為人們對執行單元能夠更加高效的處理數學關係的渴求。在以前的文章中我們曾經面對過類似的問題,當方程的數量達到一定級別之後,對於運算單元的壓迫將讓任何本來看上去很和諧優雅的方程式變得醜陋無比。而隨著程式設計師和用戶對效果要求的不斷提升,對於像素處理的方程總量在未來將呈現出明顯的只增不減的態勢。更加複雜真實的光照模型,更加多變且逼近現實的光線傳遞效果,甚至包括更多像素透明度遮蔽所帶來的混合,這些都讓目前的ALU單元承受著巨大的壓力。在這種情況下,要讓ALU去獨立生成全新的像素,為ALU添加一個近乎於100%增幅、甚至比這個還要大得多的像素處理壓力,顯然是不現實的。
材質操作可以大幅減輕像素處理的負擔
為了減輕這種壓力,人們歷經10年,不斷地壓榨著半導體工藝的極限,以期能夠在GPU內部塞下更多的運算單元;不斷地改進著運算單元集合的邏輯關係,以期能夠讓它們儘可能高效率甚至全功率的運作起來;不斷地開發著如Compute Shader之類能夠儘可能多的以靈活的運作手段和更加貼近純數學的應用方式來解決問題的方法,以此來進一步提升ALU在處理過程中的效率,並進一步壓榨ALU的價值。對於材質的操作,也是這些努力中的一部分。預先烘焙好的材質可以帶來大量已經具備基本效果關係的像素,這些操作上相對廉價的像素中會有相當一部分不需要被處理,直接使用材質可以大大減輕ALU單元的負擔,讓已經不堪重負的ALU得到喘息。
現代遊戲均大量採用像素結合材質的處理方式
所以,在我們可見的未來中,對於shader的處理依舊只能跟材質操作緊密的聯繫起來,Direct Pixel這種東西,相對來說還是一個遙遠的夢想。
● 下一章是……番外篇?根據慣例,每一章GPU大百科全書的結尾,我們都會對本章的內容進行小結,總覽一下該章所介紹的單元,總結和評價它在圖形渲染流水線中的作用和地位,並且承前啟後的引出流水線上的下一個單元。但是今天,我們要打破這個慣例。因為關於Pixel Shader以及相關單元的故事,其實還遠未結束呢。
像素處理單元是一個有故事的地方
對像素的處理,本質上就是對色彩相關的方程的處理,這種處理的背後又附庸著大規模的數學運算和大量的指令執行,所以像素對於硬體的邏輯結構設計,存在著極高的要求。不同的邏輯結構對於龐大數學運算和指令的吞吐以及處理能力,顯然是存在差距的。每一家硬體廠商對於數學以及效率的理解都不相同,設計出來的硬體也存在著極大的差異。
ALU團簇結構的設計分歧
另外,我們的活雷鋒大商人微軟同學非常頑皮,雖然他帶領著大家不斷的改進著像素的世界,但愛耍小性子的他有時候會跟這位小同學走得近一些,有時候又會和那位小朋友玩的好一點,這就導致了微軟在新API規則制定時或多或少的傾向性,以及大家對每一代API意義及精髓理解方面的差異。
好了,有差異,就會有分歧,有分歧,就會有爭執,當爭執最終達到了商業競爭的你死我活以後,戰爭也就爆發了。
自shader出現至今,圍繞著Pixel Shader以及相關單元設計方面的激烈競爭,可以說是人類IC史上最為燦爛的一場大碰撞,這10年間ATI/AMD與NVIDIA的此消彼長,不僅帶來了甚至已經事實上打破摩爾定律的技術進步,更我們上演了一幕又一幕驚心動魄又異彩紛呈的戰爭大戲。宣傳戰、精度戰、諜報戰、工藝戰、晶片策略戰、產能戰……相信經歷過的人都會覺得,這10年間發生在Pixel Shader以及相關單元身上的種種奇遇,絕對可以寫成一部相當跌宕起伏的小說或者劇本了。
GPU發展還能有諜報戰攙和?
在下一章的GPU大百科全書中,我們將繼續關於像素的未講完的故事。這個特別準備的番外篇最終會讓你明白,究竟是怎樣巨大的差異,能夠讓一個處理像素的單元的設計工作迸發出如此耀眼的火花,這火花又是多麼的奪目璀璨。敬請期待吧。