OpenGL渲染YUV數(shù)據(jù)

本文主要介紹使用OpenGL ES來(lái)渲染I420(YUV420P) , NV12(YUV420SP)的方法,關(guān)于YUV的知識(shí),可以看這里《YUV顏色編碼解析》,同樣會(huì)用到一些簡(jiǎn)單的OpenGL shader知識(shí)撑帖,可以看看OpenGL的著色器語(yǔ)言。為了書(shū)寫(xiě)方便澳眷,以下所談的OpenGL特指OpenGL ES胡嘿。

OpenGL ES是OpenGL的精簡(jiǎn)版本,主要針對(duì)于手機(jī)钳踊、游戲主機(jī)等嵌入式設(shè)備衷敌,它提供了一套設(shè)備圖形硬件的軟件接口,通過(guò)直接操作圖形硬件拓瞪,使我們能夠高效地繪制圖形缴罗。OpenGL在iOS架構(gòu)中屬于媒體層,與quartz(core graphics)類似祭埂,是相對(duì)底層的技術(shù)面氓,可以控制每一幀的圖形繪制。由于圖形渲染是通過(guò)圖形硬件(GPU)來(lái)完成的,相對(duì)于使用CPU舌界,能夠獲得更高的幀率同時(shí)不會(huì)因?yàn)樨?fù)載過(guò)大而造成卡頓掘譬。


OpenGL處于繪制接口的底層
OpenGL處于繪制接口的底層

創(chuàng)建GLView

我們需要?jiǎng)?chuàng)建一個(gè)用來(lái)展示OpenGL繪制內(nèi)容的View,只需要將UIView的根圖層(underlying layer)替換成CAEAGLLayer實(shí)例即可禀横。通過(guò)覆蓋UIView的類方法+(Class)layerClass屁药,可以實(shí)現(xiàn)這一點(diǎn),CAEAGLLayer默認(rèn)是透明的柏锄,這會(huì)影響性能酿箭,所以將它設(shè)為不透明。

+ (class)layerClass {
    return [CAEAGLLayer class];
}
- (void)setupLayer {
    _eaglLayer = (CAEAGLLayer*) self.layer;
    _eaglLayer.opaque = YES;
}

創(chuàng)建EAGLContext

EAGLContext對(duì)象管理OpenGL繪制所需要的所有信息趾娃,和Quartz 2D所使用的CGContext類似缭嫡。

- (void)setupContext {   
//創(chuàng)建一個(gè)OpenGLES 2.0接口的context
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    _context = [[EAGLContext alloc] initWithAPI:api];
    if (!_context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }
 //將其設(shè)置為current context
    if (![EAGLContext setCurrentContext:_context]) {
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
}

創(chuàng)建render buffer

render buffer用來(lái)存儲(chǔ)將要繪制到屏幕上圖像。OpenGL中的對(duì)象都需要?jiǎng)?chuàng)建抬闷、綁定妇蛀,并且都是ID引用的。

- (void)setupRenderBuffer {
    //創(chuàng)建一個(gè)render buffer對(duì)象,并綁定到GL_RENDERBUFFER目標(biāo)上
    glGenRenderbuffers(1, &_renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); 
    //為render buffer分配存儲(chǔ)空間
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];    
}

創(chuàng)建frame buffer

一個(gè)frame buffer對(duì)象包括render buffer, depth buffer, stencil buffer等笤成,擁有OpenGL繪制時(shí)需要的信息评架。

- (void)setupFrameBuffer {    
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    //將之前創(chuàng)建的render buffer附著到frame buffer作為其logical buffer
    //GL_COLOR_ATTACHMENT0指定第一個(gè)顏色緩沖區(qū)附著點(diǎn)
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
        GL_RENDERBUFFER, _renderBuffer);
 }

glFramebufferRenderbuffer調(diào)用后,render buffer通過(guò)GL_COLOR_ATTACHMENT0引用使用render buffer

渲染

