前言
前面學(xué)習(xí)了opengl es渲染管線基跑,可編程語言GLSL,常用的opengl es函數(shù)歹篓,有了這些基礎(chǔ),現(xiàn)在就可以實(shí)現(xiàn)如何將一張圖片渲染到屏幕上了揉阎。圖片庄撮,最終在內(nèi)存中的表現(xiàn)形式就是由一個個像素組成的,而每個像素又由RGB組成毙籽,其實(shí)opengl es最終渲染的像素是由RGBA四個通道組成的洞斯。
opengl es系列文章
opengl es之-基礎(chǔ)概念(一)
opengl es之-GLSL語言(二)
opengl es之-GLSL語言(三)
opengl es之-常用函數(shù)介紹(四)
opengl es之-渲染兩張圖片(五)
opengl es之-在圖片上添加對角線(六)
opengl es之-離屏渲染簡介(七)
opengl es之-CVOpenGLESTextureCache介紹(八)
opengl es之-播放YUV文件(九)
需求
渲染兩張圖片,并將它們混合坑赡,最終呈現(xiàn)到屏幕上
準(zhǔn)備
引入頭文件烙如,在ios平臺,上下文和窗口管理的是由EAGL實(shí)現(xiàn)的毅否,這里例子中的opengl es版本為2.0
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/EAGL.h>
創(chuàng)建上下文環(huán)境
基于UIView創(chuàng)建上下文環(huán)境亚铁,首先要重寫class方法,返回return [CAEAGLLayer class];
// 必不可少螟加,否則無法成功創(chuàng)建opengl es環(huán)境
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
1徘溢、首先設(shè)置當(dāng)前UIView的layer的屬性,這里要注意opaque最好為NO捆探,這樣可以降低性能消耗甸昏。
2、contentsScale系統(tǒng)默認(rèn)為1.0徐许,這里最好設(shè)置為[UIScreen mainScreen].scale施蜜,否則由EAGL創(chuàng)建的上下文窗口真正的像素大小就是ios中基于point單位的大小了,這樣很明顯會造成最終渲染的圖片壓縮
3雌隅、drawableProperties屬性按照下面的例子來進(jìn)行即可翻默,沒什么好講的
- (void)setupContext
{
CAEAGLLayer calayer = (CAEAGLLayer*)self.layer;
calayer.opaque = NO; //CALayer默認(rèn)是透明的缸沃,透明的對性能負(fù)荷大,故將其關(guān)閉
// 表示屏幕的scale修械,默認(rèn)為1趾牧;會影響后面renderbufferStorage創(chuàng)建的renderbuffer的長寬值;
// 它的長寬值等于=layer所在視圖的邏輯長寬*contentsScale
// 最好這樣設(shè)置肯污,否則后面按照紋理的實(shí)際像素渲染翘单,會造成圖片被放大。
calayer.contentsScale = [UIScreen mainScreen].scale;
calayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
// 由應(yīng)用層來進(jìn)行內(nèi)存管理
@(NO),kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,
nil];
// 創(chuàng)建指定OpenGL ES的版本的上下文蹦渣,一般選擇2.0的版本
_context = [[EAGLContext alloc] initWithAPI:version];
// 當(dāng)為yes的時候哄芜,所有關(guān)于Opengl渲染,指令真正執(zhí)行都在另外的線程中柬唯。NO认臊,則關(guān)于渲染,指令真正執(zhí)行在當(dāng)前調(diào)用的線程
// 對于多核設(shè)備有大的性能提升
_context.multiThreaded = yesOrnot;
_memoryPool = CMMemoryPoolCreate(NULL);
// 收到內(nèi)存不足警告后锄奢,需要清除部分內(nèi)存
__unsafe_unretained __typeof__ (self) weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
object:nil
queue:nil
usingBlock:^(NSNotification *notification) {
__typeof__ (self) strongSelf = weakSelf;
if (strongSelf) {
CVOpenGLESTextureCacheFlush([strongSelf coreVideoTextureCache], 0);
}
}];
}
備注:這里特別要說明的就是上面代碼中有一個CMMemoryPoolCreate()創(chuàng)建的內(nèi)存池失晴,該函數(shù)來自于<CoreMedia/CoreMedia.h>框架,其實(shí)正常渲染一張圖片不需要這個東西拘央,對于渲染視頻才需要這個內(nèi)存池(可以避免重復(fù)創(chuàng)建內(nèi)存)涂屁,后面在說這個,這里可以先略過
創(chuàng)建幀緩沖區(qū)和渲染緩沖區(qū)
先說兩個概念灰伟,幀緩沖區(qū)和渲染緩沖區(qū)
幀緩沖區(qū):在opengl es中也成為FBO拆又,即用于渲染物體(比如渲染一個白色的正方形,比如將一張圖片渲染出來)的一塊內(nèi)存(可能是GPU或者內(nèi)存條中內(nèi)存)
渲染緩沖區(qū):在opengl es中袱箱,其實(shí)將物體渲染到幀緩沖區(qū)整個渲染過程就已經(jīng)結(jié)束了,渲染緩沖區(qū)是為了將物體再呈現(xiàn)到屏幕上時的一個過渡緩沖區(qū)义矛,系統(tǒng)要將物體渲染到屏幕上會先將幀緩沖區(qū)中內(nèi)容拷貝到渲染緩沖區(qū)发笔,然后在呈現(xiàn)到屏幕上
具體代碼如下
創(chuàng)建幀緩沖區(qū)
// 創(chuàng)建frameBuffer
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
創(chuàng)建渲染緩沖區(qū)
// 創(chuàng)建渲染buffer
glGenRenderbuffers(1, &_renderBuffer); //第一個參數(shù) 創(chuàng)建buffer的數(shù)量 第二個 參數(shù) 創(chuàng)建的bufferId(為0表示創(chuàng)建失敗)
// 綁定剛剛創(chuàng)建的buffer為GL_RENDERBUFFER類型。
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); // 第一個參數(shù)凉翻,buffer的類型了讨,要與前面創(chuàng)建的buffer類型對應(yīng)。第二個參數(shù)制轰,前面創(chuàng)建的buffer id
將渲染緩沖區(qū)關(guān)聯(lián)到幀緩沖區(qū)前计,并且由EAGL分配上下文
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
// 為render buffer 開辟存儲空間 重要;EAGLContext必須設(shè)置正確,否則下面會出現(xiàn)36054錯誤
[_context.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_renderWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_renderHeight);
glFramebufferRenderbuffer()將渲染緩沖區(qū)和幀緩沖區(qū)關(guān)聯(lián)了起來垃杖,這樣渲染結(jié)果才能沖這個指定的幀緩沖區(qū)拷貝到渲染緩沖區(qū)中(此過程由系統(tǒng)完成)男杈;renderbufferStorage則是為渲染緩沖區(qū)分配了內(nèi)存,可以看到调俘,其實(shí)由glGen glGenRenderbuffers()和glGenFramebuffers()函數(shù)并沒有真正創(chuàng)建內(nèi)存伶棒,它只是生產(chǎn)了一個可以用來指向這個內(nèi)存的句柄(也可以成為內(nèi)存地址)
加載著色器程序
著色器程序由頂點(diǎn)著色器程序和片元著色器程序組成旺垒,頂點(diǎn)著色器程序用于渲染管線中的頂點(diǎn)確定階段,片元著色器程序則用于片元處理階段肤无。完成一個著色器程序需要經(jīng)過編譯GLSL源代碼先蒋,鏈接頂點(diǎn)著色器和片元著色器,執(zhí)行著色器程序三個步驟宛渐。
1竞漾、編寫GLSL代碼
這里的頂點(diǎn)著色器和片元著色器代碼如下:
頂點(diǎn)著色器;這里定義了position變量,它表示最終要渲染的圖元的頂點(diǎn)的坐標(biāo); texcoord表示要加載的圖片的紋理的坐標(biāo)
attribute vec4 position;
attribute vec2 texcoord;
varying highp vec2 v_texcoord;
void main()
{
gl_Position = position;
v_texcoord = texcoord.xy;
}
片元著色器
這個片元著色器的含義就是窥翩,將兩張圖片混合
uniform sampler2D inputImageTexture1;
uniform sampler2D inputImageTexture2;
varying highp vec2 v_texcoord;
void main() {
gl_FragColor = texture2D(inputImageTexture1, v_texcoord)+texture2D(inputImageTexture2, v_texcoord);
}
2业岁、編譯GLSL源代碼(頂點(diǎn)著色器和片元著色器方式一樣)
- (BOOL)compileShader:(GLenum)type sString:(NSString*)sString shader:(GLuint*)shaderRet
{
if (sString.length == 0) {
NSLog(@"著色器程序不能為nil");
return NO;
}
const GLchar *sources = (GLchar*)[sString UTF8String];
// 創(chuàng)建著色器程序句柄
GLuint shader = glCreateShader(type);
if (shader == 0 || shader == GL_INVALID_ENUM) {
NSLog(@"glCreateShader fail");
return NO;
}
// 為著色器句柄添加GLSL代碼;可一次添加多個代碼,一般添加一個鳍烁,若添加一個源代碼則最后一個參數(shù)為NULL 即可
glShaderSource(shader, 1, &sources, NULL);
// 編譯該GLSL代碼
glCompileShader(shader);
// 打印出編譯過程中產(chǎn)生的GLSL的日志
#ifdef DEBUG
GLint logLenght;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLenght);
if (logLenght > 0) {
GLchar *log = (GLchar*)malloc(logLenght);
glGetShaderInfoLog(shader, logLenght, &logLenght, log);
NSLog(@"Shader compile log:\n%s", log);
free(log);
}
#endif
// 檢查編譯結(jié)果
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
NSLog(@"compile fail %d",status);
return NO;
}
*shaderRet = shader;
return YES;
}
可以看到大致分為這幾個步驟
a叨襟、由glCreateShader(type)函數(shù)創(chuàng)建著色器句柄,type有兩種類型:GL_VERTEX_SHADER(頂點(diǎn)著色器)和GL_FRAGMENT_SHADER(片元著色器),他們分別是opengl es的常量
b幔荒、glShaderSource(shader, 1, &sources, NULL);將源程序載入到上面創(chuàng)建的著色器句柄中
c糊闽、glCompileShader(shader);編譯程序
d、【可選】爹梁,可以打印編譯過程中的日志右犹,測試階段用來調(diào)試GLSL代碼的正確性
e、glGetShaderiv(shader, GL_COMPILE_STATUS, &status);檢查編譯是否正確
3姚垃、生成最終的著色器程序
// 創(chuàng)建一個最終程序句柄念链;它由頂點(diǎn)著色器和片段著色器組成
filterProgram = glCreateProgram();
// 分別添加頂點(diǎn)著色器程序和片段著色器程序
glAttachShader(filterProgram, vShader);
glAttachShader(filterProgram, fShader);
/** 連接成一個最終程序
* 當(dāng)這一步完成之后,app就可以和opengl es進(jìn)行交互了积糯,
* 1掂墓、比如app獲取glsl中的頂點(diǎn)變量,設(shè)置幾何圖元的頂點(diǎn)以確定圖元的最終形狀
* 2看成、app獲取glsl中的紋理變量君编,然后將本地圖片傳遞給這個紋理變量以實(shí)現(xiàn)將圖片傳遞給顯卡進(jìn)行渲染和其
* 它處理
*/
glLinkProgram(filterProgram);
// 輸出連接過程中的日志
GLint status;
glValidateProgram(filterProgram);
#ifdef DEBUG
GLint logLength;
glGetProgramiv(filterProgram, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0){
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(filterProgram, logLength, &logLength, log);
NSLog(@"Program validate log:\n%s", log);
free(log);
}
#endif
// 檢查連接結(jié)果
glGetProgramiv(filterProgram, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
NSLog(@"link program fail %d",status);
return nil;
}
由如下幾個步驟完成
a、filterProgram = glCreateProgram();創(chuàng)建最終著色器程序句柄
b川慌、glAttachShader(filterProgram, vShader);加載頂點(diǎn)著色器和片元著色器到上面的句柄中
c吃嘿、glLinkProgram(filterProgram);執(zhí)行鏈接
d、【可選】打印鏈接過程中日志梦重,測試階段使用
e兑燥、glGetProgramiv(filterProgram, GL_INFO_LOG_LENGTH, &logLength);檢查連接是否成功
4、執(zhí)行著色器程序
著色器程序必須得執(zhí)行才能最終使用
glUseProgram(filterProgram);
渲染圖片到幀緩沖區(qū)
開辟渲染緩沖區(qū)域
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, _renderWidth, _renderHeight);
int w1,h1;
int w2,h2;
void *image1 = [CGImageUtils rgbaImageDataForPath:path1 width:&w1 height:&h1];
NSLog(@"圖片的寬和高 w1 %d h1 %d",w1,h1);
void *image2 = [CGImageUtils rgbaImageDataForPath:path2 width:&w2 height:&h2];
NSLog(@"圖片的寬和高 w2 %d h2 %d",w2,h2);
1琴拧、glViewport(0, 0, _renderWidth, _renderHeight);這個函數(shù)很重要降瞳,在渲染到幀緩沖區(qū)之前,必須開辟一塊渲染區(qū)域蚓胸,否則無法渲染
2力崇、rgbaImageDataForPath是我自己封裝的一個方法斗塘,具體參考Demo,將一張圖片轉(zhuǎn)換成RGBA像素數(shù)據(jù)
給著色器程序傳頂點(diǎn)和圖片
[self.blendTwoTextureProgram use];
GLuint position = [self.blendTwoTextureProgram attribLocationForName:@"position"];
GLuint texcoord = [self.blendTwoTextureProgram attribLocationForName:@"texcoord"];
GLuint s_texture1 = [self.blendTwoTextureProgram uniformLocationForName:@"inputImageTexture1"];
GLuint s_texture2 = [self.blendTwoTextureProgram uniformLocationForName:@"inputImageTexture2"];
glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, verData1);
glEnableVertexAttribArray(position);
glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 0, uvData);
glEnableVertexAttribArray(texcoord);
GLint active,textureSize;
glGetIntegerv(GL_ACTIVE_TEXTURE, &active);
NSLog(@"該設(shè)備支持的最大紋理單元數(shù)目 %d",active);
// 超過此大小則必須先壓縮再傳給opengl es亮靴,否則opengl es無法渲染
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &textureSize);
NSLog(@"該設(shè)備支持的紋理最大的長或?qū)?%d",textureSize);
glActiveTexture(GL_TEXTURE1);
glGenTextures(1,&texturexId2);
glBindTexture(GL_TEXTURE_2D, texturexId2);
// 此方法必須有馍盟,第二個參數(shù)要與前面激活的紋理單元數(shù)對應(yīng),前面是GL_TEXTURE1茧吊,這里就是1
glUniform1i(s_texture2, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // S方向上的貼圖模式
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // T方向上的貼圖模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)w2, (int)h2, 0, GL_RGBA, GL_UNSIGNED_BYTE, image2);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
/** 紋理的工作原理
* 紋理單元用來將app中圖片像素數(shù)據(jù)傳遞給OpenGL ES,對應(yīng)片段著色器中的uniform sampler2D變量贞岭。它是一個全局對象,每臺設(shè)備都會創(chuàng)建數(shù)個獨(dú)立的紋理單元搓侄,要使用
* 一個紋理單元之前必須先激活它才能使用瞄桨,激活之后還需要綁定,則才可以使用多個紋理對象;
* 如果應(yīng)用中只有一個激活的紋理讶踪,則不需要調(diào)用glBindTexture()函數(shù)也可以芯侥,但是如果要使用多個紋理,則必須要調(diào)用glBindTexture()進(jìn)行區(qū)分乳讥,否則會造成數(shù)據(jù)被覆蓋
*/
glActiveTexture(GL_TEXTURE0);
glGenTextures(1,&texturexId1);
NSLog(@"texturexId %d",texturexId1);
glBindTexture(GL_TEXTURE_2D, texturexId1);
// 此方法必須有柱查,第二個參數(shù)要與前面激活的紋理單元數(shù)對應(yīng),前面是GL_TEXTURE0云石,這里就是0
glUniform1i(s_texture1, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // S方向上的貼圖模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // T方向上的貼圖模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 1字節(jié)對齊唉工,效率比較低,默認(rèn)是4汹忠;必須在glTexImage2D前面設(shè)置
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// 因?yàn)閕mage1像素數(shù)據(jù)本身就是由RGBA構(gòu)成的淋硝,所以這里format只能是GL_RGBA,如果傳其它值則會造成數(shù)據(jù)不對稱而出錯
// 比如進(jìn)行視頻渲染是宽菜,分別傳遞YUV三個分量的像素給Opengl es谣膳,則那個時候format取值就必須為GL_LUMINANCE或者GL_ALPHA等只有一個字節(jié)的
// 最后一個參數(shù)可以為NULL,只是分配一塊w1xh1的內(nèi)存空間铅乡,當(dāng)為NULL時继谚,如果前后調(diào)用了CGContextDrawImage,這個函
// 數(shù)的繪制結(jié)果會傳給glTexImage2D()開辟的內(nèi)存空間
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w1, h1, 0, GL_RGBA, GL_UNSIGNED_BYTE, image1); // border參數(shù)為1 渲染不出來
// 釋放圖片像素數(shù)據(jù)
if (image1) {
free(image1);
}
if (image2) {
free(image2);
}
1隆判、blendTwoTextureProgram是定義的著色器變量對象犬庇,通過它可以獲取著色器程序中頂點(diǎn)變量(即前面attribute vec4 position;)僧界、紋理坐標(biāo)變量(attribute vec2 texcoord;)侨嘀、紋理單元變量(uniform sampler2D inputImageTexture1;)的地址
glVertexAttribPointer()函數(shù)用于給頂點(diǎn)變量和紋理坐標(biāo)變量傳值,這里verData1為:
static float verData1[8] = {
-1.0f,-1.0f,// 左下角
1.0f,-1.0f, // 右下角
-1.0f,1.0f, // 左上角
1.0f,1.0f, // 右上角
};
static float uvData[8] = {
0.0f, 1.0f, // 左下角
1.0f, 1.0f, // 右下角
0.0f, 0.0f, // 左上角
1.0f, 0.0f, // 右上角
};
2捂襟、glActiveTexture(GL_TEXTURE1);激活紋理單元1咬腕,表示使用紋理單元1來處理第二張圖
glGenTextures(1,&texturexId2);生成一個紋理句柄
glBindTexture(GL_TEXTURE_2D, texturexId2);將紋理句柄綁定到紋理單元1的GL_TEXTURE_2D類型紋理對象上,那么后續(xù)通過glTexxxx()函數(shù)設(shè)置的參數(shù)都是針對這個紋理句柄設(shè)置的
glUniform1i()指定前面片元著色中變量uniform sampler2D inputImageTexture1;和uniform sampler2D inputImageTexture2;分別使用哪個紋理單元(在opengl es一般都有數(shù)百個紋理單元)來處理圖片葬荷,
glTexImage2D()用來給著色器傳圖片
開始渲染
// 確定要繪制的幾何圖形涨共,該指令執(zhí)行后才opengl es指令才開始真正的執(zhí)行纽帖;處于渲染管線的第一階段,每次glDrawArrays()的調(diào)用
// 代表前面所有的指令是一次完整的渲染
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
將渲染結(jié)果呈現(xiàn)到屏幕上
// 即光柵化階段举反,它與opengl es的渲染是獨(dú)立的懊直,不相干的。當(dāng)它會將前面所有的渲染結(jié)果呈現(xiàn)到屏幕上;
[_context.context presentRenderbuffer:GL_RENDERBUFFER];
項(xiàng)目地址
具體可參考項(xiàng)目中GLView的方法火鼻,里面有很詳細(xì)的注釋
// 一次渲染多張圖片室囊;同時渲染兩張張圖片,效果是混合這兩張圖片
- (void)renderTextureWithPath:(NSString*)path path2:(NSString*)path2;