SceneKit - 在不使用单独的 SCNRenderer 的情况下从 SCNView 获取渲染场景作为 MTLTexture
SceneKit - Get the rendered scene from a SCNView as a MTLTexture without using a separate SCNRenderer
我的 SCNView 使用 Metal 作为渲染 API 我想知道是否有办法将渲染的场景抓取为 MTLTexture 而不必使用单独的 SCNRenderer?当我试图通过 SCNView 显示场景并通过 [=18=18=18=]MTLTexture 将场景重新渲染到屏幕外时,性能下降=]SCNRenderer(我试图抓取每一帧的输出)。
SCNView 让我可以访问 MTLDevice、MTLRenderCommandEncoder 和 [=18=它使用的 ]MTLCommandQueue,但不是底层的 MTLRenderPassDescriptor,我需要它才能获得 MTLTexture(通过 renderPassDescriptor.colorAttachments[0].texture
)
我尝试的一些替代方法是尝试使用 SCNView.snapshot()
获取 UIImage 并转换它,但性能更差。
** 警告:这可能不是适用于 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)
更新为 Swift 4:
Swift 4 不支持 dispatch_once(),并在替换函数中添加了@objc。这是更新的 swizzle 设置。经过测试,这对我来说效果很好。
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 即可访问它,而不是让扩展程序在外部存储它。
我的 SCNView 使用 Metal 作为渲染 API 我想知道是否有办法将渲染的场景抓取为 MTLTexture 而不必使用单独的 SCNRenderer?当我试图通过 SCNView 显示场景并通过 [=18=18=18=]MTLTexture 将场景重新渲染到屏幕外时,性能下降=]SCNRenderer(我试图抓取每一帧的输出)。
SCNView 让我可以访问 MTLDevice、MTLRenderCommandEncoder 和 [=18=它使用的 ]MTLCommandQueue,但不是底层的 MTLRenderPassDescriptor,我需要它才能获得 MTLTexture(通过 renderPassDescriptor.colorAttachments[0].texture
)
我尝试的一些替代方法是尝试使用 SCNView.snapshot()
获取 UIImage 并转换它,但性能更差。
** 警告:这可能不是适用于 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)
更新为 Swift 4:
Swift 4 不支持 dispatch_once(),并在替换函数中添加了@objc。这是更新的 swizzle 设置。经过测试,这对我来说效果很好。
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 即可访问它,而不是让扩展程序在外部存储它。