一、编程指南PDF下载链接(中英文档)
  • 1、Metal编程指南PDF链接
    https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 编程指南.pdf

  • 2、Metal着色语言(Metal Shader Language:简称MSL)编程指南PDF链接
    https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 着色语言指南.pdf

二、渲染流程图

三、内容前述

此示例展示如何配置渲染管线并将其用作渲染通道的一部分,以将简单的图片渲染到视图中和立体锥体中,效果图如下:

四、Metal渲染管线

渲染管线处理绘图命令并将数据写入渲染通道的目标。此示例侧重于管道的三个主要阶段:顶点阶段、光栅化阶段和片段阶段。
渲染从绘图命令开始,其中包括顶点数和要渲染的图元类型。例如,这是此示例中的绘图命令:(参数数据解析请看下一步)

commandEncoder.drawIndexedPrimitives(type: .triangle, indexCount: 6,indexType: .uint32, indexBuffer: vertexIndexs,indexBufferOffset: 0)
五、Metal渲染管道处理数据

这里使用的是顶点索引的方式关联顶点数据。例如顶点数据:

var vertexs:[Float] = [//顶点坐标 //纹理坐标 //颜色值1, -1,  1,1,  1.0,0.0,0.0,1.0,//长方形的右下角-1,-1,  0,1,  1.0,0.0,0.0,1.0,//长方形的左下角-1, 1,  0,0,  1.0,0.0,0.0,1.0,//长方形的左上角1,  1,  1,0,  1.0,0.0,0.0,1.0,//长方形的右上角
]

顶点参考坐标系如图一,因为只是把图片渲染到视图上,相当于是2D坐标(二维),只看最前面那个面即可;纹理坐标参考图二

颜色值有四个通道RGBA:红色、绿色、蓝色、透明度,所以是四个数据。

顶点索引值数据,索引0、1、2对应的顶点以顺时针的顺序构成一个三角形;0、2、3对应的顶点以顺时针的顺序构成一个三角形,两个三角形组合起来渲染构成长方形(Metal默认是以顺时针的方式渲染,也可以设置成逆时针,此处使用默认的方式):

let indexs : [Int32] = [0,1,2,0,2,3]
vertexIndexs = device?.makeBuffer(bytes: indexs,length: MemoryLayout<Int32>.size * 6,options: .storageModeShared)

MTLPrimitiveType绘制类型有如下五种:(第四步的参数1)

@available(iOS 8.0, *)
public enum MTLPrimitiveType : UInt, @unchecked Sendable { case point = 0case line = 1case lineStrip = 2case triangle = 3case triangleStrip = 4
}

绘制方式如下图所示:

在此示例中,管道的输入数据是顶点的位置、纹理坐标位置和自定义的颜色(自定义颜色:可堆叠在图片上)
声明一个VertexInput结构,保存顶点坐标、纹理坐标和颜色数据

typedef struct {//顶点坐标float2 position;//纹理坐标float2 textureCoordinate;//颜色float4 color;
} VertexInput;

顶点阶段为顶点生成数据,需要提供变换后的位置和颜色。声明一个包含顶点位置、纹理位置和颜色值的RasterizerData结构

typedef struct {//顶点坐标float4 position [[position]];//纹理坐标float2 textureCoordinate;//颜色float4 color;
} RasterizerData;

位置声明为float4意味着它包含四个32位浮点值(将保存x、y坐标、z左边和w值);纹理位置声明为float2意味着它包含两个32位浮点值(将保存x和y坐标);颜色使用float4存储,因为它们有四个通道:红色、绿色、蓝色和alpha。

因为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 VertexInput *vertexs [[buffer(0)]]){RasterizerData out;out.position = vector_float4(0.0,0.0,0.0,1.0);out.position.xy = vertexs[vertexId].position;//纹理坐标out.textureCoordinate = vertexs[vertexId].textureCoordinate;//自定义传过来的颜色out.color = vertexs[vertexId].color;return out;
};

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

第二个参数 vertices 是一个包含顶点数据的数组,使用之前定义的Vertex结构
第二个具有[[buffer(n)]]属性限定符。默认情况下,Metal会自动为每个参数分配参数表中的槽。当将[[buffer(n)]]限定符添加到缓冲区参数时,明确地告诉Metal要使用哪个插槽。显式声明插槽可以更轻松地修改着色器,而无需更改应用程序代码.

