Metal框架詳細解析(三十三) —— Metal渲染管道教程(二)

版本記錄

版本號 時間
V1.0 2018.10.13 星期六

前言

很多做視頻和圖像的,相信對這個框架都不是很陌生,它渲染高級3D圖形,并使用GPU執(zhí)行數(shù)據(jù)并行計算。接下來的幾篇我們就詳細的解析這個框架。感興趣的看下面幾篇文章。
1. Metal框架詳細解析(一)—— 基本概覽
2. Metal框架詳細解析(二) —— 器件和命令(一)
3. Metal框架詳細解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細解析(四) —— 關于GPU Family 4(一)
5. Metal框架詳細解析(五) —— 關于GPU Family 4之關于Imageblocks(二)
6. Metal框架詳細解析(六) —— 關于GPU Family 4之關于Tile Shading(三)
7. Metal框架詳細解析(七) —— 關于GPU Family 4之關于光柵順序組(四)
8. Metal框架詳細解析(八) —— 關于GPU Family 4之關于增強的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細解析(九) —— 關于GPU Family 4之關于線程組共享(六)
10. Metal框架詳細解析(十) —— 基本組件(一)
11. Metal框架詳細解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)
12. Metal框架詳細解析(十二) —— 基本組件之器件選擇 - 計算處理的設備選擇(三)
13. Metal框架詳細解析(十三) —— 計算處理(一)
14. Metal框架詳細解析(十四) —— 計算處理之你好,計算(二)
15. Metal框架詳細解析(十五) —— 計算處理之關于線程和線程組(三)
16. Metal框架詳細解析(十六) —— 計算處理之計算線程組和網(wǎng)格大?。ㄋ模?/a>
17. Metal框架詳細解析(十七) —— 工具、分析和調試(一)
18. Metal框架詳細解析(十八) —— 工具、分析和調試之Metal GPU Capture(二)
19. Metal框架詳細解析(十九) —— 工具、分析和調試之GPU活動監(jiān)視器(三)
20. Metal框架詳細解析(二十) —— 工具、分析和調試之關于Metal著色語言文件名擴展名、使用Metal的命令行工具構建庫和標記Metal對象和命令(四)
21. Metal框架詳細解析(二十一) —— 基本課程之基本緩沖區(qū)(一)
22. Metal框架詳細解析(二十二) —— 基本課程之基本紋理(二)
23. Metal框架詳細解析(二十三) —— 基本課程之CPU和GPU同步(三)
24. Metal框架詳細解析(二十四) —— 基本課程之參數(shù)緩沖 - 基本參數(shù)緩沖(四)
25. Metal框架詳細解析(二十五) —— 基本課程之參數(shù)緩沖 - 帶有數(shù)組和資源堆的參數(shù)緩沖區(qū)(五)
26. Metal框架詳細解析(二十六) —— 基本課程之參數(shù)緩沖 - 具有GPU編碼的參數(shù)緩沖區(qū)(六)
27. Metal框架詳細解析(二十七) —— 高級技術之圖層選擇的反射(一)
28. Metal框架詳細解析(二十八) —— 高級技術之使用專用函數(shù)的LOD(一)
29. Metal框架詳細解析(二十九) —— 高級技術之具有參數(shù)緩沖區(qū)的動態(tài)地形(一)
30. Metal框架詳細解析(三十) —— 延遲照明(一)
31. Metal框架詳細解析(三十一) —— 在視圖中混合Metal和OpenGL渲染(一)
32. Metal框架詳細解析(三十二) —— Metal渲染管道教程(一)

The Rendering Pipeline - 渲染管道

你終于開始研究GPU管道了! 在下圖中,您可以看到管道的各個階段。

圖形管道將頂點通過多個階段,在此期間頂點的坐標在各個空間之間轉換。

作為Metal編程者,您只關注頂點和片段處理階段(Vertex and Fragment Processing),因為它們是唯一的兩個可編程階段。 在本教程的后面,您將同時編寫頂點著色器和片段著色器。 對于所有非可編程流水線階段,例如Vertex Fetch,Primitive AssemblyRasterization,GPU都有專門設計的硬件單元來為這些階段提供服務。

接下來,您將完成每個階段。


1 – Vertex Fetch - 頂點提取

此階段的名稱因各種圖形應用程序編程接口(Application Programming Interfaces (APIs))而異。 例如,DirectX將其稱為輸入?yún)R編(Input Assembling)。

