Metal 框架之使用 MetalKit 来绘制视图内容

概述

《 Metal 框架之 MTKView 》 文章中介绍了如何使用 MTKView 来创建、配置和展示 Metal 对象,本文可作为上篇文章的补充篇,将介绍有关 Metal 渲染图形内容的基础知识。

本示例中,会创建一个 MetalKit 视图和一个渲染通道,然后指定擦除背景色,通过提交渲染通道命令来更新视图。

准备 MetalKit 视图

MetalKit 提供了一个叫做 MTKView 的类,它是 NSView(在 macOS 中)或 UIView(在 iOS 和 tvOS 中)的子类。 MTKView 封装了一些使用 Metal 渲染内容到屏幕的许多细节。

MTKView 是依托于 Metal 设备对象来创建资源的,因此需要将视图的设备属性与现有的 MTLDevice 相关联 。

```

_view.device = MTLCreateSystemDefaultDevice();

```

设置 MTKView 上的 clearColor 属性来更改视图内容的背景色,可以通过使用 MTLClearColorMake(_ : : : _:) 来设置颜色。

```

_view.clearColor = MTLClearColorMake(0.0, 0.5, 1.0, 1.0);

```

示例中没有动画的展示,所以当视图改变形状时才需要重新绘制:

```

_view.enableSetNeedsDisplay = YES;

```

委托绘图职责

MTKView 依赖于 App 向 Metal 提交命令来生成视图内容。 MTKView 使用委托模式来通知何时应该绘制。要接收委托回调,需要将视图的 delegate 指定为遵循 MTKViewDelegate 协议的对象。

```

_view.delegate = _renderer;

```

委托需要实现如下两个方法:

  • 每当内容的大小发生变化时,视图都会调用 mtkView(_ :drawableSizeWillChange:) 方法。当视图的窗口大小变化,或者当设备方向改变时(在 iOS 上),就会发生这种情况。这允许 App 根据视图的大小来调整分辨率。

  • 每当需要更新视图的内容时,视图都会调用 draw(in:) 方法。在此方法中,需要创建一个命令缓冲区,对命令进行编码来告诉 GPU 绘制的内容以及显示的时机,并把该命令缓冲区入队列以供 GPU 执行。这有时称为一帧,它所做的事情就是在屏幕上显示单个图像。

创建渲染通道描述符

绘图时,GPU 将结果存储到纹理中,纹理是包含图像数据并可被 GPU 访问的内存块。在本示例中,使用 MTKView 创建了许多纹理,视图显示的内容将被渲染到这些纹理中。一次性创建多个纹理,以便渲染到一个纹理时可以显示另一个纹理的内容。

绘制需要创建一个渲染通道,它是一系列渲染命令,通过它可以绘制一组纹理。在渲染通道中,纹理也称为渲染目标。要创建渲染通道,需要一个渲染通道描述符,Metal 中使用一个 MTLRenderPassDescriptor 的实例来表示。

```

MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;

if (renderPassDescriptor == nil)

{

    return;

}

```

渲染通道描述符描述了一组渲染目标,以及在渲染通道的开始和结束时应如何处理它们。视图返回一个渲染通道描述符,该描述符带有一个单一的颜色附件,附件指向一个纹理。默认情况下,在渲染通道开始时,渲染目标被擦除为与视图的 clearColor 属性匹配的纯色,并且在渲染通道结束时,所有更改都存储回纹理。

由于视图的渲染通道描述符可能为空,所以在创建渲染通道之前需要确保渲染通道描述符非空。

创建渲染通道

通过使用 MTLRenderCommandEncoder 对象将渲染通道描述符编码到命令缓冲区来创建渲染通道。调用命令缓冲区的 makeRenderCommandEncoder(descriptor:) 方法并传入渲染通道描述符生成 MTLRenderCommandEncoder 对象。

```

id commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

```

在本例中,没有对任何绘图命令进行编码,渲染通道所做的唯一事情就是擦除纹理,因此可以直接调用编码器的 endEncoding 方法来指示传递完成。

```

[commandEncoder endEncoding];

```

渲染到屏幕

