Metal入门学习:绘制渲染三角形

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情
一、编程指南PDF下载链接(中英文档)
二、内容前述

此示例展示如何配置渲染管线并将其用作渲染通道的一部分,以将简单的2D彩色三角形绘制到视图中。该示例为每个顶点提供位置和颜色,渲染管线使用该数据渲染三角形,在为三角形顶点指定的颜色之间插入颜色值。效果如下在这里插入图片描述

三、了解Metal渲染管线

渲染管线处理绘图命令并将数据写入渲染通道的目标。渲染管线有许多阶段,一些使用着色器编程,另一些具有固定或可配置的行为。此示例侧重于管道的三个主要阶段:顶点阶段、光栅化阶段和片段阶段。顶点阶段和片段阶段是可编程的,可以使用Metal着色语言(MSL)编写函数。光栅化阶段具有固定的行为,如下图所示:
在这里插入图片描述
渲染从绘图命令开始,其中包括顶点数和要渲染的图元类型。 例如,这是此示例中的绘图命令:

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

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

四、渲染管道处理数据

顶点函数为单个顶点生成数据,片段函数为单个片段生成数据,可以自定义它们的工作方式。在配置管道阶段时要牢记目标,想象希望管道生成什么以及它如何生成这些结果。决定将哪些数据传递到渲染管道以及将哪些数据传递到管道的后续阶段。通常在三个地方执行此操作:

管道的输入,由应用程序提供并传递到顶点阶段。
顶点阶段的输出,传递到光栅化阶段。
片段阶段的输入,由应用程序提供或由光栅化阶段生成。

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

声明一个Vertex 结构,使用SIMD向量类型来保存位置和颜色数据。要共享结构在内存中的布局方式的单一定义,请在公共标头中声明结构并将其导入Metal着色器和应用程序。

//输入的顶点数据信息
typedef struct {
    //向量:顶点位置
    vector_float2 position;
    //向量:颜色值
    vector_float4 color;
} Vertex;

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

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

//三个顶点位置和颜色值
static const Vertex vertices[] = {
    //位置       //颜色值
    {{250,-250},{1.0,0.0,0.0,1.0}},
    {{-250,-250},{0.0,1.0,0.0,1.0}},
    {{0,250},   {0.0,0.0,1.0,1.0}},
};

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

struct RasterizerData{
    //[[position]]:在顶点着色函数中,表示当前的顶点信息,类型是float4、
    //还可以表示描述了片元的窗口的相对坐标(x,y,z,1/w),
    //即该像素点在屏幕上的位置信息
    //----[[position]]----请在着色函数编程指南文档查看解释
    float4 position [[position]];
    //颜色值
    float4 color;
};

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

因为Metal需要知道光栅化数据中的哪个字段提供位置数据,而Metal不会对结构中的字段强制执行任何特定的命名约定,所以使用[[position]]属性限定符注释位置字段以声明该字段保存输出位置。

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

五、定义顶点着色函数