要開始渲染3D內容,首先需要一個場景scene。 場景由具有頂點網(wǎng)格的模型組成。 最簡單的模型之一是具有6個面(12個三角形)的立方體。

您可以使用頂點描述符(vertex descriptor)來定義頂點的讀取方式及其屬性,例如位置,紋理坐標,法線和顏色。 你可以選擇不使用頂點描述符,只是在MTLBuffer中發(fā)送一個頂點數(shù)組,但是,如果你決定不使用它,你需要知道頂點緩沖區(qū)是如何提前組織的。

當GPU獲取頂點緩沖區(qū)時,MTLRenderCommandEncoder繪制調用會告知GPU是否對緩沖區(qū)建立索引。 如果緩沖區(qū)未編入索引,則GPU假定緩沖區(qū)是一個數(shù)組,并按順序一次讀入一個元素。

此索引很重要,因為頂點被緩存以供重用。 例如,立方體有十二個三角形和八個頂點(在角落處)。 如果不進行索引,則必須為每個三角形指定頂點并向GPU發(fā)送三十六個頂點。 這可能聽起來不是很多,但在具有幾千個頂點的模型中,頂點緩存很重要!

還有一個用于著色頂點的第二個緩存,以便多次訪問的頂點僅被著色一次。著色頂點是已應用顏色的頂點。 但這種情況發(fā)生在下一階段。

一個名為Scheduler的特殊硬件單元將頂點及其屬性發(fā)送到Vertex Processing階段。


2 – Vertex Processing - 頂點處理

在此階段,頂點將單獨處理。 您編寫代碼來計算每個頂點的光照和顏色。 更重要的是,您通過各種坐標空間發(fā)送頂點坐標,以達到它們在最終幀緩沖區(qū)(framebuffer)中的位置。

現(xiàn)在是時候看看硬件層面下發(fā)生了什么。 看看這款AMD GPU的現(xiàn)代架構:

自上而下,GPU具有:

  • 1 Graphics Command Processor - 圖形命令處理器:這協(xié)調工作過程。
  • 4 Shader Engines(SE)- 著色引擎SE是GPU上的一個組織單元,可以為整個管道提供服務。 每個SE都有一個幾何處理器,一個光柵化器和計算單元。
  • 9 Compute Units (CU) - 計算單位:CU只不過是一組著色器核心。
  • 64 shader cores - 著色器核心:著色器核心是GPU的基本構建塊,其中所有著色工作在這里完成。

總共36個CU具有2304shader cores。 將其與四核CPU中的內核數(shù)量進行比較。 不公平,我知道!

對于移動設備,情況就有點不同。 為了進行比較,請查看以下圖像,其中顯示的GPU與最近的iOS設備中的GPU類似。 PowerVR GPU沒有SECU,而是具有Unified Shading Clusters(USC)。 這種特殊的GPU模型有6個USC和每個USC有32個核心,總共只有192個核心。

注意:iPhone X擁有最新的移動GPU,完全由Apple內部設計。 不幸的是,Apple沒有公開GPU硬件規(guī)范。

那么你可以用那么多核心做什么呢? 由于這些內核專門用于頂點和片段著色,因此一個顯而易見的事情是讓所有內核并行工作,以便更快地完成頂點或片段的處理。 但是有一些規(guī)則。 在CU內部,您一次只能處理頂點或片段。 好的方面就是那里有三十六個! 另一個規(guī)則是每個SE只能處理一個著色器函數(shù)。 擁有四個SE可以讓您以有趣和有用的方式結合工作。 例如,您可以一次在一個SE上運行一個片段著色器,在另一個SE上運行第二個片段著色器。 或者,您可以將頂點著色器與片段著色器分開,讓它們在不同的SE上并行運行。

現(xiàn)在是時候看頂點處理了! 您要編寫的頂點著色器(vertex shader)很小,但封裝了您需要的大部分必要的頂點著色器語法。

使用Metal File模板創(chuàng)建一個新文件,并將其命名為Shaders.metal。 然后,在文件末尾添加此代碼:

// 1
struct VertexIn {
  float4 position [[ attribute(0) ]];
};

// 2
vertex float4 vertex_main(const VertexIn vertexIn [[ stage_in ]]) {
  return vertexIn.position;
}

