Рендеринг закадрового SCNScene в UIImage

Как мне преобразовать за кадром SCNScene в UIImage?

Я знаю, что SCNView предоставляет метод -snapshot, но, к сожалению, он не работает для просмотра вне экрана. Ранее уже задавался похожий вопрос, где один из ответы предполагают чтение растровых данных из OpenGL с использованием glReadPixels, но этот подход не работает для меня со сценой за кадром.

Я безуспешно пытался выполнить рендеринг в контексте GLKView с помощью SCNRenderer.


person Erik Aigner    schedule 05.07.2014    source источник
comment
Дублированный вопрос имеет ответ для OS X до Yosemite, а также ответ для iOS 8 и OS X 10.10.   -  person David Rönnqvist    schedule 05.07.2014
comment
@DavidRönnqvist, это неправда. Ответы для SCNView на экране, и, как я уже сказал, мне нужно отобразить сцену за пределами экрана на iOS. Метод -snapshot не работает для просмотра вне экрана.   -  person Erik Aigner    schedule 06.07.2014
comment
Насколько я знаю, glReadPixels все еще должен быть допустимым подходом для OpenGL вне экрана.   -  person David Rönnqvist    schedule 06.07.2014
comment
glReadPixels также не работает на SCNView   -  person Erik Aigner    schedule 06.07.2014
comment
Например, не работает для просмотра вне экрана или не работает вообще?   -  person David Rönnqvist    schedule 06.07.2014
comment
Вне экрана, со снимком экрана и другими вещами работает нормально. Я уже подал радар для этого. snapshot из SCNView также должен работать за кадром, как это делает GLKView.   -  person Erik Aigner    schedule 06.07.2014
comment
Я повторно открыл ваш вопрос и отредактировал его, чтобы выделить то, что вы уже пробовали. Я по-прежнему думаю, что было бы полезно увидеть некоторые настройки, которые у вас есть для сцены, и некоторые подходы, которые не увенчались успехом.   -  person David Rönnqvist    schedule 06.07.2014


Ответы (2)


Swift 4 с SCNRenderer:

Вы можете использовать метод снимка SCNRenderer для довольно простого рендеринга внеэкранного SCNScene в UIImage.

Некоторые предостережения здесь, это использует металл. Я не знаю, где находится ограничение по версии устройства/iOS, но вам понадобится более новое устройство. Вы также не сможете запустить его на симуляторе.

Шаг 1. Настройте сцену как обычно:

// Set up your scene which won't be displayed
let hiddenScene = SCNScene()
[insert code to set up your nodes, cameras, and lights here]

Шаг 2 — Настройте SCNRenderer — на симуляторе рендерер будет равен нулю:

// Set up the renderer -- this returns nil on simulator
let renderer = SCNRenderer(device: MTLCreateSystemDefaultDevice(), options: nil)
renderer!.scene = hiddenScene

Шаг 3. Рендеринг сцены в UIImage:

// You can use zero for renderTime unless you are using animations,
// in which case, renderTime should be the current scene time.
let renderTime = TimeInterval(0)

// Output size
let size = CGSize(width:300, height: 150)

// Render the image
let image = renderer!.snapshot(atTime: renderTime, with: size,
                antialiasingMode: SCNAntialiasingMode.multisampling4X)

Если вы запускаете анимацию, вам нужно увеличить значение renderTime или установить его на индекс времени, который вы хотите отобразить. Например, если вы хотите визуализировать кадр на 4 секунды в сцене, вы должны установить его на 4. Это влияет только на анимацию — он не вернется во времени и не покажет вам исторический вид вашей сцены.

Например, если вы запускаете анимацию с помощью SCNNode.runAction, вы можете увеличивать время рендеринга каждые 60 секунд (0,16667 секунды), чтобы всякий раз, когда вы решите выполнить рендеринг, у вас было бы обновленное время рендеринга:

var timer : Timer
var renderTime = TimeInterval(0)

timer = Timer.scheduledTimer(withTimeInterval: 0.016667, repeats: true, block: { (t) in 
        self?.renderTime += 0.016667
    }   
})

Однако использование CADisplayLink, вероятно, является лучшим решением для определения времени.

Вот очень быстрый и грязный пример реализации.

person drewster    schedule 12.02.2018

Вот небольшой код, который я написал. Вы можете использовать его примечание к примечанию.

public extension SCNRenderer {

