當我們在不能使用GLKit的情況下笋颤,也可以使用GLSL來加載一張圖片。那么我們今天就通過小案例來看一下代碼實現(xiàn)都毒。同時了解一下幀緩沖區(qū)和渲染緩沖區(qū)的關(guān)系色罚。
一、效果圖
二账劲、思路 & frameBuffer與renderBuffer的關(guān)系
首先戳护,我們的大體思路主要分6個步驟:
- 設(shè)置圖層CAEAGLLayer
- 設(shè)置上下文EAGLContext
- 清空緩沖區(qū)
- 設(shè)置renderBuffer
- 設(shè)置frameBuffer
- 開始繪制
注意金抡,4、5 要先設(shè)置renderBuffer再設(shè)置frameBuffer腌且,是因為frameBuffer是renderBuffer的管理者梗肝。
what?
那么我們來看一下他們的關(guān)系:
FrameBuffer是RenderBuffer的管理者铺董,兩者共同組成了幀緩存區(qū)巫击。FrameBuffer是沒有存儲功能的,具體的存儲功能實際是RenderBuffer精续。
FrameBuffer上有3個附著點:
顏色附著點(Color Attachment):管理紋理坝锰、顏色緩沖區(qū)
深度附著點(depth Attachment):會影響顏色緩沖區(qū),管理深度緩沖區(qū)(Depth Buffer)
模板附著點(Stencil Attachment):管理模板緩沖區(qū)(Stencil Buffer)RenderBuffer有3種緩存區(qū):
深度緩存區(qū)(Depth Buffer):存儲深度值等
模板緩存區(qū)(Stencil Buffer):存儲模板
紋理緩存區(qū)( Texture mip Images):保存的是MipMap中當前深度的切片重付。所以需要深度附著點和顏色附著點共同協(xié)作顷级。
三、流程圖
四确垫、源碼部分
1弓颈、頂點著色器
//特意加的注釋,真是項目中删掀,切記不要寫中文翔冀,避免不必要的錯誤
attribute vec4 position;//4維向量-頂點坐標
attribute vec2 textCoordinate;//2維向量-紋理坐標
varying lowp vec2 varyTextCoord;//與fsh中一模一樣
void main()
{
varyTextCoord = textCoordinate;//把紋理坐標 橋接到fsh
gl_Position = position;//最終的頂點結(jié)果要賦值給GLSL的內(nèi)建變量`gl_Position`
}
2、片元著色器
//特意加的注釋披泪,真是項目中纤子,切記不要寫中文,避免不必要的錯誤
precision highp float;//表示:聲明這里的float默認使用高精度
varying lowp vec2 varyTextCoord;//與vsh中一模一樣
uniform sampler2D colorMap;//紋理
void main()
{
/*
**注意**
假如有1000個像素點付呕,片元著色器會執(zhí)行1000次计福。
在 模擬器運行的時候其實沒有GPU。是CPU來模擬GPU來執(zhí)行的徽职。 當我們用復(fù)雜特效的時候象颖,就需要用真機跑了
*/
//最終每個,單個像素的顏色賦值給內(nèi)建變量gl_FragColor
//texture2D(紋理姆钉,紋理坐標)return顏色值
gl_FragColor = texture2D(colorMap,varyTextCoord);
}
3说订、View.m文件中
#import "GLSLView.h"
#import <OpenGLES/ES2/gl.h>
@interface GLSLView()
//1、EAGL提供的繪制表面:CAEAGLLayer(屬于核心動畫的特殊圖層的一種)
@property (nonatomic,strong) CAEAGLLayer *myEaglLayer;
//2潮瓶、上下文
@property (nonatomic,strong) EAGLContext *myContext;
//3陶冷、渲染緩沖區(qū)
@property (nonatomic,assign) GLuint myColorRenderBuffer;
//4、幀緩沖區(qū)
@property (nonatomic,assign) GLuint myColorFrameBuffer;
//5毯辅、程序?qū)ο蟮膇d
@property (nonatomic,assign) GLuint myPrograme;
@end
@implementation GLSLView
- (void)layoutSubviews
{
//1埂伦、設(shè)置圖層
[self setUpLayer];
//2、設(shè)置上下文
[self setUpContext];
//3思恐、清空緩沖區(qū)
[self deleteBuffers];
//4沾谜、設(shè)置renderBuffer
[self setUpRenderBuffer];
//5膊毁、設(shè)置frameBuffer
[self setUpframeBuffer];
//6、開始繪制
[self renderDraw];
}
#pragma mark - 1基跑、設(shè)置圖層
-(void)setUpLayer{
//1婚温、創(chuàng)建圖層
//注意,重寫layerClass媳否,將我們自定義的GLSLView的圖層栅螟,從CALayer替換成CAEAGLLayer。要重寫layerClass方法
self.myEaglLayer = (CAEAGLLayer *)self.layer;
//2篱竭、設(shè)置規(guī)格scale
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
//3力图、設(shè)置描述屬性
/*
1)kEAGLDrawablePropertyRetainedBacking 表示繪圖表面顯示后,是否保留其內(nèi)容室抽。
2)kEAGLDrawablePropertyColorFormat 可繪制表面的內(nèi)部顏色緩存區(qū)格式.
kEAGLColorFormatRGBA8:32位RGBA的顏色搪哪,4*8=32位(默認)
kEAGLColorFormatRGB565:16位RGB的顏色,
kEAGLColorFormatSRGBA8:sRGB代表了標準的紅坪圾、綠、藍惑朦,即CRT顯示器兽泄、LCD顯示器、投影機漾月、打印機以及其他設(shè)備中色彩再現(xiàn)所使用的三個基本色素病梢。sRGB的色彩空間基于獨立的色彩坐標,可以使色彩在不同的設(shè)備使用傳輸中對應(yīng)于同一個色彩坐標體系梁肿,而不受這些設(shè)備各自具有的不同色彩坐標的影響蜓陌。
*/
self.myEaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
@false,kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,
nil];
}
+(Class)layerClass{
return [CAEAGLLayer class];
}
#pragma mark - 2、設(shè)置上下文
-(void)setUpContext{
//1吩蔑、使用2.0版本初始化上下文
EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
//2钮热、判斷是否創(chuàng)建成功
if (!context) {
NSLog(@"context failed!");
return;
}
//3、設(shè)置圖形上下文
if(![EAGLContext setCurrentContext:context]){
NSLog(@"currentContext failed!");
return;
}
self.myContext = context;
}
#pragma mark - 3烛芬、清空緩沖區(qū)
-(void)deleteBuffers{
/*
buffer分frameBuffer 和 renderBuffer兩大類
其中隧期,frameBuffer 相當于 renderBuffer 的管理者
frame buffer object 即稱為FBO
renderBuffer又分為3類:colorBuffer、depthBuffer赘娄、stencilBuffer
*/
//1仆潮、刪除renderBuffer
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
//2、刪除frameBuffer
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
#pragma mark - 4遣臼、設(shè)置renderBuffer
-(void)setUpRenderBuffer{
//1性置、定義一個緩沖區(qū)id
GLuint buffer;
//2、申請一個緩沖區(qū)
glGenRenderbuffers(1, &buffer);
//3揍堰、賦值成全局變量
self.myColorRenderBuffer = buffer;
//4鹏浅、將申請的id綁定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
//5辟灰、將可繪制對象的CAEAGLLayer的存儲 綁定到 renderBuffer對象。此處把context和layer綁定到一起了篡石,一定要寫
//指定存儲在 renderbuffer 中圖像的寬高以及顏色格式(從myLayer中獲冉胬),并按照此規(guī)格為之分配存儲空間
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEaglLayer];
}
#pragma mark - 5凰萨、設(shè)置frameBuffer
-(void)setUpframeBuffer{
//1继控、定義id
GLuint buffer;
//2、申請一個和緩沖區(qū)
glGenFramebuffers(1, &buffer);
//3胖眷、賦值
self.myColorFrameBuffer = buffer;
//4武通、綁定
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
//5、renderBuffer與frameBuffer綁定到一起
/*
1)生成幀緩存區(qū)之后珊搀,則需要將renderbuffer跟framebuffer進行綁定冶忱,
2)調(diào)用glFramebufferRenderbuffer函數(shù)進行綁定到對應(yīng)的附著點上,后面的繪制才能起作用
3)將渲染緩存區(qū)myColorRenderBuffer 通過glFramebufferRenderbuffer函數(shù)綁定到 GL_COLOR_ATTACHMENT0上境析。
*/
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}
#pragma mark - 6囚枪、開始繪制
-(void)renderDraw{
//1、設(shè)置背景色&清空顏色緩沖區(qū)
glClearColor(0.3, 0.45, 0.6, 1);
glClear(GL_COLOR_BUFFER_BIT);
//2劳淆、設(shè)置視口
CGFloat scale = [[UIScreen mainScreen]scale];
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
//3链沼、讀取頂點和片元著色器地址
NSString *vertexFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"vsh"];
NSString *fragmentFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"fsh"];
NSLog(@"vertFile:%@",vertexFile);
NSLog(@"fragFile:%@",fragmentFile);
//4、加載shader沛鸵,拿到progrme
self.myPrograme = [self loadShaderWithVertexFile:vertexFile andFragmentFile:fragmentFile];
//5括勺、鏈接link
glLinkProgram(self.myPrograme);
//6、檢測link
GLint linkStatus;
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
//獲取link失敗的信息
GLchar message[512];
glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSLog(@"program link error:%@",messageString);
return;
}
//7曲掰、使用program
glUseProgram(self.myPrograme);
//8疾捍、設(shè)置頂點坐標、紋理坐標
GLfloat attrArr[] =
{
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
};
//9栏妖、轉(zhuǎn)存到頂點緩沖區(qū)
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
//10乱豆、打開attribute通道,讀取頂點數(shù)據(jù)
//1)獲取頂點數(shù)據(jù)通道id 注意第二個參數(shù)要和vsh中的變量一模一樣
GLuint position = glGetAttribLocation(self.myPrograme, "position");
//2)設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
glEnableVertexAttribArray(position);
//3)設(shè)置讀取方式
/*
參數(shù)1:index,頂點數(shù)據(jù)的索引
參數(shù)2:size,每個頂點屬性的組件數(shù)量,1底哥,2咙鞍,3,或者4.默認初始值是4.
參數(shù)3:type,數(shù)據(jù)中的每個組件的類型趾徽,常用的有GL_FLOAT,GL_BYTE,GL_SHORT续滋。默認初始值為GL_FLOAT
參數(shù)4:normalized,固定點數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值孵奶。(GL_FALSE)
參數(shù)5:stride,連續(xù)頂點屬性之間的偏移量疲酌,默認為0;
參數(shù)6:指定一個指針,指向數(shù)組中的第一個頂點屬性的第一個組件朗恳。默認為0
*/
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL + 0);
//11湿颅、讀取紋理數(shù)據(jù)
GLuint textCoordinate = glGetAttribLocation(self.myPrograme, "textCoordinate");
glEnableVertexAttribArray(textCoordinate);
glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float *)NULL + 3);
//12、加載紋理粥诫,解壓圖片
[self setUpTexture:@"mark.jpeg"];
//13油航、設(shè)置紋理采樣器sampler2D
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
//14、繪制
glDrawArrays(GL_TRIANGLES, 0, 6);
//15怀浆、從渲染緩沖區(qū)顯示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERER];
}
#pragma mark - 加載著色器shader谊囚,并且與程序附著,拿到最后的程序id
-(GLuint)loadShaderWithVertexFile:(NSString *)vertexFile andFragmentFile:(NSString *)fragmentFile
{
//1执赡、定義頂點著色器對象镰踏、片元著色器對象
GLuint verShader,fragShader;
//2、創(chuàng)建一個程序?qū)ο? GLuint program = glCreateProgram();
//3沙合、編譯2個著色器
/*
編譯的步驟一模一樣奠伪,直接封裝起來
參數(shù)1:編譯完存儲的底層地址
參數(shù)2:編譯的類型,GL_VERTEX_SHADER(頂點)首懈、GL_FRAGMENT_SHADER(片元)
參數(shù)3:文件路徑
*/
[self compileShader:&verShader type:GL_VERTEX_SHADER filePath:vertexFile];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER filePath:fragmentFile];
//4绊率、把著色器附著到程序上
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//5、著色器附著之后就沒啥用了 釋放掉
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
#pragma mark - 編譯著色器
-(void)compileShader:(GLuint *)shader type:(GLenum)type filePath:(NSString *)filePath{
//1猜拾、讀取著色器文件路徑即舌,轉(zhuǎn)換成c語言字符串
NSString *pathString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
const GLchar* source = (GLchar*)[pathString UTF8String];
//2、創(chuàng)建一個對應(yīng)的shader
*shader = glCreateShader(type);
//3挎袜、將著色器源碼 附著到著色器對象上
/*
參數(shù)1:shader,要編譯的著色器對象 *shader
參數(shù)2:numOfStrings,傳遞的源碼字符串數(shù)量 1個
參數(shù)3:strings,著色器程序的源碼(真正的著色器程序源碼)
參數(shù)4:lenOfStrings,長度,具有每個字符串長度的數(shù)組肥惭,或NULL盯仪,這意味著字符串是NULL終止的
*/
glShaderSource(*shader, 1, &source, NULL);
//4、把 著色器源代碼 編譯成 目標代碼
glCompileShader(*shader);
}
#pragma mark - 加載紋理蜜葱,解壓圖片
-(GLuint)setUpTexture:(NSString *)imageName{
//1全景、紋理解壓縮
CGImageRef spriImage = [UIImage imageNamed:imageName].CGImage;
//2、判斷圖片有沒有拿到
if (!spriImage) {
NSLog(@"load image faile");
exit(1);
}
//3牵囤、創(chuàng)建一個上下文
/*
CGBitmapContextCreate
參數(shù)1:data,指向要渲染的繪制圖像的內(nèi)存地址
參數(shù)2:width,bitmap的寬度爸黄,單位為像素
參數(shù)3:height,bitmap的高度,單位為像素
參數(shù)4:bitPerComponent,內(nèi)存中像素的每個組件的位數(shù)揭鳞,比如32位RGBA炕贵,就設(shè)置為8
參數(shù)5:bytesPerRow,bitmap的沒一行的內(nèi)存所占的比特數(shù)
參數(shù)6:colorSpace,bitmap上使用的顏色空間 kCGImageAlphaPremultipliedLast:RGBA
*/
//1)拿到圖片的寬高
size_t width = CGImageGetWidth(spriImage);
size_t height = CGImageGetHeight(spriImage);
//2)拿到圖片的大小
GLubyte *spriData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
//3)創(chuàng)建上下文 CGContextRef
CGContextRef spriContext = CGBitmapContextCreate(spriData, width, height, 8, width*4, CGImageGetColorSpace(spriImage), kCGImageAlphaPremultipliedLast);
//4、將圖片繪制出來
/*
CGContextDrawImage 使用的是Core Graphics框架野崇,坐標系與UIKit 不一樣称开。UIKit框架的原點在屏幕的左上角,Core Graphics框架的原點在屏幕的左下角。
CGContextDrawImage
參數(shù)1:繪圖上下文
參數(shù)2:rect坐標
參數(shù)3:繪制的圖片
*/
//1)拿到坐標
CGRect rect = CGRectMake(0, 0, width, height);
//2)使用默認方式繪制
CGContextDrawImage(spriContext, rect, spriImage);
//5鳖轰、畫圖完畢就釋放上下文
CGContextRelease(spriContext);
//6清酥、綁定紋理id (小技巧,如果只有一個紋理id蕴侣,默認是0焰轻,就可以省略glGenTexture代碼了)
glBindTexture(GL_TEXTURE_2D, 0);
//7、設(shè)置紋理屬性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//8昆雀、載入2D紋理
/*
參數(shù)1:紋理模式辱志,GL_TEXTURE_1D、GL_TEXTURE_2D忆肾、GL_TEXTURE_3D
參數(shù)2:加載的層次荸频,一般設(shè)置為0
參數(shù)3:紋理的顏色值GL_RGBA
參數(shù)4:寬
參數(shù)5:高
參數(shù)6:border,邊界寬度
參數(shù)7:format
參數(shù)8:type
參數(shù)9:紋理數(shù)據(jù)
*/
float fw = width,fh = height;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriData);
//9客冈、釋放spriData
free(spriData);
return 0;
}
@end