Metal 基本任务和概念 - 03

使用渲染管道渲染基元

渲染一个简单的2D 三角形

概述

在“使用金属绘制视图的内容”中,
您学习了如何设置MTLKView对象以及如何使用渲染过程更改视图的内容。该示例只是将视图擦出为背景色。此示例向您展示如何配置渲染管道并将其用作渲染过程的一部分,以将简单的2D彩色三角形绘制到视图中。该示例为每个顶点提供位置和颜色,并且渲染管线使用该数据渲染三角形,在为三角形顶点指定的颜色之间插入颜色值。

xcode项目包含用于在Mac,iOS 和 tvOS上运行示例的方案。

了解金属渲染管线

一个渲染管线流程绘图命令和数据写入到一个渲染通道的目标渲染管线具有许多阶段,其中一些阶段使用着色器进行编程,而其它阶段则具有固定和可配置的行为。该示例着重于流水线的三个主要阶段:顶点阶段、栅格化阶段和片段阶段。顶点阶段和片段阶段是可编程的,因此您可以使用金属阴影语言(msl)为其编写函数,光栅化阶段具有固定的行为。

**图1 Metal图形渲染管线的主要阶段

59a9ce5d-9c10-4aee-a6aa-446fef3fab31.png

渲染从绘制命令开始,该命令包含一个顶点数和要渲染的图元类型,例如:这是此示例中的绘图命令:

 [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];

顶点阶段为每个顶点提供数据,处理完足够的顶点后,渲染管线将对图元进行光栅化,确定渲染目标中的那些像素位于图元的边界内。片段阶段确定要写入这些像素的渲染目标的值.

在本示例的其余部分,您将看到如何编写顶点和片段函数,如何创建渲染管道状态对象,最后如何对使用该管道的绘制命令进行编码。

确定自定义渲管道如何处理数据

顶点函数为单个顶点生成数据,片段函数为单个片段生成数据,但是您可以决定他们的工作方式。
您在配置管道的阶段是要牢记目标,这意味着您知道要管道生成的内容以及它如何生成这些结果。

确定哪些数据要传递到渲染管道中,哪些数据将传递到管道的后期,通常在三个地方执行此操作:

  • 管道的输入,这些输入由您的应用提供,并传递到顶点阶段。

  • 顶点阶段的输出,传递到光栅化阶段。

  • 片段阶段的输入,由您的应用程序提供或由栅格化阶段生成。

在此示例中,管道的输入数据是顶点的位置以及其颜色,为了演示您通常在顶点函数中执行的变换类型,输入坐标是在自定义坐标空间中定义的,以自视图中心的像素为单位,这些坐标需要转换为Metal的坐标系。

AAPLVertex使用SIMD向量类型声明结构。以保存位置和颜色数据,要共享有关结构在内存中的布局方式的单一定义,请在通用标头中声明该结构,然后将其导入metal着色器和应用程序中。

typedef struct 

{
    vector_float2 position;
    vector_float4 color;
}. AAPLVertex;

SIMD类型在金属着色器语言中很常见,您还应该在您的应用程序中使用simd库使用他们。SIMD类型包含特定数据类型的多个通道,因此将位置声明为vertcor_float2意味着它包含两个32为浮点值(将保存x和y坐标)使用a verctor_float4 存储颜色。因此他们具有四个通道- 红色,绿色,蓝色和alpha。

在应用程序中,使用常量数组指定输入数据:

 static const AAPLVertex triangleVertices[] = {
   {{250,-250},{1,0,0,1}},
  {{-250,-250},{0,1,0,1}},
{{0,250},{0,0,1,1}}
}

顶点阶段为顶点生成数据,因此需要提供颜色和变化后的位置。再次使用SIMD类型声明RasterizerData包含位置和颜色的结构

struct RasterizerData {
    float2 position [[position]];
   float4 color;
}

输出位置 必须定义为vector_float2 .声明颜色与输入数据中的颜色相同。

您需要告诉metal栅格化数据中的哪个字段提供位置数据,因为metal不会对结构中的字段实施任何特定的命令约定,positon 用[[position]] 属性限定符注释该字段,以声明该字段占据输出位置。

片段函数只是将栅格化阶段的数据传递到后期阶段,因此不需要任何其它参数。