- (void)render {
//設(shè)置用來(lái)清除屏幕的顏色炕泳,類似于quartz中設(shè)置CGcontext畫(huà)筆的顏色
    glClearColor(0, 0, 0, 1.0);
    //執(zhí)行清除操作纵诞,設(shè)置render buffer中的像素顏色為上一步指定的顏色
    glClear(GL_COLOR_BUFFER_BIT);
    //渲染render buffer中的圖像到GLView的CAEAGLLayer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}

shader(著色器)

shader是上是在GPU上執(zhí)行的程序,保存在.glsl文件中或以字符串形式寫(xiě)在OpenGL代碼里培遵,使用GLSL(OpenGL shading language)語(yǔ)言編寫(xiě)浙芙,shader在運(yùn)行時(shí)編譯,鏈接籽腕,最終在GPU上執(zhí)行采樣操作嗡呼。
OpenGL中有兩種shader:

  1. vertex shader(頂點(diǎn)著色器):vertex shader在每個(gè)頂點(diǎn)上都執(zhí)行執(zhí)行一次,通過(guò)不同世界的坐標(biāo)系轉(zhuǎn)化定位頂點(diǎn)的最終位置皇耗。它可以數(shù)據(jù)給fragment shader南窗,如紋理坐標(biāo)、頂點(diǎn)坐標(biāo)郎楼,變換矩陣等万伤。
  2. fragment shader(片段著色器):fragment shader在每個(gè)像素上都會(huì)執(zhí)行一次,通過(guò)插值確定像素的最終顯示顏色箭启。

創(chuàng)建shader

以下是兩個(gè)簡(jiǎn)單的shader壕翩,用來(lái)說(shuō)明GLSL的語(yǔ)法特點(diǎn)。

vertex shader:

//attribute 關(guān)鍵字用來(lái)描述傳入shader的變量
attribute vec4 vertexPosition; // 需要從外部獲取的4分量vector
attribute vec4 pixelColor; 
//varying 關(guān)鍵字用來(lái)描述從vertex shader傳遞給fragment shader的變量
//精度修飾符分為三種:highp, mediump, lowp
varying mediump vec4 finalPixelColor; //mediumP修飾代表中等精度傅寡,提高效率放妈。
 
void main(void) { 
    finalPixelColor = pixelColor; // 將pixelColor的值通過(guò)finalPixelColor傳遞給fragment shader
    gl_Position = vertexPosition; // gl_Position是vertex shader的內(nèi)建變量北救,gl_Position中的頂點(diǎn)值最終輸出到渲染管線中
}

fragment shader:

varying mediump vec4 finalPixelColor; 
 
void main(void) { 
    gl_FragColor = finalPixelColor; // gl_FragColor是fragment shader的內(nèi)建變量,gl_FragColor中的像素值最終輸出到渲染管線中
}
}

使用shader

shader在運(yùn)行時(shí)完成編譯芜抒、鏈接珍策,是在GPU上執(zhí)行的小程序,以下是shader編譯宅倒、鏈接的過(guò)程攘宙,為了閱讀方便,省略了調(diào)試異常情況的判斷和調(diào)試log輸出拐迁。

//編譯shader函數(shù)
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
    // 讀取shader文件的內(nèi)容為字符串
    NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName 
        ofType:@"glsl"];
    NSError* error;
    NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath 
        encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString) {
        NSLog(@"Error loading shader: %@", error.localizedDescription);
        exit(1);
    }
    // 創(chuàng)建shader對(duì)象蹭劈,返回其引用
    GLuint shaderHandle = glCreateShader(shaderType);    
    // 獲取C字符串,作為源代碼傳給OpenGL
    const char * shaderStringUTF8 = [shaderString UTF8String];    
    int shaderStringLength = [shaderString length];
    glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
    // 運(yùn)行時(shí)編譯shader
    glCompileShader(shaderHandle);
    return shaderHandle;
}
//編譯线召、鏈接shader
-(void)configuerShader{
    // 創(chuàng)建并編譯shader
    GLuint vertexShader = [self compileShader:@"vertexShader" 
        withType:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShader:@"fragmentShader" 
        withType:GL_FRAGMENT_SHADER];
    //創(chuàng)建一個(gè)程序?qū)ο笃倘停祷仄湟?    _programHandle = glCreateProgram();
    //將兩個(gè)shader綁定到程序?qū)ο? 不需要時(shí)可以使用glDetachShader解綁
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    //鏈接兩個(gè)shader
    glLinkProgram(_programHandle);
    //選擇創(chuàng)建的程序?qū)ο鬄楫?dāng)前使用的程序,類似setCurrentContext, 不需要時(shí)使用glDeleteProgram刪除
    glUseProgram(_programHandle);
  }
