写在前面:
对Metal技术感兴趣的同学,可以关注我的专题:Metal专辑
也可以关注我个人的简书账号:张芳涛
所有的代码存储的Github地址是:Metal
OSX平台相关技术实现
在本系列的第一部分中,我们介绍了MetalKit
框架。 让我们重新使用第1部分中的项目,并从我们离开的地方拿起。 如果再看一下render()
函数,它看起来像这样:
func render() {
let device = MTLCreateSystemDefaultDevice()!
self.device = device
let rpd = MTLRenderPassDescriptor()
let bleen = MTLClearColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
rpd.colorAttachments[0].texture = currentDrawable!.texture
rpd.colorAttachments[0].clearColor = bleen
rpd.colorAttachments[0].loadAction = .Clear
let commandQueue = device.newCommandQueue()
let commandBuffer = commandQueue.commandBuffer()
let encoder = commandBuffer.renderCommandEncoderWithDescriptor(rpd)
encoder.endEncoding()
commandBuffer.presentDrawable(currentDrawable!)
commandBuffer.commit()
}
让我们稍微改进一下这个代码。 首先,由于我们的类子类MTKView
,它已经有了自己的设备,因此不需要声明另一个设备。 这可以让我们将前两行减少到一个:
device = MTLCreateSystemDefaultDevice()
上一篇博客我们说过,我们应该确保currentDrawable
和currentRenderPassDescriptor
不是零,否则应用程序会崩溃。 为了简单起见,我们在第一部分中没有这样做,现在就让我们来做。 这也将帮助我们摆脱更多行代码。 该函数的最终版本如下所示:
func render() {
device = MTLCreateSystemDefaultDevice()
if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let command_buffer = device!.newCommandQueue().commandBuffer()
let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
command_encoder.endEncoding()
command_buffer.presentDrawable(drawable)
command_buffer.commit()
}
}
你可能想知道colorAttachments [0]
是什么意思。 为了设置rendering pipeline state
(渲染管道状态),Metal
框架提供了3种我们可以写入的附件类型:
colorAttachments
depthAttachmentPixelFormat
stencilAttachmentPixelFormat
我们只对现在存储颜色数据感兴趣,colorAttachments
是一组纹理,用于保存绘图结果并将其显示在屏幕上。 我们目前只有一个这样的纹理 - 数组的第一个元素(在索引0
处)。 好的,现在是运行应用程序的好时机,并且确保您仍然看到上次看到的相同颜色的背景。 好多了! 只需9
行代码,我们就可以在我们的GPU
上运行安全的Metal
代码。
接下来,让我们深入一个新的Metal
主题 - 在屏幕上绘制几何图形。 所有关于OpenGL
的图形教程都以Hello,Triangle
类型的程序开始,因为三角形是可以在屏幕上绘制的几何形状的最简单形式。 它是一个2D graphics
基本元素,图形世界中的所有其他对象都由三角形组成,因此这是一个很好的开始。 想象一下屏幕坐标系统的轴线穿过屏幕的中心,坐标系(0,0)
。 屏幕边缘的值分别为-1
和1
。 让我们创建一个浮点数组和一个缓冲区来保存三角形的顶点值。 初始化device
后立即插入这些行:
let vertex_data:[Float] = [-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0]
let data_size = vertex_data.count * sizeof(Float)
let vertex_buffer = device!.newBufferWithBytes(vertex_data, length: data_size, options: [])
上面的顶点按照左下角,右下角和顶部中心的顺序排列。 我们注意到每个顶点使用4
个浮点数作为其坐标。 前两个是x
和y
轴。 我们这次没有使用的是:第三个是depth(Z轴)
,第四个是W coordinate
(W坐标),使得我们的坐标是homogeneous
(均匀)的。 我们将在下一篇博客中谈论它们。 然后我们计算这个数组的大小,简单地说就是12
个浮点数的大小,最后我们根据数组和它的大小创建缓冲区。 现在我们已经存储了顶点,我们需要一种方法将它们发送到GPU
,以便它们可以显示在屏幕上。 让我们看看有助于在屏幕上绘制图形的整个过程(pipeline
):
到目前为止,我们已经完成了第一阶段,存储顶点。 您注意到下一个阶段需要我们有一个名为shader
(着色器)的新构造。 shader
(着色器)是允许程序员用自定义函数干涉图形管线的地方。 Metal
提供了几种着色器,但是,今天我们只看其中的两种:负责点的location
(位置)的vertex shader
(顶点着色器)和负责点的color
(颜色)的fragment shader
(片段着色器)。
Metal
框架提供了一个函数,我们可以调用该函数来创建一个函数库(着色器),所以我们来创建它:
let library = device!.newDefaultLibrary()!
let vertex_func = library.newFunctionWithName("vertex_func")
let frag_func = library.newFunctionWithName("fragment_func")
我们创建了两个新的Functions
(函数),并将它们指向它们相应的着色器(我们将在后面创建它们)。 下一步是创建一个Render Pipeline Descriptor
(渲染管线描述符),它需要知道我们的着色器:
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm
您可能想知道.BGRA8Unorm
的含义。 此设置配置像素格式,以便通过渲染管线的所有内容都符合相同的颜色分量顺序(在本例中为Blue
(蓝色),Green
(绿色),Red
(红色),Alpha
(阿尔法))以及尺寸(在这种情况下,8-bit
(8位)颜色值变为 从0
到255
)。 最后一步是根据上述descriptor
(描述符)创建Render Pipeline State
(渲染管线状态):
let rps = try! device!.newRenderPipelineStateWithDescriptor(rpld)
最后,我们只需要让命令encoder
(编码器)知道我们的三角形,因此在创建encoder
(编码器)后立即添加以下几行:
command_encoder.setRenderPipelineState(rps)
command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
现在让我们回到我们在创建Library
(库)时承诺创建的两个shaders
(着色器)。 为此,我们需要在Xcode
中创建一个新文件。 选择Metal File
类型,将其命名为Shaders.metal
或类似的东西,然后单击Create
(创建)。 您将立即注意到代码与Swift
不太相似,这是因为Metal shading language
(Metal着色语言)基于C ++
。 让我们添加下面的代码:
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]]) {
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]) {
return float4(0.7, 1, 1, 1);
}
代码非常简单。 我们首先创建一个名为Vertex
的结构体,它只有一个成员 - 一个位置数组数组。 我们注意到数组是float4
,它在着色语言中与我们前面创建的顶点相同,每个顶点使用4
个浮点数。 我们在下一次留下[[...]]
语法的解释。 然后我们有返回当前顶点location
(位置)的vertex_func
着色器和返回当前顶点颜色的fragment_func
着色器。 我们对特定的颜色值进行了硬编码,但是我们可以将color
(颜色)结构成员添加到Vertex
(顶点)并为每个顶点分别设置颜色。
如果你运行该应用程序,你应该看到一个这样的三角形:
代码地址:Ch03-OSX
iOS平台相关技术实现
Shader.metal相关代码:
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],uint vid [[vertex_id]]){
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]){
return float4(0.7,1,1,1);
}
MetalView.swift相关代码实现:
import MetalKit
class MetalView: MTKView {
var commandQueue: MTLCommandQueue?
var rps: MTLRenderPipelineState?
var vertexData: [Float]?
var vertexBuffer: MTLBuffer?
required init(coder: NSCoder) {
super.init(coder: coder)
render()
}
func render(){
device = MTLCreateSystemDefaultDevice()
commandQueue = device?.makeCommandQueue()
vertexData = [-1.0,-1.0,0.0,1.0,
1.0,-1.0,0.0,1.0,
0.0,1.0,0.0,1.0]
let dataSize = vertexData!.count * MemoryLayout<Float>.size
vertexBuffer = device?.makeBuffer(bytes: vertexData!, length: dataSize, options: [])
let library = device?.makeDefaultLibrary()!
let vertex_func = library?.makeFunction(name: "vertex_func")
let frag_func = library?.makeFunction(name: "fragment_func")
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .bgra8Unorm
do{
try rps = device?.makeRenderPipelineState(descriptor: rpld)
}catch let error{
fatalError("\(error)")
}
}
override func draw(_ rect: CGRect) {
if let drawable = currentDrawable, let rpd = currentRenderPassDescriptor {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let commandBuffer = commandQueue!.makeCommandBuffer()
let commandEncode = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
commandEncode?.setRenderPipelineState(rps!)
commandEncode?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncode?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
commandEncode?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
}
}
执行效果:
需要在真机上执行代码地址: Ch03-iOS
TvOS平台相关技术实现
Shader.metal相关代码:
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],uint vid [[vertex_id]]){
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]){
return float4(0.7,1,1,1);
}
MetalView.swift相关代码实现:
import MetalKit
class MetalView: MTKView {
var commandQueue: MTLCommandQueue?
var rps: MTLRenderPipelineState?
var vertexData: [Float]?
var vertexBuffer: MTLBuffer?
required init(coder: NSCoder) {
super.init(coder: coder)
render()
}
func render(){
device = MTLCreateSystemDefaultDevice()
commandQueue = device?.makeCommandQueue()
vertexData = [-1.0,-1.0,0.0,1.0,
1.0,-1.0,0.0,1.0,
0.0,1.0,0.0,1.0]
let dataSize = vertexData!.count * MemoryLayout<Float>.size
vertexBuffer = device?.makeBuffer(bytes: vertexData!, length: dataSize, options: [])
let library = device?.makeDefaultLibrary()!
let vertex_func = library?.makeFunction(name: "vertex_func")
let frag_func = library?.makeFunction(name: "fragment_func")
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .bgra8Unorm
do{
try rps = device?.makeRenderPipelineState(descriptor: rpld)
}catch let error{
fatalError("\(error)")
}
}
override func draw(_ rect: CGRect) {
if let drawable = currentDrawable, let rpd = currentRenderPassDescriptor {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let commandBuffer = commandQueue!.makeCommandBuffer()
let commandEncode = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
commandEncode?.setRenderPipelineState(rps!)
commandEncode?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncode?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
commandEncode?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
}
}
执行效果:
这个也需要在真机上测试代码地址: Ch03-TvOS
网友评论
这里有两个小问题请教一下:
1、rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm指定渲染管线的像素格式为BGRA,但在 shader 中输出的颜色值return float4(0.7,1,1,1);仍然是按 RGBA 的顺序,这个会有问题吗?
2、如果需要渲染更多的三角形要怎么做?一个MTLRenderPipelineDescriptor只渲染一个模型吗?