Metal 使用渲染管道渲染基本圖元

在上篇文章中我們學(xué)會了如何使用Metal來繪制視圖內(nèi)容,在這個(gè)篇章中我們將展示如何使用自定義渲染管道來繪制一個(gè)2D彩色圖形墩莫。該示例為每個(gè)頂點(diǎn)提供了位置和顏色芙委,并且渲染管線使用該數(shù)據(jù)渲染三角形,在為三角形頂點(diǎn)指定的顏色之間插入顏色值狂秦。

image

Metal渲染管道

一個(gè)渲染管線流程繪圖命令和數(shù)據(jù)寫入到一個(gè)渲染通道的目標(biāo)灌侣。渲染管線具有許多階段,其中一些階段使用著色器進(jìn)行編程裂问,而其他階段則具有固定或可配置的行為侧啼。該示例著重于流水線的三個(gè)主要階段:頂點(diǎn)階段牛柒,柵格化階段和片元階段。頂點(diǎn)階段和片元階段是可編程的痊乾,因此您可以使用Metal Shading Language(MSL)為其編寫函數(shù)皮壁。光柵化階段具有固定的行為。

下圖:Metal圖形渲染管線的主要階段


image

渲染從繪制命令開始哪审,該命令包括一個(gè)頂點(diǎn)數(shù)和要渲染的圖元類型蛾魄。例如,這是此示例中的繪圖命令:

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

頂點(diǎn)階段為每個(gè)頂點(diǎn)提供數(shù)據(jù)湿滓。處理完足夠的頂點(diǎn)后滴须,渲染管線將對圖元進(jìn)行柵格化,確定渲染目標(biāo)中的哪些像素位于圖元的邊界內(nèi)茉稠。片元階段確定要寫入這些像素的渲染目標(biāo)的值。

在本示例的其余部分把夸,您將看到如何編寫頂點(diǎn)和片元函數(shù)而线,如何創(chuàng)建渲染管道狀態(tài)對象,最后如何對使用該管道的繪圖命令進(jìn)行編碼恋日。

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

頂點(diǎn)函數(shù)為單個(gè)頂點(diǎn)生成數(shù)據(jù)膀篮,片元函數(shù)為單個(gè)片元生成數(shù)據(jù),但是您可以決定它們的工作方式岂膳。

決定哪些數(shù)據(jù)要傳遞到渲染管道誓竿,哪些數(shù)據(jù)要傳遞到管道的后續(xù)階段。通常有三個(gè)地方可以做到這一點(diǎn):

  • 管道的輸入谈截,由應(yīng)用程序提供并傳遞到頂點(diǎn)階段筷屡。
  • 頂點(diǎn)階段的輸出,傳遞到光柵化階段簸喂。
  • 片元階段的輸入毙死,由您的應(yīng)用程序提供或由柵格化階段生成。

在此示例中喻鳄,管道的輸入數(shù)據(jù)是頂點(diǎn)的位置及其顏色扼倘。為了演示您通常在頂點(diǎn)函數(shù)中執(zhí)行的變換類型,輸入坐標(biāo)是在自定義坐標(biāo)空間中定義的除呵,以自視圖中心的像素為單位再菊。這些坐標(biāo)需要轉(zhuǎn)換為Metal的坐標(biāo)系。

AAPLVertex使用SIMD向量類型聲明結(jié)構(gòu)颜曾,以保存位置和顏色數(shù)據(jù)纠拔。要共享有關(guān)結(jié)構(gòu)在內(nèi)存中的布局方式的單一定義,請?jiān)诠矘?biāo)頭中聲明該結(jié)構(gòu)泛豪,然后將其導(dǎo)入Metal著色器和應(yīng)用程序中绿语。

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

SIMD類型包含特定數(shù)據(jù)類型的多個(gè)通道秃症,因此將位置聲明為vector_float2意味著它包含兩個(gè)32位浮點(diǎn)值(將保存x和y坐標(biāo)。)使用vector_float4存儲顏色吕粹,因此它們具有四個(gè)通道-紅色种柑,綠色,藍(lán)色和Alpha匹耕。

