Metal入门教程(八)Metal与OpenGL ES交互
前言
Metal入门教程(一)图片绘制
Metal入门教程(二)三维变换
Metal入门教程(三)摄像头采集渲染
Metal入门教程(四)灰度计算
Metal入门教程(五)视频渲染
Metal入门教程(六)边界检测
Metal入门教程(七)天空盒全景
前面的教程详情了Metal的图片绘制、三维变换、视频渲染、天空盒,使用计算管道实现灰度计算和sobel边界检测,这次探索Metal与OpenGL ES的交互。
Metal系列教程的代码地址;
OpenGL ES系列教程在这里;
你的star和fork是我的源动力,你的意见能让我走得更远。
正文
UIImage是我们常使用的图像类,可以转成CVPixelBufferRef,表示存储在内存的图像数据;
id<MTLTexture> 是Metal的纹理,表示的是存储在显存的图像数据;
GLuint 是OpenGL ES的纹理,表示的是存储在显存的图像数据。
CVPixelBufferRef <==> Metal纹理在Metal入门教程(五)视频渲染有详细的详情;
CVPixelBufferRef <==> OpenGL ES纹理在OpenGL ES文集也有相关的详情;
本文就是基于CVPixelBufferRef,将Metal的纹理转成CVPixelBufferRef,再使用CVPixelBufferRef转成OpenGL ES的纹理,实现Metal到OpenGL ES的交互。
效果展现
三种渲染效果展现
具体步骤
1、Metal渲染
Metal渲染包括两步,分别是渲染到纹理和渲染到屏幕。
纹理是由CVMetalTextureGetTexture方法生成,需要事前创立CVOpenGLESTextureCacheRef,再CVMetalTextureCacheCreateTextureFromImage创立具体的CVOpenGLESTextureRef,具体的创立过程如下:
- (void)setupRenderTargetWithSize:(CGSize)size { CFDictionaryRef empty= CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFMutableDictionaryRef attrs= CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty); CVPixelBufferRef renderTarget; CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget); size_t width = CVPixelBufferGetWidthOfPlane(renderTarget, 0); size_t height = CVPixelBufferGetHeightOfPlane(renderTarget, 0); MTLPixelFormat pixelFormat = MTLPixelFormatBGRA8Unorm; CVMetalTextureRef texture = NULL; CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, renderTarget, NULL, pixelFormat, width, height, 0, &texture); if(status == kCVReturnSuccess) { self.destTexture = CVMetalTextureGetTexture(texture); self.renderPixelBuffer = renderTarget; CFRelease(texture); } else { NSAssert(NO, @"CVMetalTextureCacheCreateTextureFromImage fail"); }}渲染到纹理使用的是computeShader实现,设置对应的输入和输出纹理就可。
需要注意的是,这次给commandBuffer增加了结束回调。在Metal渲染完毕后,会把CVPixelBufferRef里面的数据转成UIImage,同时也会传给OpenGL ES渲染:
// 渲染到纹理 { // 创立计算指令的编码器 id<MTLComputeCommandEncoder> renderToTextureEncoder = [commandBuffer computeCommandEncoder]; // 设置计算管道,以调使用shaders.metal中的内核计算函数 [renderToTextureEncoder setComputePipelineState:self.computePipelineState]; // 输入纹理 [renderToTextureEncoder setTexture:self.sourceTexture atIndex:LYFragmentTextureIndexTextureSource]; // 输出纹理 [renderToTextureEncoder setTexture:self.destTexture atIndex:LYFragmentTextureIndexTextureDest]; // 计算区域 [renderToTextureEncoder dispatchThreadgroups:self.groupCount threadsPerThreadgroup:self.groupSize]; // 调使用endEncoding释放编码器,下个encoder才能创立 [renderToTextureEncoder endEncoding]; } [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { if (kCVReturnSuccess == CVPixelBufferLockBaseAddress(self.renderPixelBuffer, kCVPixelBufferLock_ReadOnly)) { // 获取到buffer dispatch_async(dispatch_get_main_queue(), ^{ UIImage *image = [self lyGetImageFromPixelBuffer:self.renderPixelBuffer]; // 转成UIImage if (!self.imageView) { self.imageView = [[UIImageView alloc] initWithImage:image]; [self.view addSubview:self.imageView]; } // OpenGL ES渲染 [self.glView displayPixelBuffer:self.renderPixelBuffer]; // 释放资源 CVPixelBufferUnlockBaseAddress(self.renderPixelBuffer, kCVPixelBufferLock_ReadOnly); }); } }];最后再把上一步的输出纹理作为输入,直接渲染到屏幕。
2、UIImageView渲染
UIImageView显示的数据对象是UIImage,需要把CVPixelBufferRef转成UIImage,具体的步骤如下:
/** * 根据CVPixelBufferRef返回图像 * * @param pixelBufferRef 像素缓存引使用 * * @return UIImage对象 */- (UIImage *)lyGetImageFromPixelBuffer:(CVPixelBufferRef)pixelBufferRef { CVImageBufferRef imageBuffer = pixelBufferRef; CVPixelBufferLockBaseAddress(imageBuffer, 0); void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer); size_t bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0); // CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, baseAddress, bufferSize, NULL); // rgba的时候是kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrderDefault,这样会导致出现蓝色的图片 CGImageRef cgImage = CGImageCreate(width, height, 8, 32, bytesPerRow, rgbColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, provider, NULL, true, kCGRenderingIntentDefault); UIImage *image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); CGDataProviderRelease(provider); CGColorSpaceRelease(rgbColorSpace); CVPixelBufferUnlockBaseAddress(imageBuffer, 0); return image;}CGImageCreate方法的bitmapInfo参数(第七个)需要特别注意,这里的格式要和CVPixelBufferCreate方法的pixelFormatType(第四个)对应,否则会出现图片颜色异常的情况。
3、OpenGL ES渲染
OpenGL ES的渲染关键在于把CVPixelBufferRef转成纹理,我们可以使用CVOpenGLESTextureCacheCreateTextureFromImage方法获取到CVOpenGLESTextureRef,再使用CVOpenGLESTextureGetTarget和CVOpenGLESTextureGetName获取纹理的相应信息,之后便是正常的OpenGL ES渲染逻辑。
遇到的问题
1、OpenGL ES渲染的图片不透明
按照设计,左上角是UIKit渲染的图片,右上角是OpenGL ES渲染的图片,中间是Metal渲染的图片,并UIKit和OpenGL ES渲染区域应该是透明的。但是实际的效果如下:
Metal与OpenGL
OpenGL ES渲染的图片不透明!
- 首先猜测的点是由于图片本身不透明,导致底为黑色;这个通过UIImageView渲染的效果排除;
- 其次猜测的是OpenGL ES渲染的时候,把透明的部分设置成黑色;下面一段是shader 代码:
varying highp vec2 texCoordVarying;uniform sampler2D inputTexture;precision mediump float;void main(){ lowp vec3 rgb = texture2D(inputTexture, texCoordVarying).rgb; gl_FragColor = vec4(rgb, 1);}通过上面的代码可以看出,的确所有像素点的alpha值都是1!
但这个并不是问题所在,由于这里只决定三角形区域内的像素点颜色,黑色部分为背景,并不受shader的影响。
- 通过上面的猜测,发现问题可能在glClearColor设置了不透明的颜色。检查代码果然有一行
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);,把背景设置成不透明的黑色!
但在把这段代码修改成glClearColor(0.0f, 0.0f, 0.0f, 0.0f);,发现黑色背景仍旧没有消除!这次问题出现在哪里? - 沿着我们分析的路径,我们已经排除掉shader、GL指令的影响,当渲染出来透明的区域时,理应和UIKit的视图进行混合。回忆下OpenGL ES教程里的内容,苹果使用CAEAGLLayer来承载OpenGL ES的内容,最终渲染结果都是CAEAGLLayer上颜色缓冲区的数据。既然我们判断渲染结果没有问题,那么应该从CAEAGLLayer与UILayer的混合开始检查。通过排查CAEAGLLayer的属性设置,终于发现问题所在是
eaglLayer.opaque = YES;!CAEAGLLayer的不透明属性设置为YES,有利于性能的优化,但是在demo里是会造成额外的影响!
通过把opaque属性设置成NO,问题终于处理。
总结
本文详情基于CVPixelBufferRef如何把Metal纹理转成OpenGL ES纹理,而OpenGL ES纹理转成Metal纹理的操作也相似。
在了解转换过程中,有两个等式非常关键:CVBufferRef = CVImageBufferRef = CVMetalTextureRefCVImageBufferRef=CVPixelBufferRef
在理清这些数据结构后,能更好了解苹果如何对CPU和GPU的数据进行转换。
预报,下一篇是对入门教程的总结。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Metal入门教程(八)Metal与OpenGL ES交互