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
相关产品推荐
相关产品推荐

