概述

在 《 Metal 框架之使用 Metal 来绘制视图内容 》中,介绍了如何设置 MTKView 对象并使用渲染通道更改视图的内容,实现了将背景色渲染为视图的内容。本示例将介绍如何配置渲染管道,作为渲染通道的一部分,在视图中绘制一个简单的 2D 彩色三角形。该示例为每个顶点提供位置和颜色,渲染管道使用该数据,在指定的顶点颜色之间插入颜色值来渲染三角形。

在本示例中,将介绍如何编写顶点和片元函数、如何创建渲染管道状态对象,以及最后对绘图命令进行编码。

理解 Metal 渲染管线

渲染管线处理绘图命令并将数据写入渲染通道的目标中。一个完整地渲染管线有许多阶段组成,一些阶段需要使用着色器进行编程,而一些阶段则需要配置固定的功能件。本示例的管线主要包含三个阶段:顶点阶段、光栅化阶段和片元阶段。其中,顶点阶段和片元阶段是可编程的,这可以使用 Metal Shading Language (MSL) 来编写函数,而光栅化阶段则是不可编程的,直接使用固有功能件来配置。

渲染从绘图命令开始,其中包括顶点个数和要渲染的图元类型。如下是本例子的绘图命令:

```

// Draw the triangle.

[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle

vertexStart:0

vertexCount:3];

```

顶点阶段会处理每个顶点的数据。当顶点经过顶点阶段处理后,渲染管线会对图元光栅化处理,以此来确定渲染目标中的哪些像素位于图元的边界内(即图元可以转化成的像素)。片元阶段是要确定渲染目标的像素值。

自定义渲染管线

顶点函数为单个顶点生成数据,片元函数为单个片元生成数据,可以通过编写函数来指定它们的工作方式。我们可以依据希望管道完成什么功能以及如何完成来配置管道的各个阶段。

决定将哪些数据传递到渲染管道以及将哪些数据传递到管道的后期阶段,通常可以在三个地方执行此操作:

  • 管道的输入,由 App 提供并传递到顶点阶段。

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

  • 片元阶段的输入,由 App 提供或由光栅化阶段生成。

在本示例中,管道的输入数据包括顶点的位置及其颜色。为了演示顶点函数中执行的转换类型,输入坐标在自定义坐标空间中定义,以距视图中心的像素为单位进行测量。这些坐标需要转换成 Metal 的坐标系。

声明一个 AAPLVertex 结构,使用 SIMD 向量类型来保存位置和颜色数据。

```

typedef struct

{

vector_float2 position;

vector_float4 color;

} AAPLVertex;

```

SIMD 类型在 Metal Shading Language 中很常见,相应的需要在 App 中使用 simd 库。 SIMD 类型包含特定数据类型的多个通道,因此将位置声明为 vectorfloat2 意味着它包含两个 32 位浮点值(x 和 y 坐标)。颜色使用 vectorfloat4 存储,因此它们有四个通道:红色、绿色、蓝色和 alpha。

在 App 中,输入数据使用常量数组指定:

```

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

};

```

顶点阶段为顶点生成数据,需要提供颜色和变换的位置。使用 SIMD 类型声明一个包含位置和颜色值的 RasterizerData 结构。

```

struct RasterizerData

{

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

};

```

输出位置(在下面详细描述)必须定义为 vector_float4 类型。颜色在输入数据结构中声明。

需要告诉 Metal 光栅化数据中的哪个字段提供位置数据,因为 Metal 不会对结构中的字段强制执行任何特定的命名约定。使用 [[position]] 属性限定符来标记位置字段,使用它来保存该字段输出位置。

fragment 函数只是将光栅化阶段的数据传递给后面的阶段,因此它不需要任何额外的参数。

定义顶点函数

需要使用 vertex 关键字来定义顶点函数,包含入参和出参。

```

vertex RasterizerData

vertexShader(uint vertexID [[vertex_id]],

constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],

constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])

```