-(void)configuerSlot{
   //獲取shader中attribute變量的引用
   _vertexPosition = glGetAttribLocation(programHandle, "vertexPosition");
   _pixelColor = glGetAttribLocation(programHandle, "pixelColor");
   //啟用attribute變量缓淹,使其對(duì)GPU可見(jiàn)哈打,默認(rèn)為關(guān)閉
   glEnableVertexAttribArray(_vertexPosition);
   glEnableVertexAttribArray(_pixelColor);
 }

-(void)initOpenGl{
  [self configuerShader];
  [self coniigureSlot];
}

使用OpenGL繪制一個(gè)簡(jiǎn)單的矩形

以上內(nèi)容介紹了OpenGL的基本數(shù)據(jù)結(jié)構(gòu),現(xiàn)在先來(lái)繪制一個(gè)簡(jiǎn)單的矩形

初始化

現(xiàn)在需要給OpenGL提供attribute變量值與頂點(diǎn)數(shù)據(jù)讯壶。頂點(diǎn)數(shù)據(jù)用來(lái)提供繪制時(shí)的幾何信息料仗。OpenGL中只能繪制三角形,三角形保證了其內(nèi)部像素都在同一個(gè)平面伏蚊。要繪制復(fù)雜的幾何圖形立轧,可以用三角形組合的方式實(shí)現(xiàn)。
頂點(diǎn)數(shù)據(jù)使用VBO(vertex buffer object)來(lái)傳遞給GPU丙挽。

初始化VBO

OpenGL需要有兩種VBO來(lái)確定幾何圖形肺孵,vertex VBO提供頂點(diǎn)本身匀借,index VBO提供三角形所使用的頂點(diǎn)的index序列颜阐。這樣保證了顯存中存儲(chǔ)的頂點(diǎn)數(shù)據(jù)是唯一的,不會(huì)浪費(fèi)資源吓肋。VBO中存儲(chǔ)著CPU傳給GPU的數(shù)據(jù)凳怨,存儲(chǔ)在顯存里,在執(zhí)行大量重復(fù)的繪制操作時(shí)是鬼,可以提高效率肤舞。

初始化attribute變量

之前創(chuàng)建的shader中,有兩個(gè)attribute變量均蜜,需要使用glVertexAttribPointer輸入給shader李剖。

//我們需要在一個(gè)矩形中繪制圖像,需要兩個(gè)三角形模擬囤耳,所以需要四個(gè)頂點(diǎn)篙顺,索引數(shù)組說(shuō)明了兩個(gè)三角形頂點(diǎn)組成偶芍。
//默認(rèn)情況下,OpenGL 的Viewport左下角頂點(diǎn)為(-1德玫,-1)匪蟀,右上角頂點(diǎn)為(1,1)宰僧。
const float vertices[] = {
 1, -1, 0,//index 0
 1,  1, 0,//index 1
-1,  1, 0,//index 2
-1, -1, 0 //index 3
}
const GLubyte Indices[] = {
     0, 1, 2,
     2, 3, 0
};

- (void)setupVBOs {
    //頂點(diǎn)VBO
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    //將頂點(diǎn)坐標(biāo)寫(xiě)入頂點(diǎn)VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), Vertices, GL_STATIC_DRAW);
    //索引VBO
    GLuint indexBuffer;
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    //將頂點(diǎn)索引數(shù)據(jù)寫(xiě)入索引VBO
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
  }
-(void)feedAttributeSlot{
    //由于使用了VBO材彪,所以最后一個(gè)參數(shù)傳數(shù)據(jù)在VBO中的偏移量,這點(diǎn)需要注意
    glVertexAttribPointer(_vertexPosition, 3, GL_FLOAT, GL_FALSE, sizeof(float)*3, 0);
    //沒(méi)有使用VBO琴儿,直接傳指針給函數(shù),
    float blueColor[] = {0, 0, 1, 0};
    glVertexAttribPointer(_pixelColor, 4, GL_FLOAT, GL_FALSE, 0, blueColor);
  }

