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

时间:2016-10-28 01:00:02

标签: ios scenekit metal

我的 SCNView 正在使用Metal作为渲染API,我想知道是否有办法将渲染的场景作为 MTLTexture 抓取而无需单独使用的 SCNRenderer ?当我尝试通过 SCNView 显示场景并通过 SCNRenderer 将场景重新渲染到 MTLTexture 时,性能下降(我试图每帧抓取输出。

SCNView 让我可以访问它使用的 MTLDevice MTLRenderCommandEncoder MTLCommandQueue ,但不能访问我需要的基础 MTLRenderPassDescriptor 才能获得 MTLTexture (通过renderPassDescriptor.colorAttachments[0].texture

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

2 个答案:

答案 0 :(得分:2)

针对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
}

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

答案 1 :(得分:1)

**警告:这可能不适合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中设置调配: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上的drawable是您的目标。 (如果同时有多个CAMetalLayer)