如何将 SCNRenderer 与现有的 MTLCommandBuffer 结合起来?

Posted

技术标签:

【中文标题】如何将 SCNRenderer 与现有的 MTLCommandBuffer 结合起来?【英文标题】:How to combine SCNRenderer with an existing MTLCommandBuffer? 【发布时间】:2019-02-01 06:11:51 【问题描述】:

我通过将 SDK 提供的 OpenGL 上下文 (EAGLContext) 与 SceneKit 的 SCNRenderer 的实例相结合,成功地将 Vuforia SDK Image Target Tracking feature 集成到 ios 项目中。这让我能够利用 SceneKit 的 3D API 的简单性,同时受益于 Vuforia 的高精度图像检测。现在,我想通过用 Metal 替换 OpenGL 来做同样的事情。

一些背景故事

我能够在 Vuforia 使用 OpenGL 绘制的实时视频纹理之上绘制 SceneKit 对象,而不会出现重大问题。

这是我在 OpenGL 中使用的简化设置:

func configureRenderer(for context: EAGLContext) 
    self.renderer = SCNRenderer(context: context, options: nil)
    self.scene = SCNScene()
    renderer.scene = scene

    // other scenekit setup


func render() 
    // manipulate scenekit nodes

    renderer.render(atTime: CFAbsoluteTimeGetCurrent())

Apple 在 iOS 12 上弃用 OpenGL

由于Apple announced that it is deprecating OpenGL on iOS 12,我认为尝试迁移此项目以使用Metal 而不是OpenGL 是一个好主意。

这在理论上应该很简单,因为 Vuforia 开箱即用地支持 Metal。但是,在尝试整合它时,我碰壁了。

问题

视图似乎只渲染 SceneKit 渲染器的结果,或 Vuforia 编码的纹理,但绝不会同时渲染两者。这取决于首先编码的内容。我该怎么做才能将这两个结果混合在一起?

简而言之,这是有问题的设置:

func configureRenderer(for device: MTLDevice) 
    let renderer = SCNRenderer(device: device, options: nil)
    self.scene = SCNScene()
    renderer.scene = scene

    // other scenekit setup


func render(viewport: CGRect, commandBuffer: MTLCommandBuffer, drawable: CAMetalDrawable) 
    // manipulate scenekit nodes

    let renderPassDescriptor = MTLRenderPassDescriptor()
    renderPassDescriptor.colorAttachments[0].texture = drawable.texture
    renderPassDescriptor.colorAttachments[0].loadAction = .load
    renderPassDescriptor.colorAttachments[0].storeAction = .store
    renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0, blue: 0, alpha: 0)

    renderer!.render(withViewport: viewport, commandBuffer: commandBuffer, passDescriptor: renderPassDescriptor)

我尝试在encoder.endEncoding 之后或commandBuffer.renderCommandEncoderWithDescriptor 之前调用render

metalDevice = MTLCreateSystemDefaultDevice();
metalCommandQueue = [metalDevice newCommandQueue];
id<MTLCommandBuffer>commandBuffer = [metalCommandQueue commandBuffer];

//// -----> call the `render(viewport:commandBuffer:drawable) here <------- \\\\

id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

// calls to encoder to render textures from Vuforia

[encoder endEncoding];

//// -----> or here <------- \\\\

[commandBuffer presentDrawable:drawable];
[commandBuffer commit];

在任何一种情况下,我只看到SCNRenderer 的结果OR 的结果encoder,但从来没有在同一个视图中。

在我看来,上面的编码传递和SCNRenderer.render 似乎正在覆盖彼此的缓冲区。

我在这里缺少什么?

【问题讨论】:

在我看来,您肯定想先绘制 Vuforia 内容。当您第二次绘制 SceneKit 时,SceneKit 未绘制到的帧部分是什么颜色?此外,您是否尝试过在 Xcode 中捕获 GPU 帧并仔细检查渲染通道属性是否正确?这将告诉您比我们可以从这些代码 sn-ps 推断出的更多信息。 您是否尝试过在屏幕外将 SceneKit 场景渲染为单独的纹理,然后将该纹理与 vuforia 结果过度混合?另外,支持 GPU 帧捕获的想法。 你解决了吗? 【参考方案1】:

我想我找到了答案。 我在 endEncoding 之后渲染 scnrenderer,但我正在创建一个新描述符。

    // Pass Metal context data to Vuforia Engine (we may have changed the encoder since
    // calling Vuforia::Renderer::begin)
    finishRender(UnsafeMutableRawPointer(Unmanaged.passRetained(drawable!.texture).toOpaque()), UnsafeMutableRawPointer(Unmanaged.passRetained(encoder!).toOpaque()))
    
    // ========== Finish Metal rendering ==========
    encoder?.endEncoding()
    
    // Commit the rendering commands
    // Command completed handler
    commandBuffer?.addCompletedHandler  _ in self.mCommandExecutingSemaphore.signal()
    let screenSize = UIScreen.main.bounds.size
    let newDescriptor = MTLRenderPassDescriptor()
    
    // Draw to the drawable's texture
    newDescriptor.colorAttachments[0].texture = drawable?.texture

    // Store the data in the texture when rendering is complete
    newDescriptor.colorAttachments[0].storeAction = MTLStoreAction.store
    // Use textureDepth for depth operations.
    newDescriptor.depthAttachment.texture = mDepthTexture;
    renderer?.render(atTime: 0, viewport: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height), commandBuffer: commandBuffer!, passDescriptor: newDescriptor)
    
    // Present the drawable when the command buffer has been executed (Metal
    // calls to CoreAnimation to tell it to put the texture on the display when
    // the rendering is complete)
    commandBuffer?.present(drawable!)
    
    // Commit the command buffer for execution as soon as possible
    commandBuffer?.commit()

【讨论】:

以上是关于如何将 SCNRenderer 与现有的 MTLCommandBuffer 结合起来?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Gatling 与现有的 Spring-Boot + Gradle 应用程序集成

如何将 Entity Framework Code First 与现有的 MS Access (.accdb) 数据库一起使用?

iOS - 如何将 Firebase Analytics 与现有的 Google Analytics、GoogleService-Info.plist 集成

将 grunt 与现有的 wordpress 实例一起使用

我的 react js 代码如何与现有的 Obj-C 逻辑交互?

尝试将 LowDB 与现有的 db.json 集成