位置:首页 > Swift > Metal离屏渲染进阶教程与实战技巧

Metal离屏渲染进阶教程与实战技巧

时间:2026-08-21  |  作者:实验室老王  |  阅读:0

引言

Hi,大家好,我是一牛。今天我想和大家分享的是Metal的离屏渲染。我们先回顾下离屏渲染的概念。

离屏渲染(Off-screen Rendering) 是指在图形处理过程中,图像数据的渲染并不直接显示在屏幕上,而是先渲染到一个内存缓冲区(如纹理、帧缓冲区等),然后再将该图像数据从内存缓冲区传输到屏幕或其他输出设备上。离屏渲染常用于各种图形操作,如图像处理、阴影生成、后期处理效果等。

我们将创建两个渲染通道,一个通道用来渲染(离屏)生成纹理,另一个通道将新生成的纹理渲染到屏幕上。我们也会用到绘制三角形渲染图片的知识,让我们开启今天的学习吧。

创建纹理

func createOffscreenTetureDescriptor() -> MTLTextureDescriptor {
    let descriptor = MTLTextureDescriptor()
    descriptor.textureType = .type2D
    descriptor.width = 512
    descriptor.height = 512
    descriptor.pixelFormat = .rgba8Unorm
    descriptor.usage = [.renderTarget, .shaderRead]
    return descriptor
}
offscreenTexture = device.makeTexture(descriptor: textureDescriptor)

在这里,有个要点得格外留意,那就是usage属性。咱们得把数据渲染到纹理中,而且在第二个渲染通道里还得读取这个纹理。为了实现这一点,就得把它设置成 MTLTextureUsage.renderTarget以及 MTLTextureUsage.shaderRead。在这儿,我们构建了一个长宽均为512的纹理。

创建离屏渲染通道

offscreenRenderPassDescriptor = MTLRenderPassDescriptor()
offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture
offscreenRenderPassDescriptor.colorAttachments[0].loadAction = .clear
offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .store
offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.5, green: 1.0, blue: 1.0, alpha: 1.0)

这是我们创建的第一个渲染通道,这个通道负责渲染生成纹理。我们将新生成的纹理赋值给colorAttachments[0].texture, 告诉这个渲染通道将数据渲染到这个离屏的纹理上。

加载操作(load action)决定了在渲染通道开始时,纹理的初始内容。这里我们给它设置的是.clear,表明在GPU绘制之前抹除渲染目标的内容。

存储操作(store action)只在渲染通道完成后执行,在这里,我们给它配置成.store,表明我们将使用这个纹理。

如图,这是离屏渲染产生的纹理

创建渲染管道

// Offscreen pipeline
let offscreenPipelineDescriptor = MTLRenderPipelineDescriptor()
offscreenPipelineDescriptor.label = "Offscreen Render Pinepline"
offscreenPipelineDescriptor.vertexFunction = library.makeFunction(name: "offscreenVertexShader")
offscreenPipelineDescriptor.fragmentFunction = library.makeFunction(name: "offscreenFragmentShader")
offscreenPipelineDescriptor.colorAttachments[0].pixelFormat = offscreenTexture.pixelFormat
do {
    offscreenPipelineState = try device.makeRenderPipelineState(descriptor: offscreenPipelineDescriptor)
} catch {
    fatalError("fail to create offscreen pipelineState")
}

在这里,我们需要注意的是,描述符的像素格式pixelFormat需要和离屏纹理的像素格式一样。

离屏渲染纹理

let commanderEncoder = commanderBuffer.makeRenderCommandEncoder(descriptor: offscreenRenderPassDescriptor)
commanderEncoder.setRenderPipelineState(offscreenPipelineState)
let vertices = [
    //左下角
    OffscreenVertex(position: [-1.0, -1.0, 1.0, 1.0], color: [1.0, 0.0, 0.0, 1.0]),
    //正上方
    OffscreenVertex(position: [ 0.0,  1.0, 1.0, 1.0], color: [1.0, 0.5, 0.0, 1.0]),
    //右下角
    OffscreenVertex(position: [ 1.0, -1.0, 1.0, 1.0], color: [0.0, 1.0, 0.0, 1.0]),
]
let vertexBuffer = device.makeBuffer(bytes: vertices, length: MemoryLayout<OffscreenVertex>.stride * vertices.count)
commanderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commanderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count)
commanderEncoder.endEncoding()

在这里我们绘制了一个简单三角形,感到困惑的话请参阅之前的文章。

绘制纹理到屏幕

这里基本上是复制了渲染图片的过程

// Drawable Render Pass
guard let renderPassDesriptor = view.currentRenderPassDescriptor else {
    return
}
let commanderEncoder = commanderBuffer.makeRenderCommandEncoder(descriptor: renderPassDesriptor)
commanderEncoder.setRenderPipelineState(drawablePipelineState)
if let offscreenTexture {
    let vertices = [
        // 左上角
        Vertex(pixelPosition: [-256,  256], textureCoordinate: [0.0, 0.0]),
        // 左下角
        Vertex(pixelPosition: [-256, -256], textureCoordinate: [0.0, 1.0]),
        // 右下角
        Vertex(pixelPosition: [ 256, -256], textureCoordinate: [1.0, 1.0]),
        // 左上角
        Vertex(pixelPosition: [-256,  256], textureCoordinate: [0.0, 0.0]),
        // 右下角
        Vertex(pixelPosition: [ 256, -256], textureCoordinate: [1.0, 1.0]),
        // 右上角
        Vertex(pixelPosition: [ 256,  256], textureCoordinate: [1.0, 0.0]),
    ]
    let vertexBuffer = device.makeBuffer(bytes: vertices, length: MemoryLayout<Vertex>.stride * vertices.count)
    commanderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
    commanderEncoder.setVertexBytes(&viewPortSize, length: MemoryLayout.stride, index: 1)
    commanderEncoder.setFragmentTexture(offscreenTexture, index: 0)
    commanderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count)
}
commanderEncoder.endEncoding()
guard let drawable = view.currentDrawable else {
    return
}
commanderBuffer.present(drawable)
commanderBuffer.commit()

在第一个渲染通道结束后,我们将渲染得到的纹理交给第二个渲染通道,最终绘制到屏幕上。

结语

离屏渲染作为图形渲染领域的一项关键技术,在图像处理、后期效果以及复杂渲染管线中有着广泛应用。它将渲染结果输出至内存缓冲区,而非直接显示,这一独特方式不仅显著提升了性能,有效减少了屏幕更新时的延迟,还为后期处理与多重效果合成提供了有力支撑。在当下的图形应用中,熟练掌握并合理运用离屏渲染技术,无疑是提升渲染效率与视觉效果的重要途径。随着图形硬件的持续发展,离屏渲染在未来必将继续发挥其关键作用,助力开发者打造更加精细、动态的视觉体验。

项目源码

《夸克》非常好用的免费AI浏览器

下载APP查看

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多