目前我有一些物体的 3D 场景。我想将其渲染为 2D 纹理(例如 MTLTexture)。之后,我想在屏幕上实时显示这个渲染纹理(例如在 MTKView 或 SKScene 中),并同时将其写入电影中。
现在我有 SCNRenderer 和 offscreenTexture 用于渲染:
func setupMetal() {
device = MTLCreateSystemDefaultDevice()
commandQueue = device.makeCommandQueue()
renderer = SCNRenderer(device: device, options: nil)
}
func setupTexture() {
var rawData0 = [UInt8](repeating: 0, count: Int(textureSizeX) * Int(textureSizeY) * 4)
let bytesPerRow = 4 * Int(textureSizeX)
let bitmapInfo = CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue
let context = CGContext(data: &rawData0, width: Int(textureSizeX), height: Int(textureSizeY), bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: bitmapInfo)!
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(x: 0, y: 0, width: CGFloat(textureSizeX), height: CGFloat(textureSizeY)))
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: MTLPixelFormat.rgba8Unorm, width: Int(textureSizeX), height: Int(textureSizeY), mipmapped: false)
textureDescriptor.usage = MTLTextureUsage(rawValue: MTLTextureUsage.renderTarget.rawValue | MTLTextureUsage.shaderRead.rawValue)
let textureA = device.makeTexture(descriptor: textureDescriptor)
let region = MTLRegionMake2D(0, 0, Int(textureSizeX), Int(textureSizeY))
textureA?.replace(region: region, mipmapLevel: 0, withBytes: &rawData0, bytesPerRow: Int(bytesPerRow))
offscreenTexture = textureA
}
private func initializeRenderPipelineState() {
guard let device = device, let library = device.makeDefaultLibrary() else { return }
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.sampleCount = 1
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgr10_xr_srgb
pipelineDescriptor.depthAttachmentPixelFormat = .invalid
pipelineDescriptor.vertexFunction = library.makeFunction(name: "mapTexture")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "displayTexture")
do {
renderPipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
}
catch {
assertionFailure("Failed creating a render state pipeline. Can't render the texture without one.")
return
}
}
和 MTKView 在屏幕上呈现它:
func initializeMetalView() {
metalView = MTKView(frame: bounds, device: device)
metalView.delegate = self
metalView.framebufferOnly = true
metalView.colorPixelFormat = .bgr10_xr_srgb
metalView.contentScaleFactor = UIScreen.main.scale
metalView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(metalView, at: 0)
}
在每一帧上,我都试图将场景绘制到纹理并将其呈现在 MTKView
func draw(in view: MTKView) {
let commandBuffer = commandQueue.makeCommandBuffer()
let viewport = CGRect(x: 0, y: 0, width: CGFloat(textureSizeX), height: CGFloat(textureSizeY))
//write to offscreenTexture
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = offscreenTexture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0.0)
renderPassDescriptor.colorAttachments[0].storeAction = .store
renderer.scene = scene
renderer.pointOfView = scene.rootNode.childNodes[0]
renderer.render(atTime: 0, viewport: viewport, commandBuffer: commandBuffer!, passDescriptor: renderPassDescriptor)
guard let currentRenderPassDescriptor = metalView.currentRenderPassDescriptor, let currentDrawable = metalView.currentDrawable, let renderPipelineState = renderPipelineState else {
return
}
let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: currentRenderPassDescriptor)
encoder?.pushDebugGroup("RenderFrame")
encoder?.setRenderPipelineState(renderPipelineState)
encoder?.setFragmentTexture(offscreenTexture, index: 0)
encoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1)
encoder?.popDebugGroup()
encoder?.endEncoding()
commandBuffer?.present(currentDrawable)
commandBuffer?.commit()
}
这种技术甚至可以工作,但太慢了。我设法用正确的 fps 和另一个场景视图来做到这一点。我创建了一个平面并将 offscreenTexture 作为材质附加到这个平面上。但我想在 2D 引擎中进行(因为我认为它应该更简单)(并且因为我希望能够像 2D 图像一样在屏幕上移动它)。
PS如果您对SCNView有所了解,请看我的另一个问题。也许您会提出更好的解决方案。基本上我想要 1)向用户呈现全屏背景和 3D 对象(使用透视投影)(简单) 2)我希望能够将 SCNView 的一部分渲染到电影中。(我知道如何用全屏纹理来做,但我没有找到如何裁剪它的快速解决方案)
更新 当我看到这个电话的主要问题时:
guard let currentDrawable = metalView.currentDrawable else { return }
当我的应用程序大约需要“0.013198”秒,但一段时间后只需要“5.388259e-05”秒并且一切正常。