仔細閱讀以下代碼:

  • 1) 創(chuàng)建結構體VertexIn以描述與先前設置的頂點描述符匹配的頂點屬性。在這種情況下,只是position。
  • 2) 實現(xiàn)一個頂點著色器vertex_main,它接受VertexIn結構并返回float4類型的頂點位置。

請記住,頂點在頂點緩沖區(qū)中被索引。頂點著色器通過[[stage_in]]屬性獲取當前索引,并解包為當前索引處的頂點緩存的VertexIn結構。

計算單元(Compute Units)可以(一次)處理批量頂點,直到最大數(shù)量的著色器核心(shader cores)。該批處理可以完全適合CU高速緩存,因此可以根據(jù)需要重用頂點。批處理將使CU保持忙碌,直到處理完成,但其他CU應該可用于處理下一批。

頂點處理完成后,清除緩存方便進行下一批頂點批處理。此時,頂點現(xiàn)在被排序和分組,準備發(fā)送到基元組裝(primitive assembly)階段。

回顧一下,CPU向GPU發(fā)送了一個從模型網(wǎng)格mesh創(chuàng)建的頂點緩沖區(qū)。 您使用頂點描述符配置頂點緩沖區(qū),該描述符告訴GPU如何構造頂點數(shù)據(jù)。 在GPU上,您創(chuàng)建了一個用于封裝頂點屬性的結構。 頂點著色器接受此結構作為函數(shù)參數(shù),并通過[[stage_in]]限定符,通過頂點緩沖區(qū)中的[[attribute(0)]]位置確認該位置來自CPU。 然后頂點著色器處理所有頂點并將其位置作為float4返回。

名為Distributer的特殊硬件單元將分組的頂點塊發(fā)送到Primitive Assembly階段。


3 – Primitive Assembly - 圖元組裝

前一階段將處理后的頂點分組為數(shù)據(jù)塊到此階段。 要記住的重要一點是,屬于相同幾何形狀(圖元)的頂點始終位于同一個塊中。 這意味著一個點的一個頂點,或一個線的兩個頂點,或三角形的三個頂點,將始終位于同一個塊中,因此永遠不需要第二個塊提取。

與頂點一起,CPU在發(fā)出draw call命令時也會發(fā)送頂點連接信息,如下所示:

renderEncoder.drawIndexedPrimitives(type: .triangle,
                          indexCount: submesh.indexCount,
                          indexType: submesh.indexType,
                          indexBuffer: submesh.indexBuffer.buffer,
                          indexBufferOffset: 0)

draw函數(shù)的第一個參數(shù)包含有關頂點連接的最重要信息。 在這種情況下,它告訴GPU它應該從它發(fā)送的頂點緩沖區(qū)中繪制三角形。

Metal API提供五種基本類型:

  • point:對于每個頂點柵格化一個點。您可以在頂點著色器中指定具有[[point_size]]屬性的點的大小。
  • line:對于每對頂點,柵格化它們之間的一條線。如果某個頂點已包含在一行中,則不能再將其包含在其他行中。如果存在奇數(shù)個頂點,則忽略最后一個頂點。
  • lineStrip:與簡單線相同,只是線條連接所有相鄰頂點并形成折線。每個頂點(第一個除外)都連接到前一個頂點。
  • triangle:對于三個頂點的每個序列,柵格化三角形。如果它們不能形成另一個三角形,則忽略最后的頂點。
  • triangleStrip:與簡單三角形相同,但相鄰頂點也可以連接到其他三角形。

還有一種稱為patch的圖元類型,但這需要特殊處理,不能與索引繪制調用函數(shù)一起使用。

管道指定頂點的纏繞順序。如果繞組順序是逆時針方向,并且三角形頂點順序是逆時針方向,則意味著它們是正面的。否則,它們是背面的,可以剔除,因為我們看不到它們的顏色和光線。

當圖元圖像被其他圖元完全遮擋時,它們將被剔除,然而,當它們僅部分偏離屏幕時,它們將被剪裁。

為了提高效率,您應指定纏繞順序并啟用背面剔除。

此時,基元從連接的頂點完全組裝,然后移動到光柵化器。


4 – Rasterization - 光柵化

目前有兩種現(xiàn)代渲染技術在不同的路徑上發(fā)展,但有時一起使用:光線跟蹤和光柵化(ray tracing and rasterization)。他們是完全不同的;兩者都有利有弊。

