[MetalKit]18-Using-MetalKit-part-12使用MetalKit12

647 阅读3分钟

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

MetalKit系统文章目录


让我们从上次第11部分 Part 11开始,继续处理我们美丽的分形.还用上次我们工作的playground,我们将看到如何将它变成活的,也就是变成动画.为此,我们又要使用uniforms了.我们是在第5部分 Part 5介绍过,如果你想再读一遍为什么它们在本例中有用的话.

首先,在MetalView.swift文件的顶部,让我们创建一个全局变量命名为timer并用一个数据缓冲器来保存它:

var timer: Float = 0
var timerBuffer: MTLBuffer!

下一步,在registerShaders()里面,我们要初始化这个缓冲器:

timerBuffer = device!.newBufferWithLength(sizeof(Float), options: [])

然后,我们需要创建一个update() 函数,它将增加计时时间并发送新的值到缓冲器:

func update() {
    timer += 0.01
    var bufferPointer = timerBuffer.contents()
    memcpy(bufferPointer, &timer, sizeof(Float))
}

下一步,在drawRect()中,就在我们设置纹理到当前drawable的那行下面,我们需要设置计时器缓冲器到索引1.然后我们还需要调用update函数,因为drawRect()每帧都运行,所以计时器时间就会随着帧数变化而增加:

commandEncoder.setBuffer(timerBuffer, offset: 0, atIndex: 1)
update()

然后,在Shaders.metal中我们需要更新我们的内核签名来包含计时器缓冲器:

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    constant float &timer [[buffer(1)]],
                    uint2 gid [[thread_position_in_grid]])

最有意思的部分来了!将下面这行:

float2 cc = 1.1*float2( 0.5*cos(0.1) - 0.25*cos(0.2), 0.5*sin(0.1) - 0.25*sin(0.2) );

替换为:

float2 cc = 1.1*float2( 0.5*cos(0.1*timer) - 0.25*cos(0.2*timer), 0.5*sin(0.1*timer) - 0.25*sin(0.2*timer) );

如果你现在运行playground,你会看到类似的东西:

chapter12_1.gif

是不是简单又有趣?还可以添加一个重要又有用的特性,就是鼠标交互.显然,我们又要用到uniforms了.我们让MetalView类遵守NSWindowDelegate协议,这样我们就可以用它的mouse方法了.

public class MetalView: MTKView, NSWindowDelegate {

下一步,参考计时器,我们再创建一个全局变量命名为pos,来记录鼠标位置(坐标)并用一个数据缓冲器来保存它.我们现在可以重写mouseDown() 方法并拿到坐标了:

var mouseBuffer: MTLBuffer!
var pos: NSPoint!

override public func mouseDown(event: NSEvent) {
    pos = convertPointToLayer(convertPoint(event.locationInWindow, fromView: nil))
    let scale = layer!.contentsScale
    pos.x *= scale
    pos.y *= scale
}

正如你看到的那样,我们在缩放坐标,从整个屏幕到只有MetalView大小,同时我们用从视图的层那里拿到的缩放比例来更新坐标.下一步,在registerShaders()函数中我们初始化鼠标缓冲器:

mouseBuffer = device!.newBufferWithLength(sizeof(NSPoint), options: [])

现在回到update()函数,添加下面几行到末尾,这样我们就能发送当前鼠标坐标到缓冲器了:

bufferPointer = mouseBuffer.contents()
memcpy(bufferPointer, &pos, sizeof(NSPoint))

下一步,在drawRect()中我们设置鼠标缓冲器到索引2:

commandEncoder.setBuffer(mouseBuffer, offset: 0, atIndex: 2)

然后,在Shaders.metal中我们再升级内核签名来包含鼠标缓冲器:

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    constant float &timer [[buffer(1)]],
                    constant float2 &mouse [[buffer(2)]],
                    uint2 gid [[thread_position_in_grid]])

最后,我们将下面这行:

float3 color = float3( dmin.w );

替换为这行:

float3 color = float3(mouse.x - mouse.y);

我们这里做的是改变颜色的计算方式,改用鼠标坐标传递到color变量.在playground中运行,并单击可变视图区,观察效果.输出的图像应该看起来像这样:

chapter12_2.gif

在代码的不同位置引入鼠标坐标,可以使内核代码实现更漂亮的效果.还有一个需要关注的问题,事实上NSPoint可能并不完全对应内核中的float2类型.

源代码source code 已发布在Github上.

下次见!