版本記錄
版本號 | 時(shí)間 |
---|---|
V1.0 | 2018.10.13 星期六 |
前言
很多做視頻和圖像的,相信對這個(gè)框架都不是很陌生奔坟,它渲染高級3D圖形识藤,并使用GPU執(zhí)行數(shù)據(jù)并行計(jì)算。接下來的幾篇我們就詳細(xì)的解析這個(gè)框架祷蝌。感興趣的看下面幾篇文章茅撞。
1. Metal框架詳細(xì)解析(一)—— 基本概覽
2. Metal框架詳細(xì)解析(二) —— 器件和命令(一)
3. Metal框架詳細(xì)解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細(xì)解析(四) —— 關(guān)于GPU Family 4(一)
5. Metal框架詳細(xì)解析(五) —— 關(guān)于GPU Family 4之關(guān)于Imageblocks(二)
6. Metal框架詳細(xì)解析(六) —— 關(guān)于GPU Family 4之關(guān)于Tile Shading(三)
7. Metal框架詳細(xì)解析(七) —— 關(guān)于GPU Family 4之關(guān)于光柵順序組(四)
8. Metal框架詳細(xì)解析(八) —— 關(guān)于GPU Family 4之關(guān)于增強(qiáng)的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細(xì)解析(九) —— 關(guān)于GPU Family 4之關(guān)于線程組共享(六)
10. Metal框架詳細(xì)解析(十) —— 基本組件(一)
11. Metal框架詳細(xì)解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)
12. Metal框架詳細(xì)解析(十二) —— 基本組件之器件選擇 - 計(jì)算處理的設(shè)備選擇(三)
13. Metal框架詳細(xì)解析(十三) —— 計(jì)算處理(一)
14. Metal框架詳細(xì)解析(十四) —— 計(jì)算處理之你好,計(jì)算(二)
15. Metal框架詳細(xì)解析(十五) —— 計(jì)算處理之關(guān)于線程和線程組(三)
16. Metal框架詳細(xì)解析(十六) —— 計(jì)算處理之計(jì)算線程組和網(wǎng)格大芯揠(四)
17. Metal框架詳細(xì)解析(十七) —— 工具米丘、分析和調(diào)試(一)
18. Metal框架詳細(xì)解析(十八) —— 工具、分析和調(diào)試之Metal GPU Capture(二)
19. Metal框架詳細(xì)解析(十九) —— 工具糊啡、分析和調(diào)試之GPU活動監(jiān)視器(三)
20. Metal框架詳細(xì)解析(二十) —— 工具拄查、分析和調(diào)試之關(guān)于Metal著色語言文件名擴(kuò)展名、使用Metal的命令行工具構(gòu)建庫和標(biāo)記Metal對象和命令(四)
21. Metal框架詳細(xì)解析(二十一) —— 基本課程之基本緩沖區(qū)(一)
22. Metal框架詳細(xì)解析(二十二) —— 基本課程之基本紋理(二)
23. Metal框架詳細(xì)解析(二十三) —— 基本課程之CPU和GPU同步(三)
24. Metal框架詳細(xì)解析(二十四) —— 基本課程之參數(shù)緩沖 - 基本參數(shù)緩沖(四)
25. Metal框架詳細(xì)解析(二十五) —— 基本課程之參數(shù)緩沖 - 帶有數(shù)組和資源堆的參數(shù)緩沖區(qū)(五)
26. Metal框架詳細(xì)解析(二十六) —— 基本課程之參數(shù)緩沖 - 具有GPU編碼的參數(shù)緩沖區(qū)(六)
27. Metal框架詳細(xì)解析(二十七) —— 高級技術(shù)之圖層選擇的反射(一)
28. Metal框架詳細(xì)解析(二十八) —— 高級技術(shù)之使用專用函數(shù)的LOD(一)
29. Metal框架詳細(xì)解析(二十九) —— 高級技術(shù)之具有參數(shù)緩沖區(qū)的動態(tài)地形(一)
30. Metal框架詳細(xì)解析(三十) —— 延遲照明(一)
31. Metal框架詳細(xì)解析(三十一) —— 在視圖中混合Metal和OpenGL渲染(一)
32. Metal框架詳細(xì)解析(三十二) —— Metal渲染管道教程(一)
The Rendering Pipeline - 渲染管道
你終于開始研究GPU管道了棚蓄! 在下圖中堕扶,您可以看到管道的各個(gè)階段碍脏。
圖形管道將頂點(diǎn)通過多個(gè)階段,在此期間頂點(diǎn)的坐標(biāo)在各個(gè)空間之間轉(zhuǎn)換稍算。
作為Metal編程者典尾,您只關(guān)注頂點(diǎn)和片段處理階段(Vertex and Fragment Processing)
,因?yàn)樗鼈兪俏ㄒ坏膬蓚€(gè)可編程階段邪蛔。 在本教程的后面,您將同時(shí)編寫頂點(diǎn)著色器和片段著色器扎狱。 對于所有非可編程流水線階段侧到,例如Vertex Fetch
,Primitive Assembly
和Rasterization
淤击,GPU都有專門設(shè)計(jì)的硬件單元來為這些階段提供服務(wù)匠抗。
接下來,您將完成每個(gè)階段污抬。
1 – Vertex Fetch - 頂點(diǎn)提取
此階段的名稱因各種圖形應(yīng)用程序編程接口(Application Programming Interfaces (APIs))
而異汞贸。 例如,DirectX
將其稱為輸入?yún)R編(Input Assembling)
印机。
要開始渲染3D內(nèi)容矢腻,首先需要一個(gè)場景scene
。 場景由具有頂點(diǎn)網(wǎng)格的模型組成射赛。 最簡單的模型之一是具有6個(gè)面(12個(gè)三角形)的立方體多柑。
您可以使用頂點(diǎn)描述符(vertex descriptor)
來定義頂點(diǎn)的讀取方式及其屬性,例如位置楣责,紋理坐標(biāo)竣灌,法線和顏色。 你可以選擇不使用頂點(diǎn)描述符秆麸,只是在MTLBuffer
中發(fā)送一個(gè)頂點(diǎn)數(shù)組初嘹,但是,如果你決定不使用它沮趣,你需要知道頂點(diǎn)緩沖區(qū)是如何提前組織的屯烦。
當(dāng)GPU獲取頂點(diǎn)緩沖區(qū)時(shí),MTLRenderCommandEncoder
繪制調(diào)用會告知GPU是否對緩沖區(qū)建立索引房铭。 如果緩沖區(qū)未編入索引漫贞,則GPU假定緩沖區(qū)是一個(gè)數(shù)組,并按順序一次讀入一個(gè)元素育叁。
此索引很重要迅脐,因?yàn)轫旤c(diǎn)被緩存以供重用。 例如豪嗽,立方體有十二個(gè)三角形和八個(gè)頂點(diǎn)(在角落處)谴蔑。 如果不進(jìn)行索引豌骏,則必須為每個(gè)三角形指定頂點(diǎn)并向GPU發(fā)送三十六個(gè)頂點(diǎn)。 這可能聽起來不是很多隐锭,但在具有幾千個(gè)頂點(diǎn)的模型中窃躲,頂點(diǎn)緩存很重要!
還有一個(gè)用于著色頂點(diǎn)的第二個(gè)緩存钦睡,以便多次訪問的頂點(diǎn)僅被著色一次蒂窒。著色頂點(diǎn)是已應(yīng)用顏色的頂點(diǎn)。 但這種情況發(fā)生在下一階段荞怒。
一個(gè)名為Scheduler
的特殊硬件單元將頂點(diǎn)及其屬性發(fā)送到Vertex Processing
階段洒琢。
2 – Vertex Processing - 頂點(diǎn)處理
在此階段,頂點(diǎn)將單獨(dú)處理褐桌。 您編寫代碼來計(jì)算每個(gè)頂點(diǎn)的光照和顏色衰抑。 更重要的是,您通過各種坐標(biāo)空間發(fā)送頂點(diǎn)坐標(biāo)荧嵌,以達(dá)到它們在最終幀緩沖區(qū)(framebuffer)
中的位置呛踊。
現(xiàn)在是時(shí)候看看硬件層面下發(fā)生了什么。 看看這款AMD GPU
的現(xiàn)代架構(gòu):
自上而下啦撮,GPU具有:
- 1 Graphics Command Processor - 圖形命令處理器:這協(xié)調(diào)工作過程谭网。
-
4 Shader Engines(SE)- 著色引擎:
SE
是GPU上的一個(gè)組織單元,可以為整個(gè)管道提供服務(wù)赃春。 每個(gè)SE都有一個(gè)幾何處理器蜻底,一個(gè)光柵化器和計(jì)算單元。 - 9 Compute Units (CU) - 計(jì)算單位:CU只不過是一組著色器核心聘鳞。
- 64 shader cores - 著色器核心:著色器核心是GPU的基本構(gòu)建塊薄辅,其中所有著色工作在這里完成。
總共36
個(gè)CU具有2304
個(gè)shader cores
抠璃。 將其與四核CPU中的內(nèi)核數(shù)量進(jìn)行比較站楚。 不公平,我知道搏嗡!
對于移動設(shè)備窿春,情況就有點(diǎn)不同。 為了進(jìn)行比較采盒,請查看以下圖像旧乞,其中顯示的GPU與最近的iOS設(shè)備中的GPU類似。 PowerVR GPU
沒有SE
和CU
磅氨,而是具有Unified Shading Clusters(USC)
尺栖。 這種特殊的GPU模型有6個(gè)USC和每個(gè)USC有32個(gè)核心,總共只有192個(gè)核心烦租。
注意:iPhone X擁有最新的移動GPU延赌,完全由Apple內(nèi)部設(shè)計(jì)除盏。 不幸的是,Apple沒有公開GPU硬件規(guī)范挫以。
那么你可以用那么多核心做什么呢者蠕? 由于這些內(nèi)核專門用于頂點(diǎn)和片段著色,因此一個(gè)顯而易見的事情是讓所有內(nèi)核并行工作掐松,以便更快地完成頂點(diǎn)或片段的處理踱侣。 但是有一些規(guī)則。 在CU
內(nèi)部大磺,您一次只能處理頂點(diǎn)或片段抡句。 好的方面就是那里有三十六個(gè)! 另一個(gè)規(guī)則是每個(gè)SE
只能處理一個(gè)著色器函數(shù)量没。 擁有四個(gè)SE可以讓您以有趣和有用的方式結(jié)合工作玉转。 例如突想,您可以一次在一個(gè)SE上運(yùn)行一個(gè)片段著色器殴蹄,在另一個(gè)SE上運(yùn)行第二個(gè)片段著色器。 或者猾担,您可以將頂點(diǎn)著色器與片段著色器分開袭灯,讓它們在不同的SE上并行運(yùn)行。
現(xiàn)在是時(shí)候看頂點(diǎn)處理了绑嘹! 您要編寫的頂點(diǎn)著色器(vertex shader)
很小稽荧,但封裝了您需要的大部分必要的頂點(diǎn)著色器語法。
使用Metal File
模板創(chuàng)建一個(gè)新文件工腋,并將其命名為Shaders.metal
姨丈。 然后,在文件末尾添加此代碼:
// 1
struct VertexIn {
float4 position [[ attribute(0) ]];
};
// 2
vertex float4 vertex_main(const VertexIn vertexIn [[ stage_in ]]) {
return vertexIn.position;
}
仔細(xì)閱讀以下代碼:
- 1) 創(chuàng)建結(jié)構(gòu)體
VertexIn
以描述與先前設(shè)置的頂點(diǎn)描述符匹配的頂點(diǎn)屬性擅腰。在這種情況下蟋恬,只是position
。 - 2) 實(shí)現(xiàn)一個(gè)頂點(diǎn)著色器
vertex_main
趁冈,它接受VertexIn
結(jié)構(gòu)并返回float4類型的頂點(diǎn)位置歼争。
請記住,頂點(diǎn)在頂點(diǎn)緩沖區(qū)中被索引渗勘。頂點(diǎn)著色器通過[[stage_in]]
屬性獲取當(dāng)前索引沐绒,并解包為當(dāng)前索引處的頂點(diǎn)緩存的VertexIn
結(jié)構(gòu)。
計(jì)算單元(Compute Units)
可以(一次)處理批量頂點(diǎn)旺坠,直到最大數(shù)量的著色器核心(shader cores)
乔遮。該批處理可以完全適合CU高速緩存,因此可以根據(jù)需要重用頂點(diǎn)取刃。批處理將使CU保持忙碌申眼,直到處理完成瞒津,但其他CU應(yīng)該可用于處理下一批。
頂點(diǎn)處理完成后括尸,清除緩存方便進(jìn)行下一批頂點(diǎn)批處理巷蚪。此時(shí),頂點(diǎn)現(xiàn)在被排序和分組濒翻,準(zhǔn)備發(fā)送到基元組裝(primitive assembly)
階段屁柏。
回顧一下,CPU向GPU發(fā)送了一個(gè)從模型網(wǎng)格mesh
創(chuàng)建的頂點(diǎn)緩沖區(qū)有送。 您使用頂點(diǎn)描述符配置頂點(diǎn)緩沖區(qū)淌喻,該描述符告訴GPU如何構(gòu)造頂點(diǎn)數(shù)據(jù)。 在GPU上雀摘,您創(chuàng)建了一個(gè)用于封裝頂點(diǎn)屬性的結(jié)構(gòu)裸删。 頂點(diǎn)著色器接受此結(jié)構(gòu)作為函數(shù)參數(shù),并通過[[stage_in]]
限定符阵赠,通過頂點(diǎn)緩沖區(qū)中的[[attribute(0)]]
位置確認(rèn)該位置來自CPU涯塔。 然后頂點(diǎn)著色器處理所有頂點(diǎn)并將其位置作為float4
返回。
名為Distributer
的特殊硬件單元將分組的頂點(diǎn)塊發(fā)送到Primitive Assembly
階段清蚀。
3 – Primitive Assembly - 圖元組裝
前一階段將處理后的頂點(diǎn)分組為數(shù)據(jù)塊到此階段匕荸。 要記住的重要一點(diǎn)是,屬于相同幾何形狀(圖元)的頂點(diǎn)始終位于同一個(gè)塊中枷邪。 這意味著一個(gè)點(diǎn)的一個(gè)頂點(diǎn)榛搔,或一個(gè)線的兩個(gè)頂點(diǎn),或三角形的三個(gè)頂點(diǎn)东揣,將始終位于同一個(gè)塊中践惑,因此永遠(yuǎn)不需要第二個(gè)塊提取。
與頂點(diǎn)一起嘶卧,CPU在發(fā)出draw call
命令時(shí)也會發(fā)送頂點(diǎn)連接信息尔觉,如下所示:
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: 0)
draw
函數(shù)的第一個(gè)參數(shù)包含有關(guān)頂點(diǎn)連接的最重要信息。 在這種情況下脸候,它告訴GPU它應(yīng)該從它發(fā)送的頂點(diǎn)緩沖區(qū)中繪制三角形穷娱。
Metal API提供五種基本類型:
-
point:對于每個(gè)頂點(diǎn)柵格化一個(gè)點(diǎn)。您可以在頂點(diǎn)著色器中指定具有
[[point_size]]
屬性的點(diǎn)的大小运沦。 - line:對于每對頂點(diǎn)泵额,柵格化它們之間的一條線。如果某個(gè)頂點(diǎn)已包含在一行中携添,則不能再將其包含在其他行中嫁盲。如果存在奇數(shù)個(gè)頂點(diǎn),則忽略最后一個(gè)頂點(diǎn)。
- lineStrip:與簡單線相同羞秤,只是線條連接所有相鄰頂點(diǎn)并形成折線缸托。每個(gè)頂點(diǎn)(第一個(gè)除外)都連接到前一個(gè)頂點(diǎn)。
- triangle:對于三個(gè)頂點(diǎn)的每個(gè)序列瘾蛋,柵格化三角形俐镐。如果它們不能形成另一個(gè)三角形,則忽略最后的頂點(diǎn)哺哼。
- triangleStrip:與簡單三角形相同佩抹,但相鄰頂點(diǎn)也可以連接到其他三角形。
還有一種稱為patch的圖元類型取董,但這需要特殊處理棍苹,不能與索引繪制調(diào)用函數(shù)一起使用。
管道指定頂點(diǎn)的纏繞順序茵汰。如果繞組順序是逆時(shí)針方向枢里,并且三角形頂點(diǎn)順序是逆時(shí)針方向,則意味著它們是正面的蹂午。否則栏豺,它們是背面的,可以剔除画侣,因?yàn)槲覀兛床坏剿鼈兊念伾凸饩€冰悠。
當(dāng)圖元圖像被其他圖元完全遮擋時(shí)堡妒,它們將被剔除配乱,然而,當(dāng)它們僅部分偏離屏幕時(shí)皮迟,它們將被剪裁搬泥。
為了提高效率,您應(yīng)指定纏繞順序并啟用背面剔除伏尼。
此時(shí)忿檩,基元從連接的頂點(diǎn)完全組裝,然后移動到光柵化器爆阶。
4 – Rasterization - 光柵化
目前有兩種現(xiàn)代渲染技術(shù)在不同的路徑上發(fā)展燥透,但有時(shí)一起使用:光線跟蹤和光柵化(ray tracing and rasterization)
。他們是完全不同的辨图;兩者都有利有弊班套。
在渲染靜態(tài)且遠(yuǎn)距離的內(nèi)容時(shí)卷拘,首選光線跟蹤叁扫,而當(dāng)內(nèi)容距離相機(jī)較近且更具動態(tài)性時(shí)肢预,首選光柵化夸盟。
使用光線跟蹤惋增,對于屏幕上的每個(gè)像素,它會將光線發(fā)送到場景中喊儡,以查看是否存在與對象的交叉點(diǎn)输瓜。如果是,請將像素顏色更改為該對象的顏色猿规,但前提是該對象比當(dāng)前像素的先前保存對象更靠近屏幕衷快。
光柵化反過來:對于場景中的每個(gè)對象,將光線發(fā)送回屏幕并檢查對象覆蓋的像素姨俩。深度信息的保持方式與光線跟蹤的方式相同烦磁,因此如果當(dāng)前對象比先前保存的對象更近,它將更新像素顏色哼勇。
此時(shí)都伪,從前一階段發(fā)送的所有連接頂點(diǎn)需要使用它們的X和Y坐標(biāo)在二維網(wǎng)格上表示。此步驟稱為三角形設(shè)置(triangle setup)积担。
這是光柵化器需要計(jì)算任意兩個(gè)頂點(diǎn)之間的線段的斜率或陡度的位置陨晶。當(dāng)已知三個(gè)頂點(diǎn)的三個(gè)斜率時(shí),可以從這三個(gè)邊緣形成三角形帝璧。
接下來先誉,在屏幕的每一行上運(yùn)行一個(gè)稱為掃描轉(zhuǎn)換的過程,以查找交叉點(diǎn)并確定哪些是可見的的烁,哪些是不可見的褐耳。此時(shí)要在屏幕上繪制,只需要它們確定的頂點(diǎn)和斜率渴庆。掃描算法確定線段上的所有點(diǎn)或三角形內(nèi)的所有點(diǎn)是否可見铃芦,在這種情況下,三角形完全用顏色填充襟雷。
對于移動設(shè)備刃滓,光柵化利用PowerVR GPU
的平鋪(tiled)架構(gòu),通過并行光柵化32×32平鋪網(wǎng)格上的基元耸弄。在這種情況下咧虎,32是分配給(tiled)的屏幕像素的數(shù)量,但是該尺寸完全適應(yīng)USC
中的核心數(shù)量计呈。
如果一個(gè)對象在另一個(gè)對象后面怎么辦砰诵?柵格化器如何確定要渲染的對象?通過使用存儲的深度信息(早期Z測試)來確定每個(gè)點(diǎn)是否在場景中的其他點(diǎn)之前捌显,可以解決該隱藏表面移除問題茁彭。
光柵化完成后,三個(gè)更專業(yè)的硬件單元進(jìn)入舞臺:
- 名為Hierarchical-Z的緩沖區(qū)負(fù)責(zé)刪除光柵化器標(biāo)記為剔除的片段苇瓣。
- 然后尉间,Z and Stencil Test單元通過將它們與深度和模板緩沖區(qū)進(jìn)行比較來移除不可見的碎片。
- 最后,Interpolator單元獲取剩余的可見片段哲嘲,并從組合的三角形屬性生成片段屬性贪薪。
此時(shí),Scheduler單元再次將工作分派給著色器核心(shader cores)
眠副,但這次是為Fragment Processing發(fā)送的光柵化片段画切。
5 – Fragment Processing - 片段處理
是時(shí)候快速審查管道了。
-
Vertex Fetch
單元從內(nèi)存中抓取頂點(diǎn)并將它們傳遞給Scheduler
單元囱怕。 -
Scheduler
單元知道哪些著色器核心可用霍弹,因此它會調(diào)度它們的工作。 - 完成工作后娃弓,
Distributer
單元知道此工作是Vertex
還是Fragment Processing
典格。 - 如果是
Vertex Processing
工作,它會將結(jié)果發(fā)送到Primitive Assembly
單元台丛。 此路徑繼續(xù)到Rasterization
單元耍缴,然后返回到Scheduler
單元。 - 如果是
Fragment Processing
工作挽霉,它會將結(jié)果發(fā)送到Color Writing
單元防嗡。 - 最后,彩色像素被發(fā)送回存儲器侠坎。
前一階段的primitive processing
是連續(xù)的蚁趁,因?yàn)橹挥幸粋€(gè)Primitive Assembly
單元和一個(gè)Rasterization
單元。 但是实胸,只要片段到達(dá)Scheduler
單元他嫡,就可以將工作分叉(劃分)為許多微小部分,并將每個(gè)部分分配給可用的著色器核心童芹。
現(xiàn)在有數(shù)百甚至數(shù)千個(gè)內(nèi)核正在進(jìn)行并行處理涮瞻。 工作完成后鲤拿,結(jié)果將被連接(合并)并再次順序發(fā)送到內(nèi)存假褪。
片段處理階段是另一個(gè)可編程階段。 您可以創(chuàng)建一個(gè)片段著色器函數(shù)近顷,該函數(shù)將接收頂點(diǎn)函數(shù)輸出的光照生音,紋理坐標(biāo),深度和顏色信息窒升。
片段著色器輸出是該片段的單一顏色缀遍。 這些片段中的每一個(gè)都將有助于framebuffer
中最終像素的顏色。 為每個(gè)片段插入所有屬性饱须。
例如域醇,要渲染此三角形,頂點(diǎn)函數(shù)將處理三個(gè)頂點(diǎn),顏色為紅色譬挚,綠色和藍(lán)色锅铅。 如圖所示,構(gòu)成該三角形的每個(gè)片段都是從這三種顏色中插入的减宣。 線性插值簡單地平均兩個(gè)端點(diǎn)之間的線上每個(gè)點(diǎn)的顏色盐须。 如果一個(gè)端點(diǎn)具有紅色,而另一個(gè)端點(diǎn)具有綠色漆腌,則它們之間的線上的中點(diǎn)將為黃色贼邓。 等等。
插值方程是參數(shù)化的并且具有這種形式闷尿,其中參數(shù)p
是顏色存在的百分比(或0到1的范圍):
newColor = p * oldColor1 + (1 - p) * oldColor2
顏色很容易可視化塑径,但所有其他頂點(diǎn)函數(shù)輸出也為每個(gè)片段進(jìn)行了類似的插值。
注意:如果不希望頂點(diǎn)輸出進(jìn)行插值填具,請將屬性
[[flat]]
添加到其定義中晓勇。
在Shaders.Metal
中,將片段函數(shù)添加到文件末尾:
fragment float4 fragment_main() {
return float4(1, 0, 0, 1);
}
這是最簡單的片段函數(shù)灌旧。您以float4
的形式返回插值顏色紅色绑咱。構(gòu)成立方體的所有片段都是紅色的。
GPU獲取片段并進(jìn)行一系列后處理測試:
- alpha-testing確定繪制哪些不透明對象枢泰,哪些不是基于深度測試描融。
- 在半透明對象的情況下,alpha-blending將新對象的顏色與先前已保存在顏色緩沖區(qū)中的顏色相結(jié)合衡蚂。
- scissor testing - 剪刀測試 檢查片段是否在指定的矩形內(nèi)窿克;此測試對于屏蔽渲染很有用。
- stencil testing - 模板測試 檢查幀緩沖區(qū)中存儲片段的模板值如何與我們選擇的指定值進(jìn)行比較毛甲。
- 在前一階段early-Z testing運(yùn)行年叮;現(xiàn)在進(jìn)行了late-Z testing以解決更多的可見性問題;模板和深度測試對于環(huán)境遮擋和陰影也很有用玻募。
- 最后只损,還計(jì)算了抗鋸齒- antialiasing,以便到達(dá)屏幕的最終圖像看起來不會鋸齒狀七咧。
6 – Framebuffer - 幀緩沖區(qū)
一旦片段被處理成像素跃惫,Distributer單元就將它們發(fā)送到Color Writing單元。該單元負(fù)責(zé)將最終顏色寫入稱為framebuffer的特殊存儲器位置艾栋。從這里開始爆存,視圖的每個(gè)幀都會刷新其彩色像素。但這是否意味著在屏幕上顯示時(shí)會將顏色寫入幀緩沖區(qū)蝗砾?
一種稱為double-buffering - 雙緩沖 的技術(shù)用于解決這種情況先较。當(dāng)?shù)谝粋€(gè)緩沖區(qū)顯示在屏幕上時(shí)携冤,第二個(gè)緩沖區(qū)在后臺更新。然后闲勺,交換兩個(gè)緩沖區(qū)噪叙,并在屏幕上顯示第二個(gè)緩沖區(qū),同時(shí)更新第一個(gè)緩沖區(qū)霉翔,并繼續(xù)循環(huán)睁蕾。
這需要很多硬件信息。但是债朵,您編寫的代碼是每個(gè)Metal
渲染器使用的代碼子眶,盡管剛剛開始,您應(yīng)該在查看Apple的示例代碼時(shí)開始識別渲染過程序芦。
Build并運(yùn)行應(yīng)用程序臭杰,您的應(yīng)用程序?qū)⒊尸F(xiàn)此紅色立方體:
注意立方體不是方形的。 請記住谚中,Metal使用X軸上-1到1的Normalized Device Coordinates (NDC)
渴杆。 調(diào)整窗口大小,立方體將保持相對于窗口大小的大小宪塔。
Send Data to the GPU - 將數(shù)據(jù)發(fā)送到GPU
Metal
是關(guān)于華麗的圖形和快速和流暢的動畫磁奖。 下一步,您將使您的立方體在屏幕上上下移動某筐。 要做到這一點(diǎn)比搭,你將有一個(gè)更新每一幀的計(jì)時(shí)器,立方體的位置將取決于這個(gè)計(jì)時(shí)器南誊。 在頂點(diǎn)函數(shù)中更新頂點(diǎn)位置身诺,因此您可以將計(jì)時(shí)器數(shù)據(jù)發(fā)送到GPU。
在Renderer
的頂部抄囚,添加timer
屬性:
var timer: Float = 0
在draw(in :)
中霉赡,就在之前:
renderEncoder.setRenderPipelineState(pipelineState)
添加
// 1
timer += 0.05
var currentTime = sin(timer)
// 2
renderEncoder.setVertexBytes(¤tTime,
length: MemoryLayout<Float>.stride,
index: 1)
- 1) 您將計(jì)時(shí)器添加到每個(gè)幀。 您希望您的立方體在屏幕上上下移動幔托,因此您將使用介于-1和1之間的值穴亏。使用
sin()
是實(shí)現(xiàn)此目的的好方法,因?yàn)檎抑凳冀K為-1到1柑司。 - 2) 如果您只向GPU發(fā)送少量數(shù)據(jù)(小于4kb)迫肖,則
setVertexBytes(_:length:index :)
是設(shè)置MTLBuffer
的替代方法。 在這里攒驰,您將currentTime
設(shè)置為緩沖區(qū)參數(shù)表中的索引1。
在Shaders.metal
中故爵,將頂點(diǎn)函數(shù)替換為:
vertex float4 vertex_main(const VertexIn vertexIn [[ stage_in ]],
constant float &timer [[ buffer(1) ]]) {
float4 position = vertexIn.position;
position.y += timer;
return position;
}
這里玻粪,在緩沖區(qū)1中隅津,您的頂點(diǎn)函數(shù)將接收浮點(diǎn)型的計(jì)時(shí)器。您將計(jì)時(shí)器值添加到y(tǒng)位置并從函數(shù)返回新位置劲室。
Build并運(yùn)行應(yīng)用程序伦仍,您現(xiàn)在擁有一個(gè)動畫的立方體。
只需幾個(gè)代碼很洋,您就可以了解管道的工作原理充蓝,甚至還添加了一些動畫。
源碼
首先我們看一下工程文件喉磁。
接著看一下sb中的內(nèi)容
下面就一起看一下源碼谓苟。
1. Swift
這里給的是Mac上運(yùn)行的源碼。
1. AppDelegate.swift
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
2. ViewController.swift
import Cocoa
import MetalKit
class ViewController: NSViewController {
var renderer: Renderer?
override func viewDidLoad() {
super.viewDidLoad()
guard let metalView = view as? MTKView else {
fatalError("metal view not set up in storyboard")
}
renderer = Renderer(metalView: metalView)
}
}
3. Renderer.swift
import MetalKit
class Renderer: NSObject {
static var device: MTLDevice!
static var commandQueue: MTLCommandQueue!
var mesh: MTKMesh!
var vertexBuffer: MTLBuffer!
var pipelineState: MTLRenderPipelineState!
var timer: Float = 0
init(metalView: MTKView) {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU not available")
}
metalView.device = device
Renderer.device = device
Renderer.commandQueue = device.makeCommandQueue()!
let mdlMesh = Primitive.cube(device: device, size: 1.0)
mesh = try! MTKMesh(mesh: mdlMesh, device: device)
vertexBuffer = mesh.vertexBuffers[0].buffer
let library = device.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: "vertex_main")
let fragmentFunction = library?.makeFunction(name: "fragment_main")
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mdlMesh.vertexDescriptor)
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
do {
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
super.init()
metalView.clearColor = MTLClearColor(red: 1.0, green: 1.0,
blue: 0.8, alpha: 1)
metalView.delegate = self
}
}
extension Renderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let descriptor = view.currentRenderPassDescriptor,
let commandBuffer = Renderer.commandQueue.makeCommandBuffer(),
let renderEncoder =
commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
return
}
timer += 0.05
var currentTime: Float = sin(timer)
renderEncoder.setVertexBytes(¤tTime, length: MemoryLayout<Float>.stride, index: 1)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
for submesh in mesh.submeshes {
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: submesh.indexBuffer.offset)
}
renderEncoder.endEncoding()
guard let drawable = view.currentDrawable else {
return
}
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
4. Primitive.swift
import MetalKit
class Primitive {
class func cube(device: MTLDevice, size: Float) -> MDLMesh {
let allocator = MTKMeshBufferAllocator(device: device)
let mesh = MDLMesh(boxWithExtent: [size, size, size],
segments: [1, 1, 1],
inwardNormals: false, geometryType: .triangles,
allocator: allocator)
return mesh
}
}
5. Shaders.metal
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float4 position [[ attribute(0) ]];
};
vertex float4 vertex_main(const VertexIn vertexIn [[ stage_in ]],
constant float &timer [[ buffer(1) ]]) {
float4 position = vertexIn.position;
position.y += timer;
return position;
}
fragment float4 fragment_main() {
return float4(1, 0, 0, 1);
}
下面看一下運(yùn)行效果
后記
本篇主要講述了Metal渲染管道教程协怒,感興趣的給個(gè)贊或者關(guān)注~~~