2019-09-03metal繪制個(gè)三角形

渲染一個(gè)簡(jiǎn)單的二維三角形。

本文內(nèi)容來自蘋果

本文主要講述使用Metal繪制到屏幕時(shí),將視圖的內(nèi)容刪除為背景顏色。顯示如何配置渲染管道并將其用作渲染過程的一部分,以簡(jiǎn)單的2D彩色三角形繪制到視圖中。為每個(gè)頂點(diǎn)提供位置和顏色,渲染管道使用該數(shù)據(jù)渲染三角形,在為三角形頂點(diǎn)指定的顏色之間插入顏色值。


簡(jiǎn)單的二維三角形頂點(diǎn)

注意
iOS或tvOS模擬器不支持Metal,因此iOS和tvOS方案需要物理設(shè)備來運(yùn)行示例

了解Metal Render Pipeline

一個(gè)渲染管線流程繪圖命令和數(shù)據(jù)寫入到一個(gè)渲染通道的目標(biāo)。渲染管道有許多階段,一些使用著色器編程,另一些使用固定或可配置的行為編程。此示例主要關(guān)注管道的三個(gè)主要階段:頂點(diǎn)階段,光柵化階段和片段階段。頂點(diǎn)階段和片段階段是可編程的,因此您可以使用金屬著色語言(MSL)為它們編寫函數(shù)。光柵化階段具有固定的行為。

圖1 Metal圖形渲染管道的主要階段

金屬圖形渲染管道的主要階段

渲染從繪圖命令開始,該命令包括頂點(diǎn)計(jì)數(shù)和要渲染的基元類型。例如,以下是此示例中的繪圖命令:

// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                  vertexStart:0
                  vertexCount:3];

頂點(diǎn)階段為每個(gè)頂點(diǎn)提供數(shù)據(jù)。當(dāng)處理了足夠的頂點(diǎn)時(shí),渲染管道柵格化基元,確定渲染目標(biāo)中的哪些像素位于基元的邊界內(nèi)。片段階段確定要寫入這些像素的渲染目標(biāo)的值。

在本示例的其余部分中,您將看到如何編寫頂點(diǎn)和片段函數(shù),如何創(chuàng)建渲染管道狀態(tài)對(duì)象,以及最后如何編碼使用此管道的繪制命令。

自定義渲染管道如何處理數(shù)據(jù)

頂點(diǎn)函數(shù)為單個(gè)頂點(diǎn)生成數(shù)據(jù)片段函數(shù)生成單個(gè)片段的數(shù)據(jù),但您可以決定它們的工作方式。您可以考慮目標(biāo)來配置管道的各個(gè)階段,這意味著您知道管道要生成什么以及如何生成這些結(jié)果。

確定要傳遞到渲染管道的數(shù)據(jù)以及將哪些數(shù)據(jù)傳遞到管道的后續(xù)階段。通常有三個(gè)地方可以執(zhí)行此操作:

  • 管道的輸入,由您的應(yīng)用程序提供并傳遞到頂點(diǎn)階段。
  • 頂點(diǎn)階段的輸出,傳遞給光柵化階段。
  • 片段階段的輸入,由您的應(yīng)用提供或由光柵化階段生成。

管道的輸入數(shù)據(jù)是頂點(diǎn)的位置及其顏色。通常在頂點(diǎn)函數(shù)中執(zhí)行的變換類型,輸入坐標(biāo)在自定義坐標(biāo)空間中定義,以視圖中心的像素為單位進(jìn)行測(cè)量。這些坐標(biāo)需要轉(zhuǎn)換為Metal的坐標(biāo)系。

聲明一個(gè)PLVertex結(jié)構(gòu),使用SIMD矢量類型來保存位置和顏色數(shù)據(jù)。要共享結(jié)構(gòu)在內(nèi)存中的布局方式的單個(gè)定義,請(qǐng)?jiān)谕ㄓ脴?biāo)頭中聲明結(jié)構(gòu),并將其導(dǎo)入Metal shader和app中。

typedef struct
{
    vector_float2 position;
    vector_float4 color;
} AAPLVertex;

