Metal入门资料003-MetalKit第二部分

作者: 张芳涛 | 来源:发表于2018-06-07 14:33 被阅读19次

    写在前面:

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

    到目前为止,我们已经完成了第一阶段,存储顶点。 您注意到下一个阶段需要我们有一个名为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 <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

    相关文章

      网友评论

      • johnny孙:感谢作者翻译了这么好的入门文章。
        这里有两个小问题请教一下:
        1、rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm指定渲染管线的像素格式为BGRA,但在 shader 中输出的颜色值return float4(0.7,1,1,1);仍然是按 RGBA 的顺序,这个会有问题吗?
        2、如果需要渲染更多的三角形要怎么做?一个MTLRenderPipelineDescriptor只渲染一个模型吗?
        张芳涛:@johnnysun 其实,我也没试过,实在不好意思。

      本文标题:Metal入门资料003-MetalKit第二部分

      本文链接:https://www.haomeiwen.com/subject/qcowsftx.html