在應(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

// Vertex shader outputs and fragment shader inputs
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 position [[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;

您需要告訴Metal柵格化數(shù)據(jù)中的哪個(gè)字段提供位置數(shù)據(jù),因?yàn)镸etal不會對結(jié)構(gòu)中的字段實(shí)施任何特定的命名約定既鞠。position用[[position]]屬性限定符注釋該字段煤傍,以聲明該字段占據(jù)輸出位置。

片元函數(shù)只是將柵格化階段的數(shù)據(jù)傳遞到以后的階段嘱蛋,因此不需要任何其他參數(shù)蚯姆。

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

聲明頂點(diǎn)函數(shù),包括其輸入?yún)?shù)和其輸出的數(shù)據(jù)洒敏。就像使用kernel關(guān)鍵字聲明計(jì)算函數(shù)一樣龄恋,您可以使用關(guān)鍵字聲明頂點(diǎn)函數(shù)vertex。

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會多次調(diào)用頂點(diǎn)函數(shù)显押,從而為每個(gè)頂點(diǎn)生成唯一的值。vertexID[[vertex_id]]

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

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

第二個(gè)和第三個(gè)參數(shù)具有[[buffer(n)]]屬性限定符殖蚕。默認(rèn)情況下轿衔,Metal自動在參數(shù)表中為每個(gè)參數(shù)分配插槽。將[[buffer(n)]]限定詞添加到緩沖區(qū)參數(shù)時(shí)睦疫,您明確告訴Metal使用哪個(gè)插槽害驹。顯式聲明插槽可以使您更輕松地修改著色器,而無需更改應(yīng)用程序代碼蛤育。在共享頭文件中聲明兩個(gè)索引的常量宛官。

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

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

您的頂點(diǎn)函數(shù)必須生成輸出結(jié)構(gòu)的兩個(gè)字段。使用自變量索引數(shù)組并讀取頂點(diǎn)的輸入數(shù)據(jù)底洗。

// Index into the array of positions to get the current vertex.
// The positions are specified in pixel dimensions (i.e. a value of 100
// is 100 pixels from the origin).
float2 pixelSpacePosition = vertices[vertexID].position.xy;

// Get the viewport size and cast to float.
vector_float2 viewportSize = vector_float2(*viewportSizePointer);

頂點(diǎn)函數(shù)必須在剪貼空間坐標(biāo)中提供位置數(shù)據(jù)腋么,該數(shù)據(jù)是使用四維均勻向量(x,y,z,w)指定的3D點(diǎn)。光柵化階段需要的輸出位置亥揖,并且將x珊擂,y和z由坐標(biāo)w,以生成一個(gè)3D點(diǎn)歸一化設(shè)備坐標(biāo)费变。規(guī)范化的設(shè)備坐標(biāo)與視口大小無關(guān)摧扇。

下圖:規(guī)范化設(shè)備坐標(biāo)系


image

規(guī)范化的設(shè)備坐標(biāo)使用左手坐標(biāo)系并映射到視口中的位置。在圖元坐標(biāo)系中將圖元裁剪到一個(gè)框挚歧,然后進(jìn)行柵格化扛稽。剪輯框的左下角為的(x,y)坐標(biāo),右上角為的坐標(biāo)滑负。正z值指向遠(yuǎn)離相機(jī)的位置(進(jìn)入屏幕)在张。坐標(biāo)的可見部分在(近裁剪平面)和(遠(yuǎn)裁剪平面)之間。(-1.0,-1.0)(1.0,1.0)z0.01.0

將輸入坐標(biāo)系轉(zhuǎn)換為規(guī)范化的設(shè)備坐標(biāo)系橙困。


image

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

// To convert from positions in pixel space to positions in clip-space,
//  divide the pixel coordinates by half the size of the viewport.
out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
out.position.xy = pixelSpacePosition / (viewportSize / 2.0);

最后披摄,將顏色值復(fù)制到返回值中亲雪。

out.color = vertices[vertexID].color;

編寫片元函數(shù)

下圖:柵格化階段生成的片元


image

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

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

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

如果您的片元函數(shù)寫入多個(gè)渲染目標(biāo)蘸吓,則必須為每個(gè)渲染目標(biāo)聲明一個(gè)帶有字段的結(jié)構(gòu)。由于此示例僅具有一個(gè)渲染目標(biāo)续镇,因此您可以直接將浮點(diǎn)向量指定為函數(shù)的輸出美澳。此輸出是要寫入渲染目標(biāo)的顏色。

