[MetalKit]Using MetalKit part 3使用MetalKit3

本系列文章是对 http://metalkit.org 上面MetalKit内容的全面翻译和学习.

MetalKit系统文章目录


上一节我说我们将学习Metal shading language.在学之前,我们先做一些代码清理和重构.从下载前一节的源代码 source code开始.我们将从重构render()函数开始.所以让我们取出vertex bufferrender pipeline state,并创建3个新的函数放进去,这样我们的旧函数就减少到这样:

var vertex_buffer: MTLBuffer!
var rps: MTLRenderPipelineState! = nil

func render() {
    device = MTLCreateSystemDefaultDevice()
    createBuffer()
    registerShaders()
    sendToGPU()
}

我们先对createBuffer()函数做一些改变.回忆上一节vertex dataFloat类型的数组,像这样:

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]

让我们把它转换成更好的格式,一个带有两个vector_float4类型成员的结构体,一个position另一个是color:

struct Vertex {
    var position: vector_float4
    var color: vector_float4
}

你可能会好奇vector_float4到底是什么样的数据类型.从苹果官方文档中我们发现,这种向量类型是一种clang基础类型,比传统的SIMD类型更适合向量-向量向量-标量的算术运算.它可以通过类似数组下标来访问向量的成员分量,具体作法是用.操作符和组件名称访问(x,y,z,w,或它们的组合).除了.xyzw组件名外,下面的子向量也能通过:.lo / .hi(向量的前半部分和后半部分)来轻松访问,还有奇偶位的.even / .odd子向量:

vector_float4 x = 1.0f;         // x = { 1, 1, 1, 1 }.

vector_float3 y = { 1, 2, 3 };  // y = { 1, 2, 3 }.

x.xyz = y.zyx;                  // x = { 1/3, 1/2, 1, 1 }.

x.w = 0;                        // x = { 1/4, 1/3, 1/2, 0 }.

让我们返回到createBuffer()用新的结构体来替换vertex-data:

func createBuffer() {
    let vertex_data = [Vertex(position: [-1.0, -1.0, 0.0, 1.0], color: [1, 0, 0, 1]),
                       Vertex(position: [ 1.0, -1.0, 0.0, 1.0], color: [0, 1, 0, 1]),
                       Vertex(position: [ 0.0,  1.0, 0.0, 1.0], color: [0, 0, 1, 1])]
    vertex_buffer = device!.newBufferWithBytes(vertex_data, length: sizeof(Vertex) * 3, options:[])
}

你看,通过简单地将它转成结构体数组,我们可以轻易创建顶点数据.

同时,我们保持顶点位置仍在上次的位置上,并且我们为每个顶点添加单独的颜色(红,绿,蓝).接下来,是registerShaders()函数.我们无需改变旧代码,只需要将它移动到新的地方:

func registerShaders() {
    let library = device!.newDefaultLibrary()!
    let vertex_func = library.newFunctionWithName("vertex_func")
    let frag_func = library.newFunctionWithName("fragment_func")
    let rpld = MTLRenderPipelineDescriptor()
    rpld.vertexFunction = vertex_func
    rpld.fragmentFunction = frag_func
    rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm
    do {
        try rps = device!.newRenderPipelineStateWithDescriptor(rpld)
    } catch let error {
        self.print("\(error)")
    }
}

最后,我们对sendToGPU()函数也做同样的操作,不改变旧代码只移动到新地方:

func sendToGPU() {
    if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
        rpd.colorAttachments[0].clearColor = MTLClearColorMake(0.5, 0.5, 0.5, 1.0)
        let command_buffer = device!.newCommandQueue().commandBuffer()
        let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
        command_encoder.setRenderPipelineState(rps)
        command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
        command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
        command_encoder.endEncoding()
        command_buffer.presentDrawable(drawable)
        command_buffer.commit()
    }
}

接下来让我们转移到Shaders.metal文件.这时我们做两处修改.首先,给我们的Vertex结构体添加一个color成员,这样我们就可以在CPUGPU之间来回传递数据:

struct Vertex {
    float4 position [[position]];
    float4 color; 
};

其次,我们替换上次在fragment着色器中使用的硬编码的颜色:

fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    return float4(0.7, 1, 1, 1);
}

替换为每个顶点自带的实际颜色(通过vertex_buffer传递到GPU):

fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    return vert.color;
}

如果你运行程序,你看到一个更漂亮的彩色三角形:

chapter04.png

你也许会奇怪,为什么我们只传递给三个顶点对应颜色,但顶点之间的颜色却是渐变的?要理解这些,就必须先理解两种着色器的不同及它们在图形管线中角色的不同.让我们看看任一个着色器的语法(这里先顶点着色器作例子):

vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]])

第一个关键词,是函数限定符只能使用vertex, fragmentkernel.下一个关键词是返回值类型.接下来是带有圆括号参数的函数name名称.Metal shading language限定了指针的使用,必须用device,threadgroupconstant修饰符来声明,这些修饰符指定了函数变量或参数分配到的内存区域.[[...]]语法是用来声明属性,例如资源位置,着色器输入,以及在着色器与CPU之间来回传递的内置变量.

Metal使用[[ buffer(index) ]]属性来标识出位置,让deviceconstant buffer的参数类型能够区分.内置的输入变量和输出变量被用来在图形函数(顶点和片段)与固定图形管线流程之间传递数据.在我们例子中[[vertex_id]]是传递过程中每个顶点的标识符.Metal接收顶点函数和光栅产生的片段的输出,来产生输入到片段函数的各个片段.每个片段输入依靠[[stage_in]]属性修饰符来标识.

vertex shader用指向顶点列表的指针作为第一个参数.我们可以用第2个参数vid来索引vertices顶点,其中的vid被赋值成vertex_id,它告诉Metal插入当前正在被处理的顶点的索引作为第2个参数.然后只需传递每个顶点(包括位置和颜色)给fragment shader片段着色器去处理.fragment shader片段着色器所作的操作是,取出从vertex shader顶点着色器中传过来的顶点,直接传给每个像素而无需改变输入数据.顶点着色器运行频率不高(本例中只需3次-每个顶点1次),但fragment shader片段着色器运行几千次-每个需要绘制的像素一次.

所以你可能仍然会问:"ok,但是颜色渐变到底怎么回事呢?" 现在你理解了每个着色器的作用及运行频率,你可以认为任一个像素点的颜色都是它的附近像素颜色的平均值.例如,在red红green绿颜色像素正中间的像素颜色将会是yellow黄,只是因为fragment shader片段着色器用平均数来产生颜色插值:0.5 * red + 0.5 * green.同样的,在red红blue蓝正中间的颜色会是magenta品红,在blue蓝green绿正中间的颜色会是cyan青.就这样,剩余部分像素都是用初始颜色的插值,最终结果就是你看到的渐变范围.

源代码source code 已发布在Github上.
下次见!

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

推荐阅读更多精彩内容