版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.05 星期五 |
前言
很多做視頻和圖像的喷兼,相信對這個框架都不是很陌生,它渲染高級3D圖形,并使用GPU執(zhí)行數(shù)據(jù)并行計算琅绅。接下來的幾篇我們就詳細(xì)的解析這個框架。感興趣的看下面幾篇文章鹅巍。
1. Metal框架詳細(xì)解析(一)—— 基本概覽
2. Metal框架詳細(xì)解析(二) —— 器件和命令(一)
Overview
在上一篇中千扶,您學(xué)習(xí)了如何編寫使用Metal的應(yīng)用程序并向GPU發(fā)出基本渲染命令料祠。
在本示例中,您將學(xué)習(xí)如何在Metal中渲染基本幾何體澎羞。 特別是髓绽,您將學(xué)習(xí)如何使用頂點數(shù)據(jù)和SIMD
類型,配置圖形渲染管道妆绞,編寫GPU函數(shù)以及發(fā)出繪制調(diào)用顺呕。
The Metal Graphics Rendering Pipeline - Metal圖形渲染管道
Metal圖形渲染管道由多個圖形處理單元(GPU)階段組成,一些是可編程的摆碉,一些是固定的塘匣,用于執(zhí)行繪圖命令。 Metal將管道的輸入巷帝,過程和輸出定義為應(yīng)用于某些數(shù)據(jù)的一組渲染命令忌卤。 在最基本的形式中,管道接收頂點作為輸入并將像素渲染為輸出楞泼。 此示例主要關(guān)注管道的三個主要階段:頂點函數(shù)驰徊,光柵化階段和片段函數(shù)。 頂點函數(shù)和片段函數(shù)是可編程階段堕阔。 光柵化階段是固定的棍厂。
MTLRenderPipelineState
對象表示圖形渲染管道。 可以使用MTLRenderPipelineDescriptor
對象配置此管道的許多階段超陆,該對象定義了Metal處理輸入頂點到渲染輸出像素的大部分方式牺弹。
Vertex Data - 頂點數(shù)據(jù)
頂點只是兩個或多個線相交的空間中的一個點。 通常时呀,頂點表示為定義特定幾何的笛卡爾坐標(biāo)的集合张漂,以及與每個坐標(biāo)相關(guān)聯(lián)的可選數(shù)據(jù)。
此示例呈現(xiàn)由三個頂點組成的簡單2D三角形谨娜,每個頂點包含三角形角的位置和顏色航攒。
Position
是必需的頂點屬性,而color
是可選的趴梢。 對于此示例漠畜,管道使用兩個頂點屬性將彩色三角形渲染到drawable
的特定區(qū)域。
Use SIMD Data Types - 使用SIMD數(shù)據(jù)類型
頂點數(shù)據(jù)通常從包含從專用建模軟件導(dǎo)出的3D模型數(shù)據(jù)的文件加載坞靶。詳細(xì)模型可能包含數(shù)千個具有許多屬性的頂點憔狞,但最終它們都以某種形式的數(shù)組陣列結(jié)束,這些陣列經(jīng)過特殊打包彰阴,編碼并發(fā)送到GPU躯喇。
示例的三角形為其三個頂點中的每一個定義了2D位置(x,y)和RGBA顏色(紅色,綠色廉丽,藍(lán)色倦微,alpha)。這種相對少量的數(shù)據(jù)被直接硬編碼到結(jié)構(gòu)數(shù)組中正压,其中數(shù)組的每個元素代表單個頂點欣福。用作數(shù)組元素的數(shù)據(jù)類型的結(jié)構(gòu)定義了每個頂點的內(nèi)存布局。
頂點數(shù)據(jù)和一般的3D圖形數(shù)據(jù)通常用矢量數(shù)據(jù)類型定義焦履,簡化了常見的圖形算法和GPU處理拓劝。此示例使用SIMD庫提供的優(yōu)化矢量數(shù)據(jù)類型來表示三角形的頂點。 SIMD
庫獨立于Metal
和MetalKit
嘉裤,但強烈建議用于開發(fā)Metal應(yīng)用程序郑临,主要是因為它的便利性和性能優(yōu)勢。
三角形的2D位置組件由vector_float2 SIMD
數(shù)據(jù)類型聯(lián)合表示屑宠,該類型包含兩個32位浮點值厢洞。類似地,三角形的RGBA顏色分量用vector_float4 SIMD
數(shù)據(jù)類型聯(lián)合表示典奉,該數(shù)據(jù)類型包含四個32位浮點值躺翻。然后將這兩個屬性組合成單個AAPLVertex
結(jié)構(gòu)。
typedef struct
{
// Positions in pixel space
// (e.g. a value of 100 indicates 100 pixels from the center)
vector_float2 position;
// Floating-point RGBA colors
vector_float4 color;
} AAPLVertex;
三角形的三個頂點直接硬編碼為AAPLVertex
元素數(shù)組卫玖,從而定義每個頂點的精確屬性值公你。
static const AAPLVertex triangleVertices[] =
{
// 2D positions, RGBA colors
{ { 250, -250 }, { 1, 0, 0, 1 } },
{ { -250, -250 }, { 0, 1, 0, 1 } },
{ { 0, 250 }, { 0, 0, 1, 1 } },
};
Set a Viewport - 設(shè)置視口
視口指定Metal
渲染內(nèi)容的drawable
區(qū)域。 視口是具有x和y偏移假瞬,寬度和高度以及近和遠(yuǎn)平面的3D區(qū)域(盡管這里不需要這兩個陕靠,因為此示例僅渲染2D內(nèi)容)。
為管道分配自定義視口需要通過調(diào)用setViewport:
方法將MTLViewport
結(jié)構(gòu)編碼為渲染命令編碼器脱茉。 如果未指定視口剪芥,Metal會設(shè)置一個默認(rèn)視口,其大小與用于創(chuàng)建渲染命令編碼器的drawable
相同芦劣。
Write a Vertex Function - 寫一個頂點函數(shù)
頂點函數(shù)(也稱為頂點著色器vertex shader
)的主要任務(wù)是處理傳入的頂點數(shù)據(jù)并將每個頂點映射到視口中的位置。 這樣说榆,管道中的后續(xù)階段可以引用此視口位置并將像素渲染到drawable
中的精確位置虚吟。 頂點函數(shù)通過將任意頂點坐標(biāo)轉(zhuǎn)換為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)(也稱為剪輯空間坐標(biāo)clip-space coordinates
)來完成此任務(wù)。
剪輯空間Clip space
是一個2D坐標(biāo)系签财,它將視口區(qū)域沿x軸和y軸映射到[-1.0,1.0]范圍串慰。 視口的左下角映射到(-1.0,-1.0)唱蒸,右上角映射到(1.0,1.0)邦鲫,中心映射到(0.0,0.0)。
頂點函數(shù)對于繪制的每個頂點執(zhí)行一次。 在此示例中庆捺,對于每個幀古今,繪制三個頂點以構(gòu)成三角形。 因此滔以,頂點函數(shù)每幀執(zhí)行三次。
頂點函數(shù)是用Metal著色語言Metal shading language
編寫的,它基于C ++ 14
率触。Metal著色語言代碼可能看起來類似于傳統(tǒng)的C / C ++
代碼豹储,但兩者根本不同。 傳統(tǒng)的C / C ++
代碼通常在CPU上執(zhí)行坏匪,而Metal著色語言代碼專門在GPU上執(zhí)行拟逮。 GPU提供了更大的處理帶寬,并且可以在大量頂點和片段上并行工作适滓。 但是敦迄,它具有比CPU少的內(nèi)存,不能有效地處理控制流操作粒竖,并且通常具有更高的延遲颅崩。
此示例中的頂點函數(shù)稱為vertexShader
,這是它的聲明蕊苗。
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])
1. Declare Vertex Function Parameters - 聲明頂點函數(shù)參數(shù)
第一個參數(shù)vertexID
使用[[vertex_id]]
屬性限定符并保存當(dāng)前正在執(zhí)行的頂點的索引沿后。當(dāng)繪制調(diào)用使用此頂點函數(shù)時,此值從0開始朽砰,并在每次調(diào)用vertexShader
函數(shù)時遞增尖滚。使用[[vertex_id]]
屬性限定符的參數(shù)通常用于索引包含頂點的數(shù)組。
第二個參數(shù)vertices
是包含頂點的數(shù)組瞧柔,每個頂點定義為AAPLVertex
數(shù)據(jù)類型漆弄。指向此結(jié)構(gòu)的指針定義了這些頂點的數(shù)組。
第三個也是最后一個參數(shù)viewportSizePointer
包含視口的大小造锅,并具有vector_uint2
數(shù)據(jù)類型撼唾。
vertices
和viewportSizePointer
參數(shù)都使用SIMD
數(shù)據(jù)類型,這些類型是C和Metal著色語言代碼都能理解的類型哥蔚。因此倒谷,示例可以在共享AAPLShaderTypes.h
標(biāo)頭中定義AAPLVertex
結(jié)構(gòu),該結(jié)構(gòu)包含在AAPLRenderer.m
和AAPLShaders.metal
代碼中糙箍。因此渤愁,共享頭確保三角形頂點的數(shù)據(jù)類型在Objective-C聲明(triangleVertices)
中與在Metal
著色語言聲明(vertices)
中相同。在Metal應(yīng)用程序中使用SIMD
數(shù)據(jù)類型可確保內(nèi)存布局在CPU / GPU聲明中完全匹配深夯,并有助于將頂點數(shù)據(jù)從CPU發(fā)送到GPU抖格。
注意:對
AAPLVertex
結(jié)構(gòu)的任何更改都會同等地影響AAPLRenderer.m
和AAPLShaders.metal
代碼。
vertices
和viewportSizePointer
參數(shù)都使用[[buffer(index)]]
屬性限定符。 AAPLVertexInputIndexVertices
和AAPLVertexInputIndexViewportSize
的值是用于在AAPLRenderer.m
和AAPLShaders.metal
代碼中標(biāo)識和設(shè)置頂點函數(shù)輸入的索引雹拄。
2. Declare Vertex Function Return Values - 聲明頂點函數(shù)返回值
typedef struct
{
// The [[position]] attribute of this member indicates that this value is the clip space
// position of the vertex when this structure is returned from the vertex function
float4 clipSpacePosition [[position]];
// Since this member does not have a special attribute, the rasterizer interpolates
// its value with the values of the other triangle vertices and then passes
// the interpolated value to the fragment shader for each fragment in the triangle
float4 color;
} RasterizerData;
頂點函數(shù)必須通過[[position]]
屬性限定符為clipSpacePosition
成員使用返回每個頂點的剪輯空間位置值收奔。 聲明此屬性后,管道的下一個階段(柵格化rasterization
)使用clipSpacePosition
值來標(biāo)識三角形角的位置办桨,并確定要渲染的像素筹淫。
3. Process Vertex Data - 處理頂點數(shù)據(jù)
示例頂點函數(shù)的主體對輸入頂點做兩件事:
- 1) 執(zhí)行坐標(biāo)系轉(zhuǎn)換,將生成的頂點剪輯空間位置寫入
out.clipSpacePosition
返回值呢撞。 - 2) 將頂點顏色傳遞給
out.color
返回值损姜。
要獲取輸入頂點,vertexID
參數(shù)用于索引頂點數(shù)組殊霞。
float2 pixelSpacePosition = vertices[vertexID].position.xy;
此示例從每個vertices
元素的position
成員獲取2D頂點坐標(biāo)摧阅,并將其轉(zhuǎn)換為寫入out.clipSpacePosition
返回值的剪輯空間位置。 每個頂點輸入位置相對于從視口中心開始的x和y方向上的像素數(shù)定義绷蹲。 因此棒卷,為了將這些像素空間位置轉(zhuǎn)換為剪輯空間位置,頂點函數(shù)除以視口大小的一半祝钢。
out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
最后比规,頂點函數(shù)訪問每個vertices
元素的color
成員并將其傳遞給out.color
返回值,而不執(zhí)行任何修改拦英。
out.color = vertices[vertexID].color;
RasterizerData
返回值的內(nèi)容現(xiàn)在已完成蜒什,結(jié)構(gòu)將傳遞到管道中的下一個階段。
Rasterization - 光柵化
頂點函數(shù)執(zhí)行三次后疤估,對于每個三角形的頂點執(zhí)行一次灾常,管道中的下一個階段,即柵格化開始铃拇。
光柵化是管道光柵化器單元產(chǎn)生碎片的階段钞瀑。 片段包含原始預(yù)像素數(shù)據(jù),用于生成渲染到drawable的像素慷荔。 對于由頂點函數(shù)生成的每個完整三角形雕什,光柵化器確定目標(biāo)可繪制的哪些像素被三角形覆蓋。 它通過測試drawable
中每個像素的中心是否在三角形內(nèi)部來實現(xiàn)显晶。 在下圖中贷岸,僅生成像素中心位于三角形內(nèi)部的片段。 這些片段顯示為灰色方塊吧碾。
柵格化還確定發(fā)送到管道中下一個階段的值:片段函數(shù)凰盔。在管道的早期墓卦,頂點函數(shù)輸出RasterizerData
結(jié)構(gòu)的值倦春,該結(jié)構(gòu)包含剪輯空間位置(clipSpacePosition)
和顏色(color)
。 clipSpacePosition
成員使用所需的[[position]]
屬性限定符,指示這些值直接用于確定三角形的片段覆蓋區(qū)域睁本。color
成員沒有屬性限定符尿庐,表示應(yīng)該在三角形的片段中插入這些值。
在將每個頂點值轉(zhuǎn)換為每個片段值之后呢堰,光柵化器將color
值傳遞給片段函數(shù)抄瑟。此轉(zhuǎn)換使用固定插值函數(shù),該函數(shù)計算從三角形的三個頂點的color
值派生的單個加權(quán)顏色枉疼。插值函數(shù)的權(quán)重(也稱為重心坐標(biāo)barycentric coordinates
)是每個頂點位置與片段中心的相對距離皮假。例如:
如果片段正好位于三角形的中間,與每個三角形的三個頂點等距骂维,則每個頂點的顏色加權(quán)1/3惹资。 在下圖中,這顯示為三角形中心的灰色片段
(0.33,0.33,0.33)
航闺。如果一個片段非惩什猓靠近一個頂點并且距離另外兩個非常遠(yuǎn),則將近頂點的顏色加權(quán)為1潦刃,將遠(yuǎn)點的顏色加權(quán)為0侮措。在下圖中,這顯示為偏紅色 片段
(0.5,0.25,0.25)
靠近三角形的右下角乖杠。如果片段位于三角形的邊緣分扎,在三個頂點中的兩個頂點的中間,則每個邊緣定義頂點的顏色加權(quán)1/2滑黔,非邊緣頂點的顏色加權(quán)0笆包。在下圖中, 這顯示為三角形左邊緣的青色片段
(0.0,0.5,0.5)
略荡。
由于光柵化是固定的管道階段庵佣,因此無法通過自定義Metal
著色語言代碼修改其行為。 在光柵化器創(chuàng)建片段及其關(guān)聯(lián)值之后汛兜,結(jié)果將傳遞到管道中的下一個階段巴粪。
Write a Fragment Function - 寫一個片段函數(shù)
片段函數(shù)(也稱為片段著色器fragment shader
)的主要任務(wù)是處理傳入的片段數(shù)據(jù)并計算可繪制像素的顏色值。
此示例中的片段函數(shù)稱為fragmentShader
粥谬,這是它的簽名肛根。
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
該函數(shù)有一個參數(shù)in
,它使用由頂點函數(shù)返回的相同RasterizerData
結(jié)構(gòu)漏策。 [[stage_in]]
屬性限定符表示此參數(shù)來自光柵化器派哲。 該函數(shù)返回一個四分量浮點向量,其中包含要呈現(xiàn)給drawable
的最終RGBA顏色值掺喻。
此示例演示了一個非常簡單的片段函數(shù)芭届,該函數(shù)返回光柵化器的插值color
值储矩,無需進一步處理。 每個片段將其插值color
值渲染到三角形中的對應(yīng)像素褂乍。
return in.color;
Obtain Function Libraries and Create a Pipeline - 獲取函數(shù)庫并創(chuàng)建管道
在構(gòu)建示例時持隧,Xcode會編譯AAPLShaders.metal
文件以及Objective-C
代碼。但是逃片,Xcode無法在構(gòu)建時鏈接vertexShader
和fragmentShader
函數(shù)屡拨;相反,應(yīng)用程序需要在運行時顯式鏈接這些函數(shù)褥实。
Metal
著色語言代碼分兩個階段編譯:
1) 前端編譯在構(gòu)建時在Xcode中發(fā)生
.metal
文件從高級源代碼編譯為中間表示(IR)文
件呀狼。2) 后端編譯在運行時在物理設(shè)備中進行。然后將
IR
文件編譯為低級機器代碼损离。
每個GPU
系列都有不同的指令集赠潦。因此,Metal shading語言代碼只能在運行時由物理設(shè)備本身完全編譯為本機GPU代碼草冈。前端編譯通過將IR存儲在打包在示例的.app包中的default.metallib文件中來減少一些編譯開銷她奥。
default.metallib
文件是Metal
著色語言函數(shù)庫,由運行時通過調(diào)用newDefaultLibrary
方法檢索的MTLLibrary
對象表示怎棱。從該庫中哩俭,可以檢索由MTLFunction
對象表示的特定函數(shù)。
// Load all the shader files with a .metal file extension in the project
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
// Load the vertex function from the library
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
// Load the fragment function from the library
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
這些MTLFunction
對象用于創(chuàng)建表示圖形渲染管道的MTLRenderPipelineState
對象拳恋。調(diào)用MTLDevice
對象的newRenderPipelineStateWithDescriptor:error:
方法開始后端編譯過程凡资,該過程鏈接vertexShader
和fragmentShader
函數(shù),從而產(chǎn)生完全編譯的管道谬运。
MTLRenderPipelineState
對象包含由MTLRenderPipelineDescriptor
對象配置的其他管道設(shè)置隙赁。除頂點和片段函數(shù)外,此示例還配置colorAttachments
數(shù)組中第一個條目的pixelFormat
值梆暖。此示例僅渲染到單個目標(biāo)伞访,即視圖的drawable(colorAttachments [0])
,其像素格式由視圖本身(colorPixelFormat)
配置轰驳。視圖的像素格式定義了每個像素的內(nèi)存布局厚掷;在創(chuàng)建管道時,Metal必須能夠引用此布局级解,以便它可以正確呈現(xiàn)fragment函數(shù)生成的顏色值冒黑。
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
Send Vertex Data to a Vertex Function - 將頂點數(shù)據(jù)發(fā)送到頂點函數(shù)
創(chuàng)建管道后,可以將其分配給渲染命令編碼器勤哗。 此操作將由該特定管道處理所有后續(xù)渲染命令抡爹。
[renderEncoder setRenderPipelineState:_pipelineState];
此示例使用setVertexBytes:length:atIndex:
方法將頂點數(shù)據(jù)發(fā)送到頂點函數(shù)。如前所述芒划,示例的vertexShader
函數(shù)的簽名有兩個參數(shù)冬竟,vertices
和viewportSizePointer
昙篙,它們使用[[buffer(index)]]
屬性限定符。 setVertexBytes:length:atIndex:
方法中index
參數(shù)的值映射到[[buffer(index)]]
屬性限定符中具有相同index
值的參數(shù)诱咏。因此,調(diào)用setVertexBytes:length:atIndex:
方法為特定的頂點函數(shù)參數(shù)設(shè)置特定的頂點數(shù)據(jù)缴挖。
AAPLVertexInputIndexVertices
和AAPLVertexInputIndexViewportSize
值在AAPLRenderer.m
和AAPLShaders.metal文
件之間共享的AAPLShaderTypes.h
標(biāo)頭中定義袋狞。該示例將這些值用于setVertexBytes:length:atIndex:
方法的index
參數(shù)以及與同一頂點函數(shù)對應(yīng)的[[buffer(index)]]
屬性限定符。通過減少由于硬編碼整數(shù)(可能將錯誤的數(shù)據(jù)發(fā)送到錯誤的參數(shù))導(dǎo)致的潛在索引不匹配映屋,可以跨不同文件共享這些值苟鸯,從而使示例更加健壯。
此示例將以下頂點數(shù)據(jù)發(fā)送到頂點函數(shù):
- 使用
AAPLVertexInputIndexVertices
索引值將triangleVertices
指針發(fā)送到vertices
參數(shù) - 使用
AAPLVertexInputIndexViewportSize
索引值將_viewportSize
指針發(fā)送到viewportSizePointer
參數(shù)
// You send a pointer to the `triangleVertices` array also and indicate its size
// The `AAPLVertexInputIndexVertices` enum value corresponds to the `vertexArray`
// argument in the `vertexShader` function because its buffer attribute also uses
// the `AAPLVertexInputIndexVertices` enum value for its index
[renderEncoder setVertexBytes:triangleVertices
length:sizeof(triangleVertices)
atIndex:AAPLVertexInputIndexVertices];
// You send a pointer to `_viewportSize` and also indicate its size
// The `AAPLVertexInputIndexViewportSize` enum value corresponds to the
// `viewportSizePointer` argument in the `vertexShader` function because its
// buffer attribute also uses the `AAPLVertexInputIndexViewportSize` enum value
// for its index
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:AAPLVertexInputIndexViewportSize];
Draw the Triangle - 繪制三角形
設(shè)置管道及其關(guān)聯(lián)的頂點數(shù)據(jù)后棚点,發(fā)出繪制調(diào)用會執(zhí)行管道并繪制樣本的單個三角形早处。該示例將單個繪圖命令編碼到渲染命令編碼器(render command encoder)
中。
三角形是Metal
中的幾何圖元瘫析,需要繪制三個頂點砌梆。其他基元包括需要兩個頂點的線,或者只需要一個頂點的點贬循。 drawPrimitives:vertexStart:vertexCount:
方法允許您準(zhǔn)確指定要繪制的基元類型以及要使用的從先前設(shè)置的頂點數(shù)據(jù)派生的頂點數(shù)據(jù)咸包。為vertexStart
參數(shù)設(shè)置0表示繪圖應(yīng)以頂點數(shù)組中的第一個頂點開始。這意味著頂點函數(shù)的vertexID
參數(shù)的第一個值(使用[[vertex_id]]
屬性限定符)將為0
杖虾。設(shè)置為vertexCount
參數(shù)的3
表示應(yīng)繪制三個頂點烂瘫,從而生成一個三角形。 (也就是說奇适,對于vertexID
參數(shù)坟比,頂點函數(shù)執(zhí)行三次,值為0,1和2)嚷往。
// Draw the 3 vertices of our triangle
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
此調(diào)用是對單個三角形的渲染命令進行編碼所需的最后一次調(diào)用葛账。 繪圖完成后,渲染循環(huán)可以結(jié)束編碼皮仁,提交命令緩沖區(qū)注竿,并呈現(xiàn)包含渲染三角形的drawable
。
后記
本篇主要講述了您學(xué)習(xí)如何在Metal中渲染基本幾何體魂贬,感興趣的給個贊或者關(guān)注~~~