注意此時(shí)寫(xiě)入VBO的只是一些二進(jìn)制的數(shù)據(jù)段化,需要在讀取數(shù)據(jù)是,給出數(shù)據(jù)類型才能正確讀取造成。

繪制

- (void)render {
    //繪制黑色背景
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    //創(chuàng)建一個(gè)OpenGL繪制的窗口
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    //使用頂點(diǎn)索引穗泵,繪制圖形。調(diào)用函數(shù)后谜疤,vertex shader會(huì)在每個(gè)頂點(diǎn)執(zhí)行一遍佃延,確定頂點(diǎn)信息。fragment shader會(huì)在每個(gè)像素執(zhí)行一遍夷磕,確定像素顏色履肃。
    //在使用VBO的情況下,最后一個(gè)參數(shù)傳0
    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), 
        GL_UNSIGNED_BYTE, 0);
    //EACAGLContext 渲染OpenGL繪制好的圖像到EACAGLLayer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
} 

由于shader中pixelColor變量類型是varying類型坐桩,在處理未知值是尺棋,會(huì)自動(dòng)插值,默認(rèn)為線性插值绵跷。如果對(duì)shader的GLSL語(yǔ)法不熟悉膘螟,可以看這篇文章。

所以碾局,當(dāng)頂點(diǎn)之間的顏色不同是荆残,fragment shader在處理圖形內(nèi)部的像素后會(huì)返回一個(gè)根據(jù)頂點(diǎn)插值后的數(shù)值,整個(gè)圖形是漸變色的净当。

這里因?yàn)樗膫€(gè)頂點(diǎn)都設(shè)置為了藍(lán)色内斯,所以繪制出來(lái)是一個(gè)藍(lán)色的矩形。

(添加圖)

使用OpenGL繪制YUV數(shù)據(jù)

以上內(nèi)容簡(jiǎn)單介紹了如何使用OpenGL繪制像啼,現(xiàn)在重點(diǎn)如何使用OpenGL繪制YUV數(shù)據(jù)俘闯。
YUV是一種顏色編碼格式,常用的格式有YUV444,YUV422P,YUV420P,YUV420SP等忽冻。
本文主要研究YUV420PI420YUV420SPNV12真朗。

紋理

我們需要將YUV數(shù)據(jù)紋理的方式加載到OpenGL,再將紋理貼到之前創(chuàng)建矩形上僧诚,完成繪制遮婶。
將每個(gè)頂點(diǎn)賦予一個(gè)紋理坐標(biāo)秀菱,OpenGL會(huì)根據(jù)紋理坐標(biāo)插值得到圖形內(nèi)部的像素值。OpenGL的紋理坐標(biāo)系是歸一化的蹭睡,取值范圍是0 - 1衍菱,左下角是原點(diǎn)。


三角形貼上紋理需要的紋理坐標(biāo)
三角形貼上紋理需要的紋理坐標(biāo)

紋理目標(biāo)肩豁、紋理對(duì)象脊串、紋理單元

  1. 紋理目標(biāo)是顯卡的軟件接口中定義的句柄,指向要進(jìn)行當(dāng)前操作的顯存清钥。
  2. 紋理對(duì)象是我們創(chuàng)建的用來(lái)存儲(chǔ)紋理的顯存琼锋,在實(shí)際使用過(guò)程中使用的是創(chuàng)建后返回的ID。
  3. 紋理單元是顯卡中所有的可用于在shader中進(jìn)行紋理采樣的顯存祟昭,數(shù)量與顯卡類型相關(guān)缕坎,至少16個(gè)。在激活某個(gè)紋理單元后篡悟,紋理目標(biāo)就該紋理單元谜叹,默認(rèn)激活的是GL_TEXTURE0。

可以這么想象搬葬,紋理目標(biāo)是轉(zhuǎn)輪手槍正對(duì)彈膛的單孔荷腊,紋理對(duì)象就是子彈,紋理單元是手槍的六個(gè)彈孔急凰。下面用代碼說(shuō)明它們之間的關(guān)系女仰。

