一、前言
OpenGL是Khronos Group開發(fā)維護的一個規(guī)范,它主要為我們定義了用來操作圖形和圖片的一些列函數(shù)API,需要注意的是OpenGL本身并不是API。
GPU的硬件開發(fā)商則需要提供滿足OpenGL規(guī)范的實現(xiàn),這些實現(xiàn)通常被稱之為“驅(qū)動”,他們負責(zé)OpenGL定義的API命令翻譯為GPU指令。
OpenGL ES((OpenGL for Embedded Systems) )是OpenGL三維圖形的API的子集,針對手機、PDA和游戲主機等嵌入式設(shè)備兒設(shè)計。
二、基礎(chǔ)概念

渲染管線接受一組3D坐標(biāo),然后把它們轉(zhuǎn)變?yōu)槟闫聊簧系挠猩?D像素輸出。圖形渲染管線可以被分為上面幾個階段,每個階段會把前一個階段的輸出作為輸入。
注意藍色部分是我們可以注入自定義的著色器部分
- 1、頂點著色器:它把單獨的頂點作為輸入,主要目的是把3D坐標(biāo)轉(zhuǎn)換為另一種3D坐標(biāo)
- 2、圖元裝配:將頂點著色器輸出的所有頂點作為輸入,并所有的點裝配為指定圖元形狀
- 3、幾何著色器:把接收到的圖元形式的一系列點的集合作為輸入,它可以通過產(chǎn)生新頂點構(gòu)造出新的圖元來生車給你其他形狀
- 4、光柵化階段:把圖元映射為最終屏幕上相應(yīng)的像素,生成供片段著色器使用的片段,在片段著色器運行之前會執(zhí)行裁切,超出視圖外的所有像素都將被裁切,以提升效率
- 5、片段著色器:計算一個像素的最終顏色
- 6、測試和混合:這個階段檢測片段對應(yīng)的深度值,來判斷這個像素在其他物體的前面還是后面,決定是否丟棄。這個階段也會檢查alpha值并對物體進行混合
1、幀緩存
用來接收GPU渲染出來的2D圖像像素數(shù)據(jù)的緩沖區(qū)叫做幀緩存。幀緩存可以同時存在多個,并通過OpenGL ES讓GPU把渲染結(jié)果存儲到任意數(shù)量的幀緩存中。
程序和操作系統(tǒng)會把渲染結(jié)果保存到后幀緩存在內(nèi)的其他幀緩存中。當(dāng)渲染后的后幀緩存包含一個完整的圖像時,前幀緩存和后幀緩存會瞬間切換。

2、OpenGL 坐標(biāo)系
OpenGL ES總是開始于一個矩形的笛卡爾坐標(biāo)系,這意味著任意兩個軸之間的角度都是90度??臻g中的每個位置稱之為頂點,每個頂點通過其X、Y、Z軸上的位置被定義。
OpenGL ES坐標(biāo)都是浮點數(shù)儲存的,并且沒有單位。

3、紋理
紋理是用來保存圖像的顏色元素值的OpenGL ES緩存。
紋素:當(dāng)用圖像初始化一個紋理緩存后,這個圖像的每個像素變成了紋理的一個紋素,與像素類似,紋素保存顏色數(shù)據(jù)。像素通常表示計算機屏幕上的一個世紀(jì)的顏色點;而紋素存在于一個虛擬的沒有尺寸的坐標(biāo)系中。
4、 紋理坐標(biāo)系
紋理坐標(biāo)系為一個命名為S和T的2D軸。在一個紋理中無論有多少紋素,紋理的尺寸永遠在S軸上從0.0到1.0,T軸上從0.0到1.0.

5、片元
點陣化(rasterizing):轉(zhuǎn)換幾何圖形數(shù)據(jù)為幀緩存中的顏色像素的渲染步驟叫做點陣化,每個顏色像素叫做片元。
映射(mapping):指定怎么對齊紋理和頂點,以便讓GPU知道每個片元顏色由哪些紋素決定。每個頂點還會給出U和V的坐標(biāo)值,每個U坐標(biāo)會映射頂點到視窗中的最終位置到紋理中的沿著S軸的一個位置,V坐標(biāo)映射到T軸。

