使用 Metal 画一个三角形 的完整流程可以分为 七大步骤,这是任何 Metal 渲染任务的基本路径:


一、创建 MTKView(或 CAMetalLayer)

这是你绘图的承载视图,MTKView 是最推荐的选择:

let mtkView = MTKView(frame: someFrame, device: MTLCreateSystemDefaultDevice())

二、准备顶点数据并创建 Vertex Buffer

你需要定义一个三角形的顶点数据,并上传到 GPU:

let vertexData: [Float] = [
    -1.0, -1.0, 0.0, 1.0,  // bottom-left
     1.0, -1.0, 0.0, 1.0,  // bottom-right
     0.0,  1.0, 0.0, 1.0   // top
]

let dataSize = vertexData.count * MemoryLayout<Float>.size
vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])

三、加载 Shader 函数

你需要两个 Metal Shader 函数:一个 Vertex Shader 和一个 Fragment Shader。

let library = device.makeDefaultLibrary()
let vertexFunc = library?.makeFunction(name: "vertex_func")
let fragmentFunc = library?.makeFunction(name: "fragment_func")

对应 .metal 文件中:

#include <metal_stdlib>
using namespace metal;

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

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

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

四、创建 Render Pipeline State(管线状态对象)

将 shader 与像素格式封装成管线状态:

let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunc
pipelineDescriptor.fragmentFunction = fragmentFunc
pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat

let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)

五、创建 CommandQueue

命令队列用于提交绘图命令:

let commandQueue = device.makeCommandQueue()

六、在 draw(in:)draw(_:) 中进行渲染

每一帧的绘图过程:

guard let drawable = view.currentDrawable,
      let descriptor = view.currentRenderPassDescriptor,
      let commandBuffer = commandQueue.makeCommandBuffer(),
      let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
    return
}

encoder.setRenderPipelineState(pipelineState)
encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
encoder.endEncoding()

commandBuffer.present(drawable)
commandBuffer.commit()

七、设置刷新参数(可选)

mtkView.isPaused = false
mtkView.enableSetNeedsDisplay = false
mtkView.preferredFramesPerSecond = 60

总结:Metal 画一个三角形的完整流程

步骤 内容
1 创建 MTKView
2 准备顶点数据,创建 Vertex Buffer
3 编写 & 加载 Shader 函数
4 设置并创建 Pipeline State
5 创建 Command Queue
6 在每帧中编码绘图命令
7 提交命令并展示结果
import MetalKit

class MetalView: MTKView {
    private var commandQueue: MTLCommandQueue!
    private var pipelineState: MTLRenderPipelineState!
    private var vertexBuffer: MTLBuffer!

    // 顶点数据
    private let vertexData: [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
    ]

    override init(frame: CGRect, device: MTLDevice?) {
        super.init(frame: frame, device: device)
        self.device = device ?? MTLCreateSystemDefaultDevice()
        configure()
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func configure() {
        // 设置刷新率和渲染控制
        self.colorPixelFormat = .bgra8Unorm
        self.isPaused = false
        self.enableSetNeedsDisplay = false
        self.preferredFramesPerSecond = 60

        // 创建命令队列
        commandQueue = device?.makeCommandQueue()

        // 创建顶点缓冲区
        let dataSize = vertexData.count * MemoryLayout<Float>.size
        vertexBuffer = device?.makeBuffer(bytes: vertexData, length: dataSize, options: [])

        // 创建渲染管线
        guard let library = device?.makeDefaultLibrary(),
              let vertexFunc = library.makeFunction(name: "vertex_func"),
              let fragFunc = library.makeFunction(name: "fragment_func") else {
            fatalError("Failed to load shaders.")
        }

        let pipelineDescriptor = MTLRenderPipelineDescriptor()
        pipelineDescriptor.vertexFunction = vertexFunc
        pipelineDescriptor.fragmentFunction = fragFunc
        pipelineDescriptor.colorAttachments[0].pixelFormat = self.colorPixelFormat

        do {
            pipelineState = try device?.makeRenderPipelineState(descriptor: pipelineDescriptor)
        } catch {
            fatalError("Unable to create pipeline state: \(error)")
        }
    }

    override func draw(_ rect: CGRect) {
        guard let drawable = currentDrawable,
              let descriptor = currentRenderPassDescriptor else {
            return
        }

        descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)

        guard let commandBuffer = commandQueue.makeCommandBuffer(),
              let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
            return
        }

        encoder.setRenderPipelineState(pipelineState)
        encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
        encoder.endEncoding()

        commandBuffer.present(drawable)
        commandBuffer.commit()
    }
}
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