Metal入门资料003-MetalKit使用(下)

写在前面:

对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()

上一篇博客我们说过,我们应该确保currentDrawablecurrentRenderPassDescriptor不是零,否则应用程序会崩溃。 为了简单起见,我们在第一部分中没有这样做,现在就让我们来做。 这也将帮助我们摆脱更多行代码。 该函数的最终版本如下所示:

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)。 屏幕边缘的值分别为-11。 让我们创建一个浮点数组和一个缓冲区来保存三角形的顶点值。 初始化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个浮点数作为其坐标。 前两个是xy轴。 我们这次没有使用的是:第三个是depth(Z轴),第四个是W coordinate(W坐标),使得我们的坐标是homogeneous(均匀)的。 我们将在下一篇博客中谈论它们。 然后我们计算这个数组的大小,简单地说就是12个浮点数的大小,最后我们根据数组和它的大小创建缓冲区。 现在我们已经存储了顶点,我们需要一种方法将它们发送到GPU,以便它们可以显示在屏幕上。 让我们看看有助于在屏幕上绘制图形的整个过程(pipeline):

Metal入门资料003-MetalKit使用(下)_第1张图片

到目前为止,我们已经完成了第一阶段,存储顶点。 您注意到下一个阶段需要我们有一个名为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位)颜色值变为 从0255)。 最后一步是根据上述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 

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(顶点)并为每个顶点分别设置颜色。

如果你运行该应用程序,你应该看到一个这样的三角形:

Metal入门资料003-MetalKit使用(下)_第2张图片

代码地址:Ch03-OSX

iOS平台相关技术实现

Shader.metal相关代码:

#include 
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.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()
    }
 }
}

执行效果:

Metal入门资料003-MetalKit使用(下)_第3张图片
需要在真机上执行

代码地址: Ch03-iOS

TvOS平台相关技术实现

Shader.metal相关代码:

#include 
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.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()
    }
 }
}

执行效果:

Metal入门资料003-MetalKit使用(下)_第4张图片
这个也需要在真机上测试

代码地址: Ch03-TvOS

你可能感兴趣的:(Metal入门资料003-MetalKit使用(下))