SIMD類型在Metal Shading Language中很常見,您也應(yīng)該使用simd庫在應(yīng)用程序中使用它們。SIMD類型包含特定數(shù)據(jù)類型的多個(gè)通道,因此將位置聲明為包含兩個(gè)32位浮點(diǎn)值(它將保存x和y坐標(biāo))。顏色使用a存儲(chǔ),因此它們有四個(gè)通道 - 紅色,綠色,藍(lán)色和alpha。vector_float2vector_float4

在應(yīng)用程序中,使用常量數(shù)組指定輸入數(shù)據(jù):

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 } },
};

頂點(diǎn)階段為頂點(diǎn)生成數(shù)據(jù),因此需要提供顏色和變換位置。使用SIMD類型聲明包含位置和顏色值的結(jié)構(gòu)。RasterizerData

typedef struct
{
    //此成員的[[位置]]屬性表示此值
    //是此結(jié)構(gòu)為
    //從Vertex函數(shù)返回。
    float4 position [[position]];

    //由于此成員沒有特殊屬性,因此光柵化器
    //用其他三角形頂點(diǎn)的值插入其值
    //然后將插值值傳遞給每個(gè)
    //三角形中的片段。
    float4 color;
} RasterizerData;

輸出位置(下面詳細(xì)描述)必須定義為a 。顏色聲明為輸入數(shù)據(jù)結(jié)構(gòu)中的顏色。vector_float4

您需要告訴Metal光柵化數(shù)據(jù)中的哪個(gè)字段提供位置數(shù)據(jù),因?yàn)镸etal不對(duì)結(jié)構(gòu)中的字段強(qiáng)制執(zhí)行任何特定的命名約定。position使用[[position]]屬性限定符注釋該字段以聲明此字段包含輸出位置。

片段函數(shù)只是將光柵化階段的數(shù)據(jù)傳遞給后期階段,因此它不需要任何其他參數(shù)。

聲明頂點(diǎn)函數(shù)

vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
             constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],
             constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])

第一個(gè)參數(shù)是使用屬性限定符,它是另一個(gè)Metal關(guān)鍵字。執(zhí)行渲染命令時(shí),GPU會(huì)多次調(diào)用頂點(diǎn)函數(shù),為每個(gè)頂點(diǎn)生成唯一值。vertexID[[vertex_id]]

第二個(gè)參數(shù)vertices是一個(gè)包含頂點(diǎn)數(shù)據(jù)的數(shù)組,使用AAPLVertex先前定義的結(jié)構(gòu)。

要將位置轉(zhuǎn)換為Metal的坐標(biāo),該函數(shù)需要繪制三角形的視口大小(以像素為單位),因此將其存儲(chǔ)在參數(shù)中。viewportSizePointer

第二個(gè)和第三個(gè)參數(shù)具有[[buffer(n)]]屬性限定符。默認(rèn)情況下,Metal會(huì)自動(dòng)為參數(shù)表分配每個(gè)參數(shù)的插槽。將[[buffer(n)]]限定符添加到緩沖區(qū)參數(shù)時(shí),可以明確告知Metal使用哪個(gè)插槽。明確聲明插槽可以更輕松地修改著色器,而無需更改應(yīng)用程序代碼。在共享頭文件中聲明兩個(gè)指標(biāo)的常量。

函數(shù)的輸出是一個(gè)結(jié)構(gòu)。RasterizerData

寫入頂點(diǎn)函數(shù)

您的頂點(diǎn)函數(shù)必須生成輸出結(jié)構(gòu)的兩個(gè)字段。使用參數(shù)索引到數(shù)組并讀取頂點(diǎn)的輸入數(shù)據(jù)。另外,檢索視口尺寸。vertexIDvertices

float2 pixelSpacePosition = vertices[vertexID].position.xy;

// 獲取視區(qū)大小并將其轉(zhuǎn)換為float
vector_float2 viewportSize = vector_float2(*viewportSizePointer);

頂點(diǎn)函數(shù)必須在剪輯空間坐標(biāo)中提供位置數(shù)據(jù),這是使用四維同質(zhì)矢量(x,y,z,w)指定的3D點(diǎn)。光柵化階段獲取輸出位置并將,,和坐標(biāo)分開x,以在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)中生成3D點(diǎn)。標(biāo)準(zhǔn)化設(shè)備坐標(biāo)與視口大小無關(guān)。yzw
圖2標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系

標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系