光柵化階段為每個(gè)片元的參數(shù)計(jì)算值摸航,并使用它們調(diào)用片元函數(shù)制跟。柵格化階段將其顏色參數(shù)計(jì)算為三角形頂點(diǎn)處顏色的混合。片元離頂點(diǎn)越近酱虎,該頂點(diǎn)對最終顏色的貢獻(xiàn)就越大雨膨。

下圖:插值的片元顏色


image

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

return in.color;

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

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

// Load all the shader files with a .metal file extension in the project.
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];

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

接下來創(chuàng)建一個(gè)MTLRenderPipelineState對象排监,渲染管道又很多的可配置項(xiàng),你可以使用MTLRenderPipelineDescriptor來進(jìn)行配置杰捂。

// Configure a pipeline descriptor that is used to create a pipeline state.
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)和片元功能之外舆床,您還聲明管道將繪制到的所有渲染目標(biāo)的像素格式。像素格式(MTLPixelFormat)定義像素?cái)?shù)據(jù)的存儲布局嫁佳。對于簡單格式挨队,此定義包括每個(gè)像素的字節(jié)數(shù),存儲在一個(gè)像素中的數(shù)據(jù)通道數(shù)以及這些通道的位布局蒿往。由于此樣本僅具有一個(gè)渲染目標(biāo)盛垦,并且由視圖提供,因此將視圖的像素格式復(fù)制到渲染管道描述符中瓤漏。渲染管線狀態(tài)必須使用與渲染通道指定的像素格式兼容的像素格式腾夯。在此示例中,渲染通道和管線狀態(tài)對象都使用視圖的像素格式蔬充,因此它們始終相同蝶俱。

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

設(shè)置視口

既然已經(jīng)有了管道的渲染管道狀態(tài)對象愕提,就可以渲染三角形馒稍。您可以使用渲染命令編碼器執(zhí)行此操作。首先浅侨,設(shè)置視口纽谒,以便Metal知道要繪制到渲染目標(biāo)的哪一部分。

// 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ù)

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

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

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

// Pass in the parameter data.
[renderEncoder setVertexBytes:triangleVertices
                       length:sizeof(triangleVertices)
                      atIndex:AAPLVertexInputIndexVertices];
    
[renderEncoder setVertexBytes:&_viewportSize
                       length:sizeof(_viewportSize)
                      atIndex:AAPLVertexInputIndexViewportSize];

與使用Metal繪制到屏幕一樣酪惭,您結(jié)束編碼過程并提交命令緩沖區(qū)希痴。但是,您可以使用相同的步驟對更多的渲染命令進(jìn)行編碼春感。渲染最終圖像砌创,就像按指定順序處理命令一樣。(為了提高性能甥厦,只要最終結(jié)果看上去已經(jīng)按順序呈現(xiàn)纺铭,GPU便可以并行處理命令或什至部分命令寇钉。)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刀疙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子扫倡,更是在濱河造成了極大的恐慌谦秧,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撵溃,死亡現(xiàn)場離奇詭異疚鲤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缘挑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門集歇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人语淘,你說我怎么就攤上這事诲宇〖始撸” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵姑蓝,是天一觀的道長鹅心。 經(jīng)常有香客問我,道長纺荧,這世上最難降的妖魔是什么旭愧? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮宙暇,結(jié)果婚禮上输枯,老公的妹妹穿的比我還像新娘。我一直安慰自己占贫,他們只是感情好用押,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著靶剑,像睡著了一般蜻拨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桩引,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天缎讼,我揣著相機(jī)與錄音,去河邊找鬼坑匠。 笑死血崭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厘灼。 我是一名探鬼主播夹纫,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼设凹!你這毒婦竟也來了舰讹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤闪朱,失蹤者是張志新(化名)和其女友劉穎月匣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奋姿,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锄开,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了称诗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萍悴。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出癣诱,到底是詐尸還是另有隱情任岸,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布狡刘,位于F島的核電站享潜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗅蔬。R本人自食惡果不足惜剑按,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望澜术。 院中可真熱鬧艺蝴,春花似錦、人聲如沸鸟废。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盒延。三九已至缩擂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間添寺,已是汗流浹背胯盯。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留计露,地道東北人博脑。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像票罐,于是被迫代替她去往敵國和親叉趣。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354