Series Recommended Reading:
OpenGL / OpenGL ES Getting Started: graphics API and professional terms to resolve
OpenGL / OpenGL ES entry: the rendering process and fixed storage shader
OpenGL / OpenGL ES Getting Started: image rendering implementation and rendering problems
OpenGL / OpenGL ES Getting Started: Basic transformation - acquaintance vectors / matrices
OpenGL / OpenGL ES entry: Probe texture - analytical common API
OpenGL / OpenGL ES entry: textures - case analysis and texture coordinates (pyramid)
OpenGL / OpenGL ES entry: vertex shader and fragment shader (OpenGL transition ES OpenGL)
OpenGL / OpenGL ES entry: GLKit and API Introduction to
OpenGL / OpenGL ES entry: GLKit and use cases
OpenGL ES rendering picture
In previous articles, we use OpenGL
, GLKit
etc. to render a picture, this article we use OpenGL ES
to render a picture display.
Framebuffer object (FRAMEBUFFER)
OpenGL
The frame buffer to draw the desired state of an object is encapsulated, the frame buffer become the target ( the FBO ). Although the frame buffer contains the name of a "buffer zone" word, but in fact it is not a buffer. In fact, there is associated with a frame buffer object real memory storage space. Framebuffer object is a container, which can hold other objects do have memory storage and can be rendered, such as rendering buffer ( the RBO ), and a texture buffer ( TBO ). In this manner, frame buffer objects can be stored in OpenGL
the required state and the surface to bind together the output line.
Use FBO , the need to add images to render to a FBO . Once a FBO is created, settings and binding. Most OpenGL
operations like rendering to a window as execution, but the output will be stored bound to FBO image.
Only one FBO can bind to draw, and only one of them FBO can bind to read.
Create a new 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);
复制代码
After generating the frame buffer, it is necessary to
renderbuffer
followframebuffer
binding, callsglFramebufferRenderbuffer
a function to bind to the corresponding attachment points, the latter acting to draw
To bind to
GL_FRAMEBUFFER
the target, then all read and write operations will affect the frame buffer to the frame buffer currently bound. Also can separate from the frame buffer to bind to the target read or write, were usedGL_READ_FRAMEBUFFER
, orGL_DRAW_FRAMEBUFFER
to do it. If bound toGL_READ_FRAMEBUFFER
, all the read operations can be performed, likeglReadPixels
this function uses; bound toGL_DRAW_FRAMEBUFFER
the, allows rendering, emptying and other write operations. Most of the time you do not have to be separated by, usually the two are bound toGL_FRAMEBUFFER
the line .
FBO destroyed in finished using the FBO , or when they are cleared before exiting, you want to delete.
glDeleteFramebuffers(1, &buffer);
复制代码
Render buffer object (RenderBuffer, RBO)
An renderbuffer
object is a 2D image buffer assigned by the application. renderbuffer
Distribution and storage can be used to color, depth, stencil, or values. In one it can also framebuffer
be used as attachment color, depth, stencil. A renderbuffer
is similar to a window screen system provides a drawing surface, you may be given FBO
an arbitrary selection of desired RBO
combinations.
And FBO
similar, RBO
need to be bound to modify. When you bind render only legitimate destination buffer GL_RENDERBUFFER
.
gluint buffer;
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
复制代码
OpenGL ES rendering picture code examples
Case goals
- A
EAGL
rendering surface creation screen - Loading the vertex / fragment shader
- Creating a program object, and the link vertex / fragment shader and linking the program object
- Set Viewport
- Clear the color buffer
- Rendering simple primitives
- The contents of the color buffer in
EAGL
the rendering window performance
Image rendering processes for implementing the
Specific code implementation
Create a shader file
In Xcode
, create a new blank document, as follows
Then name
Name of the file they can easily define, and its own suffix can also be easily defined as a string, it would be best able to associate, for example, where the vertex shader namedvsh
.
Vertex shader code
// 顶点坐标
attribute vec4 position;
// 纹理坐标
attribute vec2 textCoordinate;
// 纹理坐标
varying lowp vec2 varyTextCoord;
void main() {
varyTextCoord = textCoordinate;
gl_Position = position;
}
复制代码
Here is best not to file written comments, inexplicable error may occur. And there is no code hints, it is necessary to ensure the accuracy of this code.
In main
function, the varying
modified varyTextCoord
transmitted to the fragment shader texture coordinates. Finally, to the built-in variable gl_Position
assignment, remember not to forget this step, otherwise all operations are in vain.
Code fragment shader
// 纹理坐标
varying lowp vec2 varyTextCoord;
// 纹理采样器(获取对应的纹理ID)
uniform sampler2D colorMap;
void main() {
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
复制代码
texture2D(纹理采样器,纹理坐标)
Get the texture coordinates corresponding to a pixel, gl_FragColor
but also built-in variables, this step can not forget to write.
Set layer
// 创建特殊图层
self.myEagLayer = (CAEAGLLayer *)self.layer;
// 设置scale
[self setContentScaleFactor:[[UIScreen mainScreen]scale]];
// 设置属性
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
复制代码
CAEAGLLayer
Is designed to provide OpenGL
a special layer used exclusively, furthermore necessary to be rewritten layerClass
, the layer CALayer
replaced CAEAGLLayer
.
+ (Class)layerClass {
return [CAEAGLLayer class];
}
复制代码
If you do not write, the following error message appears:
Then set the properties described, kEAGLDrawablePropertyRetainedBacking
and kEAGLDrawablePropertyColorFormat
.
kEAGLDrawablePropertyRetainedBacking
After drawing showing a display surface, whether to retain its contents. kEAGLDrawablePropertyColorFormat
The drawing represents the internal surface of the color buffer formats, this key
value corresponding to a NSString
specific color buffer specified object. Default kEAGLColorFormatRGBA8
.
kEAGLColorFormatRGBA8
: 32-bit RGBA color, 4 * 8 = 32 bitskEAGLColorFormatRGB565
: 16-bit RGB colorkEAGLColorFormatSRGBA8
: the sRGB represent standard red, green, and blue, i.e., CRT displays, LCD displays, projectors, printers, and other devices in the color reproduction three basic pigments used. sRGB color space based on the independent color coordinates, can make use of different colors corresponding to the same transmission apparatus in a color coordinate system, without being affected by each of these devices having different color coordinates.
Setting the context
// 指定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;
复制代码
Empty the cache
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
复制代码
buffer
Divided framebuffer
and renderbuffer
. Which framebuffer
is equivalent to renderbuffer
the manager. frame buffer object
That is called FBO
. renderbuffer
They can be divided into three colorBuffer
types: depthBuffer
, , stencilBuffer
.
Set 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];
复制代码
Set 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);
复制代码
After generating the frame buffer, it is necessary to renderbuffer
follow framebuffer
binding, calls glFramebufferRenderbuffer
a function to bind to the corresponding attachment points, the latter acting to draw
draw
Routine step drawing:
- Set clear color screen, the screen is cleared, the viewport size
// 设置清屏颜色
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);
复制代码
- Reading the vertex shader, fragment shader
NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
复制代码
- Load 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;
}
复制代码
- Compiled 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);
}
复制代码
- Link, use
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);
复制代码
- Provided vertex, texture coordinates, and processing
// 前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);
复制代码
This part of the code, in the previous chapter are explained in detail, so I will not explain here focused, comments in the code, it can help you too well understood.
- Load textures
// 将 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);
复制代码
The above code is drawn out of the picture is upside down, because there are comments in the code, CGContextDrawImage
using a Core Graphics
framework, coordinate system and UIKit
is not the same. UIKit
The origin of the frame in the upper left corner of the screen, Core Graphics
the lower left corner of the screen frame of origin.
Picture flip solve the problem
With the following codes, this problem can be modified
CGRect rect = CGRectMake(0, 0, width, height);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextDrawImage(spriteContext, rect, spriteImage);
复制代码
Explain the reasons:
The first step: CGContextTranslateCTM(spriteContext, 0, rect.size.height);
moving in the y-axis direction rect.size.height
distance
Step: CGContextScaleCTM(spriteContext, 1.0, -1.0);
, this code indicates rotates y-axis direction, to the following figure
After two steps above, the graphics can be flipped over.
Texture sampler is provided, the drawing, from the display to the screen rendering buffer
// 设置纹理采样器 sampler2D
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
// 从渲染缓冲区显示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
复制代码
The final renderings:
Unturned:
Flip back:
Reproduced in: https: //juejin.im/post/5cfa680b6fb9a07ef63fced8