声明顶点函数,包括它的输入参数和它输出的数据。就像使用kernel关键字声明计算函数一样,使用vertex关键字声明顶点函数。(kernel:可查看这篇文章https://blog.csdn.net/qqwyuli/article/details/130785820

//[[vertex_id]] :顶点id标识符,并不由开发者传递
//属性修饰符"[[buffer(index)]]" 为着色函数参数设定了缓存的位置
vertex RasterizerData vertexShader(uint vertexID [[vertex_id]],
                                   constant Vertex* vertices [[buffer(VertexInputIndexVertices)]],
                                   constant vector_uint2 *viewPortSizePointer [[buffer(VertexInputIndexViewportSize)]])

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

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

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

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

typedef enum AAPLVertexInputIndex
{
    VertexInputIndexVertices     = 0,
    VertexInputIndexViewportSize = 1,
} AAPLVertexInputIndex;

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

六、编写顶点函数

顶点函数必须生成输出结构的两个字段。使用vertexID参数索引顶点数组并读取顶点的输入数据。此外,检索视口尺寸

float2 pixelSpaceXY = vertices[vertexID].position.xy;
    vector_float2 viewPortSize = vector_float2(*viewPortSizePointer);

顶点函数必须提供裁剪空间坐标中的位置数据,裁剪空间坐标是使用四维齐次向量(x,y,z,w)指定的3D点。光栅化阶段采用输出位置并将x、y和z坐标除以w,以在标准化设备坐标中生成3D点。规范化设备坐标与视口大小无关。
在这里插入图片描述
规范化设备坐标使用左手坐标系并映射到视口中的位置。基元被裁剪到这个坐标系中的一个盒子,然后光栅化。裁剪框的左下角位于(x,y)坐标(-1.0,-1.0),右上角位于 (1.0,1.0)。正z值指向远离相机的方向(进入屏幕)。z坐标的可见部分介于0.0(近裁剪平面)和1.0(远裁剪平面)之间。

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

out.position = vector_float4(0,0,0,1.0);
out.position.xy = pixelSpaceXY / (viewPortSize / 2);

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

out.color = vertices[vertexID].color;

完整的顶点着色函数

//[[vertex_id]] :顶点id标识符,并不由开发者传递
//属性修饰符"[[buffer(index)]]" 为着色函数参数设定了缓存的位置
vertex RasterizerData vertexShader(uint vertexID [[vertex_id]],
                                   constant Vertex* vertices [[buffer(VertexInputIndexVertices)]],
                                   constant vector_uint2 *viewPortSizePointer [[buffer(VertexInputIndexViewportSize)]]){
    RasterizerData out;
    
    float2 pixelSpaceXY = vertices[vertexID].position.xy;
    vector_float2 viewPortSize = vector_float2(*viewPortSizePointer);
    
    out.position = vector_float4(0,0,0,1.0);
    out.position.xy = pixelSpaceXY / (viewPortSize / 2);
    
    out.color = vertices[vertexID].color;
    
    return out;
};
七、编写片段着色函数(片元着色函数)

片段是对呈现目标的可能更改。光栅化器确定渲染目标的哪些像素被基元覆盖。仅渲染像素中心位于三角形内的片段。
在这里插入图片描述
片段函数处理来自单个位置的光栅化器的传入信息,并计算每个渲染目标的输出值。这些片段值由管道中的后续阶段处理,最终写入渲染目标。
注意:
片段被称为可能更改的原因是片段阶段之后的管道阶段可以配置为拒绝某些片段或更改写入渲染目标的内容。在此示例中,片段阶段计算的所有值都按原样写入渲染目标。

此示例中的片段着色器接收与顶点着色器输出中声明的相同参数。使用fragment关键字声明片段函数。它采用单个参数,与顶点阶段提供的RasterizerData结构相同。添加 [[stage_in]]属性限定符以指示此参数由光栅器生成。
在这里插入图片描述
返回插值颜色作为函数的输出

return in.color;

完整的片元着色函数

fragment float4 fragmentShader(RasterizerData in [[stage_in]]){
    return in.color;
};
八、创建渲染Pipeline State对象

顶点函数和片元函数已经完成,可以创建一个使用它们的渲染管道。首先,获取默认库并为每个函数获取一个MTLFunction对象。

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

接下来,创建一个 MTLRenderPipelineState 对象。渲染管道有更多阶段需要配置,可以使用 MTLRenderPipelineDescriptor 来配置管道。

MTLRenderPipelineDescriptor *descriptor = [[MTLRenderPipelineDescriptor alloc] init];
descriptor.label = @"Simple pipeline";
descriptor.vertexFunction = vertexFunction;
descriptor.fragmentFunction = fragmentFunction;
descriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat;
    
_pipelineState = [_device newRenderPipelineStateWithDescriptor:descriptor error:&error];

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

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

九、设置视口

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

[commandEncoder setViewport:(MTLViewport){0.0,0.0,_viewPortSize.x,_viewPortSize.y,0.0,1.0}];
十、设置渲染Pipeline State

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

[commandEncoder setRenderPipelineState:_pipelineState];
十一、将参数数据发送到顶点函数

通常使用缓冲区(MTLBuffer)将数据传递给着色器。然而,当只需要将少量数据传递给顶点函数时,就像这里的情况一样,将数据直接复制到命令缓冲区中。

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

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

//设置顶点着色器参数
[commandEncoder setVertexBytes:vertices length:sizeof(vertices) atIndex:VertexInputIndexVertices];
[commandEncoder setVertexBytes:&_viewPortSize length:sizeof(_viewPortSize) atIndex:VertexInputIndexViewportSize];
十二、对绘图命令进行编码

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

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

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

十三、尝试颜色插值

在此示例中,颜色值被插值到整个三角形中 这通常是想要的效果,但有时希望一个值由一个顶点生成并在整个图元中保持不变。在顶点函数的输出上指定平面属性限定符来执行此操作。现在试试这个,在示例项目中找到RasterizerData的定义,并将[[flat]]限定符添加到其颜色字段。

float4 color [[flat]];

再次运行示例。渲染管线在整个三角形上统一使用第一个顶点(称为激发顶点)的颜色值,并忽略其他两个顶点的颜色。可以混合使用平面着色和插值,只需在顶点函数的输出中添加或省略平面限定符即可。金属着色语言规范定义了其他属性限定符,也可以使用它们来修改光栅化行为。

十四、完整代码

例子:github链接:https://github.com/dennie-lee/MetalDrawTriangleDemo

Metal 一、初识 Metal 及其语言规范 游戏开发者的新的技术平台,该技术能够为3D图像提高 10 倍的渲染性能。苹果2018年推出 Metal,在此之前一直使用OpenGL ES。1图形渲染;2高并发计算。苹果文档给出的 Metal 优化:CPU 低消耗、GPU 高利用、连续处理器并行、有效的资源利用Metal 官方文档。 阅读详情

相关推荐

Metal语法

Metal Syntax Metal语法从C++14派生而来 支持其中的大部分特效(部分特性如lambda等不支持) 同时在C++的基础上增加了部分的矩阵等GPU编程常用特性的支持。 Vector向量类型 基础类型+数字后缀: 1. float4 四维向量 假设用float4向量来表示rgba的一个像素 float4 pixel(1.0,2.0,3.0,4.0...

sliver.chen的博客 1508

MetalMetal与金属材质渲染_2024-07-20_21-31-46.Tex

通过上述示例,我们可以看到在Metal中如何使用纹理映射来增加模型的细节和真实感,如何应用金属度和粗糙度纹理来渲染金属材质,以及如何优化纹理采样以提高渲染性能。这些技术是实现高质量3D图形渲染的关键,对于游戏开发和虚拟现实应用尤为重要。

827

Metal 入门级02:绘制三角形

本案例的目的在于理解Metal中使用着色器绘制三角形的流程 整体效果图如下 整体的流程图如下 与Metal 入门级01:颜色的渲染加载相比,viewDidLoad函数基本没变化,主要变化的是initWithMetalKitView函数和drawInMTKView代理方法 initWithMetalKitView函数:增加了metal着色器文件初始化的准备工作 drawInMTKView代理方法:增加了metal绘制三角形的流程 准备工作 创建metal文件 command + N -->

lin1109221208的博客 1055

音视频- iOS使用metal渲染图像(一)

本文主要总结一下Metal的基本使用,用来渲染拍照的到的图像,其中涉及到的有UIKit中的MTKView,Metal中负责渲染的几个类,使用MSL(Metal Shading Language)编写着色器,最终将图片渲染出来。这一篇先总结一下Metal的工作流程。

蜗牛想飞翔 1858

Metal 编程指南

文章中的代码都是以 Objective-C, C++ 语言编写,在代码中也加有详细的注释,希望读者能通过阅读本书快速掌握 Metal 的使用。

无字教科书的博客 2311

Metal学习:用Metal画一个三角形

Metal基本概念 Metal是Apple提出的新一代的Graphics API架构,用来代替OpenGL。从2014年Metal提出开始,到2019年Apple正式废弃OpenGL/OpenGL ES的支持,Metal发展是非常快的,基本上以后在ios/Mac的开发中,metal是底层图形开发的唯一的选择。 基于以下的原因,OpenGL需要被Metal替代: OpenGL是25年前的标准,而现...

wwchen61的博客 1024

Metal渲染绘制三角形

效果展示

╰つ栺尖篴夢ゞ 607

Metal入门,使用Metal绘制3D图形

使用Metal绘制3D图形

Unlimitedz的博客 933

Metal入门学习绘制纹理图片

Metal渲染图片

掰湘果的博客 1341

Metal入门,使用Metal绘制三角形

使用 Metal 绘制三角形的完整流程可分为七个步骤:首先,创建 MTKView 作为绘图视图;其次,准备顶点数据并创建顶点缓冲区;接着,加载 Vertex Shader 和 Fragment Shader;然后,创建渲染管线状态对象;之后,创建命令队列;在每一帧中,通过命令编码器进行渲染,提交命令并展示结果;最后,可设置刷新参数以优化性能。整个过程涵盖了从视图创建到最终渲染的完整步骤,是 Metal 渲染任务的基础路径。

Unlimitedz的博客 512

Metal开发入门(二)绘制三角形

GPU绘制的时候,只会去绘制线和三角形绘制线段比较好理解,但是为啥是三角形,因为,三角形是在二维平面中利用最少的点就能绘制出来的图形。而且,它可以很容易的切割成两个。本文就是使用Metal绘制一个三角形 Metal绘制的过程 大体上分为两个部分,一个部分是在整体初始化的过程中,初始化的过程主要是编译metal文件,取得三角形定点和整体的着色函数shader,着色函数和颜色格式组成了一个叫做piplineDescriptor的对象,通过piplineDescriptor即可生成一个piplineSt.

Niap的博客 581

Metal(一) 三角形绘制

1.Metal 简介 Metal 是针对 iPhone 和 iPad 中 GPU 编程的高度优化的框架。其名字来源是因为 Metal 是 iOS 平台中最底层的图形框架 (意指 “最接近硬件”)。 优点: cpu 使用率低; Metal 将苹果的GPU 发挥最⼤性能; 最⼤限度CPU/GPU 并发性; 方便管理我们资源 2. 函数解析 2.1 //每当视图改变方向或调整大小时调用 (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:

正在注销账号的博客 548

【一步步学Metal图形引擎1】-《绘制第一个三角形

教程 1 绘制第一个三角形 教程源码下载地址: https://github.com/jiangxh1992/MetalTutorialDemos CSDN完整版专栏: https://blog.csdn.net/cordova/category_9734156.html 一、知识点 Metal渲染管线 顶点缓冲 Metal着色器(顶点着色器和片段着色器) 顶点坐标系 Metal Shading...

Mr_厚厚的博客 3885

iOS第九十九篇:Metal图形编程入门

Metal是Apple推出的高性能图形和计算API,为iOS、macOS提供接近硬件的底层访问能力,相比OpenGL ES有显著性能提升。

I_did_it的博客 842

iOS开发Metal之使用渲染管道(Pipeline)来渲染图元(Primitives):渲染一个简单的二维三角形

预览 在使用Metal来画一个视图内容的文章中,你知道了如何设置一个MTKView对象并且知道了如何通过传递渲染参数来改变视图的内容。那个示例演示了将视图内容抹除换成了一个背景颜色。这个示例将向你展示如何配置一个渲染管道(Pipeline)并将它作为一部分的渲染传递参数来在视图上画一个简单的二维彩色的三角形。这个示例为每个顶点(Vertex)提供了位置和颜色,并且渲染管道使用了那些数据来渲染三角...

johndevelop的专栏 1350

Metal入门(使用Metal画一个三角形

Metal 入门

Thinker361的专栏 2736

Metal开发入门(一)Hello Metal

环境:macOS 11 iOS14 swift4 xcode12 项目准备 新建一个iOS项目,值得注意的是:life Cycle请选择 UIKit App Delegate,后续因为需要从UIView中集成,项目还需要做一些变化。 删除 AppDelegate.swift中关于SceneDeleagete的相关函数,添加一个成员变量window类型为UIWindow。删除info.plist中关于Scene Manifest的配置,添加一个storyboard。命名为main.storyboard,.

Niap的博客 1787

Metal 框架之渲染管线渲染图元

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

SkyNullCode的博客 767
上一篇: Metal入门学习:GPU并行计算大数组相加
下一篇: Metal入门学习:绘制纹理图片
qqwyuli
博客等级 码龄9年 25粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值