[MetalKit]22-Using-MetalKit-part-15使用MetalKit15

637 阅读4分钟

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

MetalKit系统文章目录


第13部分末尾,我们说过让我们的行星看起来更真实有两种方法:添加纹理,或添加一些噪声到plante颜色中.我们在第14部分已经展示了添加噪声.这周我们看看纹理采样.纹理非常有用,因为比起为每个顶点计算颜色,它可以给表面提供更好的细节.

让我们比第13部分Part 13开始,因为我们不再需要噪声代码了.首先,在MetalView.swift中移除mouseDown函数,我们已经不再需要它了.同时移除mouseBufferpos变量,同时移除代码中对它们的引用.然后,创建一个新的纹理对象:

var texture: MTLTexture!

下一步,将这行(可能你已经在前面清理中移除过了):

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

替换为:

commandEncoder.setTexture(texture, atIndex: 1)

同时改变timer的缓冲器索引,从1改为0:

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

我在Resources文件夹添加一张图片名为texture.jpg,你可以用自己的图片代替.让我们创建一个函数来加载并使用这张图片作为纹理:

func setUpTexture() {
    let path = NSBundle.mainBundle().pathForResource("texture", ofType: "jpg")
    let textureLoader = MTKTextureLoader(device: device!)
    texture = try! textureLoader.newTextureWithContentsOfURL(NSURL(fileURLWithPath: path!), options: nil)
}

下一步,在我们的init函数调用这个函数:

override public init(frame frameRect: CGRect, device: MTLDevice?) {
    super.init(frame: frameRect, device: device)
    registerShaders()
    setUpTexture()
}

现在,清理我们Shaders.metal中的内核,只保留下面几行:

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    texture2d<float, access::read> input [[texture(1)]],
                    constant float &timer [[buffer(1)]],
                    uint2 gid [[thread_position_in_grid]])
{
    float4 color = input.read(gid);
    gid.y = input.get_height() - gid.y;
    output.write(color, gid);
}

你会首次注意到,我们从 [[texture(1)]] 属性拿到了input纹理,因为这个属性就是我们设置到命令编码器里时的索引.同时,访问权限设置为read.然后我们将其读取到color变量,然而,它却是上下颠倒的.为了修复这个问题,在下一行我们为每个像素反转Y轴.输出图片看起来应该像这样:

chapter15_1.png

如果你打开图片并与我们的输出比较,你会发现它已经旋转到正确朝向了.下一步,我们要找回我们的行星及周围的黑暗天空.用下面一大块代码替换output行:

int width = input.get_width();
int height = input.get_height();
float2 uv = float2(gid) / float2(width, height);
uv = uv * 2.0 - 1.0;
float radius = 0.5;
float distance = length(uv) - radius;
output.write(distance < 0 ? color : float4(0), gid);

这段代码看起来很熟悉,因为前些章节我们已经讨论过如何创建行星及周围的黑色空间.输出图片看起来应该像这样:

chapter15_2.png
现在很好!下一步我们让行星转动起来.用下面一大块代码替换output行:

uv = fmod(float2(gid) + float2(timer * 100, 0), float2(width, height));
color = input.read(uint2(uv));
output.write(distance < 0 ? color : float4(0), gid);

这段代码看起来又很熟悉,因为前些章节我们已经讨论过如何使用timer让行星动起来.输出图片看起来应该像这样:

chapter15_3.gif

这看起来就比较傻了!输出的图像看起来像一个人举着火把紧贴墙壁走在黑暗的洞穴里.用下面的代码替换最后三行:

uv = uv * 2;
radius = 1;
constexpr sampler textureSampler(coord::normalized,
                                 address::repeat,
                                 min_filter::linear,
                                 mag_filter::linear,
                                 mip_filter::linear );
float3 norm = float3(uv, sqrt(1.0 - dot(uv, uv)));
float pi = 3.14;
float s = atan2( norm.z, norm.x ) / (2 * pi);
float t = asin( norm.y ) / (2 * pi);
t += 0.5;
color = input.sample(textureSampler, float2(s + timer * 0.1, t));
output.write(distance < 0 ? color : float4(0), gid);

首先,我们缩小纹理尺寸到原来的一半,并设置半径为1来匹配行星对象的尺寸和纹理尺寸.然后,神奇的地方来了.让我们引入sampler采样器.sampler采样器是一个包含了各种渲染状态的对象,让纹理来配置:坐标,寻址方式(这里设置为repeat)和过滤方法(设置为linear).下一步,计算球面上每个点的normal法线.最后,我们用采样来计算color而不是像前面一样直接读取.还有一件事要做,在内核参数列表中,让我们也把纹理访问权限由read改为sample.将这一行:

texture2d<float, access::read> input [[texture(1)]],

替换为:

texture2d<float, access::sample> input [[texture(1)]],

输出图像看起来应该像这样:

chapter15_4.gif

这就是我们所说的真实的行星表面!还要感谢 Chris的帮助. 源代码source code 已发布在Github上.

下次见!