You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI+Metal创建2D三角形网格:MTKMesh应用方案求助

问题:SwiftUI + Metal 2D三角形网格绘制异常,需通过MTKMesh解决

我正在尝试使用SwiftUI与Metal创建2D三角形网格,目前拥有929个顶点和1750个索引数据用于绘制网格。已使用.drawIndexedPrimitives方法,但绘制结果不符合预期。认为该问题需通过MTKMesh解决,但不清楚如何将其应用于这类带有顶点和索引列表的2D网格场景。

Renderer类代码

import Foundation
import MetalKit

class Renderer: NSObject, MTKViewDelegate {
    
    var parent: MetalViewComponent
    var metalDevice: MTLDevice!
    var metalCommandQueue: MTLCommandQueue!
    let pipelineState: MTLRenderPipelineState
    let vertexBuffer: MTLBuffer?
//    let mesh = MTKMesh(mesh: <#T##MDLMesh#>, device: <#T##MTLDevice#>)
    
    let indexBuffer: MTLBuffer?
    
    init(_ parent: MetalViewComponent) {
        self.parent = parent
        if let metalDevice = MTLCreateSystemDefaultDevice() {
            self.metalDevice = metalDevice
        }
        self.metalCommandQueue = metalDevice.makeCommandQueue()
        
        
        let pipelineDescriptor = MTLRenderPipelineDescriptor()
        let library = metalDevice.makeDefaultLibrary()
        pipelineDescriptor.vertexFunction = library?.makeFunction(name: "vertexShader")
        pipelineDescriptor.fragmentFunction = library?.makeFunction(name: "fragmentShader")
        pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        do {
            try pipelineState = metalDevice.makeRenderPipelineState(descriptor: pipelineDescriptor)

        } catch {
            fatalError()
        }
            
        
        let vertices = [Vertex(position: [-3.49195e-17, -0.570297], color: [0, 1, 1, 1]),
                        Vertex(position: [-0.245024, -0.578218], color: [0, 1, 1, 1]),
                        Vertex(position: [-0.529488, -0.52307], color: [0, 1, 1, 1]),...

        let indices: [UInt32] = [470,469,433,
                                 433,469,432,
                                 470,433,434,
                                 506,469,507,
                                 469,470,507, // from 1 to 928
vertexBuffer = metalDevice.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Vertex>.stride, options: [])!
        indexBuffer = metalDevice.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: [])
        super.init()
        print("Число вертексов: \(vertices.count)")
    }
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        
    }
    
    func draw(in view: MTKView) {
        guard let drawable = view.currentDrawable else {
            return
        }
        
                                
        
        let commandBuffer = metalCommandQueue.makeCommandBuffer()
        let renderPassDescriptor = view.currentRenderPassDescriptor
        
        renderPassDescriptor?.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1.0)
        renderPassDescriptor?.colorAttachments[0].loadAction = .clear
        renderPassDescriptor?.colorAttachments[0].storeAction = .store
        
        let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
        
        renderEncoder?.setRenderPipelineState(pipelineState)
        renderEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        renderEncoder?.drawIndexedPrimitives(type: .lineStrip, indexCount: 1750, indexType: .uint32, indexBuffer: indexBuffer!, indexBufferOffset: 0)

renderEncoder?.endEncoding()
        
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
    }
}

绘制效果对比

当前绘制效果:
当前绘制效果

预期绘制效果:
预期绘制效果


解决方案思路

1. 先修复现有.drawIndexedPrimitives的问题

  • 索引缓冲区长度错误:代码中创建indexBuffer时使用MemoryLayout<UInt16>.size,但索引数组是[UInt32]类型,应改为MemoryLayout<UInt32>.size,否则会导致索引数据读取异常。
  • 图元类型错误:绘制三角形网格需使用.triangles而非.lineStrip,后者仅绘制线条而非填充三角形。

修改后的关键代码:

// 修复索引缓冲区创建
indexBuffer = metalDevice.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt32>.size, options: [])

// 修复绘制调用
renderEncoder?.drawIndexedPrimitives(type: .triangles, indexCount: indices.count, indexType: .uint32, indexBuffer: indexBuffer!, indexBufferOffset: 0)

2. 改用MTKMesh实现的步骤

若需基于MTKMesh重构,按以下流程操作:

步骤1:定义顶点描述符与创建MDLMesh

先匹配Vertex结构体的内存布局,创建MDLVertexDescriptor,再用顶点/索引数据生成MDLMesh。

步骤2:转换为MTKMesh

通过MTKMesh(mesh:device:)将MDLMesh转换为Metal可直接使用的MTKMesh。

步骤3:基于MTKMesh绘制

使用MTKMesh的顶点缓冲区和子网格数据完成绘制。

改造后的Renderer核心代码:

import Foundation
import MetalKit