在渲染靜態(tài)且遠距離的內容時,首選光線跟蹤,而當內容距離相機較近且更具動態(tài)性時,首選光柵化。

使用光線跟蹤,對于屏幕上的每個像素,它會將光線發(fā)送到場景中,以查看是否存在與對象的交叉點。如果是,請將像素顏色更改為該對象的顏色,但前提是該對象比當前像素的先前保存對象更靠近屏幕。

光柵化反過來:對于場景中的每個對象,將光線發(fā)送回屏幕并檢查對象覆蓋的像素。深度信息的保持方式與光線跟蹤的方式相同,因此如果當前對象比先前保存的對象更近,它將更新像素顏色。

此時,從前一階段發(fā)送的所有連接頂點需要使用它們的X和Y坐標在二維網(wǎng)格上表示。此步驟稱為三角形設置(triangle setup)。

這是光柵化器需要計算任意兩個頂點之間的線段的斜率或陡度的位置。當已知三個頂點的三個斜率時,可以從這三個邊緣形成三角形。
接下來,在屏幕的每一行上運行一個稱為掃描轉換的過程,以查找交叉點并確定哪些是可見的,哪些是不可見的。此時要在屏幕上繪制,只需要它們確定的頂點和斜率。掃描算法確定線段上的所有點或三角形內的所有點是否可見,在這種情況下,三角形完全用顏色填充。

對于移動設備,光柵化利用PowerVR GPU的平鋪(tiled)架構,通過并行光柵化32×32平鋪網(wǎng)格上的基元。在這種情況下,32是分配給(tiled)的屏幕像素的數(shù)量,但是該尺寸完全適應USC中的核心數(shù)量。

如果一個對象在另一個對象后面怎么辦?柵格化器如何確定要渲染的對象?通過使用存儲的深度信息(早期Z測試)來確定每個點是否在場景中的其他點之前,可以解決該隱藏表面移除問題。

光柵化完成后,三個更專業(yè)的硬件單元進入舞臺:

  • 名為Hierarchical-Z的緩沖區(qū)負責刪除光柵化器標記為剔除的片段。
  • 然后,Z and Stencil Test單元通過將它們與深度和模板緩沖區(qū)進行比較來移除不可見的碎片。
  • 最后,Interpolator單元獲取剩余的可見片段,并從組合的三角形屬性生成片段屬性。

此時,Scheduler單元再次將工作分派給著色器核心(shader cores),但這次是為Fragment Processing發(fā)送的光柵化片段。


5 – Fragment Processing - 片段處理

是時候快速審查管道了。

  • Vertex Fetch單元從內存中抓取頂點并將它們傳遞給Scheduler單元。
  • Scheduler單元知道哪些著色器核心可用,因此它會調度它們的工作。
  • 完成工作后,Distributer單元知道此工作是Vertex還是Fragment Processing
  • 如果是Vertex Processing工作,它會將結果發(fā)送到Primitive Assembly單元。 此路徑繼續(xù)到Rasterization單元,然后返回到Scheduler單元。
  • 如果是Fragment Processing工作,它會將結果發(fā)送到Color Writing單元。
  • 最后,彩色像素被發(fā)送回存儲器。

前一階段的primitive processing是連續(xù)的,因為只有一個Primitive Assembly單元和一個Rasterization單元。 但是,只要片段到達Scheduler單元,就可以將工作分叉(劃分)為許多微小部分,并將每個部分分配給可用的著色器核心。

現(xiàn)在有數(shù)百甚至數(shù)千個內核正在進行并行處理。 工作完成后,結果將被連接(合并)并再次順序發(fā)送到內存。

片段處理階段是另一個可編程階段。 您可以創(chuàng)建一個片段著色器函數(shù),該函數(shù)將接收頂點函數(shù)輸出的光照,紋理坐標,深度和顏色信息。

片段著色器輸出是該片段的單一顏色。 這些片段中的每一個都將有助于framebuffer中最終像素的顏色。 為每個片段插入所有屬性。

例如,要渲染此三角形,頂點函數(shù)將處理三個頂點,顏色為紅色,綠色和藍色。 如圖所示,構成該三角形的每個片段都是從這三種顏色中插入的。 線性插值簡單地平均兩個端點之間的線上每個點的顏色。 如果一個端點具有紅色,而另一個端點具有綠色,則它們之間的線上的中點將為黃色。 等等。