//創(chuàng)建一個(gè)紋理對(duì)象數(shù)組,數(shù)組里是紋理對(duì)象的ID
GLuint texture[3];
//創(chuàng)建紋理對(duì)象抡锈,第一個(gè)參數(shù)是要?jiǎng)?chuàng)建的數(shù)量疾忍,第二個(gè)參數(shù)是數(shù)組的基址
glGenTextures(3, &texture);
//激活GL_TEXTURE0這個(gè)紋理單元,用于之后的紋理采樣
glActiveTexture(GL_TEXTURE0);
//綁定紋理對(duì)象texture[0]到紋理目標(biāo)GL_TEXTURE_2D床三,接下來(lái)對(duì)紋理目標(biāo)的操作都發(fā)生在此對(duì)象上
glBindTexture(GL_TEXTURE_2D, texture[0]);
//創(chuàng)建圖像一罩,采樣工作在GL_TEXTURE0中完成,圖像數(shù)據(jù)存儲(chǔ)在GL_TEXTURE_2D綁定的對(duì)象勿璃,即texture[0]中擒抛。
glTexImage(GL_TEXTURE_2D, ...);
//解除綁定推汽,此時(shí)再對(duì)GL_TEXTURE_2D不會(huì)影響到texture[0]补疑,texture[0]的內(nèi)存不會(huì)回收。
glBindTexture(GL_TEXTURE_2D, 0);
//可以不斷創(chuàng)建新的紋理對(duì)象歹撒,直到顯存耗凈

修改shader

之前創(chuàng)建的簡(jiǎn)單shader現(xiàn)在要修改代碼莲组,實(shí)現(xiàn)對(duì)YUV數(shù)據(jù)的繪制。如果對(duì)GLSL語(yǔ)法與YUV不熟悉暖夭,可以看OpenGL的著色語(yǔ)言:GLSLYUV顏色編碼解析锹杈。

//vertex shader
ttribute vec4 position;
attribute mediump vec2 textureCoordinate;//要獲取的紋理坐標(biāo)
varying mediump vec2 coordinate;//傳遞給fragm shader的紋理坐標(biāo)撵孤,會(huì)自動(dòng)插值

void main(void) { 
    gl_Position = vertexPosition; 
    coordinate = textureCoordinate;
}
//fragment shader
precision mediump float;

uniform sampler2D SamplerY;//sample2D的常量,用來(lái)獲取I420數(shù)據(jù)的Y平面數(shù)據(jù)
uniform sampler2D SamplerU;//U平面
uniform sampler2D SamplerV;//V平面

uniform sampler2D SamplerNV12_Y;//NV12數(shù)據(jù)的Y平面
uniform sampler2D SamplerNV12_UV;//NV12數(shù)據(jù)的UV平面

varying highp vec2 coordinate;//紋理坐標(biāo)

uniform int yuvType;//0 代表 I420, 1 代表 NV12

//用來(lái)做YUV --> RGB 的變換矩陣
const vec3 delyuv = vec3(-0.0/255.0,-128.0/255.0,-128.0/255.0);
const vec3 matYUVRGB1 = vec3(1.0,0.0,1.402);
const vec3 matYUVRGB2 = vec3(1.0,-0.344,-0.714);
const vec3 matYUVRGB3 = vec3(1.0,1.772,0.0);

void main()
{
    vec3 CurResult;
    highp vec3 yuv;
    
    if (yuvType == 0){
        yuv.x = texture2D(SamplerY, coordinate).r;//因?yàn)槭荵UV的一個(gè)平面竭望,所以采樣后的r,g,b,a這四個(gè)參數(shù)的數(shù)值是一樣的
        yuv.y = texture2D(SamplerU, coordinate).r;
        yuv.z = texture2D(SamplerV, coordinate).r;
    }
    else{
        yuv.x = texture2D(SamplerY, coordinate).r;
        yuv.y = texture2D(SamplerUV, coordinate).r;//因?yàn)镹V12是2平面的邪码,對(duì)于UV平面,在加載紋理時(shí)咬清,會(huì)指定格式闭专,讓U值存在r,g,b中,V值存在a中旧烧。
        yuv.z = texture2D(SamplerUV, coordinate).a;//這里會(huì)在下面解釋
    }
    
    yuv += delyuv;//讀取值得范圍是0-255影钉,讀取時(shí)要-128回歸原值
    //用數(shù)量積來(lái)模擬矩陣變換,轉(zhuǎn)換成RGB值
    CurResult.x = dot(yuv,matYUVRGB1);
    CurResult.y = dot(yuv,matYUVRGB2);
    CurResult.z = dot(yuv,matYUVRGB3);
    //輸出像素值給光柵器
    gl_FragColor = vec4(CurResult.rgb, 1);
}

