Metal入門教程(八)Metal與OpenGL ES交互

前言

Metal入門教程(一)圖片繪制
Metal入門教程(二)三維變換
Metal入門教程(三)攝像頭采集渲染
Metal入門教程(四)灰度計(jì)算
Metal入門教程(五)視頻渲染
Metal入門教程(六)邊界檢測(cè)
Metal入門教程(七)天空盒全景

前面的教程介紹了Metal的圖片繪制、三維變換、視頻渲染钦铁、天空盒黎比,用計(jì)算管道實(shí)現(xiàn)灰度計(jì)算和sobel邊界檢測(cè)书妻,這次探究Metal與OpenGL ES的交互米诉。

Metal系列教程的代碼地址惊橱;
OpenGL ES系列教程在這里正林;

你的star和fork是我的源動(dòng)力杈绸,你的意見(jiàn)能讓我走得更遠(yuǎn)篡殷。

正文

UIImage是我們常用的圖像類,可以轉(zhuǎn)成CVPixelBufferRef,表示存儲(chǔ)在內(nèi)存的圖像數(shù)據(jù);
id<MTLTexture> 是Metal的紋理诱篷,表示的是存儲(chǔ)在顯存的圖像數(shù)據(jù)橙凳;
GLuint 是OpenGL ES的紋理茴肥,表示的是存儲(chǔ)在顯存的圖像數(shù)據(jù)础锐。

CVPixelBufferRef <==> Metal紋理Metal入門教程(五)視頻渲染有詳細(xì)的介紹意推;
CVPixelBufferRef <==> OpenGL ES紋理OpenGL ES文集也有相關(guān)的介紹趟薄;
本文就是基于CVPixelBufferRef也切,將Metal的紋理轉(zhuǎn)成CVPixelBufferRef讨越,再用CVPixelBufferRef轉(zhuǎn)成OpenGL ES的紋理漫雕,實(shí)現(xiàn)Metal到OpenGL ES的交互。

效果展示

三種渲染效果展示

具體步驟

1、Metal渲染

Metal渲染包括兩步,分別是渲染到紋理和渲染到屏幕吭历。
紋理是由CVMetalTextureGetTexture方法生成,需要事先創(chuàng)建CVOpenGLESTextureCacheRef,再CVMetalTextureCacheCreateTextureFromImage創(chuàng)建具體的CVOpenGLESTextureRef夯辖,具體的創(chuàng)建過(guò)程如下:

- (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實(shí)現(xiàn),設(shè)置對(duì)應(yīng)的輸入和輸出紋理即可蛮瞄。
需要注意的是,這次給commandBuffer添加了結(jié)束回調(diào)无蜂。在Metal渲染完畢后,會(huì)把CVPixelBufferRef里面的數(shù)據(jù)轉(zhuǎn)成UIImage午绳,同時(shí)也會(huì)傳給OpenGL ES渲染:

    // 渲染到紋理
    {
        // 創(chuàng)建計(jì)算指令的編碼器
        id<MTLComputeCommandEncoder> renderToTextureEncoder = [commandBuffer computeCommandEncoder];
        // 設(shè)置計(jì)算管道,以調(diào)用shaders.metal中的內(nèi)核計(jì)算函數(shù)
        [renderToTextureEncoder setComputePipelineState:self.computePipelineState];
        // 輸入紋理
        [renderToTextureEncoder setTexture:self.sourceTexture
                           atIndex:LYFragmentTextureIndexTextureSource];
        // 輸出紋理
        [renderToTextureEncoder setTexture:self.destTexture
                           atIndex:LYFragmentTextureIndexTextureDest];
        // 計(jì)算區(qū)域
        [renderToTextureEncoder dispatchThreadgroups:self.groupCount
                       threadsPerThreadgroup:self.groupSize];
        
        // 調(diào)用endEncoding釋放編碼器妓笙,下個(gè)encoder才能創(chuàng)建
        [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]; // 轉(zhuǎn)成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顯示的數(shù)據(jù)對(duì)象是UIImage,需要把CVPixelBufferRef轉(zhuǎn)成UIImage更振,具體的步驟如下:

/**
 *  根據(jù)CVPixelBufferRef返回圖像
 *
 *  @param pixelBufferRef 像素緩存引用
 *
 *  @return UIImage對(duì)象
 */
- (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的時(shí)候是kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrderDefault炕桨,這樣會(huì)導(dǎo)致出現(xiàn)藍(lán)色的圖片
    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參數(shù)(第七個(gè))需要特別注意,這里的格式要和CVPixelBufferCreate方法的pixelFormatType(第四個(gè))對(duì)應(yīng)肯腕,否則會(huì)出現(xiàn)圖片顏色異常的情況献宫。

3、OpenGL ES渲染

OpenGL ES的渲染關(guān)鍵在于把CVPixelBufferRef轉(zhuǎn)成紋理实撒,我們可以用CVOpenGLESTextureCacheCreateTextureFromImage方法獲取到CVOpenGLESTextureRef姊途,再用CVOpenGLESTextureGetTargetCVOpenGLESTextureGetName獲取紋理的相應(yīng)信息涉瘾,之后便是正常的OpenGL ES渲染邏輯。

遇到的問(wèn)題

1捷兰、OpenGL ES渲染的圖片不透明

按照設(shè)計(jì)立叛,左上角是UIKit渲染的圖片,右上角是OpenGL ES渲染的圖片贡茅,中間是Metal渲染的圖片秘蛇,并UIKit和OpenGL ES渲染區(qū)域應(yīng)該是透明的。但是實(shí)際的效果如下:


Metal與OpenGL

OpenGL ES渲染的圖片不透明顶考!

  • 首先猜測(cè)的點(diǎn)是因?yàn)閳D片本身不透明赁还,導(dǎo)致底為黑色;這個(gè)通過(guò)UIImageView渲染的效果排除驹沿;
  • 其次猜測(cè)的是OpenGL ES渲染的時(shí)候艘策,把透明的部分設(shè)置成黑色;下面一段是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);
}

