glReadPixels转UIImage黑屏:Vuforia相机截图在iOS9.3.5设备异常
我之前碰到过一模一样的情况!这种黑屏问题本质是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

