You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

glReadPixels转UIImage黑屏:Vuforia相机截图在iOS9.3.5设备异常

Vuforia相机截图在iOS 9.3.5的iPad 2上返回黑屏的解决方案

我之前碰到过一模一样的情况!这种黑屏问题本质是iOS 9及更早版本的系统截图机制和Vuforia的OpenGL ES渲染流程不兼容——你用的是基于UIScreen的常规截图方式,但在旧系统里,这种方法抓不到Vuforia实时渲染的相机画面,只能拿到空的图层内容,也就是黑屏。

先把你的代码补全(看片段应该是这类常见实现):

你的完整代码大概是这样的:

static public func takeScreenshot() -> UIImage? {
    let xCoord: Int = 0
    let yCoord: Int = 0
    let screen = UIScreen.main.bounds
    let scale = UIScreen.main.scale
    let width = screen.width * scale
    let height = screen.height * scale
    let dataLength: Int = Int(width) * Int(height) * 4
    
    let imageBuffer = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: dataLength)
    defer { imageBuffer.deallocate() }
    
    UIGraphicsBeginImageContextWithOptions(screen.size, false, scale)
    guard let context = UIGraphicsGetCurrentContext() else { return nil }
    UIScreen.main.layer.render(in: context)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return image
}

问题根源

在iOS 10及以上版本,UIScreen.main.layer.render(in:)可以正确捕获OpenGL ES渲染的内容,但iOS 9及更早的系统里,这个方法无法同步获取Vuforia独立GL上下文里的渲染帧——Vuforia的相机画面是在自己的GL线程里绘制的,系统的屏幕快照机制在旧系统中无法跟上这个节奏,所以只能拿到空白的图层。

解决方案

方案1:用Vuforia自带的截图API(最推荐)

Vuforia本身提供了直接获取当前视频帧的方法,这是最可靠的,完全不受系统版本限制,因为它直接从Vuforia的渲染管道里拿数据:

import Vuforia

static func takeVuforiaScreenshot() -> UIImage? {
    // 获取Vuforia的当前视频背景纹理
    guard let videoTexture = VuforiaRenderer.getInstance()?.getVideoBackgroundTexture() else {
        return nil
    }
    
    let width = videoTexture.getWidth()
    let height = videoTexture.getHeight()
    // 分配像素缓冲区,RGBA格式每个像素占4字节
    let pixelBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height * 4)
    defer { pixelBuffer.deallocate() } // 确保内存释放
    
    // 从纹理中读取像素数据
    videoTexture.getData(pixelBuffer, pixelFormat: PIXEL_FORMAT_RGBA8888)
    
    // 创建CGImage
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
    
    guard let context = CGContext(data: pixelBuffer,
                                  width: width,
                                  height: height,
                                  bitsPerComponent: 8,
                                  bytesPerRow: width * 4,
                                  space: colorSpace,
                                  bitmapInfo: bitmapInfo.rawValue) else {
        return nil
    }
    
    guard let cgImage = context.makeImage() else {
        return nil
    }
    
    // 注意:Vuforia的视频帧可能是镜像的,需要调整方向
    let image = UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .upMirrored)
    return image
}

你可能需要根据设备的实际方向调整orientation参数,确保截图方向正确。

方案2:适配iOS 9的GL视图截图

如果没法用Vuforia的API,假设你的Vuforia渲染视图是GLKView的实例,可以直接从GL上下文里读取像素:

// 传入你的Vuforia渲染视图(GLKView实例)
static func takeGLKScreenshot(from vuforiaView: GLKView) -> UIImage? {
    // 绑定GL视图的可绘制对象
    vuforiaView.bindDrawable()
    
    let width = vuforiaView.drawableWidth
    let height = vuforiaView.drawableHeight
    let pixelBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height * 4)
    defer { pixelBuffer.deallocate() }
    
    // 从GL上下文读取像素(注意OpenGL坐标系是上下颠倒的)
    glReadPixels(0, 0, GLsizei(width), GLsizei(height), GL_RGBA, GL_UNSIGNED_BYTE, pixelBuffer)
    
    // 翻转图像,修正坐标系
    let flippedBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height * 4)
    defer { flippedBuffer.deallocate() }
    
    for y in 0..<height {
        let sourceRow = (height - 1 - y) * width * 4
        let destRow = y * width * 4
        memcpy(flippedBuffer + destRow, pixelBuffer + sourceRow, width * 4)
    }
    
    // 创建CGImage和UIImage
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
    
    guard let context = CGContext(data: flippedBuffer,
                                  width: width,
                                  height: height,
                                  bitsPerComponent: 8,
                                  bytesPerRow: width * 4,
                                  space: colorSpace,
                                  bitmapInfo: bitmapInfo.rawValue) else {
        return nil
    }
    
    guard let cgImage = context.makeImage() else {
        return nil
    }
    
    return UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
}

使用这个方法时,要确保调用glReadPixels时GL上下文是活跃的,最好在Vuforia的渲染回调里执行。

总结

优先选方案1,因为Vuforia的原生API完全适配自己的渲染流程,不会有系统版本兼容问题。方案2适合无法修改Vuforia视图引用的场景,但需要处理OpenGL坐标系翻转的细节。

内容的提问来源于stack exchange,提问作者Daniil Sencov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:46:48