第一个参数 vertexID 使用 [[vertex_id]] 属性限定符来修饰,它是 Metal 关键字。当执行渲染命令时,GPU 会多次调用顶点函数,为每个顶点生成一个唯一值。

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

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

第二个和第三个参数使用 [[buffer(n)]] 属性限定符来修饰。默认情况下,Metal 自动为每个参数分配参数表中的插槽。当使用 [[buffer(n)]] 限定符修饰缓冲区参数时,明确地告诉 Metal 要使用哪个插槽。显式声明插槽可以方便的修改着色器代码,而无需更改 App 代码。

编写顶点函数

编写的顶点函数必须生成输出结构的两个字段,使用 vertexID 参数索引顶点数组并读取顶点的输入数据,还需要获取视口尺寸。

```

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

// Get the viewport size and cast to float.

vectorfloat2 viewportSize = vectorfloat2(*viewportSizePointer); ``` 顶点函数必须提供裁剪空间坐标中的位置数据,这些位置数据是 3D 的点,使用四维齐次向量 (x,y,z,w) 来表示。光栅化阶段获取输出位置,并将 x、y 和 z 坐标除以 w 以生成归一化设备坐标中的 3D 点。归一化设备坐标与视口大小无关。

归一化设备坐标使用左手坐标系来映射视口中的位置。图元被裁剪到这个坐标系中的一个裁剪框上,然后被光栅化。剪切框的左下角位于 (-1.0,-1.0) 坐标处,右上角位于 (1.0,1.0) 处。正 z 值指向远离相机(指向屏幕)。z 坐标的可见部分在 0.0(近剪裁平面)和 1.0(远剪裁平面)之间。

下图是将输入坐标系转换为归一化的设备坐标系。

因为这是一个二维应用,不需要齐次坐标,所以先给输出坐标写一个默认值,w值设置为1.0,其他坐标设置为0.0。这意味顶点函数在该坐标空间中生成的 (x,y) 已经在归一化设备坐标空间中了。将输入位置除以1/2视口大小就生成归一化的设备坐标。由于此计算是使用 SIMD 类型执行的,因此可以使用一行代码同时计算两个通道,执行除法并将结果放在输出位置的 x 和 y 通道中。

```

out.position = vector_float4(0.0, 0.0, 0.0, 1.0);

out.position.xy = pixelSpacePosition / (viewportSize / 2.0);

```

最后,将颜色值赋给 out.color 作为返回值。

```

out.color = vertices[vertexID].color;

```

编写片元函数

片元阶段对渲染目标可以做修改处理。光栅化器确定渲染目标的哪些像素被图元覆盖,仅处于三角形片元中的那些像素才会被渲染。

片元函数处理光栅化后的位置信息,并计算每个渲染目标的输出值。这些片元值由管道中的后续阶段处理,最终写入渲染目标。

本示例中的片元着色器接收与顶点着色器的输出中声明的相同参数。使用 fragment 关键字声明片元函数。它只有一个输入参数,与顶点阶段提供的 RasterizerData 结构相同。添加 [[stage_in]] 属性限定符以指示此参数由光栅化器生成。

```

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

```

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

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

将内插颜色作为函数的输出返回。

```

return in.color;

```

创建渲染管线状态对象

完成着色器函数编写后,需要创建一个渲染管道,通过 MTLLibrary 为每个着色器函数指定一个 MTLFunction 对象。

```

id defaultLibrary = [_device newDefaultLibrary];

id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];

id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];

```

接下来,创建一个 MTLRenderPipelineState 对象,使用 MTLRenderPipelineDescriptor 来配置管线。

```

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

```

除了指定顶点和片元函数之外,还可以指定渲染目标的像素格式。像素格式 (MTLPixelFormat) 定义了像素数据的内存布局。对于简单格式,此定义包括每个像素的字节数、存储在像素中的数据通道数以及这些通道的位布局。渲染管线状态必须使用与渲染通道指定的像素格式兼容的像素格式才能够正确渲染,由于此示例只有一个渲染目标并且它由视图提供,因此将视图的像素格式复制到渲染管道描述符中。