標(biāo)準(zhǔn)化設(shè)備坐標(biāo)使用左手坐標(biāo)系并映射到視口中的位置。基元被剪切到此坐標(biāo)系中的框,然后進(jìn)行柵格化。剪切框的左下角位于(x,y)坐標(biāo)處,右上角位于。正-Z值指向遠(yuǎn)離相機(jī)(進(jìn)入屏幕)。坐標(biāo)的可見部分位于(近剪裁平面)和(遠(yuǎn)剪裁平面)之間。
將輸入坐標(biāo)系轉(zhuǎn)換為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系。


頂點(diǎn)函數(shù)坐標(biāo)轉(zhuǎn)換

因?yàn)檫@是一個(gè)2D應(yīng)用程序而且不需要同質(zhì)坐標(biāo),所以首先將默認(rèn)值寫入輸出坐標(biāo),其w值設(shè)置為,其他坐標(biāo)設(shè)置為。這意味著坐標(biāo)已經(jīng)在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)空間中,并且頂點(diǎn)函數(shù)應(yīng)該在該坐標(biāo)空間中生成(x,y)坐標(biāo)。將輸入位置除以視口大小的一半以生成標(biāo)準(zhǔn)化設(shè)備坐標(biāo)。由于使用SIMD類型執(zhí)行該計(jì)算,因此可以使用單行代碼同時(shí)劃分兩個(gè)通道。執(zhí)行除法并將結(jié)果放在輸出位置的x和y通道中。

out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
out.position.xy = pixelSpacePosition / (viewportSize / 2.0);

最后,將顏色值復(fù)制到返回值中。out.color

out.color = vertices[vertexID].color;

寫一個(gè)片段函數(shù)

一個(gè)片段是一個(gè)可能改變的渲染目標(biāo)。光柵化器確定渲染目標(biāo)的哪些像素被基元覆蓋。僅渲染像素中心在三角形內(nèi)部的片段。
圖3光柵化階段生成的碎片

光柵化階段生成的碎片

片段函數(shù)處理來自光柵化器的輸入信息,用于單個(gè)位置,并計(jì)算每個(gè)渲染目標(biāo)的輸出值。這些片段值由管道中的后續(xù)階段處理,最終寫入渲染目標(biāo)。

注意
片段被稱為可能的更改的原因是因?yàn)槠坞A段之后的管道階段可以配置為拒絕某些片段或更改寫入渲染目標(biāo)的內(nèi)容。在此示例中,片段階段計(jì)算的所有值都按原樣寫入渲染目標(biāo)。

此示例中的片段著色器接收與頂點(diǎn)著色器輸出中聲明的相同參數(shù)。使用fragment關(guān)鍵字聲明片段函數(shù)。它需要一個(gè)參數(shù),與頂點(diǎn)階段提供的結(jié)構(gòu)相同。添加屬性限定符以指示此參數(shù)是由光柵化器生成的。RasterizerData[[stage_in]]

fragment float4 fragmentShader(RasterizerData in [[stage_in]])

光柵化階段計(jì)算每個(gè)片段參數(shù)的值,并使用它們調(diào)用片段函數(shù)。光柵化階段將其顏色參數(shù)計(jì)算為三角形頂點(diǎn)處顏色的混合。片段離頂點(diǎn)越近,頂點(diǎn)對(duì)最終顏色的貢獻(xiàn)越大。
圖4插值片段顏色

插值片段顏色

將插值顏色作為函數(shù)的輸出返回。

return in.color;

創(chuàng)建渲染管道狀態(tài)對(duì)象

現(xiàn)在函數(shù)已完成,您可以創(chuàng)建使用它們的渲染管道。首先,獲取默認(rèn)庫并獲取MTLFunction每個(gè)函數(shù)的對(duì)象。

id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];

id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];

接下來,創(chuàng)建一個(gè)對(duì)象。渲染管道有更多要配置的階段,因此您使用a 來配置管道。

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];

除了指定頂點(diǎn)和片段函數(shù)之外,還要聲明管道將繪制的所有渲染目標(biāo)的像素格式。像素格式()定義像素?cái)?shù)據(jù)的存儲(chǔ)器布局。對(duì)于簡(jiǎn)單格式,此定義包括每個(gè)像素的字節(jié)數(shù),存儲(chǔ)在像素中的數(shù)據(jù)通道數(shù)以及這些通道的位布局。由于此示例只有一個(gè)渲染目標(biāo)并且由視圖提供,因此將視圖的像素格式復(fù)制到渲染管道描述符中。渲染管道狀態(tài)必須使用與渲染過程指定的像素格式兼容的像素格式。在此示例中,渲染過程和管道狀態(tài)對(duì)象都使用視圖的像素格式,因此它們始終相同。MTLPixelFormat