    public func renderToImageSize(size: CGSize, floatComponents: Bool, atTime time: NSTimeInterval) -> CGImage? {

        var thumbnailCGImage: CGImage?

        let width = GLsizei(size.width), height = GLsizei(size.height)
        let samplesPerPixel = 4

        #if os(iOS)
            let oldGLContext = EAGLContext.currentContext()
            let glContext = unsafeBitCast(context, EAGLContext.self)

            EAGLContext.setCurrentContext(glContext)
            objc_sync_enter(glContext)
        #elseif os(OSX)
            let oldGLContext = CGLGetCurrentContext()
            let glContext = unsafeBitCast(context, CGLContextObj.self)

            CGLSetCurrentContext(glContext)
            CGLLockContext(glContext)
        #endif

        // set up the OpenGL buffers
        var thumbnailFramebuffer: GLuint = 0
        glGenFramebuffers(1, &thumbnailFramebuffer)
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), thumbnailFramebuffer); checkGLErrors()

        var colorRenderbuffer: GLuint = 0
        glGenRenderbuffers(1, &colorRenderbuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), colorRenderbuffer)
        if floatComponents {
            glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_RGBA16F), width, height)
        } else {
            glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_RGBA8), width, height)
        }
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), colorRenderbuffer); checkGLErrors()

        var depthRenderbuffer: GLuint = 0
        glGenRenderbuffers(1, &depthRenderbuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), depthRenderbuffer)
        glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_DEPTH_COMPONENT24), width, height)
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_DEPTH_ATTACHMENT), GLenum(GL_RENDERBUFFER), depthRenderbuffer); checkGLErrors()

        let framebufferStatus = Int32(glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER)))
        assert(framebufferStatus == GL_FRAMEBUFFER_COMPLETE)
        if framebufferStatus != GL_FRAMEBUFFER_COMPLETE {
            return nil
        }

        // clear buffer
        glViewport(0, 0, width, height)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); checkGLErrors()

        // render
        renderAtTime(time); checkGLErrors()

        // create the image
        if floatComponents { // float components (16-bits of actual precision)

            // slurp bytes out of OpenGL
            typealias ComponentType = Float

            var imageRawBuffer = [ComponentType](count: Int(width * height) * samplesPerPixel * sizeof(ComponentType), repeatedValue: 0)
            glReadPixels(GLint(0), GLint(0), width, height, GLenum(GL_RGBA), GLenum(GL_FLOAT), &imageRawBuffer)

            // flip image vertically — OpenGL has a different 'up' than CoreGraphics
            let rowLength = Int(width) * samplesPerPixel
            for rowIndex in 0..<(Int(height) / 2) {
                let baseIndex = rowIndex * rowLength
                let destinationIndex = (Int(height) - 1 - rowIndex) * rowLength

                swap(&imageRawBuffer[baseIndex..<(baseIndex + rowLength)], &imageRawBuffer[destinationIndex..<(destinationIndex + rowLength)])
            }

            // make the CGImage
            var imageBuffer = vImage_Buffer(
                data: UnsafeMutablePointer<Float>(imageRawBuffer),
                height: vImagePixelCount(height),
                width: vImagePixelCount(width),
                rowBytes: Int(width) * sizeof(ComponentType) * samplesPerPixel)

            var format = vImage_CGImageFormat(
                bitsPerComponent: UInt32(sizeof(ComponentType) * 8),
                bitsPerPixel: UInt32(sizeof(ComponentType) * samplesPerPixel * 8),
                colorSpace: nil, // defaults to sRGB
                bitmapInfo: CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue | CGBitmapInfo.FloatComponents.rawValue),
                version: UInt32(0),
                decode: nil,
                renderingIntent: kCGRenderingIntentDefault)

            var error: vImage_Error = 0
            thumbnailCGImage = vImageCreateCGImageFromBuffer(&imageBuffer, &format, nil, nil, vImage_Flags(kvImagePrintDiagnosticsToConsole), &error)!.takeRetainedValue()

        } else { // byte components

            // slurp bytes out of OpenGL
            typealias ComponentType = UInt8

            var imageRawBuffer = [ComponentType](count: Int(width * height) * samplesPerPixel * sizeof(ComponentType), repeatedValue: 0)
            glReadPixels(GLint(0), GLint(0), width, height, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), &imageRawBuffer)

            // flip image vertically — OpenGL has a different 'up' than CoreGraphics
            let rowLength = Int(width) * samplesPerPixel
            for rowIndex in 0..<(Int(height) / 2) {
                let baseIndex = rowIndex * rowLength
                let destinationIndex = (Int(height) - 1 - rowIndex) * rowLength

                swap(&imageRawBuffer[baseIndex..<(baseIndex + rowLength)], &imageRawBuffer[destinationIndex..<(destinationIndex + rowLength)])
            }

            // make the CGImage
            var imageBuffer = vImage_Buffer(
                data: UnsafeMutablePointer<Float>(imageRawBuffer),
                height: vImagePixelCount(height),
                width: vImagePixelCount(width),
                rowBytes: Int(width) * sizeof(ComponentType) * samplesPerPixel)

            var format = vImage_CGImageFormat(
                bitsPerComponent: UInt32(sizeof(ComponentType) * 8),
                bitsPerPixel: UInt32(sizeof(ComponentType) * samplesPerPixel * 8),
                colorSpace: nil, // defaults to sRGB
                bitmapInfo: CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue),
                version: UInt32(0),
                decode: nil,
                renderingIntent: kCGRenderingIntentDefault)

            var error: vImage_Error = 0
            thumbnailCGImage = vImageCreateCGImageFromBuffer(&imageBuffer, &format, nil, nil, vImage_Flags(kvImagePrintDiagnosticsToConsole), &error)!.takeRetainedValue()
        }

        #if os(iOS)
            objc_sync_exit(glContext)
            if oldGLContext != nil {
                EAGLContext.setCurrentContext(oldGLContext)
            }
        #elseif os(OSX)
            CGLUnlockContext(glContext)
            if oldGLContext != nil {
                CGLSetCurrentContext(oldGLContext)
            }
        #endif

        return thumbnailCGImage
    }
}


func checkGLErrors() {
    var glError: GLenum
    var hadError = false
    do {
        glError = glGetError()
        if glError != 0 {
            println(String(format: "OpenGL error %#x", glError))
            hadError = true
        }
    } while glError != 0
    assert(!hadError)
}
person Wil Shipley    schedule 05.05.2015
comment
Когда я попробовал этот код в Xcode9, я получил много ошибок компиляции. Мне потребовалось некоторое время, чтобы решить их, но все же одна ошибка компиляции осталась нерешенной. У вас есть обновленная версия вашего кода. Спасибо!!! - person echo; 16.08.2018
comment
У меня нет никаких обновлений для этой версии, потому что я отказался от GL/EAGL несколько лет назад. Однако у меня есть версия, которая работает с металлом. - person Wil Shipley; 17.08.2018
comment
Тогда не мог бы ты опубликовать здесь свою метал-версию? Спасибо большое! - person echo; 17.08.2018