美文网首页MetalKit专题
[MetalKit]Using MetalKit part 15

[MetalKit]Using MetalKit part 15

作者: 苹果API搬运工 | 来源:发表于2017-07-25 20:49 被阅读24次

    本系列文章是对 http://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上.
    下次见!

    相关文章

      网友评论

        本文标题:[MetalKit]Using MetalKit part 15

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