加載YUV數(shù)據(jù)到紋理對(duì)象

現(xiàn)在有了可以處理YUV數(shù)據(jù)的shader掘剪,我們需要加載YUV數(shù)據(jù)平委,來(lái)讓OpenGL完成繪制。

//創(chuàng)建紋理對(duì)象,需要3個(gè)紋理對(duì)象來(lái)獲取不同平面的數(shù)據(jù)
-(void)setupTexture{
    _planarTextureHandles = (GLuint *)malloc(3*sizeof(GLuint));
    glGenTextures(3, _planarTextureHandles);
}
-(void)feedTextureWithImageData:(Byte*)imageData imageSize:(CGSize)imageSize type:(NSInteger)type{
    //根據(jù)YUV編碼的特點(diǎn)夺谁,獲得不同平面的基址
    Byte * yPlane =  imageData;
    Byte * uPlane =  imageData + imageSize.width*imageSize.height;
    Byte * vPlane =  imageData + imageSize.width*imageSize.height * 5 / 4;
    if (type == 0) {
        [self textureYUV:yPlane widthType:imageSize.width heightType:imageSize.height index:0];
        [self textureYUV:uPlane widthType:imageSize.width/2 heightType:imageSize.height/2 index:1];
        [self textureYUV:vPlane widthType:imageSize.width/2 heightType:imageSize.height/2 index:2];
    }else{
        [self textureYUV:yPlane widthType:imageSize.width heightType:imageSize.height index:0];
        [self textureNV12:uPlane widthType:imageSize.width/2 heightType:imageSize.height/2 index:1];
    }
}
- (void) textureYUV: (Byte*)imageData widthType: (int) width heightType: (int) height index: (int) index
{
    //將紋理對(duì)象綁定到紋理目標(biāo)
    glBindTexture(GL_TEXTURE_2D, _planarTextureHandles[index]);
    //設(shè)置放大和縮小時(shí)廉赔,紋理的過(guò)濾選項(xiàng)為:線性過(guò)濾
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    //設(shè)置紋理X,Y軸的紋理環(huán)繞選項(xiàng)為:邊緣像素延伸
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    //加載圖像數(shù)據(jù)到紋理,GL_LUMINANCE指明了圖像數(shù)據(jù)的像素格式為只有亮度匾鸥,雖然第三個(gè)和第七個(gè)參數(shù)都使用了GL_LUMINANCE昂勉,
    //但意義是不一樣的,前者指明了紋理對(duì)象的顏色分量成分扫腺,后者指明了圖像數(shù)據(jù)的像素格式
    //獲得紋理對(duì)象后岗照,其每個(gè)像素的r,g,b,a值都為相同,為加載圖像的像素亮度笆环,在這里就是YUV某一平面的分量值
    glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, imageData );
    //解綁
    glBindTexture(GL_TEXTURE_2D, 0);
}

CADisplayLink定時(shí)繪制

現(xiàn)在已經(jīng)能夠?qū)UV數(shù)據(jù)加載到紋理對(duì)象了攒至,下一步來(lái)改造render方法,將其繪制到屏幕上躁劣∑韧拢可以用CADisplayLink定時(shí)調(diào)用render方法,可以根據(jù)屏幕刷新頻率來(lái)控制YUV視頻流的幀率账忘。

