一、前言
OpenGL是Khronos Group开发维护的一个规范,它主要为我们定义了用来操作图形和图片的一些列函数API,需要注意的是OpenGL本身并不是API。
GPU的硬件开发商则需要提供满足OpenGL规范的实现,这些实现通常被称之为“驱动”,他们负责OpenGL定义的API命令翻译为GPU指令。
OpenGL ES((OpenGL for Embedded Systems) )是OpenGL三维图形的API的子集,针对手机、PDA和游戏主机等嵌入式设备儿设计。
二、基础概念
渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被分为上面几个阶段,每个阶段会把前一个阶段的输出作为输入。
注意蓝色部分是我们可以注入自定义的着色器部分
- 1、顶点着色器:它把单独的顶点作为输入,主要目的是把3D坐标转换为另一种3D坐标
- 2、图元装配:将顶点着色器输出的所有顶点作为输入,并所有的点装配为指定图元形状
- 3、几何着色器:把接收到的图元形式的一系列点的集合作为输入,它可以通过产生新顶点构造出新的图元来生车给你其他形状
- 4、光栅化阶段:把图元映射为最终屏幕上相应的像素,生成供片段着色器使用的片段,在片段着色器运行之前会执行裁切,超出视图外的所有像素都将被裁切,以提升效率
- 5、片段着色器:计算一个像素的最终颜色
- 6、测试和混合:这个阶段检测片段对应的深度值,来判断这个像素在其他物体的前面还是后面,决定是否丢弃。这个阶段也会检查alpha值并对物体进行混合
1、帧缓存
用来接收GPU渲染出来的2D图像像素数据的缓冲区叫做帧缓存。帧缓存可以同时存在多个,并通过OpenGL ES让GPU把渲染结果存储到任意数量的帧缓存中。
程序和操作系统会把渲染结果保存到后帧缓存在内的其他帧缓存中。当渲染后的后帧缓存包含一个完整的图像时,前帧缓存和后帧缓存会瞬间切换。
2、OpenGL 坐标系
OpenGL ES总是开始于一个矩形的笛卡尔坐标系,这意味着任意两个轴之间的角度都是90度。空间中的每个位置称之为顶点,每个顶点通过其X、Y、Z轴上的位置被定义。
OpenGL ES坐标都是浮点数储存的,并且没有单位。
3、纹理
纹理是用来保存图像的颜色元素值的OpenGL ES缓存。
纹素:当用图像初始化一个纹理缓存后,这个图像的每个像素变成了纹理的一个纹素,与像素类似,纹素保存颜色数据。像素通常表示计算机屏幕上的一个世纪的颜色点;而纹素存在于一个虚拟的没有尺寸的坐标系中。
4、 纹理坐标系
纹理坐标系为一个命名为S和T的2D轴。在一个纹理中无论有多少纹素,纹理的尺寸永远在S轴上从0.0到1.0,T轴上从0.0到1.0.
5、片元
点阵化(rasterizing):转换几何图形数据为帧缓存中的颜色像素的渲染步骤叫做点阵化,每个颜色像素叫做片元。
映射(mapping):指定怎么对齐纹理和顶点,以便让GPU知道每个片元颜色由哪些纹素决定。每个顶点还会给出U和V的坐标值,每个U坐标会映射顶点到视窗中的最终位置到纹理中的沿着S轴的一个位置,V坐标映射到T轴。
- 取样(sampling):GPU根据计算出来的每个片元的U、V位置从绑定的纹理中选择纹素。
6、为缓存提供数据
1、 生成:glGenBuffers() - 请求OpenGL ES为图形处理器控制的缓存生成一个独一无二的标识
2、 绑定:glBindBuffer() - 告诉OpenGL ES接下来的运算使用哪个缓存
3、 缓存数据:glBufferData()、glBufferSubData() - 让OpenGL ES为当前的缓存分配并初始化足够的连续内存。(通常是从CPU控制的内存复制数据到分配的内存)
4、 启用或者禁止:glEnableVertexAttribArray() | glDisableVertexAttribArray() - 告诉OpenGL ES接下来渲染中是否使用缓存中的数据
5、 设置指针:glVertextAttribPoint() - 告诉OpenGL ES在缓存中的数据的类型和所有需要访问的数据的内存偏移值
6、 绘图:glDrawArrays() | glDrawElements() - 告诉OpenGL ES使用当前绑定并且用缓存中的数据渲染整个场景或者部分场景
7、 删除:glDeleteBuffers() - 告诉OpenGL ES删除以前生成的缓存并释放相关资源
7、绘制序列
三、使用GLKit渲染图片
1、定义结构体并初始化顶点和纹理坐标
typedef struct {
GLKVector3 positionCoord; // 顶点坐标(x, y, z)
GLKVector2 textureCoord; // 纹理坐标(U, V)
} SenceVertex;
const SenceVertex vertices[] = {
{{-0.5, -0.5, 0.0}, {0.0, 0.0}}, // 左下角
{{ 0.5, -0.5, 0.0}, {1.0, 0.0}}, // 右下角
{{-0.5, 0.5, 0.0}, {0.0, 1.0}}, // 左上角
{{ 0.5, 0.5, 0.0}, {1.0, 1.0}} // 右上角
};
2、初始化GLKView并设置上下文
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.context];
self.glkView = [[GLKView alloc] initWithFrame:self.view.bounds context:self.context];
self.glkView.delegate = self;
[self.view addSubview:self.glkView];
3、使用GLKTextureLoader加载图片纹理
NSString *imageFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"leaves.png"];
UIImage *image = [UIImage imageWithContentsOfFile:imageFilePath];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:image.CGImage options:nil error:NULL];
4、生成绑定顶点缓存
glGenBuffers(1, &_vertextBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertextBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(SenceVertex) * 4, vertices, GL_STATIC_DRAW);
4、实现GLKView代理的渲染回掉
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[self.baseEffect prepareToDraw];
glClear(GL_COLOR_BUFFER_BIT);
// 顶点
glEnableVertexAttribArray(GLKVertexAttribPosition); // 启用顶点缓存数据
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord)); // 设置顶点数据指针
// 纹理
glEnableVertexAttribArray(GLKVertexAttribTexCoord0); // 启用纹理缓存数据
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord)); // 设置纹理数据指针
// 绘制
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
四、使用GLSL渲染图片
1、新建顶点着色器和片元着色器
// 顶点着色器
attribute vec4 Position;
attribute vec2 InputTextureCoordinate;
varying vec2 TextureCoordinate;
void main (void) {
gl_Position = Position;
TextureCoordinate = InputTextureCoordinate;
}
// 片元着色器
// 1. 指定默认精度
precision mediump float;
uniform sampler2D InputImageTexture;
//
varying vec2 TextureCoordinate;
void main() {
gl_FragColor = texture2D(InputImageTexture, TextureCoordinate);
}
- attribute:只能存在于顶点着色器,一般用来表示顶点数据,如:顶点坐标、法线、纹理坐标、顶点颜色等
- varying:顶点着色器和片元着色器传递数据用
- uniform:外部程序传递给着色器的变量,就像是C语言的敞亮,它不能被shader程序修改(只能用,不能改)
- gl_Position和gl_FragColor:内建变量,前一个是向顶点着色器输出向量,后一个是向片段着色器输出向量
- texture2D:根据纹理坐标获取纹素
2、编译顶点着色器和片元着色器
* -loadShader;
* -compileShader:type:
* -linkProgram:
* validateProgram:
NSString *filePath = [[NSBundle mainBundle] pathForResource:shaderName
ofType:(type == GL_VERTEX_SHADER ? @"vsh" : @"fsh")];
NSError *error = nil;
NSString *shaderString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSAssert(NO, @"文件读取失败");
}
GLuint shader = glCreateShader(type);
const char *shaderStringUTF8 = [shaderString UTF8String];
GLint shaderStringLength = (GLint)[shaderString length];
glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
glCompileShader(shader);
GLint compileRes;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileRes);
if (compileRes == GL_FALSE) {
GLchar message[256];
glGetShaderInfoLog(shader, sizeof(message), 0, &message[0]);
NSAssert(NO, @"着色器编译错误");
}
3、程序编译、链接并验证
GLint program = glCreateProgram();
// 链接两个着色器
glAttachShader(program, vertextShader);
glAttachShader(program, fragmentShader);
// 链接程序
glLinkProgram(program);
GLint linkRes;
glGetProgramiv(program, GL_LINK_STATUS, &linkRes);
if (linkRes == GL_FALSE) {
GLchar message[256];
glGetProgramInfoLog(program, sizeof(message), 0, &message[0]);
NSAssert(NO, @"程序编译错误");
}
4、获取着色器参数
GLint positionAttribute = glGetAttribLocation(program, "Position"); // 顶点坐标
GLint textureCoordinateAttribute = glGetAttribLocation(program, "InputTextureCoordinate"); // 纹理坐标
GLint textureUniform = glGetUniformLocation(program, "InputImageTexture"); // 输入纹理
5、创建、绑定帧缓存和渲染缓存
if (_frameBuffer != 0) {
glDeleteFramebuffers(1, &_frameBuffer);
_frameBuffer = 0;
}
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
if (_renderBuffer != 0) {
glDeleteRenderbuffers(1, &_renderBuffer);
_renderBuffer = 0;
}
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
// 绑定RenderBuffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"绑定失败");
}
6、加载纹理
CGImageRef imageRef = image.CGImage;
GLint width = (GLint)CGImageGetWidth(imageRef);
GLint height = (GLint)CGImageGetHeight(imageRef);
CGRect rect = CGRectMake(0, 0, width, height);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
void *imageData = malloc(width * height * 4);
CGContextRef contextRef = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpaceRef, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpaceRef);
// 因为Core Graphics是以原点在左上角同时Y轴向下增大的形式来实现iOS图片保存的
// OpenGL ES的纹理坐标 原点在左下角,同时Y轴向下增大
CGContextTranslateCTM(contextRef, 0, height);
CGContextScaleCTM(contextRef, 1.0, -1.0);
CGContextDrawImage(contextRef, rect, imageRef);
GLuint textureID;
glGenBuffers(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
// level: 指定MIP贴图的初始细节级别
// internalFormat: 指定纹理缓存内每个纹素需要保存的信息数量,对于iOS设备来说,纹素信息要么是GL_RGB,要么是GL_RGBA
// format: 指定初始化缓存所使用的图像数据的每个像素所要保存的信息,这个参数总是internalFormat参数相同
/* type: 指定缓存中的纹素所使用的编码类型,
* GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT_5_6_5、GL_UNSIGNED_SHORT_4_4_4_4、GL_UNSIGNED_SHORT_5_5_5_1
* 使用GL_UNSIGNED_BYTE会提供最佳色彩
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
/* GL_TEXTURE_MIN_FILTER: 在多个纹素对应一个片元的星矿下,使用怎么样的形式取样颜色
* GL_LINEAR: 混色纹素颜色,例如:黑白交替,取样的混合纹素为灰色
* GL_NEAREST: 取样其中一个,因此最终片元颜色可能为白色或者黑色
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/*
* 当U、V坐标小于0、或者大于1时,有两种选择
* 1. GL_CLAMP_TO_EDGE: 取纹理边缘的纹素
* 2. GL_REPEAT: 循环
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
CGContextRelease(contextRef);
free(imageData);
7、将纹理传给着色器程序并绘制
// 将纹理ID传给着色器程序
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texure);
glUniform1i(textureUniform, 0);
// 顶点buffer
glGenBuffers(1, &_vertextBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertextBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(SenceVertex) * 4, self.vertices, GL_STATIC_DRAW);
glClear(GL_COLOR_BUFFER_BIT);
// 顶点
glEnableVertexAttribArray(positionAttribute);
glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
// 纹理
glEnableVertexAttribArray(textureCoordinateAttribute);
glVertexAttribPointer(textureCoordinateAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[self.context presentRenderbuffer:_renderBuffer];
源码
请到GitHub查看完整代码.