使用 Metal 创建渲染管道状态对象时,渲染管线需要转换片元函数的输出像素格式为渲染目标的像素格式。如果要针对不同的像素格式,则需要创建不同的管道状态对象,可以在不同像素格式的多个管道中使用相同的着色器。

设置视口

有了管道的渲染管道状态对象后,就可以使用渲染命令编码器来渲染三角形了。首先,需要设置视口来告诉 Metal 要绘制到渲染目标的哪个部分。

```

// Set the region of the drawable to draw into.

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

```

设置渲染管线状态

为渲染管线指定渲染管线状态对象。

```

[renderEncoder setRenderPipelineState:_pipelineState];

```

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

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

该示例将两个参数的数据复制到命令缓冲区中,顶点数据是从定义的数组复制而来的,视口数据是从设置视口的同一变量中复制的,片元函数仅使用从光栅化器接收的数据,因此没有传递参数。

```

[renderEncoder setVertexBytes:triangleVertices

length:sizeof(triangleVertices)

atIndex:AAPLVertexInputIndexVertices];

[renderEncoder setVertexBytes:&_viewportSize

length:sizeof(_viewportSize)

atIndex:AAPLVertexInputIndexViewportSize];

```

编码绘图命令

指定图元的种类、起始索引和顶点数。当三角形被渲染时,vertex 函数被调用,参数 vertexID 的值分别为 0、1 和 2。

```

// Draw the triangle.

[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle

vertexStart:0

vertexCount:3];

```

与使用 Metal 绘制到屏幕一样,需要结束编码过程并提交命令缓冲区。不同之处是,可以使用相同的一组步骤对更多渲染命令进行编码。按照指定的顺序来执行命令,生成最终渲染的图像。 (为了性能,GPU 可以并行处理命令甚至部分命令,只要最终结果是按顺序渲染的就行。)

颜色插值

在此示例中,颜色值是在三角形内部插值计算出来的。有时希望由一个顶点生成一个值并在整个图元中保持不变,这需要在顶点函数的输出上指定 flat 属性限定符来执行此操作。示例项目中,通过在颜色字段中添加 [[flat]] 限定符来实现此功能。

```

float4 color [[flat]];

```

渲染管线使用三角形的第一个顶点(称为激发顶点)的颜色值,并忽略其他两个顶点的颜色。还可以混合使用 flat 着色和内插值,只需在顶点函数的输出上添加或删除 flat 限定符即可。

总结

本文介绍了如何配置渲染管道,如何编写顶点和片元函数、如何创建渲染管道状态对象,以及最后对绘图命令进行编码,最终在视图中绘制一个简单的 2D 彩色三角形。

本文示例代码下载
​​​​​​​