声明顶点功能

声明顶点函数,包括其输入参数和其输出的数据。就像使用kernel 关键字声明计算函数一样,您可以使用关键字声明顶点函数vertex。

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

第一个参数,使用属性限定符,这是另一个metal关键字。执行渲染命令时,GPU会多次调用您的顶点函数,从而为每个顶点生成一个唯一值。vertexID[[vertex_id]]

第二个参数,vertices 是一个使用 AAPLVertex先前定义的结构包含顶点数据的数组。

要将位置转换为Metal坐标,此函数需要将三角形绘制到的视口的大小(以像素的单位),因此将其存储在viewportsizePointer参数中

第二个和第三个参数具有[[buffer(n)]]属性限定符。默认情况下,metal 自动在参数表中为每个参数分配插槽。将[[buffer(n)]]限定词添加到缓冲区参数时,您明确告诉metal使用哪个插槽。显示声明插槽可以使您更轻松的修改着色器,而无需更改您的应用程序代码。在共享头文件中声明两个索引的常量。

该函数的输出是一个RasterizerData结构。

编写顶点函数

您的顶点函数必须生成输出结构的两个字段,使用vertexID自变量索引vertices数组并读取顶点的输入数据。另外,获取视口的大小.

float2 pixelSpacePosition = vertices[vertexID].position.xy
// get the viewport size and cast to float
vector_float2 viewportsize = vector_float2(*viewportSizePointer);

顶点函数必须在剪辑空间中提供位置数据,该数据是使用四维均匀向量(X,Y,Z,W)指定的3D点,光栅化阶段需要的输出位置,并且将X,Y,Z由坐标w,以生成一个3D点归一化设备坐标。规范化的设备坐标与视口大小无关。

** 图2规范化的设备坐标


rendered2x-1582928922.png

规范化的设备坐标使用左手坐标系并映射到视口中的位置。在图元系统中将图元裁剪到一个框,然后进行光栅化。剪辑框的左下角为(x,y)的(-1.0,-1.0)坐标,右上角为(1.0,1.0)坐标正z值指向远离相机的位置(进入屏幕)。坐标的可见部分在(近裁剪平面)和(远裁剪平面)之间。

将输入坐标系转换为规范化的设备坐标系

rendered2x-1582928923.png

因为这是2D应用程序,并且不需要同质坐标,所有首先将默认值写入输出坐标,将w值设置为1.0,将其它坐标设置为0.0,这意味着坐标已经在归一化设备坐标空间中,并且顶点函数应该在该坐标空间中,生成(x,y)坐标,将输入坐标除以视口大小的一半,以生成标准化的设备坐标。由于此计算是使用SIMD类型执行的,因此可以用使用一行代码同时划分两个通道。执行除法并将结果放在输出位置的x和y通道中。

最后,将颜色值复制到out.color返回值中,

out.color =  vertices[vertexID].color;

写一个片段函数

一个片段是一个可能改变的渲染目标。光栅化器确定渲染目标的哪些像素被图元覆盖。仅渲染像素中心在三角形内的片段。

图3 栅格化阶段生成的片段

1f358faa-f8fc-443b-9af7-6013f8f64955.png

片段函数处理来自光栅化器的单个位置的传入信息,并计算每个渲染目标的输出值。这些片段由流水线中的后续阶段处理,最终被写入渲染目标。

片段成为可能更改的原因是,可以将片段阶段之后的流水线阶段配置为拒绝某些片段或更改写入渲染目标的内容。在此样本中,片段阶段计算出的所有值均按原样写入渲染目标。

此示例中的片段着色器接受与顶点着色器的输出中声明的参数相同的参数。使用fragment关键字声明片段功能。它之接受一个参数,即顶点阶段所提供的相同结构,添加属性限定符以指示此参数由光栅化器生成。RasterizerData [[statge_in]]

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

如果您的片段函数写入多个渲染目标,则必须为每个渲染目标声明为一个带有片段的结构。由于此示例只有一个渲染目标,因此您可以直接指定一个浮点向量作为函数的输出。此输出是要写入渲染目标的颜色。

