OpenGL/OpenGL ES入門: GLKit使用以及案例

系列推薦文章:
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簡介

GLKit 框架的設計目標是爲了簡化基於OpenGL/OpenGL ES的應用開發。它的出現加快OpenGLOpenGL ES應用程序開發。 使用數學庫,背景紋理加載,預先建立的着色器效果,以及標準視圖和視圖控制器來實現渲染循環。git

GLKit 框架提供了功能和類,能夠減小建立新的基於着色器的應用程序所需的工做量,或支持依賴早期版本的OpenGLOpenGL 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相似前幾篇文章說的固定管線。而kEAGLRenderingAPIOpenGLES2kEAGLRenderingAPIOpenGLES3,在這裏使用哪一個,區別不大,只是版本不一樣而已。框架

下面須要配置視圖建立的渲染緩衝區,以及設置背景顏色函數

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遊戲),你應該選擇GLKViewDrawableDepthFormat16GLKViewDrawableDepthFormat24。這裏的差異是使用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加載一張圖片而已)

案例二 繪製立方體

demo傳送門

案例二中,咱們再也不建立繼承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];
複製代碼

上面代碼建立了一個循環,來執行方法reDisplayCADisplayLink相似定時器,提供一個週期性調用,屬於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];
}
複製代碼

dealloc

- (void)dealloc {
    if ([EAGLContext currentContext] == self.glkView.context) {
        [EAGLContext setCurrentContext:nil];
    }
    if (_vertices) {
        free(_vertices);
        _vertexBuffer = 0;
    }
    //displayLink 失效
    [self.displayLink invalidate];
}
複製代碼

最終效果:

相關文章
相關標籤/搜索