iOS/macOS开发者的Metal入门:用70行代码画一个红色三角形(附完整Xcode工程)
Metal极简入门70行代码在iOS/macOS上绘制红色三角形当第一次接触Metal时很多开发者会被其庞大的API和复杂的概念吓到。但事实上用Metal完成基础图形渲染并不需要掌握所有细节。本文将带你用最精简的代码仅70行在iOS或macOS上绘制一个红色三角形让你快速获得第一个Metal渲染成果。1. 创建Metal项目基础环境首先在Xcode中新建一个项目选择iOS或macOS模板均可。Metal是跨平台的图形API在这两个系统上的基础用法几乎一致。确保在项目中引入了必要的框架import MetalKit import simdMetalKit是苹果提供的简化Metal开发的框架而simd则包含了高性能的向量和矩阵运算类型。这两个框架是Metal开发的基础依赖。接下来我们需要准备一个MTKView作为渲染的画布。这个视图会自动处理与屏幕的交互和显示let mtkView MTKView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) mtkView.device MTLCreateSystemDefaultDevice() view.addSubview(mtkView)这段代码创建了一个300x300大小的Metal视图并设置了默认的GPU设备。MTLCreateSystemDefaultDevice()会自动选择设备上最适合Metal渲染的GPU。2. 编写Metal着色器代码Metal的渲染管线依赖于着色器Shader程序这些程序运行在GPU上。我们需要编写两个基本的着色器顶点着色器和片元着色器。let shaderSource #include metal_stdlib using namespace metal; vertex float4 vertexShader(uint vertexID [[vertex_id]], constant float4 *vertices [[buffer(0)]]) { return vertices[vertexID]; } fragment float4 fragmentShader() { return float4(1.0, 0.0, 0.0, 1.0); } 这段着色器代码做了以下几件事引入Metal标准库并设置命名空间定义顶点着色器接收顶点ID和顶点缓冲区数据定义片元着色器返回固定的红色(RGBA:1,0,0,1)着色器使用Metal Shading Language(MSL)编写这是一种基于C14的着色语言。[[vertex_id]]和[[buffer(0)]]是属性限定符用于指定参数的来源。3. 配置渲染管线有了着色器代码后我们需要创建一个渲染管线来使用它们guard let device mtkView.device else { return } let library try device.makeLibrary(source: shaderSource, options: nil) let vertexFunction library.makeFunction(name: vertexShader) let fragmentFunction library.makeFunction(name: fragmentShader) let pipelineDescriptor MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction vertexFunction pipelineDescriptor.fragmentFunction fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat mtkView.colorPixelFormat let pipelineState try device.makeRenderPipelineState(descriptor: pipelineDescriptor)这个配置过程包含以下关键步骤从源代码编译着色器库从库中获取顶点和片元函数创建管线描述符并配置生成最终的管线状态对象管线状态对象是Metal渲染的核心它定义了GPU如何处理几何数据和生成像素。4. 准备顶点数据我们要绘制一个三角形需要定义三个顶点的位置。在Metal中顶点坐标使用归一化设备坐标(NDC)范围是[-1,1]let vertices: [float4] [ float4( 0.0, 1.0, 0.0, 1.0), // 顶部顶点 float4(-1.0, -1.0, 0.0, 1.0), // 左下顶点 float4( 1.0, -1.0, 0.0, 1.0) // 右下顶点 ]每个顶点使用float4类型表示包含x、y、z、w四个分量。对于2D渲染z值设为0w值设为1。5. 创建命令缓冲区并渲染Metal使用命令缓冲区来组织GPU指令。以下是完整的渲染代码guard let commandBuffer device.makeCommandQueue()?.makeCommandBuffer(), let renderPassDescriptor mtkView.currentRenderPassDescriptor, let renderEncoder commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return } renderEncoder.setRenderPipelineState(pipelineState) renderEncoder.setVertexBytes(vertices, length: MemoryLayoutfloat4.stride * vertices.count, index: 0) renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3) renderEncoder.endEncoding() if let drawable mtkView.currentDrawable { commandBuffer.present(drawable) } commandBuffer.commit()这段代码完成了以下操作创建命令缓冲区和渲染命令编码器设置管线状态和顶点数据发出绘制命令绘制三角形结束编码并提交到GPU执行setVertexBytes方法将顶点数据上传到GPUdrawPrimitives告诉GPU如何绘制这些顶点。6. 完整代码整合将上述所有部分组合起来就得到了完整的70行Metal渲染代码import MetalKit import simd class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 1. 设置MTKView let mtkView MTKView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) mtkView.device MTLCreateSystemDefaultDevice() view.addSubview(mtkView) // 2. 着色器代码 let shaderSource #include metal_stdlib using namespace metal; vertex float4 vertexShader(uint vertexID [[vertex_id]], constant float4 *vertices [[buffer(0)]]) { return vertices[vertexID]; } fragment float4 fragmentShader() { return float4(1.0, 0.0, 0.0, 1.0); } // 3. 创建渲染管线 guard let device mtkView.device else { return } let library try! device.makeLibrary(source: shaderSource, options: nil) let vertexFunction library.makeFunction(name: vertexShader) let fragmentFunction library.makeFunction(name: fragmentShader) let pipelineDescriptor MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction vertexFunction pipelineDescriptor.fragmentFunction fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat mtkView.colorPixelFormat let pipelineState try! device.makeRenderPipelineState(descriptor: pipelineDescriptor) // 4. 顶点数据 let vertices: [float4] [ float4( 0.0, 1.0, 0.0, 1.0), float4(-1.0, -1.0, 0.0, 1.0), float4( 1.0, -1.0, 0.0, 1.0) ] // 5. 渲染 guard let commandBuffer device.makeCommandQueue()?.makeCommandBuffer(), let renderPassDescriptor mtkView.currentRenderPassDescriptor, let renderEncoder commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return } renderEncoder.setRenderPipelineState(pipelineState) renderEncoder.setVertexBytes(vertices, length: MemoryLayoutfloat4.stride * vertices.count, index: 0) renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3) renderEncoder.endEncoding() if let drawable mtkView.currentDrawable { commandBuffer.present(drawable) } commandBuffer.commit() } }运行这段代码你将在屏幕上看到一个红色的三角形。这虽然简单但包含了Metal渲染的所有核心要素。7. 进一步探索掌握了基础渲染流程后你可以尝试以下扩展修改顶点数据改变三角形的位置、大小或形状动态颜色让片元着色器根据位置返回不同颜色添加动画在每帧更新顶点位置实现动画效果绘制更复杂图形尝试绘制矩形或圆形由多个三角形组成Metal的强大之处在于它的高性能和灵活性。通过这个简单的三角形示例你已经迈出了Metal开发的第一步。接下来你可以探索3D渲染、纹理映射、光照计算等更高级的主题。