系列推薦文章:
OpenGL/OpenGL ES入門:圖形API以及專業名詞解析
OpenGL/OpenGL ES入門:渲染流程以及固定存儲着色器
OpenGL/OpenGL ES入門:圖像渲染實現以及渲染問題
OpenGL/OpenGL ES入門:基礎變換 - 初識向量/矩陣
OpenGL/OpenGL ES入門:紋理初探 - 經常使用API解析
OpenGL/OpenGL ES入門: 紋理應用 - 紋理座標及案例解析(金字塔)
OpenGL/OpenGL ES入門: 頂點着色器與片元着色器(OpenGL過渡OpenGL ES)
OpenGL/OpenGL ES入門: GLKit以及API簡介
OpenGL/OpenGL ES入門: GLKit使用以及案例html
GLKit
框架的設計目標是爲了簡化基於OpenGL/OpenGL ES
的應用開發。它的出現加快OpenGL
或OpenGL ES
應用程序開發。 使用數學庫,背景紋理加載,預先建立的着色器效果,以及標準視圖和視圖控制器來實現渲染循環。git
GLKit
框架提供了功能和類,能夠減小建立新的基於着色器的應用程序所需的工做量,或支持依賴早期版本的OpenGL
或OpenGL ES
提供的固定函數頂點或片斷處理的現有應用程序。github
GLKView
提供繪製場所(view
)
GLKViewController
擴展於標準的UIKit
設計模式,用於繪製視圖內容的管理與呈現
蘋果棄用OpenGL ES
,但iOS開發者能夠繼續使用。設計模式
第一個案例,咱們建立一個繼承GLKViewController
的類,並導入#import <GLKit/GLKit.h>
頭文件,.h
文件以下數組
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
@end
複製代碼
.m
文件導入下面兩個文件#import <OpenGLES/ES3/gl.h>
、#import <OpenGLES/ES3/glext.h>
緩存
下面看具體步驟:bash
// 1.初始化上下文&設置當前上下文
_context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!_context) {
NSLog(@"Create ES context Failed");
}
// 設置當前上下文
[EAGLContext setCurrentContext:_context];
// 2.獲取GLKView & 設置context
GLKView *view = [[GLKView alloc]initWithFrame:self.view.bounds context:_context];
view.backgroundColor = [UIColor clearColor];
view.delegate = self;
[self.view addSubview:view];
複製代碼
在初始化context
時,咱們須要選擇咱們使用的OpenGL ES
的版本,分別有以下幾種:數據結構
kEAGLRenderingAPIOpenGLES1 = 1, 固定管線
kEAGLRenderingAPIOpenGLES2 = 2,
kEAGLRenderingAPIOpenGLES3 = 3,
複製代碼
EAGLContext
是蘋果iOS平臺下實現OpenGL ES
渲染層。其中kEAGLRenderingAPIOpenGLES1
相似前幾篇文章說的固定管線。而kEAGLRenderingAPIOpenGLES2
和kEAGLRenderingAPIOpenGLES3
,在這裏使用哪一個,區別不大,只是版本不一樣而已。框架
下面須要配置視圖建立的渲染緩衝區,以及設置背景顏色函數
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
glClearColor(1, 1, 1, 1);
複製代碼
drawableColorFormat
: 顏色緩衝區格式
在OpenGL ES
中有一個緩存區,它用以存儲在屏幕中顯示的顏色。你可使用其屬性設置緩衝區中的每一個像素的顏色格式。
GLKViewDrawableColorFormatRGBA8888
= 0
默認值,緩衝區的每一個像素的最小組成部分(RGBA)使用8個bit,(因此每一個像素4個字節, 4*8個bit)
GLKViewDrawableColorFormatRGB565
若是你的APP容許更小範圍的顏色,便可設置這個。會讓你的APP消耗更小的資源(內存和處理時間)
drawableDepthFormat
: 深度緩存區格式
GLKViewDrawableDepthFormatNone
= 0, 意味着徹底沒有深度緩衝區
GLKViewDrawableDepthFormat16
,
GLKViewDrawableDepthFormat24
,
若是你要使用這個屬性(通常用於3D遊戲),你應該選擇GLKViewDrawableDepthFormat16
或GLKViewDrawableDepthFormat24
。這裏的差異是使用GLKViewDrawableDepthFormat16
將消耗更少的資源
GLBaseEffect
)使用本地圖片做爲紋理,代碼以下:
//1.獲取紋理圖片路徑
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"kunkun" ofType:@"jpg"];
//2.設置紋理參數
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//3.使用蘋果GLKit 提供GLKBaseEffect 完成着色器工做(頂點/片元)
cEffect = [[GLKBaseEffect alloc]init];
cEffect.texture2d0.enabled = GL_TRUE;
cEffect.texture2d0.name = textureInfo.name;
複製代碼
在設置紋理參數的時,須要注意一點,紋理座標默認左下角爲原點(0,0),而iOS座標系中,默認手機屏幕的左上角爲原點(0,0),因此須要特別注意這一點,須要設置GLKTextureLoaderOriginBottomLeft
,否則圖片顯示的時候,會發生翻轉。
先看代碼
// 設置頂點數組
GLfloat vertexData[] = {
1, -0.5, 0.0f, 1.0f, 0.0f, //右下
1, 0.5, -0.0f, 1.0f, 1.0f, //右上
-1, 0.5, 0.0f, 0.0f, 1.0f, //左上
1, -0.5, 0.0f, 1.0f, 0.0f, //右下
-1, 0.5, 0.0f, 0.0f, 1.0f, //左上
-1, -0.5, 0.0f, 0.0f, 0.0f, //左下
};
// 開闢頂點緩存區
// 1.建立頂點緩存區標識符ID
GLuint bufferID;
glGenBuffers(1, &bufferID);
// 2.綁定頂點緩存區.(明確做用)
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
// 3.將頂點數組的數據copy到頂點緩存區中(GPU顯存中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
// 頂點座標數據
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
// 紋理座標數據
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
複製代碼
這個案例中,咱們把頂點座標、紋理座標放在數組中,後面說的案例則使用了聯合體的方式存放,主要是爲了讓你們認識不一樣方式而已。 也能夠把頂點座標和紋理座標分開寫到不一樣的數組中,可是爲了簡化代碼量,因此選擇放在一塊兒寫。
頂點數組,開發者能夠選擇設定函數指針,在調用繪製方法的時候,直接由內存傳入頂點數據,也就是說這部分數據以前是存儲在內存中的。然後面要作的是開闢頂點緩衝區,主要目的是爲了追求性能更高,提早分配一塊顯存,將頂點數據預先傳入到顯存中,而關於頂點相關的計算都是在GPU中進行的,因此這樣性能有了很大的提升。
注意點:
在iOS中,默認狀況下,出於性能考慮,全部頂點着色器的屬性(Attribute
)變量都是關閉的,意味着,頂點數據在着色器(服務端)是不可用的。即便你已經使用glBufferData
方法,將頂點數據從內存拷貝到頂點緩衝區中(GPU顯存中); 因此,必須由glEnableVertexAttribArray
方法打開通道,指定訪問屬性,才能讓頂點着色器可以訪問到從CPU複製到GPU的數據。
注意:
數據在GPU端是否可見,即着色器可否讀取到數據,由是否啓用了對應的屬性決定,這就是glEnableVertexAttribArray
的功能,容許頂點着色器讀取GPU(服務端)數據。
GLKVertexAttribPosition, // 頂點
GLKVertexAttribNormal, // 法線
GLKVertexAttribColor, // 顏色
GLKVertexAttribTexCoord0, // 紋理
GLKVertexAttribTexCoord1
方法glVertexAttribPointer
表示上傳頂點數據到顯存的方法,即設置合適的方式從buffer
裏讀取數據。
參數1: 指定要修改的屬性的索引值
參數2: 每次讀取數量(如position
是由3個(x,y,z)組成,而顏色是4個(r,g,b,a),紋理則是2個.)
參數3: 指定數組中每一個組件的數據類型。可用的符號常量有GL_BYTE
,GL_UNSIGNED_BYTE
,GL_SHORT
,GL_UNSIGNED_SHORT
,GL_FIXED
, 和GL_FLOAT
,初始值爲GL_FLOAT
。
參數4: 指定當被訪問時,固定點數據值是否應該被歸一化(GL_TRUE
)或者直接轉換爲固定點值(GL_FALSE
)
參數5: 指定連續頂點屬性之間的偏移量。若是爲0,那麼頂點屬性會被理解爲:它們是緊密排列在一塊兒的。初始值爲0
參數6: 指定一個指針,指向數組中第一個頂點屬性的第一個組件。初始值爲0
上述代碼中,對於頂點來講,每次讀取3個,數據類型爲浮點型,偏移量即讀取下一組頂點數據,須要移動5個位置,頂點讀取位置對應從數組的一開始讀取,因此最後一個參數寫成(GLfloat *)NULL + 0
,這裏這樣的寫法是爲了方便讀者理解,也能夠直接寫成NULL
。因此對於紋理的最後一個參數寫成(GLfloat *)NULL + 3
也不難理解。
GLKView
對象使OpenGL ES
上下文成爲當前上下文,並將其framebuffer
綁定爲OpenGL ES
呈現命令的目標。而後,委託方法應該繪製視圖的內容。看下面代碼
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
//1.清除顏色緩存區
glClear(GL_COLOR_BUFFER_BIT);
//2.準備繪製
[cEffect prepareToDraw];
//3.開始繪製
glDrawArrays(GL_TRIANGLES, 0, 6);
}
複製代碼
OK,案例一就這麼多內容,下面看實現效果(其實就是咱們平時用UIImageView
加載一張圖片而已)
案例二中,咱們再也不建立繼承GLKViewController
的類,而是直接在ViewController
中寫代碼,直接在.m
文件中導入#import <GLKit/GLKit.h>
頭文件。
而後聲明一些須要的屬性:
typedef struct {
GLKVector3 positionCoord; // 頂點座標
GLKVector2 textureCoord; // 紋理座標
GLKVector3 normal; // 法線座標
} ZBVertex;
// 頂點個數
static NSInteger const KCoordCount = 36;
@interface ViewController () <GLKViewDelegate>
@property (nonatomic, strong) GLKView *glkView;
@property (nonatomic, strong) GLKBaseEffect *baseEffect;
@property (nonatomic, assign) ZBVertex *vertices;
// 計時器
@property (nonatomic, strong) CADisplayLink *displayLink;
// 弧度
@property (nonatomic, assign) NSInteger angle;
// 頂點緩存區標識符ID
@property (nonatomic, assign) GLuint vertexBuffer;
@end
複製代碼
// 1.建立context
EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:context];
// 2.建立GLKView並設置代理
CGRect frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width);
self.glkView = [[GLKView alloc]initWithFrame:frame context:context];
self.glkView.backgroundColor = [UIColor redColor];
self.glkView.delegate = self;
// 3. 使用深度緩衝區
self.glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
self.glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
// 默認爲(0,1),這裏用於翻轉z軸,是正方形朝屏幕外
// glDepthRangef(1, 0);
[self.view addSubview:self.glkView];
NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"timg.jpeg"];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft : @(YES)};
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:[image CGImage] options:options error:nil];
// 使用蘋果GLKit提供GLKBaseEffect完成着色器工做(頂點/片元)
self.baseEffect = [[GLKBaseEffect alloc]init];
self.baseEffect.texture2d0.name = textureInfo.name;
self.baseEffect.texture2d0.target = textureInfo.target;
複製代碼
上面初始化代碼和案例一中的幾乎是同樣的,同時把加載紋理數據的代碼也放在了這裏,因此這裏再也不多說。
// 開闢頂點數據空間(數據結構SenceVertex 大小 * 頂點個數kCoordCount)
self.vertices = malloc(sizeof(ZBVertex) * KCoordCount);
// 前面
self.vertices[0] = (ZBVertex){{-0.5, 0.5, 0.5}, {0, 1}};
self.vertices[1] = (ZBVertex){{-0.5, -0.5, 0.5}, {0, 0}};
self.vertices[2] = (ZBVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[3] = (ZBVertex){{-0.5, -0.5, 0.5}, {0, 0}};
self.vertices[4] = (ZBVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[5] = (ZBVertex){{0.5, -0.5, 0.5}, {1, 0}};
// 上面
self.vertices[6] = (ZBVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[7] = (ZBVertex){{-0.5, 0.5, 0.5}, {0, 1}};
self.vertices[8] = (ZBVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[9] = (ZBVertex){{-0.5, 0.5, 0.5}, {0, 1}};
self.vertices[10] = (ZBVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[11] = (ZBVertex){{-0.5, 0.5, -0.5}, {0, 0}};
// 下面
self.vertices[12] = (ZBVertex){{0.5, -0.5, 0.5}, {1, 1}};
self.vertices[13] = (ZBVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[14] = (ZBVertex){{0.5, -0.5, -0.5}, {1, 0}};
self.vertices[15] = (ZBVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[16] = (ZBVertex){{0.5, -0.5, -0.5}, {1, 0}};
self.vertices[17] = (ZBVertex){{-0.5, -0.5, -0.5}, {0, 0}};
// 左面
self.vertices[18] = (ZBVertex){{-0.5, 0.5, 0.5}, {1, 1}};
self.vertices[19] = (ZBVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[20] = (ZBVertex){{-0.5, 0.5, -0.5}, {1, 0}};
self.vertices[21] = (ZBVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[22] = (ZBVertex){{-0.5, 0.5, -0.5}, {1, 0}};
self.vertices[23] = (ZBVertex){{-0.5, -0.5, -0.5}, {0, 0}};
// 右面
self.vertices[24] = (ZBVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[25] = (ZBVertex){{0.5, -0.5, 0.5}, {0, 1}};
self.vertices[26] = (ZBVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[27] = (ZBVertex){{0.5, -0.5, 0.5}, {0, 1}};
self.vertices[28] = (ZBVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[29] = (ZBVertex){{0.5, -0.5, -0.5}, {0, 0}};
// 後面
self.vertices[30] = (ZBVertex){{-0.5, 0.5, -0.5}, {0, 1}};
self.vertices[31] = (ZBVertex){{-0.5, -0.5, -0.5}, {0, 0}};
self.vertices[32] = (ZBVertex){{0.5, 0.5, -0.5}, {1, 1}};
self.vertices[33] = (ZBVertex){{-0.5, -0.5, -0.5}, {0, 0}};
self.vertices[34] = (ZBVertex){{0.5, 0.5, -0.5}, {1, 1}};
self.vertices[35] = (ZBVertex){{0.5, -0.5, -0.5}, {1, 0}};
// 開闢頂點緩存區
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(ZBVertex) * KCoordCount, self.vertices, GL_STATIC_DRAW);
// 頂點數據
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(ZBVertex), NULL + offsetof(ZBVertex, positionCoord));
// 紋理數據
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(ZBVertex), NULL + offsetof(ZBVertex, textureCoord));
複製代碼
咔咔咔,這麼一大串,基本上都是在設置頂點座標和紋理座標,這裏使用了聯合體存放數據,一開始就開闢了相應的空間self.vertices = malloc(sizeof(ZBVertex) * KCoordCount);
,後面代碼也和案例一同樣。
self.angle = 0;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reDisplay)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
複製代碼
上面代碼建立了一個循環,來執行方法reDisplay
。CADisplayLink
相似定時器,提供一個週期性調用,屬於QuartzCore.framework
中。
具體能夠參考該博客www.cnblogs.com/panyangjun/…
- (void)reDisplay {
// 計算旋轉度數
self.angle = (self.angle + 1) % 360;
// 修改baseEffect.transform.modelviewMatrix
self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(self.angle), 0.3, 1, -0.7);
// 從新渲染
[self.glkView display];
}
複製代碼
- (void)dealloc {
if ([EAGLContext currentContext] == self.glkView.context) {
[EAGLContext setCurrentContext:nil];
}
if (_vertices) {
free(_vertices);
_vertexBuffer = 0;
}
//displayLink 失效
[self.displayLink invalidate];
}
複製代碼
最終效果: