MetalKit介绍

这是MetalKit和Swift多部分系列的第2部分,阅读它可能会有帮助 第1部分. 在第2部分中,我们将继续使用第1部分中的代码进行开发. 我们的目标是创建一个旋转的立方体. 在接下来的部分,我们将介绍照明和输入检测. 为此,我们需要创建一些新类作为对象,并修改第1部分中的一些现有代码. 如果你对其他MetalKit和Swift开发感兴趣,可以看看我朋友的 通道.

节点.斯威夫特

首先,我们将创建一个名为 节点 哪个将作为我们的基对象类. 节点可以有子节点,并且能够呈现所有子节点.

进口MetalKit

{类节点
    var child: [节点] = []
    
    func add(child: 节点) {
        孩子.追加(孩子)
    }
    
    func render(comm和编码器: MTL渲染Comm和编码器, deltaTime: Float) {
        孩子.forEach {$ 0.呈现(comm和编码器: comm和编码器, deltaTime: deltaTime)}
    }
}

原始的.斯威夫特

其次,我们需要创建一个名为 原始的 这将处理 MTL缓冲区MTLStates. 原语 将用于在场景中创建对象. 稍后我们将创建一个多维数据集对象.

进口MetalKit

类原始的: 节点 {
    / /缓冲区
    var:于vertexBuffer之中MTLBuffer!
    var indexBuffer: MTLBuffer!
    / / BufferData
    var顶点(顶点):!
    var指数(UInt16):!
    / /状态
    var renderPipelineState: MTL渲染PipelineState!
    var depthStencilState: MTLDepthStencilState!
    / /限制
    var modelConstraints = modelConstraints ()
    // ...

var depthStencilState: MTLDepthStencilState

深度和模板状态对象,指定渲染通道中使用的深度和模板配置和操作. MTL渲染Comm和编码器使用MTLDepthStencilState对象来设置渲染通道的深度和模板状态.

现在我们将创建 初始化 的方法 原始的. 类似于 渲染器 在第一部分中,我们将使用该设备来帮助我们创建大多数我们需要的对象.

初始化(withDevice: MTLDevice) {
        超级.初始化 ()
        build顶点 ()
        build缓冲区(设备:设备)
        buildPipelineState(设备:设备)
        buildDepthStencil(设备:设备)
}

构建 顶点 将为对象创建顶点和索引数组. 它将从继承的对象中被重写 原始的.

public func build顶点 () {
    
}

构建 缓冲区 我们将使用这种方法 makeBuffer. 这将接受三个参数:字节、长度和选项.

makeBuffer(字节:长度:选择:)

分配一个给定长度的新缓冲区,并通过将现有数据复制到其中来初始化其内容.

private func build缓冲区(设备:MTLDevice) {
        =设备于vertexBuffer之中.makeBuffer(字节:顶点,
                                         length: MemoryLayout<顶点>.步*顶点.数,
                                         选择:[])
        indexBuffer =设备.makeBuffer(字节:指标,
                                        length: MemoryLayout.步*指数.数,
                                        选择:[])
}

构建 管道状态 将设置顶点和碎片着色器功能. 我们还将定义一个顶点tDescriptor, 这将有助于金属顶点着色功能, 以便更好地理解我们想传递给它的是什么.

private func buildPipelineState(设备:MTLDevice) {
        让库=设备.makeDefaultLibrary ()
        //获取shader函数
        让vertexFunction = library?.makeFunction(名字:“basic_vertex_function”)
        让fragmentFunction = library?.makeFunction(名字:“basic_fragment_function”)
        
        //创建renderPiplineDescriptor
        让renderPipelineDescriptor = MTL渲染PipelineDescriptor()
        renderPipelineDescriptor.colorAttachments [0].pixelFormat = .bgra8Unorm
        renderPipelineDescriptor.vertexFunction = vertexFunction
        renderPipelineDescriptor.fragmentFunction = fragmentFunction
        renderPipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
        
        //创建顶点描述符
        让vertexDescriptor = MTL顶点Descriptor()
        vertexDescriptor.属性[0].bufferIndex = 0
        vertexDescriptor.属性[0].格式= .float3
        vertexDescriptor.属性[0].抵消= 0
        
        vertexDescriptor.属性[1].bufferIndex = 0
        vertexDescriptor.属性[1].格式= .float4
        vertexDescriptor.属性[1].offset = MemoryLayout.大小
        
        vertexDescriptor.布局[0].步 = MemoryLayout<顶点>.步
        
        //创建管道
        renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
        
        do {
            renderPipelineState =尝试设备.make渲染PipelineState(描述符:renderPipelineDescriptor)
        } catch {
            (打印错误.localizedDescription)
        }
}

MTL顶点Descriptor

描述如何组织顶点数据并将其映射到顶点函数的对象.

构建 深度模板 将创建一个 MTLDepthStencilDescriptor. 我们将使用它来创建 深度模板.

private func buildDepthStencil(设备:MTLDevice) {
        让depthStencilDescriptor = MTLDepthStencilDescriptor()
        depthStencilDescriptor.isDepthWriteEnabled = true
        depthStencilDescriptor.depthCompareFunction = .少
        depthStencilState =设备.makeDepthStencilState(描述符:depthStencilDescriptor)
}

最后,我们有 渲染 函数处理 Comm和 编码器. 这是在 渲染器的画 函数,并将为场景中的每个节点调用. 我们将传递大部分我们内置的价值观 命令编码器.

override func render(comm和编码器: MTL渲染Comm和编码器, deltaTime: Float) {
        comm和编码器.set渲染PipelineState (renderPipelineState)
        超级.呈现(comm和编码器: comm和编码器, deltaTime: deltaTime)
        comm和编码器.set顶点Buffer(,于vertexBuffer之中
                                       抵消:0,
                                       指数:0)
        comm和编码器.setDepthStencilState (depthStencilState)
        comm和编码器.set顶点Bytes (&modelConstraints, length: MemoryLayout.步,指数:1)
        comm和编码器.画Indexed原语(类型: .三角形,
                                             indexCount:指数.数,
                                             indexType: .uint16,
                                             indexBuffer: indexBuffer,
                                             indexBufferOffset: 0)
}

类型.斯威夫特

这就是我们要创建 ModelConstraints, sceneconconstraints和constrable. 同时,我们将移动 顶点 结构,我们从第一部分.

进口MetalKit

结构顶点{
    var位置:float3
    var颜色:float4
}

struct ModelConstraints {
    var modelMatrix = matrix_identity_float4x4
}

struct 场景Constraints {
    var projectionMatrix = matrix_identity_float4x4
}

协议Constraintable {
    func量表(轴:float3)
    
    func(方向:float3)翻译
    
    func旋转(角度:Float,轴:float3)
}

我们将使用约束来修改场景中对象的值. 我们将需要创建缩放、平移和旋转的数学函数.

数学.斯威夫特

进口MetalKit

扩展浮动{
    var 拉德:浮动{
        return (自我 / 180) * Float.pi
    }
}

扩展matrix_float4x4 {
    
    初始化(degreesFov: Float, aspectRatio: Float, nearZ: Float, farZ: Float) {
        设fov = degreesFov.拉德
        
        设y = 1 / tan(fov * 0.5)
        设x = y / aspectRatio
        让z1 = farZ / (nearZ - farZ)
        设w = (z1 * nearZ)
        
        列= (
            float4 (x, 0, 0, 0),
            float4 (0, 0, 0),
            float4 (0, 0, z1, 1),
            float4 (0, 0, w, 0)
        )
    }
    
    函数缩放(轴:float3) {
        Var 结果 = matrix_identity_float4x4
        
        让x, y, z:浮动
        (x, y, z) =(轴.x轴.y轴.z)
        
        结果.列= (
            float4 (x, 0, 0, 0),
            float4 (0, 0, 0),
            float4 (0, 0, z, 0),
            float4 (0, 0, 0, 1)
        )
        
        = matrix_multiply(Self, 结果)
    }
    
    func旋转(角度:Float,轴:float3) {
        Var 结果 = matrix_identity_float4x4
        
        让x, y, z:浮动
        (x, y, z) =(轴.x轴.y轴.z)
        让c: Float = cos(角度)
        Float = sin(角度)
        
        让mc: Float = (1 - c)
        
        设r1c1 = x * x * MC + c
        设r2c1 = x * y * MC + z * s
        设r3c1 = x * z * MC - y * s
        让r4c1: Float = 0.0
        
        设r1c2 = y * x * MC - z * s
        设r2c2 = y * y * MC + c
        设r3c2 = y * z * MC + x * s
        让r4c2: Float = 0.0
        
        设r1c3 = z * x * MC + y * s
        设r2c3 = z * y * MC - x * s
        设r3c3 = z * z * MC + c
        让r4c3: Float = 0.0
        
        让r1c4: Float = 0.0
        让r2c4: Float = 0.0
        让r3c4: Float = 0.0
        让r4c4: Float = 1.0
        
        结果.列= (
            float4 (r1c1 r2c1、r3c1 r4c1),
            float4 (r1c2 r2c2、r3c2 r4c2),
            float4 (r1c3 r2c3、r3c3 r4c3),
            float4 (r1c4 r2c4、r3c4 r4c4)
        )
        
        = matrix_multiply(Self, 结果)
    }
    
    浮点数转换(方向:float3) {
        Var 结果 = matrix_identity_float4x4
        
        让x, y, z:浮动
        (x, y, z) =(方向.x方向.y方向.z)
        
        结果.列= (
            float4 (1, 0, 0, 0),
            float4 (0, 1, 0, 0),
            float4 (0, 0, 1, 0),
            float4 (x, y, z, 1)
        )
        
        = matrix_multiply(Self, 结果)
    }
}

现在我们已经添加了类型并实现了数学函数. 我们可以扩展我们的 原始的 对象,添加以下行.

原始的:约束性的{
    Func缩放(轴:float3) {
        modelConstraints.modelMatrix.量表(轴:轴)
    }
    
    Func translate(direction: float3) {
        modelConstraints.modelMatrix.翻译(方向:方向)
    }
    
    func旋转(角度:Float,轴:float3) {
        modelConstraints.modelMatrix.旋转(角度:角度,轴:轴)
    }
}

场景.斯威夫特

现在我们将创建一个 场景 对象,该对象包含 原始的 对象.

进口MetalKit

类场景:节点{
    var装置:MTLDevice!
    var sceneConstraints = sceneConstraints ()
    var对象:[原始的] = []
    
    初始化(设备:MTLDevice) {
        自我.设备=设备
        超级.初始化 ()
        sceneConstraints.projectionMatrix = matrix_float4x4(degreesFov: 45, aspectRatio: 1, nearZ: 0.1, farZ: 100)
    }
    
    override func render(comm和编码器: MTL渲染Comm和编码器, deltaTime: Float) {
        comm和编码器.set顶点Bytes (&sceneConstraints, length: MemoryLayout<场景Constraints>.步,指数:2)
        超级.呈现(comm和编码器: comm和编码器, deltaTime: deltaTime)
    }
}

多维数据集.斯威夫特

现在我们将创建第一个 原始的 object. 它非常简单,因为我们只需要定义一个函数, build顶点.

进口MetalKit

类多维数据集: 原始的 {
    
    覆盖func build顶点 () {
        顶点= [
            顶点(position: float3(-1,1,1),颜色:float4(1,0,0,1)),
            顶点(position: float3(-1,-1,1),颜色:float4(0,1,0,1)),
            顶点(position: float3(1,1,1),颜色:float4(0,0,1,1)),
            顶点(position: float3(1,1,1),颜色:float4(1,0,1,1)),
            顶点(position: float3(-1,1,-1),颜色:float4(1,1,0,1)),
            顶点位置:float3(1,1,-1),颜色:float4(0,1,1,1),
            顶点(position: float3(-1,-1,-1),颜色:float4(0.5,0.5,0,1)),
            顶点(位置:float3(1,-1,-1),颜色:float4(1,0,0).5,1))
        ]
        指数= [
            0、1、2、2、1,3,/ /前面
            5,2,3,  5,3,7,
            0,2,4,  2,5,4,
            0,1,4,  4,1,6,
            5,4,6,  5,6,7,
            3,1,6,  3,6,7
            
        ]
    }
}

多维数据集场景.斯威夫特

进口MetalKit

类立方场景:场景{
    override 初始化(设备:MTLDevice) {
        超级.初始化(设备:设备)
        //创建立方体
        让c = 多维数据集(withDevice: device)
        对象.追加(c)
        //移动立方体远离摄像机
        c.翻译(方向:float3 (0, 0, 6))
        //添加立方体到场景
        添加(孩子:c)
    }
    
    override func render(comm和编码器: MTL渲染Comm和编码器, deltaTime: Float) {
        //旋转场景中的对象
        对象.forEach {$ 0.旋转(角度:deltaTime,轴:float3(1,1,0))}
        超级.呈现(comm和编码器: comm和编码器, deltaTime: deltaTime)
    }
}

更新渲染器.斯威夫特

现在我们可以更新 渲染器的 变量并删除一些不需要的代码!

渲染器类:NSObject {
    var comm和Queue: MTLComm和Queue!
    var 场景: [场景] = []
    // ...

接下来,我们只需要这些方法 初始化

初始化(设备:MTLDevice) {
        超级.初始化 ()
        createComm和Queue(设备:设备)
        场景.追加(多维数据集场景(设备:设备))
}
    
    func createComm和Queue(设备:MTLDevice) {
        comm和Queue =设备.makeComm和Queue ()
}

最后,我们将改变 函数的方法是替换以下行

// ...
comm和编码器?.set渲染PipelineState (renderPipelineState)
//将vertexBuffer传入索引0
comm和编码器?.set顶点Buffer(,于vertexBuffer之中 抵消:0, 指数:0)
//在vertexStart 0处绘制原语
comm和编码器?.画原语(类型: .vertexStart: 0, vertexCount:顶点.数)
// ...

// ...
让deltaTime = 1 /浮动(查看.preferredFramesPerSecond)
        
场景.forEach {$ 0.呈现(comm和编码器: comm和编码器!,
                               deltaTime: deltaTime)}
// ...

更新MetalView.斯威夫特

内部 MetalView,我们只需添加一行. depthStencilPixelFormat = .depth32Float

require 初始化(coder: NSCoder) {
        超级.初始化(编码器:编码器)
        //确保我们在一个可以运行金属的设备上!
        guard let defaultDevice = MTLCreateSystemDefaultDevice() else {
            fatalError(“设备加载错误”)
        }
        设备= defaultDevice
        depthStencilPixelFormat = .depth32Float //添加这一行!
        colorPixelFormat = .bgra8Unorm
        //我们的清晰颜色,可以设置为任何颜色
        MTLClearColor(红色:0.1,格林:0.57岁的蓝色:0.25日,α:1)
        create渲染器(设备:defaultDevice)
}

更新着色器.金属

我们要做的第一个更新 着色器 是要加什么 顶点属性.

顶点属性

顶点函数可以通过索引一个缓冲区来读取每个顶点的输入
使用顶点和实例id的顶点函数. 此外,每个顶点的输入也可以
通过使用属性[[stage_in]]声明它们,将它们作为参数传递给顶点函数.
(第66页)
struct 顶点In {
    Float3 position [[attribute(0)]];
    Float4 color [[attribute(1)]];
};

接下来,我们将为在Swift中创建的约束添加两个简单的结构体.

struct ModelConstraints {
    float4x4 modelMatrix;
};

struct 场景Constraints {
    float4x4 projectionMatrix;
};

最后,我们将更新 basic_vertex_function 使用场景和模型约束.

顶点In vIn [[stage_in]],
                                       常数ModelConstraints &modelConstants [[buffer(1)]],
                                       常数场景Constraints &sceneConstants [[buffer(2)]]) {
    顶点Out输出电压;
    输出电压.位置= sceneConstants.projectionMatrix * modelConstants.modelMatrix * float4 (vIn.位置,1);
    输出电压.颜色= vIn.颜色;
    返回输出电压;
}

希望当你运行它时,你会看到一个旋转的立方体! 如果您有任何问题或想查看完整的源代码,请检查此 GitHub 页面!

旋转立方体

Swift开发人员,热情地把咖啡变成代码. 目前在Objective-C和Swift中使用MetalKit, ARKit和场景Kit. 请随我来 GitLab 或者加入我的开源敏捷开发团队 oneleif.

联系