SceneKit - 从 SCNView 获取渲染场景作为 MTLTexture 而不使用单独的 SCNRenderer
Posted
技术标签:
【中文标题】SceneKit - 从 SCNView 获取渲染场景作为 MTLTexture 而不使用单独的 SCNRenderer【英文标题】:SceneKit - Get the rendered scene from a SCNView as a MTLTexture without using a separate SCNRenderer 【发布时间】:2017-03-10 20:00:43 【问题描述】:我的 SCNView 使用 Metal 作为渲染 API,我想知道是否有一种方法可以将渲染的场景作为 MTLTexture 来获取,而无需使用单独的SCNRenderer?当我尝试通过 SCNView 显示场景并通过 SCNRenderer 将屏幕外场景重新渲染到 MTLTexture 时,性能下降(我正在尝试获取每一帧的输出)。
SCNView 允许我访问它使用的 MTLDevice、MTLRenderCommandEncoder 和 MTLCommandQueue,但不能访问为了获得 MTLTexture(通过renderPassDescriptor.colorAttachments[0].texture
)
我尝试过的一些替代方法是尝试使用 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 作为视图的支持层)。 你是在做镜子吗? 我在想这将是一个不反射的非常奇怪的镜子,纳米!不过我很好奇你是如何解决这个问题的。 【参考方案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以上是关于SceneKit - 从 SCNView 获取渲染场景作为 MTLTexture 而不使用单独的 SCNRenderer的主要内容,如果未能解决你的问题,请参考以下文章
SceneKit:在 SCNView 上渲染 SpriteKit 粒子系统时应用程序崩溃,当所有代码似乎都是系统代码的一部分时如何调试
SceneKit开发教程02 SCNScene和SCNView
无法使用 SceneKit 从 OpenGL 获取 CVPixelBufferRef 数据
iOS 12 中autoenablesDefaultLighting 太亮,SCNView.pointOfView 无效