- 取樣(sampling):GPU根據(jù)計算出來的每個片元的U、V位置從綁定的紋理中選擇紋素。
6、為緩存提供數(shù)據(jù)
1、 生成:glGenBuffers() - 請求OpenGL ES為圖形處理器控制的緩存生成一個獨一無二的標(biāo)識
2、 綁定:glBindBuffer() - 告訴OpenGL ES接下來的運算使用哪個緩存
3、 緩存數(shù)據(jù):glBufferData()、glBufferSubData() - 讓OpenGL ES為當(dāng)前的緩存分配并初始化足夠的連續(xù)內(nèi)存。(通常是從CPU控制的內(nèi)存復(fù)制數(shù)據(jù)到分配的內(nèi)存)
4、 啟用或者禁止:glEnableVertexAttribArray() | glDisableVertexAttribArray() - 告訴OpenGL ES接下來渲染中是否使用緩存中的數(shù)據(jù)
5、 設(shè)置指針:glVertextAttribPoint() - 告訴OpenGL ES在緩存中的數(shù)據(jù)的類型和所有需要訪問的數(shù)據(jù)的內(nèi)存偏移值
6、 繪圖:glDrawArrays() | glDrawElements() - 告訴OpenGL ES使用當(dāng)前綁定并且用緩存中的數(shù)據(jù)渲染整個場景或者部分場景
7、 刪除:glDeleteBuffers() - 告訴OpenGL ES刪除以前生成的緩存并釋放相關(guān)資源
7、繪制序列

三、使用GLKit渲染圖片
1、定義結(jié)構(gòu)體并初始化頂點和紋理坐標(biāo)
typedef struct {
GLKVector3 positionCoord; // 頂點坐標(biāo)(x, y, z)
GLKVector2 textureCoord; // 紋理坐標(biāo)(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并設(shè)置上下文
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、實現(xiàn)GLKView代理的渲染回掉
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[self.baseEffect prepareToDraw];
glClear(GL_COLOR_BUFFER_BIT);
// 頂點
glEnableVertexAttribArray(GLKVertexAttribPosition); // 啟用頂點緩存數(shù)據(jù)
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord)); // 設(shè)置頂點數(shù)據(jù)指針
// 紋理
glEnableVertexAttribArray(GLKVertexAttribTexCoord0); // 啟用紋理緩存數(shù)據(jù)
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord)); // 設(shè)置紋理數(shù)據(jù)指針
// 繪制
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. 指定默認(rèn)精度
precision mediump float;
uniform sampler2D InputImageTexture;
//
varying vec2 TextureCoordinate;
void main() {
gl_FragColor = texture2D(InputImageTexture, TextureCoordinate);
}
- attribute:只能存在于頂點著色器,一般用來表示頂點數(shù)據(jù),如:頂點坐標(biāo)、法線、紋理坐標(biāo)、頂點顏色等
- varying:頂點著色器和片元著色器傳遞數(shù)據(jù)用
- uniform:外部程序傳遞給著色器的變量,就像是C語言的敞亮,它不能被shader程序修改(只能用,不能改)
- gl_Position和gl_FragColor:內(nèi)建變量,前一個是向頂點著色器輸出向量,后一個是向片段著色器輸出向量
- texture2D:根據(jù)紋理坐標(biāo)獲取紋素
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、獲取著色器參數(shù)
GLint positionAttribute = glGetAttribLocation(program, "Position"); // 頂點坐標(biāo)
GLint textureCoordinateAttribute = glGetAttribLocation(program, "InputTextureCoordinate"); // 紋理坐標(biāo)
GLint textureUniform = glGetUniformLocation(program, "InputImageTexture"); // 輸入紋理
5、創(chuàng)建、綁定幀緩存和渲染緩存
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軸向下增大的形式來實現(xiàn)iOS圖片保存的
// OpenGL ES的紋理坐標(biāo) 原點在左下角,同時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貼圖的初始細節(jié)級別
// internalFormat: 指定紋理緩存內(nèi)每個紋素需要保存的信息數(shù)量,對于iOS設(shè)備來說,紋素信息要么是GL_RGB,要么是GL_RGBA
// format: 指定初始化緩存所使用的圖像數(shù)據(jù)的每個像素所要保存的信息,這個參數(shù)總是internalFormat參數(shù)相同
/* 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: 在多個紋素對應(yīng)一個片元的星礦下,使用怎么樣的形式取樣顏色
* GL_LINEAR: 混色紋素顏色,例如:黑白交替,取樣的混合紋素為灰色
* GL_NEAREST: 取樣其中一個,因此最終片元顏色可能為白色或者黑色
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/*
* 當(dāng)U、V坐標(biāo)小于0、或者大于1時,有兩種選擇
* 1. GL_CLAMP_TO_EDGE: 取紋理邊緣的紋素
* 2. GL_REPEAT: 循環(huán)
*/
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查看完整代碼.