使用 Metal 对 SceneKit 渲染进行抗锯齿处理

Posted

技术标签:

【中文标题】使用 Metal 对 SceneKit 渲染进行抗锯齿处理【英文标题】:Antialiasing a SceneKit rendering with Metal 【发布时间】:2021-03-05 13:50:07 【问题描述】:

我是 Metal 新手。我正在使用 this Apple sample code 使用 Metal 渲染 SceneKit 场景。 TLDR;它调用SCNRenderer'srender 函数并传入命令缓冲区。我正在为 Big Sur 编译。

它可以工作,但它没有抗锯齿。我已经尝试了几种方法来实现它,您可以在下面的更新中看到。

如果没有 Metal,我只需在 SCNRenderer 上将 isJitteringEnabled 设置为 true,就可以得到漂亮(而且速度很慢)的 96-ish-pass 渲染。如果我尝试用 Metal 来做这件事,我会得到奇怪的像素格式不匹配,所以我怀疑这两者不兼容。

据我所知,对于 Metal,实现抗锯齿的最简单方法是在渲染管道中启用多重采样(我知道怎么做)——并且使用多重采样纹理(MTLTextureType.type2DMultisample)。 This partial answer 支持我的假设。

这就是问题所在。从CVMetalTextureCacheCVMetalTextureCacheCreateTextureFromImage 获取纹理时,我不知道如何更改纹理类型。这似乎是 Core Video 对 Metal 支持的限制?

我的完整source is here

就是这样。这篇文章的其余部分是关于我尝试过的东西的更多细节。

(我认为这可能使用着色器。我也愿意接受该解决方案,但我不知道从哪里开始。This example 无法编译,this example 用于 GSLS)


我的像素缓冲区是这样的

        let pixelbufferAttributes = [
            kCVPixelBufferPixelFormatTypeKey : kCVPixelFormatType_32BGRA,
            kCVPixelBufferWidthKey: exportSettings.width,
            kCVPixelBufferHeightKey : exportSettings.height,
        kCVPixelBufferMetalCompatibilityKey: true] as [String: Any]

对于每一帧,它从池中创建一个新的像素缓冲区,将其包装在缓存中的金属纹理中,就像这样

        let pixelFormat = MTLPixelFormat.bgra8Unorm_srgb
        var optionalMetalTexture: CVMetalTexture?
        err = CVMetalTextureCacheCreateTextureFromImage(
            kCFAllocatorDefault,
            metalTextureCache, // object prop
            pixelBuffer,
            nil, // texture attributes
            pixelFormat,
            exportSettings.width,
            exportSettings.height,
            0, // planeIndex
            &optionalMetalTexture)
        guard err == noErr, let metalTexture = optionalMetalTexture else 
            fatalError("Failed to create metal texture wrapper from pixel bufffer \(err)")
        

尝试:更改纹理描述符

由于我是从 CVPixelbufferCVMetalTextureCacheCreateTextureFromImage 创建金属纹理,我不知道如何设置它的属性并使其成为多样本。

尝试:尝试 H264

没有改变任何东西。还尝试使用带 alpha 的 HEVC 仅更改 alpha 质量,但没有任何变化。

尝试:启用多重采样

我能够让我的管道接收我想要的多重采样,但是由于没有为多重采样设置纹理(更准确地说是 .2DMultisample (docs) 类型的 MTLTexture),它崩溃了

尝试:复制Core Video创建的MTLTexture

我尝试使用MTLBlitCommandEncoder 将Core Video 给我的纹理复制到我设置的具有正确属性的纹理中。但它崩溃告诉我属性不匹配。

我开始认为没有办法解决这个问题?

【问题讨论】:

你的目标设备是什么? 这是一个 macOS 应用程序,我的设备是 16",配备 AMD Radeon Pro 5500M 8 GB 你的 github 源 == 苹果示例? @0xBFE1A8 是的,我在那次提交中唯一改变的是抖动线。样本本身在这里:developer.apple.com/documentation/avfoundation/… 【参考方案1】:

启用多重采样是正确的想法。 以下补丁显示了如何启用它。

--- a/HEVC-Videos-With-Alpha-AssetWriting/HEVC-Videos-With-Alpha-AssetWriting/AppDelegate.swift
+++ b/HEVC-Videos-With-Alpha-AssetWriting/HEVC-Videos-With-Alpha-AssetWriting/AppDelegate.swift
@@ -32,6 +32,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCNSceneRendererDelegate 
     let renderer = SCNRenderer(device: nil, options: nil)
     var lampMaterials: SCNNode!
     var metalTextureCache: CVMetalTextureCache!
+    let msaaSampleCount = 1
+    var metalMultisampledTexture: MTLTexture!
     
     // Export
     var frameCounter = 0
@@ -61,6 +63,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCNSceneRendererDelegate 
             fatalError("Cannot create metal texture cache: \(err)")
         
         metalTextureCache = optionalMetalTextureCache
+        
+        if (msaaSampleCount > 1) 
+            let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: MTLPixelFormat.bgra8Unorm_srgb,
+                                                                             width: ExportSettings.width,
+                                                                             height: ExportSettings.height,
+                                                                             mipmapped: false)
+            textureDescriptor.usage = .renderTarget
+            textureDescriptor.storageMode = .private
+            textureDescriptor.textureType = .type2DMultisample
+            textureDescriptor.sampleCount = msaaSampleCount
+            metalMultisampledTexture = renderer.device!.makeTexture(descriptor: textureDescriptor)
+        
     
     
     /// Render next frame and call the frame completion handler
@@ -106,7 +120,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCNSceneRendererDelegate 
         let renderPassDescriptor = MTLRenderPassDescriptor()
         renderPassDescriptor.colorAttachments[0].loadAction = .clear
         renderPassDescriptor.colorAttachments[0].clearColor = clearColor
-        renderPassDescriptor.colorAttachments[0].texture = CVMetalTextureGetTexture(metalTexture)
+        if (msaaSampleCount > 1) 
+            renderPassDescriptor.colorAttachments[0].texture = metalMultisampledTexture
+            renderPassDescriptor.colorAttachments[0].resolveTexture = CVMetalTextureGetTexture(metalTexture)
+            renderPassDescriptor.colorAttachments[0].storeAction = .multisampleResolve
+        
+        else 
+            renderPassDescriptor.colorAttachments[0].texture = CVMetalTextureGetTexture(metalTexture)
+        
         renderer.render(atTime: currentPresentationTime.seconds,
                         viewport: ExportSettings.viewport,
                         commandBuffer: commandBuffer,

【讨论】:

就是这样!谢谢。是 resolveTexture 绊倒了我 对于将来需要此功能的任何人,请将 sampleCount 设置为 4,并考虑使用 supportsTextureSampleCount(_:) 测试设备是否支持 8 个样本

以上是关于使用 Metal 对 SceneKit 渲染进行抗锯齿处理的主要内容,如果未能解决你的问题,请参考以下文章

高质量渲染——RealityKit vs SceneKit vs Metal

Metal / SceneKit Fragment Shaders - 如何避免在其他几何体上渲染?

Metal 2:MetalKit (MTKView) 和 SceneKit 互操作性

将纯 Metal-API 与 SceneKit 或 SpriteKit 一起使用

SceneKit 中的金属生成网格

SceneKit - 从 SCNView 获取渲染场景作为 MTLTexture 而不使用单独的 SCNRenderer