对命令的参数进行编码时,设置顶点函数的参数

commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
七、计算纹理位置和编写片段着色函数(片元着色函数)

对纹理进行采样以从纹理中的某个位置计算颜色。要对纹理数据进行采样,片段函数需要纹理坐标和对要采样的纹理的引用。除了从光栅化器阶段传入的参数外,还传入一个带有 texture2d 类型和 [[texture(index)]] 属性限定符的colorTexture参数。此参数是对要采样的 MTLTexture 对象的引用。

fragment float4 fragmentShader(RasterizerData input [[stage_in]],texture2d<float> colorTexture [[texture(0)]])

使用内置纹理 sample() 函数对纹素数据进行采样。sample()函数有两个参数:一个采样器 (textureSampler),它描述了希望如何采样纹理,以及纹理坐标(input.textureCoordinate),它描述了纹理中要采样的位置。sample()函数从纹理中获取一个或多个像素并返回从这些像素计算出的颜色。

当渲染到的区域与纹理的大小不同时,采样器可以使用不同的算法来准确计算sample()函数应该返回的纹素颜色。设置mag_filter模式来指定当面积大于纹理大小时采样器应该如何计算返回的颜色,设置min_filter模式来指定当面积小于纹理大小时采样器应该如何计算返回颜色质地。为两个过滤器设置线性模式会使采样器对给定纹理坐标周围像素的颜色进行平均,从而产生更平滑的输出图像。

constexpr sampler textureSampler (mag_filter::linear,min_filter::linear);
//float4 color = colorTexture.sample(textureSampler, input.textureCoordinate) * input.color;
float4 color = colorTexture.sample(textureSampler, input.textureCoordinate);

对命令的参数进行编码时,设置片段函数的纹理参数。此示例使用索引0来识别Metal着色语言代码中的纹理。

commandEncoder.setFragmentTexture(texture, index: 0)

完整的片段函数

fragment float4 fragmentShader(RasterizerData input [[stage_in]],texture2d<float> colorTexture [[texture(0)]]){constexpr sampler textureSampler (mag_filter::linear,min_filter::linear);//float4 color = colorTexture.sample(textureSampler, input.textureCoordinate) * input.color;float4 color = colorTexture.sample(textureSampler, input.textureCoordinate);return color;
};
八、创建渲染Pipeline State对象

顶点函数和片元函数已经完成,可以创建一个使用它们的渲染管道。首先,获取默认库并为每个函数获取一个MTLFunction对象和创建一个 MTLRenderPipelineState 对象。渲染管道有更多阶段需要配置,可以使用 MTLRenderPipelineDescriptor 来配置管道。

var library = device?.makeDefaultLibrary()
if let url = Bundle.main.url(forResource: "TextureShaders", withExtension: "metal"){library = try? device?.makeLibrary(URL: url)
}
let descriptor = MTLRenderPipelineDescriptor()
descriptor.vertexFunction = library?.makeFunction(name: "vertexShader")
descriptor.fragmentFunction = library?.makeFunction(name: "fragmentShader")
descriptor.colorAttachments[0].pixelFormat = viewColorPixelformat
pipelineState = try? device?.makeRenderPipelineState(descriptor: descriptor)
九、设置视口

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

commandEncoder.setViewport(MTLViewport(originX: 0, originY: 0,width: viewSize.width, height: viewSize.height,znear: -1.0, zfar: 1.0))
十、设置渲染Pipeline State

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

commandEncoder.setRenderPipelineState(pipelineState)
十一、将参数数据发送到顶点函数

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

commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
十二、对绘图命令进行编码

指定图元的种类、起始索引和索引数。渲染纹理图片时,将使用vertexID参数的值和顶点索引调用顶点函数。

commandEncoder.drawIndexedPrimitives(type: .triangle, indexCount: 6,indexType: .uint32, indexBuffer: vertexIndexs,indexBufferOffset: 0)

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

十三、完整代码

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

