内容简介:在前面几篇文章中,我们使用了使用
在前面几篇文章中,我们使用了 OpenGL 、 GLKit 等方式去渲染一张图片,这篇文章我们使用 OpenGL ES 来渲染一张图片的显示。
帧缓冲区对象(FrameBuffer)
OpenGL 将绘制帧缓冲区到一个对象所需要的状态进行了封装,成为帧缓冲区对象( FBO )。 虽然帧缓冲区的名字包含一个“缓冲区”字眼,但是其实它不是缓冲区。实际上,并不存在与一个帧缓冲区对象相关联的真正内存存储空间。帧缓冲区对象是一种容器,它可以保存其他确实有内存存储并且可以进行渲染的对象,例如渲染缓冲区( RBO )和纹理缓冲区( TBO )。采用这种方式,帧缓冲区对象能够在保存 OpenGL 管线的输出时将需要的状态和表面绑定到一起。
使用 FBO 时,需要先添加图像,才能渲染到一个 FBO 。一旦一个 FBO 被创建、设置和绑定。大多数 OpenGL 操作就像是在渲染到一个窗口一样执行,但是输出结果将存储在绑定到 FBO 的图像中。
同一时间只有一个 FBO 可以绑定用来进行绘制,并且同一时间只有一个 FBO 可以绑定来进行读取。
创建新的FBO
GLuint buffer; glGenFramebuffers(1, &buffer); // 然后在绑定一个新的FBO来修改和使用 glBindFramebuffer(GL_FRAMEBUFFER, buffer); // 将渲染缓存区RenderBuffer通过glFramebufferRenderbuffer函数绑定到 GL_COLOR_ATTACHMENT0上。 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, buffer); 复制代码
生成帧缓冲区之后,则需要将 renderbuffer 跟 framebuffer 进行绑定,调用 glFramebufferRenderbuffer 函数进行绑定到对应的附着点上,后面的绘制才能起作用
绑定到 GL_FRAMEBUFFER 目标后,接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲。 也可以把帧缓冲分开绑定到读或写目标上,分别使用 GL_READ_FRAMEBUFFER 或 GL_DRAW_FRAMEBUFFER 来做这件事。如果绑定到了 GL_READ_FRAMEBUFFER ,就能执行所有读取操作,像 glReadPixels 这样的函数使用了;绑定到 GL_DRAW_FRAMEBUFFER 上,就允许进行渲染、清空和其他的写入操作。 大多数时候你不必分开用,通常把两个都绑定到 GL_FRAMEBUFFER 上就行 。
销毁FBO在使用完 FBO ,或者在退出前进行清除时,要删除。
glDeleteFramebuffers(1, &buffer); 复制代码
渲染缓冲区对象(RenderBuffer,RBO)
一个 renderbuffer 对象是通过应用分配的一个2D图像缓冲区。 renderbuffer 能够被用来分配和存储颜色、深度或模版值。也能够在一个 framebuffer 被用作颜色、深度、模版的附件。一个 renderbuffer 是一个类似于屏幕窗口系统提供可绘制的表面,可以为给定的 FBO 挑选需要的任意 RBO 组合。
和 FBO 类似, RBO 需要先进行绑定才能修改。绑定渲染缓冲区唯一合法目标时 GL_RENDERBUFFER .
gluint buffer; glGenRenderbuffers(1, &buffer); glBindRenderbuffer(GL_RENDERBUFFER, buffer); 复制代码
OpenGL ES 渲染图片代码实例
案例目标
EAGL EAGL
图片渲染实现流程图
具体代码实现
创建着色器文件
在 Xcode 中,新建一个空白文件,如下
然后命名
vsh
。
顶点着色器代码
// 顶点坐标
attribute vec4 position;
// 纹理坐标
attribute vec2 textCoordinate;
// 纹理坐标
varying lowp vec2 varyTextCoord;
void main() {
varyTextCoord = textCoordinate;
gl_Position = position;
}
复制代码
这里文件里最好不要写注释,可能会出现莫名的错误。且这里没有代码提示,所以需要保证这里代码的准确性。
在 main 函数中,通过 varying 修饰的 varyTextCoord 将纹理坐标传递到片元着色器。 最后给内建变量 gl_Position 赋值,这一步切记不能忘,不然所有操作都徒劳无功。
片元着色器代码
// 纹理坐标
varying lowp vec2 varyTextCoord;
// 纹理采样器(获取对应的纹理ID)
uniform sampler2D colorMap;
void main() {
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
复制代码
texture2D(纹理采样器,纹理坐标) 获取对应坐标的纹理的像素, gl_FragColor 也是内建变量,这一步也不能忘记写。
设置图层
// 创建特殊图层
self.myEagLayer = (CAEAGLLayer *)self.layer;
// 设置scale
[self setContentScaleFactor:[[UIScreen mainScreen]scale]];
// 设置属性
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
复制代码
CAEAGLLayer 是专门提供给 OpenGL 专门使用的一个特殊图层,此外必要要重写 layerClass ,将图层 CALayer 替换成 CAEAGLLayer 。
+ (Class)layerClass {
return [CAEAGLLayer class];
}
复制代码
如果不写,则会出现下面的错误提示:
然后进行设置描述属性, kEAGLDrawablePropertyRetainedBacking 与 kEAGLDrawablePropertyColorFormat 。
kEAGLDrawablePropertyRetainedBacking 表示绘图表面显示后,是否保留其内容。
kEAGLDrawablePropertyColorFormat 则表示可绘制表面的内部颜色缓存区格式,这个 key 对应的值是一个 NSString 指定特定颜色缓存区对象。默认是 kEAGLColorFormatRGBA8 。
kEAGLColorFormatRGBA8 :32位RGBA的颜色,4*8=32位.
kEAGLColorFormatRGB565 :16位RGB的颜色,
kEAGLColorFormatSRGBA8 :sRGB代表了标准的红、绿、蓝,即CRT显示器、LCD显示器、投影机、打印机以及其他设备中色彩再现所使用的三个基本色素。sRGB的色彩空间基于独立的色彩坐标,可以使色彩在不同的设备使用传输中对应于同一个色彩坐标体系,而不受这些设备各自具有的不同色彩坐标的影响。
设置上下文
// 指定OpenGL ES 渲染API版本,我们使用3.0
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES3;
// 创建图形上下文
EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
// 判断是否创建成功
if (!context) {
NSLog(@"Create context failed!");
return;
}
// 设置图形上下文
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"setCurrentContext failed!");
return;
}
// 将局部context,变成全局的
self.myContext = context;
复制代码
清空缓存区
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
复制代码
buffer 分为 framebuffer 和 renderbuffer 。其中 framebuffer 相当于 renderbuffer 的管理者。 frame buffer object 即称为 FBO 。 renderbuffer 则又可以分为三种: colorBuffer 、 depthBuffer 、 stencilBuffer .
设置RenderBuffer
GLuint buffer;
glGenRenderbuffers(1, &buffer);
self.myColorRenderBuffer = buffer;
// 将标识符绑定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
// 将可绘制对象drawable object's CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
复制代码
设置FrameBuffer
GLuint buffer;
glGenRenderbuffers(1, &buffer);
self.myColorFrameBuffer = buffer;
// 将标识符绑定到GL_FRAMEBUFFER
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
// 将渲染缓存区myColorRenderBuffer 通过glFramebufferRenderbuffer函数绑定到 GL_COLOR_ATTACHMENT0上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
复制代码
生成帧缓存区之后,则需要将 renderbuffer 跟 framebuffer 进行绑定,调用 glFramebufferRenderbuffer 函数进行绑定到对应的附着点上,后面的绘制才能起作用
绘制
进行绘制的常规步骤:
- 设置清屏颜色、清屏、视口大小
// 设置清屏颜色
glClearColor(0.3f, 0.45f, 0.5f, 1.0f);
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT);
// 设置视口大小
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);
复制代码
- 读取顶点着色程序、片元着色程序
NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
复制代码
- 加载shader
- (GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag {
// 定义2个临时着色器对象
GLuint verShader, fragShader;
// 创建program
GLint program = glCreateProgram();
// 编译顶点着色程序、片元着色器程序
//参数1:编译完存储的底层地址
//参数2:编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)
//参数3:文件路径
[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
// 创建最终的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
// 释放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
复制代码
- 编译shader
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
// 读取文件路径字符串
NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
const GLchar* source = (GLchar *)[content UTF8String];
// 创建一个shader(根据type类型)
*shader = glCreateShader(type);
// 将着色器源码附加到着色器对象上。
//参数1:shader,要编译的着色器对象 *shader
//参数2:numOfStrings,传递的源码字符串数量 1个
//参数3:strings,着色器程序的源码(真正的着色器程序源码)
//参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
glShaderSource(*shader, 1, &source,NULL);
// 把着色器源代码编译成目标代码
glCompileShader(*shader);
}
复制代码
- 链接、使用
glLinkProgram
// 链接(这里的self.myPrograme就是在上面加载得到的)
glLinkProgram(self.myPrograme);
GLint linkStatus;
// 获取链接状态
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
// 打印报错信息
GLchar message[512];
glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSLog(@"Program Link Error:%@",messageString);
return;
}
NSLog(@"Program Link Success!");
// 使用program
glUseProgram(self.myPrograme);
复制代码
- 设置顶点、纹理坐标,并处理
// 前3个是顶点坐标,后2个是纹理坐标
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,
};
// -----处理顶点数据--------
// 1.顶点缓存区
GLuint attrBuffer;
// 2.申请一个缓存区标识符
glGenBuffers(1, &attrBuffer);
// 3.将attrBuffer绑定到GL_ARRAY_BUFFER标识符上
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
// 4.把顶点数据从CPU内存复制到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
// 将顶点数据通过myPrograme中的传递到顶点着色程序的position
// 1.glGetAttribLocation,用来获取vertex attribute的入口的.
// 2.告诉OpenGL ES,通过glEnableVertexAttribArray,
// 3.最后数据是通过glVertexAttribPointer传递过去的。
// 注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致
GLuint position = glGetAttribLocation(self.myPrograme, "position");
// 设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(position);
// 设置读取方式
//参数1:index,顶点数据的索引
//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
//参数5:stride,连续顶点属性之间的偏移量,默认为0;
//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
// ----处理纹理数据-------
// glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致
GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");
// 设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(textCoor);
// 设置读取方式
//参数1:index,顶点数据的索引
//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
//参数5:stride,连续顶点属性之间的偏移量,默认为0;
//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
复制代码
这部分代码,在之前的篇章中都详细的讲解过,所以这里不再着重解释,代码中的注释,也能过帮助你很好的理解。
- 加载纹理
// 将 UIImage 转换为 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
//判断图片是否获取成功
if (!spriteImage) {
NSLog(@"Failed to load image %@", fileName);
exit(1);
}
// 读取图片的大小,宽和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
// 获取图片字节数 宽*高*4(RGBA)
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
// 创建上下文
/*
参数1:data,指向要渲染的绘制图像的内存地址
参数2:width,bitmap的宽度,单位为像素
参数3:height,bitmap的高度,单位为像素
参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
参数6:colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
// 在CGContextRef上--> 将图片绘制出来
/*
CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。
CGContextDrawImage
参数1:绘图上下文
参数2:rect坐标
参数3:绘制的图片
*/
CGRect rect = CGRectMake(0, 0, width, height);
// 使用默认方式绘制
CGContextDrawImage(spriteContext, rect, spriteImage);
// 画图完毕就释放上下文
CGContextRelease(spriteContext);
// 绑定纹理到默认的纹理ID(
glBindTexture(GL_TEXTURE_2D, 0);
// 设置纹理属性
/*
参数1:纹理维度
参数2:线性过滤、为s,t坐标设置模式
参数3:wrapMode,环绕模式
*/
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);
float fw = width, fh = height;
// 载入纹理2D数据
/*
参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数2:加载的层次,一般设置为0
参数3:纹理的颜色值GL_RGBA
参数4:宽
参数5:高
参数6:border,边界宽度
参数7:format
参数8:type
参数9:纹理数据
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
//11.释放spriteData
free(spriteData);
复制代码
上面代码绘制出来的图片是翻转过来的,原因在代码中也有注释, CGContextDrawImage 使用的是 Core Graphics 框架,坐标系与 UIKit 不一样。 UIKit 框架的原点在屏幕的左上角, Core Graphics 框架的原点在屏幕的左下角。
解决图片翻转问题
利用下面代码,可修改这个问题
CGRect rect = CGRectMake(0, 0, width, height);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextDrawImage(spriteContext, rect, spriteImage);
复制代码
原因解释:
第一步:
CGContextTranslateCTM(spriteContext, 0, rect.size.height); ,在y轴方向移动 rect.size.height 的距离
第二步:
CGContextScaleCTM(spriteContext, 1.0, -1.0); ,这句代码表示在y轴方向进行旋转,效果如下图
经过上面2个步骤,图形即可翻转过来。
设置纹理采样器、绘图、从渲染缓冲区显示到屏幕上
// 设置纹理采样器 sampler2D
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
// 从渲染缓冲区显示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
复制代码
最终效果图:
未翻转:
翻转后:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Octane渲染入门-渲染设置图文版
- Vue 服务端渲染原理及入门
- Nuxt.js服务端渲染入门
- OpenGL ES 入门之旅(15)--分屏滤镜渲染图片
- Vue 服务端渲染(SSR)、Nuxt.js - 从入门到实践
- OpenGL ES 入门之旅--OpenGL 下的坐标系和着色器渲染流程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Think Python
Allen B. Downey / O'Reilly Media / 2012-8-23 / GBP 29.99
Think Python is an introduction to Python programming for students with no programming experience. It starts with the most basic concepts of programming, and is carefully designed to define all terms ......一起来看看 《Think Python》 这本书的介绍吧!