【问题标题】:SceneKit - Get the rendered scene from a SCNView as a MTLTexture without using a separate SCNRendererSceneKit - 从 SCNView 获取渲染场景作为 MTLTexture 而不使用单独的 SCNRenderer
【发布时间】:2017-03-10 20:00:43
【问题描述】:

我的 SCNView 使用 Metal 作为渲染 API,我想知道是否有一种方法可以将渲染的场景作为 MTLTexture 来获取,而无需使用单独的SCNRenderer?当我尝试通过 SCNView 显示场景并通过 SCNRenderer 将屏幕外场景重新渲染到 MTLTexture 时,性能下降(我正在尝试获取每一帧的输出)。

SCNView 允许我访问它使用的 MTLDeviceMTLRenderCommandEncoderMTLCommandQueue,但不能访问为了获得 MTLTexture(通过renderPassDescriptor.colorAttachments[0].texture

,我需要底层的 MTLRenderPassDescriptor

我尝试过的一些替代方法是尝试使用 SCNView.snapshot() 获取 UIImage 并对其进行转换,但性能更差。

【问题讨论】:

  • 根据这里所说的developer.apple.com/reference/metal/…,您需要创建一个新的 MTLRenderPassDescriptor,获取 MTLRenderPassAttachmentDescriptor 并将 MTLTexture 设置为您的渲染目标。鉴于您有可用的东西,例如 MTLRenderCommandEncoder,似乎他们让您可以绘制,而不是从中获取缓冲区。如果您可以创建自己的 MTLRenderPassDescriptor 并将其提供给 SCNView,您可以设置渲染目标。
  • 哦,看起来您可以使用 SCNRenderer developer.apple.com/reference/scenekit/scnrenderer 设置您自己的 MTLRenderPassDescriptor,这可能就是您需要做的。
  • 设置 SCNRenderer 会导致它重新渲染场景(因为它在自己的渲染周期中),从而导致性能下降。我希望只从已经渲染的场景中获取底层纹理(假设 SCNView 渲染成一个,这似乎是这种情况,因为它使用 CAMetalLayer 作为视图的支持层)。
  • 你是在做镜子吗?
  • 我在想这将是一个不反射的非常奇怪的镜子,纳米!不过我很好奇你是如何解决这个问题的。

标签: ios scenekit metal


【解决方案1】:

为 Swift 4 更新:

Swift 4 不支持 dispatch_once(),并且 @objc 添加到替换函数中。这是更新的调酒设置。经过测试,这对我来说效果很好。

extension CAMetalLayer {

    // Interface so user can grab this drawable at any time
    private struct nextDrawableExtPropertyData {
        static var _currentSceneDrawable : CAMetalDrawable? = nil
    }
    var currentSceneDrawable : CAMetalDrawable? {
        get {
            return nextDrawableExtPropertyData._currentSceneDrawable
        }
    }

    // The rest of this is just swizzling
    private static let doJustOnce : Any? = {
        print ("***** Doing the doJustOnce *****")
        CAMetalLayer.setupSwizzling()

        return nil
    }()

    public static func enableNextDrawableSwizzle() {
        _ = CAMetalLayer.doJustOnce
    }

    public static func setupSwizzling() {
        print ("***** Doing the setupSwizzling *****")

        let copiedOriginalSelector = #selector(CAMetalLayer.originalNextDrawable)
        let originalSelector = #selector(CAMetalLayer.nextDrawable)
        let swizzledSelector = #selector(CAMetalLayer.newNextDrawable)

        let copiedOriginalMethod = class_getInstanceMethod(self, copiedOriginalSelector)
        let originalMethod = class_getInstanceMethod(self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

        let oldImp = method_getImplementation(originalMethod!)
        method_setImplementation(copiedOriginalMethod!, oldImp)

        let newImp = method_getImplementation(swizzledMethod!)
        method_setImplementation(originalMethod!, newImp)

    }


    @objc func newNextDrawable() -> CAMetalDrawable? {
        // After swizzling, originalNextDrawable() actually calls the real nextDrawable()
        let drawable = originalNextDrawable()

        // Save the drawable
        nextDrawableExtPropertyData._currentSceneDrawable = drawable

        return drawable
    }

    @objc func originalNextDrawable() -> CAMetalDrawable? {
        // This is just a placeholder. Implementation will be replaced with nextDrawable.
        // ***** This will never be called *****
        return nil
    }
}

在您的 AppDelegate 中:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    // Swizzle
    CAMetalLayer.enableNextDrawableSwizzle()

    return true
}

更新为向 CAMetalLayer 添加 currentSceneDrawable 属性,因此您只需使用 layer.currentSceneDrawable 即可访问它,而不是让扩展程序将其存储在外部。

【讨论】:

    【解决方案2】:

    ** 警告:这可能不是 App Store 的正确方法。但它正在工作。

    第1步:使用swizzling将CAMetalLayer的nextDrawable方法换成新的。 为每个渲染循环保存 CAMetalDrawable。

    extension CAMetalLayer {
      public static func setupSwizzling() {
        struct Static {
          static var token: dispatch_once_t = 0
        }
    
        dispatch_once(&Static.token) {
          let copiedOriginalSelector = #selector(CAMetalLayer.orginalNextDrawable)
          let originalSelector = #selector(CAMetalLayer.nextDrawable)
          let swizzledSelector = #selector(CAMetalLayer.newNextDrawable)
    
          let copiedOriginalMethod = class_getInstanceMethod(self, copiedOriginalSelector)
          let originalMethod = class_getInstanceMethod(self, originalSelector)
          let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
    
          let oldImp = method_getImplementation(originalMethod)
          method_setImplementation(copiedOriginalMethod, oldImp)
          method_exchangeImplementations(originalMethod, swizzledMethod)
        }
      }
    
    
      func newNextDrawable() -> CAMetalDrawable? {
        let drawable = orginalNextDrawable()
        // Save the drawable to any where you want
        AppManager.sharedInstance.currentSceneDrawable = drawable
        return drawable
      }
    
      func orginalNextDrawable() -> CAMetalDrawable? {
        // This is just a placeholder. Implementation will be replaced with nextDrawable.
        return nil
      }
    }
    

    第 2 步: 在 AppDelegate 中设置 swizzling:didFinishLaunchingWithOptions

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
      CAMetalLayer.setupSwizzling()
      return true
    }
    

    第 3 步: 为您的 SCNView 的 CAMetalLayer 禁用 framebufferOnly(以便为 MTLTexture 调用 getBytes)

    if let metalLayer = scnView.layer as? CAMetalLayer {
      metalLayer.framebufferOnly = false
    }
    

    第 4 步: 在您的 SCNView 的委托 (SCNSceneRendererDelegate) 中,使用纹理

    func renderer(renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
        if let texture = AppManager.sharedInstance.currentSceneDrawable?.texture where !texture.framebufferOnly {
          AppManager.sharedInstance.currentSceneDrawable = nil
          // Play with the texture
        }
    }
    

    第 5 步(可选): 您可能需要在 CAMetalLayer 确认您获得的可绘制对象是您的目标。 (如果同时有多个 CAMetalLayer)

    【讨论】:

    • 我什至没有想到!非常聪明!谢谢你:)
    • @KevinBui 你在你的应用程序上试过这个吗?如果是这样,App Store 是否有任何问题?
    • @halileohalilei 我在 App Store 中发布了一个应用程序,审核没有任何问题。 itunes.apple.com/us/app/facemous/id1213785099?mt=8
    猜你喜欢
    • 2019-06-25
    • 2021-05-10
    • 2016-06-06
    • 2015-05-17
    • 2016-01-03
    • 1970-01-01
    • 1970-01-01
    • 2015-12-28
    • 2023-03-23
    相关资源
    最近更新 更多