當(dāng)Metal創(chuàng)建渲染管道狀態(tài)對(duì)象時(shí),管道配置為將片段函數(shù)的輸出轉(zhuǎn)換為渲染目標(biāo)的像素格式。如果要定位不同的像素格式,則需要?jiǎng)?chuàng)建不同的管道狀態(tài)對(duì)象。您可以在針對(duì)不同像素格式的多個(gè)管道中重復(fù)使用相同的著色器。

設(shè)置視口

現(xiàn)在您已擁有管道的渲染管道狀態(tài)對(duì)象,您將渲染三角形。您可以使用渲染命令編碼器執(zhí)行此操作。首先,設(shè)置視口,以便Metal知道要繪制的渲染目標(biāo)的哪個(gè)部分。

// Set the region of the drawable to draw into.
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, 0.0, 1.0 }];

設(shè)置渲染管道狀態(tài)

設(shè)置要使用的管道的渲染管道狀態(tài)。

[renderEncoder setRenderPipelineState:_pipelineState];

將參數(shù)數(shù)據(jù)發(fā)送到頂點(diǎn)函數(shù)

通常,您使用buffers(MTLBuffer)將數(shù)據(jù)傳遞給著色器。但是,當(dāng)您需要將少量數(shù)據(jù)傳遞給頂點(diǎn)函數(shù)時(shí)(如此處所示),將數(shù)據(jù)直接復(fù)制到命令緩沖區(qū)中。

該示例將兩個(gè)參數(shù)的數(shù)據(jù)復(fù)制到命令緩沖區(qū)中。頂點(diǎn)數(shù)據(jù)從樣本中定義的數(shù)組中復(fù)制。視口數(shù)據(jù)是從用于設(shè)置視口的相同變量中復(fù)制的。

在此示例中,片段函數(shù)僅使用從光柵化器接收的數(shù)據(jù),因此沒有要設(shè)置的參數(shù)。

[renderEncoder setVertexBytes:triangleVertices
                       length:sizeof(triangleVertices)
                      atIndex:AAPLVertexInputIndexVertices];

[renderEncoder setVertexBytes:&_viewportSize
                       length:sizeof(_viewportSize)
                      atIndex:AAPLVertexInputIndexViewportSize];

編碼繪圖命令

指定基元的類型,起始索引和頂點(diǎn)數(shù)。渲染三角形時(shí),將為參數(shù)調(diào)用頂點(diǎn)函數(shù),值為0,1和2 。vertexID

// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                  vertexStart:0
                  vertexCount:3];

與使用Metal繪制屏幕一樣,您可以結(jié)束編碼過程并提交命令緩沖區(qū)。但是,您可以使用同一組步驟編碼更多渲染命令。渲染最終圖像,就好像命令按指定順序處理一樣。(為了提高性能,允許GPU并行處理命令甚至部分命令,只要最終結(jié)果看起來按順序呈現(xiàn)。)

使用顏色插值進(jìn)行實(shí)驗(yàn)

在此示例中,顏色值在三角形中進(jìn)行插值。這通常是你想要的,但有時(shí)候你想要一個(gè)頂點(diǎn)生成一個(gè)值,并在整個(gè)基元上保持不變。flat在頂點(diǎn)函數(shù)的輸出上指定屬性限定符以執(zhí)行此操作。現(xiàn)在試試吧。在示例項(xiàng)目中找到定義并將限定符添加到其字段中。RasterizerData[[flat]]color``float4 color [[flat]];

再次運(yùn)行該示例。渲染管道在三角形上均勻地使用第一個(gè)頂點(diǎn)(稱為激發(fā)頂點(diǎn))的顏色值,并忽略其他兩個(gè)頂點(diǎn)的顏色。您可以使用平面著色和插值的混合,只需flat在頂點(diǎn)函數(shù)的輸出上添加或省略限定符即可。“ 金屬著色語言”規(guī)范定義了您還可以用來修改光柵化行為的其他屬性限定符。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容