class Renderer: NSObject, MTKViewDelegate {
    
    var parent: MetalViewComponent
    var metalDevice: MTLDevice!
    var metalCommandQueue: MTLCommandQueue!
    let pipelineState: MTLRenderPipelineState
    let mesh: MTKMesh // 替换原vertexBuffer和indexBuffer
    
    init(_ parent: MetalViewComponent) {
        self.parent = parent
        if let metalDevice = MTLCreateSystemDefaultDevice() {
            self.metalDevice = metalDevice
        }
        self.metalCommandQueue = metalDevice.makeCommandQueue()
        
        // 初始化渲染管线(保留原有代码)
        let pipelineDescriptor = MTLRenderPipelineDescriptor()
        let library = metalDevice.makeDefaultLibrary()
        pipelineDescriptor.vertexFunction = library?.makeFunction(name: "vertexShader")
        pipelineDescriptor.fragmentFunction = library?.makeFunction(name: "fragmentShader")
        pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        do {
            try pipelineState = metalDevice.makeRenderPipelineState(descriptor: pipelineDescriptor)
        } catch {
            fatalError()
        }
        
        // 顶点与索引数据(保留原有定义)
        let vertices = [Vertex(position: [-3.49195e-17, -0.570297], color: [0, 1, 1, 1]),
                        Vertex(position: [-0.245024, -0.578218], color: [0, 1, 1, 1]),
                        Vertex(position: [-0.529488, -0.52307], color: [0, 1, 1, 1]),...]

        let indices: [UInt32] = [470,469,433,
                                 433,469,432,
                                 470,433,434,
                                 506,469,507,
                                 469,470,507, ...]
        
        // 1. 创建顶点描述符
        let vertexDescriptor = MDLVertexDescriptor()
        // 位置属性:float2类型,偏移0
        vertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition,
                                                            format: .float2,
                                                            offset: 0,
                                                            bufferIndex: 0)
        // 颜色属性:float4类型,偏移为position的内存大小
        vertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeColor,
                                                            format: .float4,
                                                            offset: MemoryLayout<SIMD2<Float>>.stride,
                                                            bufferIndex: 0)
        // 设置顶点缓冲区步长为Vertex结构体的内存大小
        vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: MemoryLayout<Vertex>.stride)
        
        // 2. 生成MDLMesh
        let vertexData = Data(bytes: vertices, count: vertices.count * MemoryLayout<Vertex>.stride)
        let indexData = Data(bytes: indices, count: indices.count * MemoryLayout<UInt32>.size)
        
        let mdlMesh = MDLMesh(vertexBufferData: vertexData,
                              vertexCount: vertices.count,
                              vertexDescriptor: vertexDescriptor,
                              indexBufferData: indexData,
                              indexCount: indices.count,
                              indexType: .uInt32,
                              submeshes: [MDLSubmesh(indexCount: indices.count,
                                                     indexType: .uInt32,
                                                     geometryType: .triangles,
                                                     material: nil)])
        
        // 3. 转换为MTKMesh
        do {
            mesh = try MTKMesh(mesh: mdlMesh, device: metalDevice)
        } catch {
            fatalError("Failed to create MTKMesh: \(error)")
        }
        
        super.init()
        print("Число вертексов: \(vertices.count)")
    }
    
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
    
    func draw(in view: MTKView) {
        guard let drawable = view.currentDrawable else { return }
        
        let commandBuffer = metalCommandQueue.makeCommandBuffer()
        let renderPassDescriptor = view.currentRenderPassDescriptor
        
        renderPassDescriptor?.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1.0)
        renderPassDescriptor?.colorAttachments[0].loadAction = .clear
        renderPassDescriptor?.colorAttachments[0].storeAction = .store
        
        let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
        renderEncoder?.setRenderPipelineState(pipelineState)
        
        // 设置MTKMesh的顶点缓冲区
        let vertexBuffer = mesh.vertexBuffers[0].buffer
        renderEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        
        // 遍历子网格绘制
        for submesh in mesh.submeshes {
            renderEncoder?.drawIndexedPrimitives(type: .triangles,
                                                 indexCount: submesh.indexCount,
                                                 indexType: submesh.indexType,
                                                 indexBuffer: submesh.indexBuffer.buffer,
                                                 indexBufferOffset: submesh.indexBuffer.offset)
        }
        
        renderEncoder?.endEncoding()
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
    }
}

额外注意事项

  • 确保Vertex结构体的内存布局与MDLVertexDescriptor定义完全匹配,比如position为SIMD2<Float>、color为SIMD4<Float>。
  • 若顶点坐标基于自定义坐标系,需在顶点着色器中添加转换逻辑,适配Metal的NDC坐标系(范围[-1,1],Y轴向上)。

内容的提问来源于stack exchange,提问作者Danil Peregorodiev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 16:25:25