Metal入门学习:绘制纹理图片相关推荐

  1. Matlab 图像处理入门学习笔记—1.图片格式转换

    前言:作为一个编程思维极弱的小白,就算是matlab的学习也稍有困难.但也想学好自己选择的路.从最简单的开始,记录学习,也算监督自己. 今天的笔记是图片格式转换,废话不说,直接上代码: a=512; ...

  2. Metal之加载TGA与PNG/JPEG纹理图片

    TGA纹理 ① 效果展示 ② 环境准备 视图控制器类:在 viewDidLoad 函数中创建 MTKView 对象.自定义 render 对象,并设置 view 的代理为 render,其流程请参考: ...

  3. OpenGL入门学习[二] 绘制简单的几何图形

    OpenGL入门学习[二] 本次课程所要讲的是绘制简单的几何图形,在实际绘制之前,让我们先熟悉一些概念. 一.点.直线和多边形 我们知道数学(具体的说,是几何学)中有点.直线和多边形的概念,但这些概念 ...

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

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

  5. AD入门学习—原理图的绘制3

    目录 2.4 CAN&24C02及DS18B20温度传感单元的绘制 2.5 USB单元的绘制 2.6 SD卡及TFT单元的绘制 2.7 NRF24L01单元的绘制 2.8 COM口及PS/2接 ...

  6. PCB入门学习—原理图的绘制2

    目录 2.2 TEA5767音频模块的绘制 2.3 ENC28J60以太网模块的绘制 学习目录: 2.2 TEA5767音频模块的绘制 首先有个问题:为什么这个电容放在原理图上怎么移动也对不齐?? 解 ...

  7. 学习opengl之为立方体六个面贴上不同的纹理图片

    效果 相比于立方体自转,这一博客增添了 为立方体的不同面添加不同纹理图片的操作,不再是六个面单一纹理了. 工具 glad.glfw(这个无所谓).glm(这个也主要是为了和glad配合,使用eigen ...

  8. 最全面的openGL 入门学习

    自己在找openGL学习资料的时候,找到此篇openGL入门学习(虽然不是移动开发,但给我提供了非常好的思路),所以转一下让更多人知道,本文来自http://www.cppblog.com/doing ...

  9. OpenGL编程入门学习

    OpenGL编程入门学习  非常详细的教程,很适合初学者 本文转自:http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html === ...

最新文章

  1. ReentrantLock 实现原理
  2. 详解Struts2 Action名称的搜索顺序
  3. 【Android工具】安卓手机轻松获取硬件数据和状态信息
  4. 全国计算机等级考试题库二级C操作题100套(第04套)
  5. C# WPF DataGrid获取单元格并改变背景色
  6. self 实例对象-代码详细解释
  7. vSphereClient向ESXi主机分配许可证
  8. php zip class,DedeCMS 自带压缩工具 zip.class.php 的用法
  9. 【C++实现】HeadFirst策略模式设计模式
  10. 【软考高级:信息系统项目管理师】【信息项目十大管理】第二天:项目立项管理
  11. JAVA在线考试系统毕业设计 开题报告
  12. android 玻璃背景,Android 弹窗毛玻璃背景实践
  13. 计算机桌面分页,你的电脑桌面还会一团糟吗?这款软件可以帮你整理文件
  14. EXTJS资源库管理平台 2013.5.26-在线制作头像
  15. 猿辅导、掌门教育悄然转身,发力素质教育
  16. 工具类源码 IP辅助类 验证IP地址或地址段是否有效 验证指定的IP是否有效 即验证IP是否属于某个IP段
  17. Effective Java---No.7 避免使用终结方法(稀里糊涂)
  18. mac安装python3并配置,Mac安装python3和环境配置
  19. 数据智能公司袋鼠云完成 6000 万元 A 轮融资
  20. Houdini pdg arnold渲染报错

热门文章

  1. 微视linux swapper进程
  2. LeetCode刷题笔记第171题: Excel 表列序号
  3. Linux中proc浅析
  4. Modbus通讯协议的C语言实现
  5. unity快捷键整理搜集
  6. 最优化——粒子群算法(PSO)
  7. 三星c9000刷android7.0,三星C9 Pro刷机教程_三星C9000线刷官方系统rom包_可救砖
  8. 读书笔记——读《不断进步的电容器-片状陶瓷电容篇》
  9. 51单片机——IO口
  10. Android 10.0修改RAM(运行内存)的值