Как сделать MTLTexture, сгенерированный SCNRenderer? - PullRequest
0 голосов
/ 03 июня 2018

В настоящее время у меня есть 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) представить пользователю полноэкранный фон и трехмерный объект (используя перспективную проекцию) (легко) 2) я хочу иметь возможность визуализировать ЧАСТЬ SCNView для фильма.(Я знаю, как сделать это с полноэкранной текстурой, но я не нашел быстрого решения, как обрезать его)

Обновление Как я вижу основную проблему в этом вызове:

guard let currentDrawable = metalView.currentDrawable else { return }

Когда мое приложение занимает около 0,013198 секунд, но через некоторое время это занимает всего «5,388259e-05» секунд, и все работает отлично.

...