光栅化阶段为每个片段的参数计算值,并使用他们调用片段函数。栅格化阶段将其颜色参数计算为三角形顶点处颜色的混合。片段离顶点越近,该顶点对最终颜色的贡献就越大。

图4插值的片段颜色

bdf27a14-4430-45e5-99a5-d950d25a9e3b.png

返回插值的颜色作为函数的输出

 return in.color;

创建渲染管道状态对象

现在功能已完成,您可以创建使用它们的渲染管道。首先,获取默认库并使用MTLFunction为每个函数获取一个对象

 id  defaultLibrary.= [_device newDefaultLibrary];
id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexFunction"];
id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentFunction"];

接下来,创建一个MTLPipeLineState对象。渲染管道具有更多配置阶段,因此您可以使用MTLPipelineDescriptor 来配置管道。


 MTLRenderPipelineDescriptor *pipelineStateDescriptor = 【MTLRenderPipelineDescriptor alloc】init];
pipelinestateDescriptor.label = @"simple pipeline";
pipelinestateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescritor.fragementFunction = fragementFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormart = mtkview.colorPixelFormat;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:$error];

除了指定顶点和片段功能之外,您还声明管道将绘制到的所有渲染目标的像素格式。像素格式MTLPixelFormat定义像素数据的存储布局.对于简单格式,此定义包含每个像素的字节数,存储在像素中的数据通道数以及这些通道的维布局。由于此示例仅具有一个渲染目标,并且由视图提供,因此将视图的像素格式复制到渲染管道描述符中。渲染管线状态必须使用与渲染通道指定的像素格式兼容的像素格式。在此示例中,渲染通道和管线状态对象都使用视图的像素格式,因此它们始终相同。

当metal 创建渲染管道状态对象时,将管道配置为将片段函数的输出转换为渲染目标的像素格式,如果要指定其他像素格式,则需要创建其他管道状态对象,您可以在针对不同像素格式的多个管线中重复使用相同的着色器。

设置窗口

现在,您已经拥有管道的渲染管道状态对象,您将渲染三角形。您可以使用渲染命令编码器执行此操作,首先,设置视口,以便metal知道要渲染目标的哪一部分。

 [renderEncoder setViewport:(MTLViewport){0.0,0.0,_viewportSize.x,_viewpostSize.y}];

设置管道渲染状态

设置要使用的管道的渲染管道状态。

[renderEncorder setRenderpipelineState:_pipelineState];

将参数数据发送到顶点函数

通常,您使用缓冲区(mtlbuffer)将数据传递到着色器。但是,当您只需要向顶点函数传递少量数据时,可以将数据直接复制到命令缓冲区中。

该示例将两个的数据复制到命令缓冲区中,从样本中定义的数组复制到顶点数据,视口数据是从用于设置视口的同一变量中复制的。

在此示例子中,fragment 函数仅使用从光栅化器接收的数据,因此没有要设置的参数。

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

[renderEncoder setVertexByters:&_viewportSize length:sizeof(_viewportSize) atIndex:AAPLVertexInputIndexViewportsize];

编码绘图命令

指定图元类型,起始索引和顶点数。渲染三角形后,将使用参数VertexID的值0、1、和2 调用顶点函数。

[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];

与使用metal 绘制到屏幕一样,您结束编码过程并提交命令缓冲区,但是,您可以使用相同的步骤对更多的渲染命令进行编码。渲染最终图像,就像按指定顺序处理命令一样。(为了提高性能,只要最终结果看上去已经按照顺序呈现,GPU便可以并行出处理命令或甚至部分命令)

试用颜色插值

在此示例中,颜色值插在整个三角形上。这通常是您想要的,但有时您希望一个值由一个顶点生成并在整个图元中保持不变,flat 为此,请在顶点函数的输出上指定属性限定符。在此例项目中找到RasterizerData,然后将限定符添加到其字段中。

float4 color [[flat]];

在此运行示例。渲染管线使用均匀地分布在三角形上的第一个顶点(称为激发顶点)的颜色值,并且忽略其他两个顶点的颜色。您可以混合使用平面阴影值和插值,只需flat在顶点函数的输出上添加或省略限定符即可。该金属着色语言规范 定义了其他属性修饰词也可以用来修改光栅化行为。

你可能感兴趣的:(Metal 基本任务和概念 - 03)