- (void)render {
    //繪制黑色背景
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    //獲取平面的scale
    CGFloat scale = [[UIScreen mainScreen] scale];
    CGFloat width = _frame.size.width*scale;
    CGFloat height = _frame.size.height*scale;
    //創(chuàng)建一個(gè)OpenGL繪制的窗口
    glViewport(0, 0,width,height);
    [self drawTexture];
    //EACAGLContext 渲染OpenGL繪制好的圖像到EACAGLLayer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
} 
//fragment shader的sample數(shù)組
GLint sampleHandle[3];
//繪制紋理
- (void) drawTexture{
    //傳紋理坐標(biāo)給fragment shader
    glVertexAttribPointer([AVGLShareInstance shareInstance].texCoordAttributeLocation, 2, GL_FLOAT, GL_FALSE,
                          sizeof(Vertex), (void*)offsetof(Vertex, TexCoord));
    glEnableVertexAttribArray([AVGLShareInstance shareInstance].texCoordAttributeLocation);
    //傳紋理的像素格式給fragment shader
    GLint yuvType = glGetUniformLocation(_programHandle, "yuvType");
    glUniform1i([AVGLShareInstance shareInstance].drawTypeUniform, yuvType);
    //type: 0是I420, 1是NV12
    int planarCount = 0;
    if (type == 0) {
        planarCount = 3;//I420有3個(gè)平面
        sampleHandle[1] = glGetUniformLocation(_programHandle, "samplerY");
        sampleHandle[2] = glGetUniformLocation(_programHandle, "samplerU");
        sampleHandle[3] = glGetUniformLocation(_programHandle, "samplerV");
    }else{
        planarCount = 2;//NV12有兩個(gè)平面
        sampleHandle[1] = glGetUniformLocation(_programHandle, "SamplerNV12_Y");
        sampleHandle[2] = glGetUniformLocation(_programHandle, "SamplerNV12_UV");
    }
        for (int i=0; i<planarCount; i++){
            glActiveTexture(GL_TEXTURE0+i);
            glBindTexture(GL_TEXTURE_2D, _planarTextureHandles[i]);
            glUniform1i(sampleHandle[i], i);
        }
    //繪制函數(shù)志膀,使用三角形作為圖元構(gòu)造要繪制的幾何圖形,由于頂點(diǎn)的indexs使用了VBO鳖擒,所以最后一個(gè)參數(shù)傳0
    //調(diào)用這個(gè)函數(shù)后溉浙,vertex shader先在每個(gè)頂點(diǎn)執(zhí)行一次,之后fragment shader在每個(gè)像素執(zhí)行一次蒋荚,繪制后的圖像存儲(chǔ)在render buffer中戳稽。
    glDrawElements(GL_TRIANGLES, 6,GL_UNSIGNED_BYTE, 0);
}

可以想象的應(yīng)用場(chǎng)景

使用OpenGL繪制視頻,是實(shí)現(xiàn)簡(jiǎn)單AR最簡(jiǎn)單的方式期升,也可以根據(jù)業(yè)務(wù)來(lái)對(duì)視頻播放做進(jìn)一步的個(gè)性定制惊奇,比如動(dòng)態(tài)打碼互躬,貼紙等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颂郎,一起剝皮案震驚了整個(gè)濱河市吼渡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乓序,老刑警劉巖诞吱,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異竭缝,居然都是意外死亡房维,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)抬纸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咙俩,“玉大人,你說(shuō)我怎么就攤上這事湿故“⒊茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵坛猪,是天一觀的道長(zhǎng)脖阵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)墅茉,這世上最難降的妖魔是什么命黔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮就斤,結(jié)果婚禮上悍募,老公的妹妹穿的比我還像新娘。我一直安慰自己洋机,他們只是感情好坠宴,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著绷旗,像睡著了一般喜鼓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衔肢,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天庄岖,我揣著相機(jī)與錄音,去河邊找鬼膀懈。 笑死顿锰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的启搂。 我是一名探鬼主播硼控,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼胳赌!你這毒婦竟也來(lái)了牢撼?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤疑苫,失蹤者是張志新(化名)和其女友劉穎熏版,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捍掺,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撼短,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挺勿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曲横。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖不瓶,靈堂內(nèi)的尸體忽然破棺而出禾嫉,到底是詐尸還是另有隱情,我是刑警寧澤蚊丐,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布熙参,位于F島的核電站,受9級(jí)特大地震影響麦备,放射性物質(zhì)發(fā)生泄漏孽椰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一凛篙、第九天 我趴在偏房一處隱蔽的房頂上張望弄屡。 院中可真熱鬧,春花似錦鞋诗、人聲如沸膀捷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)全庸。三九已至,卻和暖如春融痛,著一層夾襖步出監(jiān)牢的瞬間壶笼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工雁刷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留覆劈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像责语,于是被迫代替她去往敵國(guó)和親炮障。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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