插值方程是參數(shù)化的并且具有這種形式,其中參數(shù)p是顏色存在的百分比(或0到1的范圍):

newColor = p * oldColor1 + (1 - p) * oldColor2

顏色很容易可視化,但所有其他頂點函數(shù)輸出也為每個片段進行了類似的插值。

注意:如果不希望頂點輸出進行插值,請將屬性[[flat]]添加到其定義中。

Shaders.Metal中,將片段函數(shù)添加到文件末尾:

fragment float4 fragment_main() {
  return float4(1, 0, 0, 1);
}

這是最簡單的片段函數(shù)。您以float4的形式返回插值顏色紅色。構成立方體的所有片段都是紅色的。

GPU獲取片段并進行一系列后處理測試:

  • alpha-testing確定繪制哪些不透明對象,哪些不是基于深度測試。
  • 在半透明對象的情況下,alpha-blending將新對象的顏色與先前已保存在顏色緩沖區(qū)中的顏色相結合。
  • scissor testing - 剪刀測試 檢查片段是否在指定的矩形內;此測試對于屏蔽渲染很有用。
  • stencil testing - 模板測試 檢查幀緩沖區(qū)中存儲片段的模板值如何與我們選擇的指定值進行比較。
  • 在前一階段early-Z testing運行;現(xiàn)在進行了late-Z testing以解決更多的可見性問題;模板和深度測試對于環(huán)境遮擋和陰影也很有用。
  • 最后,還計算了抗鋸齒- antialiasing,以便到達屏幕的最終圖像看起來不會鋸齒狀。

6 – Framebuffer - 幀緩沖區(qū)

一旦片段被處理成像素,Distributer單元就將它們發(fā)送到Color Writing單元。該單元負責將最終顏色寫入稱為framebuffer的特殊存儲器位置。從這里開始,視圖的每個幀都會刷新其彩色像素。但這是否意味著在屏幕上顯示時會將顏色寫入幀緩沖區(qū)?

一種稱為double-buffering - 雙緩沖 的技術用于解決這種情況。當?shù)谝粋€緩沖區(qū)顯示在屏幕上時,第二個緩沖區(qū)在后臺更新。然后,交換兩個緩沖區(qū),并在屏幕上顯示第二個緩沖區(qū),同時更新第一個緩沖區(qū),并繼續(xù)循環(huán)。

這需要很多硬件信息。但是,您編寫的代碼是每個Metal渲染器使用的代碼,盡管剛剛開始,您應該在查看Apple的示例代碼時開始識別渲染過程。

Build并運行應用程序,您的應用程序將呈現(xiàn)此紅色立方體:

注意立方體不是方形的。 請記住,Metal使用X軸上-1到1的Normalized Device Coordinates (NDC)。 調整窗口大小,立方體將保持相對于窗口大小的大小。


Send Data to the GPU - 將數(shù)據(jù)發(fā)送到GPU

Metal是關于華麗的圖形和快速和流暢的動畫。 下一步,您將使您的立方體在屏幕上上下移動。 要做到這一點,你將有一個更新每一幀的計時器,立方體的位置將取決于這個計時器。 在頂點函數(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(&currentTime, 
                              length: MemoryLayout<Float>.stride, 
                              index: 1)
  • 1) 您將計時器添加到每個幀。 您希望您的立方體在屏幕上上下移動,因此您將使用介于-1和1之間的值。使用sin()是實現(xiàn)此目的的好方法,因為正弦值始終為-1到1。
  • 2) 如果您只向GPU發(fā)送少量數(shù)據(jù)(小于4kb),則setVertexBytes(_:length:index :)是設置MTLBuffer的替代方法。 在這里,您將currentTime設置為緩沖區(qū)參數(shù)表中的索引1。

Shaders.metal中,將頂點函數(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中,您的頂點函數(shù)將接收浮點型的計時器。您將計時器值添加到y(tǒng)位置并從函數(shù)返回新位置。

Build并運行應用程序,您現(xiàn)在擁有一個動畫的立方體。

只需幾個代碼,您就可以了解管道的工作原理,甚至還添加了一些動畫。


源碼

首先我們看一下工程文件。

接著看一下sb中的內容

下面就一起看一下源碼。

1. Swift

這里給的是Mac上運行的源碼。

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(&currentTime, 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);
}

下面看一下運行效果

后記

本篇主要講述了Metal渲染管道教程,感興趣的給個贊或者關注~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容