Metal 框架之渲染管线渲染图元相关推荐

  1. Metal 框架之自定义设置渲染通道

    概述 渲染通道是一系列渲染命令,用于绘制一组纹理.本示例执行一对渲染通道来渲染视图的内容.对于第一个通道,示例创建了一个自定义渲染,将图像渲染成纹理.这个通道是一个离屏渲染通道,因为样本渲染为普通纹理 ...

  2. Metal 框架之使用 Metal Debugger 查看 GPU 工作负载

    概述 为了了解计算机是如何运行 App 或调试问题,通常要使用调试器.传统的调试器通过暂停一个线程来工作,但对基于 Metal 的 App 效果不佳. Xcode 通过帧捕获工作流专门为 Metal ...

  3. Metal 框架之创建纹理及纹理采样

    概述 Metal 中使用纹理来绘制和处理图像,它是由像素组成的.使用2维数组的纹理来保存图像,每个元素都包含颜色数据.通过纹理映射技术将纹理绘制到几何图元上. 在片段着色器中,使用片段函数对纹理采样来 ...

  4. Metal 框架之从可绘制纹理中读取像素数据

    概述 Metal 优化了纹理以供 GPU 快速访问,但不允许直接从 CPU 访问纹理的内容.当 App 需要更改或读取纹理的内容时,需要 Metal 在纹理和可访问的 CPU 内存(系统内存或使用共享 ...

  5. 基于 Metal 框架的 GPU 计算

    概述 GPU 的优势在于并发计算能力,在本示例中,你将学习如何使用 Apple 的新框架 Metal 来实现并发计算. 你将学会如何将用C编写的简单函数转换为 Metal Shading Langua ...

  6. Metal 框架之渲染到多个纹理切片

    概述 使用图层选择为顶点着色器中的每个图元选择目标切片,可以将图元渲染到纹理数组.立方体纹理或 3D 纹理的多个层(切片)中.层是单个 1D.2D 或 3D 像素块,由目标纹理中的切片和 mipmap ...

  7. Metal 框架之渲染到多个视口

    概述 视口定义了渲染目标的一部分,绘图命令会渲染到该区域内.使用视口选择,可以为绘图命令提供多个视口,并且可以为绘图命令渲染的每个图元动态选择其中的一个视口.通过视口选择,使用更少的绘图命令,可以更轻 ...

  8. ios跨线程通知_一种基于Metal、Vulkan多线程渲染能力的渲染架构

    快手Y-tech  原创最新技术干货分享 随着3D渲染场景规模越来越复杂,单线程渲染架构在满足业务性能要求时已经捉襟见肘,因此,多线程渲染显得愈发重要.本文首先介绍了新一代图形渲染接口Metal.Vu ...

  9. Metal 框架之资源存储模式

    概述 Metal 中使用 MTLStorageMode 来指定资源的内存位置和访问权限. MTLStorageMode 是个枚举类型,定义如下: public enum MTLStorageMode ...

最新文章

  1. Windows 不能在 本地计算机 启动 SQL Server 服务
  2. UpdatePanel里使用FileUpload
  3. 精通jQuery选择器使用 转一篇
  4. 利用doc命令启动与关闭服务
  5. 图神经网络(GNN)综述
  6. 笔记本无线网通过网线共享给其他主机
  7. 华为hs8545m如何复位_在华为东莞松山湖基地,见证一场始于AI质检的智能制造变革...
  8. 常见排序算法、查找算法(中英文命名)
  9. 【libuv】实现UDP转发
  10. Windows下的发包工具推荐[Colasoft Packet Builder]含使用教程
  11. linux(计划任务)
  12. 【院校信息】2021北京航空航天大学计算机考研数据汇总
  13. Android 暗黑模式
  14. 【DA】单侧T检验p值与双侧T检验p值的关系
  15. mysql报错You do not have the SUPER privilege and binary logging is enabled
  16. (附源码)anjule客户信息管理系统 毕业设计 181936
  17. 多线程&高并发(全网最新:面试题 + 导图 + 核心学习笔记)面试手稳心不慌,轻松拿下 offer,秋招跳槽必不可少的底层能力
  18. UnityWebPlayer屏蔽右键及全屏
  19. 微信小程序--云开发数据库操作之where()
  20. Linux---/proc目录全讲解

热门文章

  1. 一起写个Dubbo——0. 一些不得不说的话
  2. 《HelloGitHub》第 82 期
  3. 【基金量化研究系列】基金绩效归因模型(三)——基于CAPM、T-M、H-M、C-L模型的基金绩效归因研究
  4. 【Java案例】为新员工分配部门
  5. [BZOJ1132][POI2008]Tro(计算几何)
  6. SpringBoot @RequestHeader注解接收请求头
  7. 面试官:你期望薪资多少?用这个套路回答,95%不会吃亏
  8. SAP HANA2.0数据库安装
  9. Mybatis的动态SQL
  10. [HAOI 2006]旅行comf