Rendering a SceneKit scene to video output

前端 未结 4 655
隐瞒了意图╮
隐瞒了意图╮ 2020-12-12 23:17

As a primarily high-level/iOS dev, I\'m interested in using SceneKit for animation projects.

I\'ve been having fun with SceneKit for some months now, despite it obvi

4条回答
  •  野趣味
    野趣味 (楼主)
    2020-12-12 23:31

    ** This is the answer for SceneKit using Metal.

    ** Warning: This may not be a proper method for App Store. But it's working.

    Step 1: Swap the method of nextDrawable of CAMetalLayer with a new one using swizzling. Save the CAMetalDrawable for each render loop.

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

    Step 2: Setup the swizzling in AppDelegate: didFinishLaunchingWithOptions

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

    Step 3: Disable framebufferOnly for your's SCNView's CAMetalLayer (In order to call getBytes for MTLTexture)

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

    Step 4: In your SCNView's delegate (SCNSceneRendererDelegate), play with the texture

    func renderer(renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
        if let texture = AppManager.sharedInstance.currentSceneDrawable?.texture where !texture.framebufferOnly {
          AppManager.sharedInstance.currentSceneDrawable = nil
          // Get image from texture
          let image = texture.toImage()
          // Use the image for video recording
        }
    }
    
    extension MTLTexture {
      func bytes() -> UnsafeMutablePointer {
        let width = self.width
        let height   = self.height
        let rowBytes = self.width * 4
        let p = malloc(width * height * 4) //Beware for memory leak
        self.getBytes(p, bytesPerRow: rowBytes, fromRegion: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
        return p
      }
    
      func toImage() -> UIImage? {
        var uiImage: UIImage?
        let p = bytes()
        let pColorSpace = CGColorSpaceCreateDeviceRGB()
        let rawBitmapInfo = CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
        let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
    
        let selftureSize = self.width * self.height * 4
        let rowBytes = self.width * 4
        let provider = CGDataProviderCreateWithData(nil, p, selftureSize, {_,_,_ in })!
    
        if let cgImage = CGImageCreate(self.width, self.height, 8, 32, rowBytes, pColorSpace, bitmapInfo, provider, nil, true, CGColorRenderingIntent.RenderingIntentDefault) {
          uiImage = UIImage(CGImage: cgImage)
        }
        return uiImage
      }
    
      func toImageAsJpeg(compressionQuality: CGFloat) -> UIImage? {
      }
    }
    

    Step 5 (Optional): You may need to confirm the drawable at CAMetalLayer you are getting is your target. (If more then one CAMetalLayer at the same time)

提交回复
热议问题