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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • <转>我也忘了转自哪里,抱歉,感谢原作者 什么是Shader Shader(着色器)是一段能够针对3D对象进行操作...
    星易乾川阅读 5,501评论 1 16
  • 1 着色器和程序(Shaders and Programs) 1.1 着色器语言(Language Overvie...
    RichardJieChen阅读 9,205评论 3 12
  • 转载注明出处:点击打开链接 Shader(着色器)是一段能够针对3D对象进行操作、并被GPU所执行的程序。Shad...
    游戏开发小Y阅读 3,235评论 0 4
  • 图元处理(Primitive Processing) 如何在场景中使用曲面细分来添加几何细节 如何使用几何着色器处...
    RichardJieChen阅读 6,564评论 2 4
  • 高山中国红(新韵) 林忠顺 远山寺宇鸟朦胧,雨步长阶落叶松。 未见僧尼行礼意,先得玫瑰点迎恭。 环林壁海全乏趣,背...
    林忠顺阅读 394评论 4 7