前言
此系列文章仅在记录和分享本人的Metal学习过程,如有错误欢迎指正。
本章代码
iOS 13.5
Xcode 11.5
swift 5
正文
Metal
的真正目的是在移动设备上实现前所未有的画面逼真度,例如Epic Games Zen Garden,iPad上以1440×1080分辨率、4xMSAA抗锯齿运行,可以同时展示3500个活动的蝴蝶,平均每帧有4000个绘制调用,这可是OpenGL ES做不到的;目前仅支持Imagination PowerVR Rogue架构,也就是A7、A8、A8X,或者说iPhone 5S、iPad Air及其后的设备
在阅读本系列教程之前,已经默认你有基本的
iOS
、OS X
开发经验,但如果你没有计算机图形学的相关知识则可能比较难以理解,建议学习GAMES101或其他图形学相关知识的教程
本节目标:了解基本的Metal API
并绘制一个简单的2D三角形
-
p1 获取系统默认的GPU,在
iOS
设备上只有一个
device = MTLCreateSystemDefaultDevice()
-
p2 创建用来显示的
Layer
metalLayer = CAMetalLayer()
metalLayer.device = device
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.frame = view.layer.frame
-
p3 顶点缓冲对象
在Metal
坐标系中,屏幕范围是[-1, 1]
,则屏幕的中心点为坐标原点(0, 0),因为我们绘制的图形是2D三角形,所以不考虑z
轴
则三角形顶点坐标
let vertexData: [Float] = [-0.5, 0.5, 0.0,
0.0, -0.5, 0.0,
-1.0, -0.5, 0.0]
由顶点坐标可以看出,三角形是处于屏幕左侧位置,宽度和高度均为屏幕的一半
接下来将 CPU创建的顶点缓冲到GPU中
vertexBuffer = device?.makeBuffer(bytes: vertexData,
length: vertexData.count * MemoryLayout<Float>.size,
options: MTLResourceOptions(rawValue: 0))
-
p4 顶点着色器
Vertex
在处理完顶点数据后,需要对顶点进行着色处理,这里需要使用到Metal
的着色语言MSL (Metal shading language)
,网上教程很多,我这里就不详细介绍
创建一个.metal
文件编写MSL
代码
Xcode -> File -> New -> File -> Source -> Metal File
编写顶点着色器代码
vertex float4 vertex_shader(constant packed_float3 *vertex_array[[buffer(0)]],
unsigned int vid[[vertex_id]])
{
return float4(vertex_array[vid], 1.0f);
}
这里返回的是(x, y, z, w)齐次坐标, 因为
w = 1.0f
,所以转化为欧式空间的坐标系中等价于 (x, y, z)
-
p5 片段着色器
Fragment
fragment float4 fragment_shader()
{
return float4(0.1f, 0.3f, 0.6f, 1.0f);
}
这里返回的是颜色
RGBA
-
p6 渲染管线
首先我们需要调用.metal
文件中的着色器方法
let defaultLibrary = device?.makeDefaultLibrary()
let fragmentFunc = defaultLibrary?.makeFunction(name: "fragment_shader")
let vertexFunc = defaultLibrary?.makeFunction(name: "vertex_shader")
在渲染管线中设置这些方法以及像素格式等...
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.fragmentFunction = fragmentFunc
pipelineStateDescriptor.vertexFunction = vertexFunc
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineState = try? device?.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
- p7 MTLCommandQueue
commandQueue = device?.makeCommandQueue()
绘制
let drawable = metalLayer.nextDrawable()
let rpd = MTLRenderPassDescriptor()
rpd.colorAttachments[0].texture = drawable?.texture
rpd.colorAttachments[0].loadAction = .clear
// 背景颜色这里写死
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.8, 0.5, 1.0)
commandBuffer = commandQueue?.makeCommandBuffer()
// 创建一个渲染命令编码器(Render Command Encoder)
// 创建一个command encoder,并指定你之前创建的pipeline和顶点
let re: MTLRenderCommandEncoder? = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
re?.setRenderPipelineState(pipelineState!)
re?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
re?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
re?.endEncoding()
commandBuffer?.present(drawable!)
commandBuffer?.commit()
运行代码(必须真机运行,模拟器没有GPU),不报错的话应该可以看到屏幕左侧的三角形了