通過(guò)上面的代碼可以看出渊季,確實(shí)所有像素點(diǎn)的alpha值都是1柬焕!
但這個(gè)并不是問(wèn)題所在,因?yàn)檫@里只決定三角形區(qū)域內(nèi)的像素點(diǎn)顏色梭域,黑色部分為背景斑举,并不受shader的影響。

  • 通過(guò)上面的猜測(cè)病涨,發(fā)現(xiàn)問(wèn)題可能在glClearColor設(shè)置了不透明的顏色富玷。檢查代碼果然有一行glClearColor(0.0f, 0.0f, 0.0f, 1.0f);,把背景設(shè)置成不透明的黑色既穆!
    但在把這段代碼修改成glClearColor(0.0f, 0.0f, 0.0f, 0.0f);赎懦,發(fā)現(xiàn)黑色背景依舊沒(méi)有消除!這次問(wèn)題出現(xiàn)在哪里幻工?
  • 沿著我們分析的路徑励两,我們已經(jīng)排除掉shader、GL指令的影響囊颅,當(dāng)渲染出來(lái)透明的區(qū)域時(shí)当悔,理應(yīng)和UIKit的視圖進(jìn)行混合√叽回憶下OpenGL ES教程里的內(nèi)容盲憎,蘋果用CAEAGLLayer來(lái)承載OpenGL ES的內(nèi)容,最終渲染結(jié)果都是CAEAGLLayer上顏色緩沖區(qū)的數(shù)據(jù)胳挎。既然我們判斷渲染結(jié)果沒(méi)有問(wèn)題饼疙,那么應(yīng)該從CAEAGLLayer與UILayer的混合開(kāi)始檢查。通過(guò)排查CAEAGLLayer的屬性設(shè)置慕爬,終于發(fā)現(xiàn)問(wèn)題所在是eaglLayer.opaque = YES;窑眯!CAEAGLLayer的不透明屬性設(shè)置為YES屏积,有利于性能的優(yōu)化,但是在demo里是會(huì)造成額外的影響磅甩!
    通過(guò)把opaque屬性設(shè)置成NO炊林,問(wèn)題終于解決。

總結(jié)

本文介紹基于CVPixelBufferRef如何把Metal紋理轉(zhuǎn)成OpenGL ES紋理更胖,而OpenGL ES紋理轉(zhuǎn)成Metal紋理的操作也類似铛铁。
在理解轉(zhuǎn)換過(guò)程中,有兩個(gè)等式非常關(guān)鍵:
CVBufferRef = CVImageBufferRef = CVMetalTextureRef
CVImageBufferRef=CVPixelBufferRef
在理清這些數(shù)據(jù)結(jié)構(gòu)后却妨,能更好理解蘋果如何對(duì)CPU和GPU的數(shù)據(jù)進(jìn)行轉(zhuǎn)換饵逐。
預(yù)告,下一篇是對(duì)入門教程的總結(jié)彪标。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末倍权,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捞烟,更是在濱河造成了極大的恐慌薄声,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件题画,死亡現(xiàn)場(chǎng)離奇詭異默辨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)苍息,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門缩幸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人竞思,你說(shuō)我怎么就攤上這事表谊。” “怎么了盖喷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵爆办,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我课梳,道長(zhǎng)距辆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任惦界,我火速辦了婚禮挑格,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沾歪。我一直安慰自己,他們只是感情好雾消,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布灾搏。 她就那樣靜靜地躺著挫望,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狂窑。 梳的紋絲不亂的頭發(fā)上媳板,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音泉哈,去河邊找鬼蛉幸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛丛晦,可吹牛的內(nèi)容都是我干的奕纫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼烫沙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匹层!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起锌蓄,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤升筏,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后瘸爽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體您访,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年剪决,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灵汪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昼捍,死狀恐怖识虚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妒茬,我是刑警寧澤担锤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站乍钻,受9級(jí)特大地震影響肛循,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜银择,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一多糠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浩考,春花似錦夹孔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)只怎。三九已至,卻和暖如春怜俐,著一層夾襖步出監(jiān)牢的瞬間身堡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工拍鲤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贴谎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓季稳,卻偏偏與公主長(zhǎng)得像擅这,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绞幌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容