绘制到纹理不会自动在屏幕展示出来。实际上,仅有一部分纹理才能在屏幕显示。在 Metal 中,可以在屏幕上显示的纹理由可绘制对象管理,通过可绘制对象纹理才会显示出来。

MTKView 自动创建可绘制对象来管理其纹理。读取 currentDrawable 属性以获取可绘制对象,渲染通道将渲染结果绘制到可绘制对象关联的纹理中。    

```

id drawable = view.currentDrawable;

```

在命令行缓冲区中调用 present(_ :)  方法,传入可绘制对象作为参数。

```

[commandBuffer presentDrawable:drawable];

```

该方法告诉 Metal,当命令缓冲区被调度执行时,Metal 应该与 Core Animation 协调配合以在渲染完成后显示纹理。当 Core Animation 呈现纹理时,该纹理成为视图的新内容。在本例中,这意味着擦除的纹理成为视图的新背景。视图背景的变化与 Core Animation 刷新用户界面元素同时发生。

提交命令缓冲区

最后提交命令缓冲区来操作 GPU 工作,进而渲染更新视图的帧。

总结

本文介绍有关 Metal 渲染图形内容的基础知识,使用 Metal 绘制视图内容时,需要创建一个 MetalKit 视图和一个渲染通道,配置必要的视图属性,将可绘制对象与命令缓冲区关联起来,最后提交命令缓冲区来完成绘制。

本文示例代码下载

​​​​​​​

 

在苹果的Metal框架中,创建一个立方体并在窗口上显示通常涉及以下几个步骤: 1. **环境设置**: 首先,你需要初始化Metal渲染管线、设备和命令队列。确保已经导入`MTLKit.h`文件,并创建一个`MTLLibrary`来加载着色器。 ```swift import MetalKit let device = MTLCreateSystemDefaultDevice() let commandQueue = device.makeCommandQueue() let library = try! MTLCreateSystemLibrary() ``` 2. **绘制缓冲区和视图**: 创建一个`MTLBuffer`来存储顶点数据(例如一个6面立方体的顶点坐标),并准备一个`MTKView`用于渲染Metal内容。 ```swift let vertexData = createCubeVertices() // 函数自行编写,生成立方体顶点数据 let verticesBuffer = device.makeBuffer(bytesNoCopy: vertexData, length: MemoryLayout<Vertex>.size * vertexData.count, options: .storageModeBuffer) let view = MTKView(frame: window.frame, device: device) view.delegate = self // 添加Metal视图的委托 ``` 3. **着色器和管线状态**: 编译顶点函数和片段函数(如果你已经有了的话),然后创建`MTLRenderPipelineState`,配置颜色格式等属性。 ```swift let vertexFunction = compileShaderFromSource(library, .vertex, "your_vertex_function") let fragmentFunction = compileShaderFromSource(library, .fragment, "your_fragment_function") let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction // 更多配置... let renderPipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDescriptor) ``` 4. **绘制循环**: 在`draw(in:commandEncoder:)`方法中,设置当前管线状态,绑定缓冲区,然后进行渲染。 ```swift func draw(_ commandEncoder: MTLRenderCommandEncoder) { let vertexBuffer = verticesBuffer.contents().assumingMemoryBound(to: Vertex.self) let descriptorSet = makeDescriptorSet(verticesBuffer) commandEncoder.setRenderPipelineState(renderPipelineState) commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) commandEncoder.drawPrimitives(type: .triangle, primitiveCount: 36, instanceCount: 1, baseVertex: 0, baseInstance: 0) // 渲染完成后的清理工作... } func makeDescriptorSet(buffer: MTLBuffer) -> MTLDescriptorSet { // 创建并配置descriptor set... } ``` 5. **展示**: 将`MTKView`添加到窗口,开始帧循环来更新和渲染。 ```swift window.addSubview(view) RunLoop.main.addUIObserver(self, forKeyPath: "displayLink.status") { _ in if view.displayLink.status == .running { // 开始渲染 view drawable.makeCurrent() draw(view.currentRenderCommandEncoder) } } ``` 记得处理`makeDescriptorSet`方法,根据需要配置着色器和纹理等资源。这就是在Metal中画立方体的基本流程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值