本文主要介紹使用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ò)大而造成卡頓掘譬。
創(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:
- 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)郎楼,變換矩陣等万伤。
- 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
等忽冻。
本文主要研究YUV420P
的I420
與YUV420SP
的NV12
真朗。
紋理
我們需要將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)肩豁、紋理對(duì)象脊串、紋理單元
- 紋理目標(biāo)是顯卡的軟件接口中定義的句柄,指向要進(jìn)行當(dāng)前操作的顯存清钥。
- 紋理對(duì)象是我們創(chuàng)建的用來(lái)存儲(chǔ)紋理的顯存琼锋,在實(shí)際使用過(guò)程中使用的是創(chuàng)建后返回的ID。
- 紋理單元是顯卡中所有的可用于在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ǔ)言:GLSL和YUV顏色編碼解析锹杈。
//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)打碼互躬,貼紙等。