OpenGL ES 2.0 (iOS)[05-1]:進(jìn)入 3D 世界筑煮,從正方體開(kāi)始


目錄

一辛蚊、目標(biāo)

1. 基礎(chǔ)知識(shí)準(zhǔn)備
2. 圖形分析

二、編寫程序

0. 工程結(jié)構(gòu)與整體渲染管線
1. Depth Render Buffer
2. 數(shù)據(jù)源的編寫與綁定
3. 深度測(cè)試與繪制
4. 讓正方體動(dòng)起來(lái)

三真仲、參考書籍袋马、文章


一、目標(biāo)

正方體.gif
1. 基礎(chǔ)知識(shí)準(zhǔn)備

a. 渲染管線的基礎(chǔ)知識(shí)
《OpenGL ES 2.0 (iOS)[01]: 一步從一個(gè)小三角開(kāi)始》

b. 3D 變換
《OpenGL ES 2.0 (iOS)[04]:坐標(biāo)空間 與 OpenGL ES 2 3D空間》

2. 圖形分析

a. 它是一個(gè)正方體秸应,由六個(gè)正方形面組成虑凛,有 8 個(gè)頂點(diǎn);

b. 正方體并不是二維圖形软啼,而是三維圖形桑谍,即頂點(diǎn)坐標(biāo)應(yīng)為{x, y, z},而且 z 不可能一直為 0祸挪;

c. 若由 OpenGL ES 繪制霉囚,z 坐標(biāo)表示深度(depth)信息;

d. 六個(gè)面均有不一樣的顏色,即 8 個(gè)頂點(diǎn)都帶有顏色信息盈罐,即渲染的頂點(diǎn)要提供相應(yīng)的顏色信息榜跌;

e. 六個(gè)正方形面,若由 OpenGL ES 繪制盅粪,需要由兩個(gè)三角面組合而成钓葫,即繪制模式為 GL_TRIANGLE*;

f. 正方體的每一個(gè)頂點(diǎn)都包含在三個(gè)面中票顾,即一個(gè)頂點(diǎn)都會(huì)被使用多次础浮,即繪制的時(shí)候應(yīng)該使用 glDrawElements 方法而不是 glDrawArrays 方法,所以除 8 個(gè)頂點(diǎn)的數(shù)據(jù)外還需增加下標(biāo)數(shù)據(jù)才有可能高效地繪制出正方體奠骄;

g. 正方體在不斷地旋轉(zhuǎn)運(yùn)動(dòng)豆同,即可能要實(shí)時(shí)改變頂點(diǎn)的信息并進(jìn)行重新繪制以達(dá)到運(yùn)動(dòng)的效果(思路:動(dòng)圖就是靜態(tài)圖的快速連續(xù)變化,只要變化的速度大于人眼可以辨別的速度含鳞,就會(huì)產(chǎn)生自然流暢的動(dòng)圖)

分析可程序化:

  1. 結(jié)合 a影锈、b、c蝉绷、d 四點(diǎn)可以知道鸭廷,頂點(diǎn)的數(shù)據(jù)格式可以為:
#define PositionCoordinateCount         (3)
#define ColorCoordinateCount            (4)
typedef struct {
    GLfloat position[PositionCoordinateCount];
    GLfloat color[ColorCoordinateCount];
} VFVertex;
static const VFVertex vertices[] = {
    {{...}, {...}}
    ......
};

當(dāng)然你也可以把 position 和 color 分開(kāi)來(lái),只不過(guò)我認(rèn)為放在一起更好管理罷了熔吗。

  1. 從 e辆床、f 兩點(diǎn)可以知道,增加的數(shù)據(jù)及繪制的方式:

因?yàn)槭褂?element 方式桅狠,所以增加下標(biāo)信息讼载;

static const GLubyte indices[] = {
    ......
};
    glDrawElements(GL_TRIANGLES,
                   sizeof(indices) / sizeof(indices[0]),
                   GL_UNSIGNED_BYTE,
                   indices);
  1. 從 g 點(diǎn)可以知道:

圖形的運(yùn)動(dòng),表明圖形在一定時(shí)間內(nèi)不斷地進(jìn)行更新(重新繪制并渲染)中跌,即只要使用具有定時(shí)功能的方法即可處理圖形的運(yùn)動(dòng)维雇,NSTimer 就可以勝任這個(gè)工作,不過(guò) iOS 提供了一個(gè) CADisplayLink 類來(lái)專門做定時(shí)更新的工作晒他,所以可以選用它進(jìn)行運(yùn)動(dòng)更新;


二逸贾、編寫程序

0. 工程結(jié)構(gòu)與整體渲染管線


結(jié)構(gòu)目錄簡(jiǎn)述

  1. 藍(lán)框是包含 CADisplayLink 子類的類陨仅,用于更新渲染,就是讓圖形動(dòng)起來(lái)铝侵;

  2. 紅框就是整體的渲染管線灼伤,所有的繪制渲染工作均在此處;

渲染管線 + Depth
Render Buffer 有三種緩存咪鲜,Color 狐赡、Depth 、Stencil 三種疟丙;而單純繪制 2D 圖形的時(shí)候因?yàn)闆](méi)有引入 z 坐標(biāo)(z != 0)而只使用了 Render Buffer 的 Color Render Buffer ;
而如今要進(jìn)行渲染的正方體颖侄,是帶有 z 坐標(biāo)鸟雏,即深度信息,所以自然要引入 Depth Render Buffer 了览祖;

引入 Depth Render Buffer 并使其工作的步驟:


Depth Render Buffer

ViewController 的程序調(diào)度

#import "ViewController.h"

#import "VFGLCubeView.h"

@interface ViewController ()
@property (strong, nonatomic) VFGLCubeView *cubeView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    CGRect rect = CGRectOffset(self.view.frame, 0, 0);
    self.cubeView = [[VFGLCubeView alloc] initWithFrame:rect];
    
    [_cubeView prepareDisplay];
    [_cubeView drawAndRender];
    
    [self.view addSubview:_cubeView];
    
}

- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];
    
    [self.cubeView update];
    
}

- (void)viewWillDisappear:(BOOL)animated {
    
    [super viewWillDisappear:animated];
    
    [self.cubeView pauseUpdate];
    
}

@end

內(nèi)容并不復(fù)雜孝鹊,所以此處不進(jìn)行贅述;

渲染管線
prepareDisplay + drawAndRender

prepareDisplay 渲染管線的準(zhǔn)備部分


- (void)prepareDisplay {
    
    // 1. Context
    [self settingContext];
    
    // 2 要在 Render Context setCurrent 后, 再進(jìn)行 OpenGL ES 的操作
    // [UIColor colorWithRed:0.423 green:0.046 blue:0.875 alpha:1.000]
    // [UIColor colorWithRed:0.423 green:0.431 blue:0.875 alpha:1.000]
    [self setRenderBackgroundColor:RGBAColorMake(0.423, 0.431, 0.875, 1.000)];
    
    // 2.? Vertex Buffer Object
    self.vboBufferID = [self createVBO];
    [self bindVertexDatasWithVertexBufferID:_vboBufferID
                               bufferTarget:GL_ARRAY_BUFFER
                                   dataSize:sizeof(vertices)
                                       data:vertices
                                   elements:NO];
    
    [self bindVertexDatasWithVertexBufferID:kInvaildBufferID
                               bufferTarget:GL_ELEMENT_ARRAY_BUFFER
                                   dataSize:sizeof(indices)
                                       data:indices
                                   elements:YES];
    
    // 3. Shader
    GLuint vertexShaderID = [self createShaderWithType:GL_VERTEX_SHADER];
    [self compileVertexShaderWithShaderID:vertexShaderID type:GL_VERTEX_SHADER];
    
    GLuint fragmentShaderID = [self createShaderWithType:GL_FRAGMENT_SHADER];
    [self compileVertexShaderWithShaderID:fragmentShaderID type:GL_FRAGMENT_SHADER];
    
    self.programID = [self createShaderProgram];
    [self attachShaderToProgram:_programID
                  vertextShader:vertexShaderID
                 fragmentShader:fragmentShaderID];
    
    [self linkProgramWithProgramID:_programID];
    
    [self updateUniformsLocationsWithProgramID:_programID];
    
    // 4. Attach VBOs
    [self attachCubeVertexArrays];
    
}

基于這部分展蒂,本文的工作在以下兩處進(jìn)行:

    // 1. Context
    [self settingContext];

它負(fù)責(zé)確定渲染上下文又活,以及 Render Buffer 與 Frame Buffer 的資源綁定處理;
[self settingContext]; 詳見(jiàn) 本章 1.Depth Render Buffer 一節(jié)

    // 2.? Vertex Buffer Object
    self.vboBufferID = [self createVBO];
    [self bindVertexDatasWithVertexBufferID:_vboBufferID
                               bufferTarget:GL_ARRAY_BUFFER
                                   dataSize:sizeof(vertices)
                                       data:vertices
                                   elements:NO];
    
    [self bindVertexDatasWithVertexBufferID:kInvaildBufferID
                               bufferTarget:GL_ELEMENT_ARRAY_BUFFER
                                   dataSize:sizeof(indices)
                                       data:indices
                                   elements:YES];

它是處理頂點(diǎn)緩存數(shù)據(jù)的锰悼;
VBO 與 數(shù)據(jù)源 詳見(jiàn) 本章 2. 數(shù)據(jù)源的編寫與綁定

drawAndRender 渲染管線的余下部分

- (void)drawAndRender {
    
    // 5. Draw Cube
    // 5.0 使用 Shader
    [self userShaderWithProgramID:_programID];
    
    // 5.1 應(yīng)用 3D 變換
    self.modelPosition = GLKVector3Make(0, -0.5, -5);
    [self transforms];
    
    // 5.2 清除舊渲染緩存
    [self clearColorRenderBuffer:YES depth:YES stencil:NO];
    
    // 5.3 開(kāi)啟深度測(cè)試
    [self enableDepthTesting];
    
    // 5.4 繪制圖形
    [self drawCube];
    
    // 5.5 渲染圖形
    [self render];
    
}

基于這部分柳骄,本文的工作在此處進(jìn)行:

    // 5.2 清除舊渲染緩存
    [self clearColorRenderBuffer:YES depth:YES stencil:NO];
    
    // 5.3 開(kāi)啟深度測(cè)試
    [self enableDepthTesting];
    
    // 5.4 繪制圖形
    [self drawCube];

詳見(jiàn) 本章 3. 深度測(cè)試與繪制 一節(jié)

關(guān)于實(shí)時(shí)更新的內(nèi)容

    [self.cubeView update];
    [self.cubeView pauseUpdate];

詳見(jiàn) 本章 4. 讓正方體動(dòng)起來(lái)

1. Depth Render Buffer

[self settingContext];
它的內(nèi)容為:

- (void)setContext:(EAGLContext *)context {
    
    if (_context != context) {
     
        [EAGLContext setCurrentContext:_context];
        
        [self deleteFrameBuffer:@[@(self.frameBufferID)]];
        self.frameBufferID = kInvaildBufferID;
        
        [self deleteRenderBuffer:@[@(self.colorRenderBufferID), @(self.depthRenderBufferID)]];
        self.colorRenderBufferID = self.depthRenderBufferID = kInvaildBufferID;
        
        _context = context;
        
        if (context != nil) {
            
            _context = context;
            [EAGLContext setCurrentContext:_context];
            
            // 2. Render / Frame Buffer
            
            // 2.0 創(chuàng)建 Frame Buffer
            [self deleteFrameBuffer:@[@(self.frameBufferID)]];
            
            self.frameBufferID = [self createFrameBuffer];
            
            // 2.1 Color & Depth Render Buffer
            [self deleteRenderBuffer:@[@(self.colorRenderBufferID)]];
            
            self.colorRenderBufferID = [self createRenderBuffer];
            
            [self renderBufferStrogeWithRenderID:self.colorRenderBufferID];
            
            [self attachRenderBufferToFrameBufferWithRenderBufferID:self.colorRenderBufferID
                                                         attachment:GL_COLOR_ATTACHMENT0];
            
            // 2.2 檢查 Frame 裝載 Render Buffer 的問(wèn)題
            [self checkFrameBufferStatus];
            
            // 2.3 Add Depth Render Buffer
            [self enableDepthRenderBuffer];
            
            [self deleteRenderBuffer:@[@(self.depthRenderBufferID)]];
            
            if ( ! CGSizeEqualToSize(self.renderBufferSize, CGSizeZero) &&
                self.depthMode != VFDrawableDepthMode_None) {
                
                self.depthRenderBufferID = [self createRenderBuffer];
                
                if (self.depthRenderBufferID == kInvaildBufferID) {
                    return;
                }
                
                [self renderBufferStrogeWithRenderID:self.depthRenderBufferID];
                
                [self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferID
                                                             attachment:GL_DEPTH_ATTACHMENT];
                
            }
            
            // 2.4 檢查 Frame 裝載 Render Buffer 的問(wèn)題
            [self checkFrameBufferStatus];
            
        }
        
    }
    
}

- (void)settingContext {

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
}

這里重寫了 setContext: 方法,核心內(nèi)容是
// 2.3 Add Depth Render Buffer

    // 2.3 Add Depth Render Buffer
    [self enableDepthRenderBuffer];
    
    [self deleteRenderBuffer:@[@(self.depthRenderBufferID)]];
    
    if ( ! CGSizeEqualToSize(self.renderBufferSize, CGSizeZero) &&
        self.depthMode != VFDrawableDepthMode_None) {
        
        self.depthRenderBufferID = [self createRenderBuffer];
        
        if (self.depthRenderBufferID == kInvaildBufferID) {
            return;
        }
        
        [self renderBufferStrogeWithRenderID:self.depthRenderBufferID];
        
        [self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferID
                                                     attachment:GL_DEPTH_ATTACHMENT];
        
    }

步驟分解:


Step One

第一步箕般,創(chuàng)建并綁定深度渲染緩存耐薯,對(duì)應(yīng)程序代碼為:

self.depthRenderBufferID = [self createRenderBuffer];
- (GLuint)createRenderBuffer {
    
    GLuint ID = kInvaildBufferID;
    glGenRenderbuffers(RenderMemoryBlock, &ID);  // 申請(qǐng) Render Buffer
    glBindRenderbuffer(GL_RENDERBUFFER, ID); // 創(chuàng)建 Render Buffer
    
    return ID;
    
}

第二步哮塞,存儲(chǔ)新創(chuàng)建的渲染緩存碌上,對(duì)應(yīng)程序代碼為:

[self renderBufferStrogeWithRenderID:self.depthRenderBufferID];
- (void)renderBufferStrogeWithRenderID:(GLuint)renderBufferID {
    
    if (renderBufferID == self.colorRenderBufferID) {
        
        // 必須要在 glbindRenderBuffer 之后 (就是使用 Render Buffer 之后), 再綁定渲染的圖層
        [self bindDrawableObjectToRenderBuffer];
        
        self.renderBufferSize = [self getRenderBufferSize];
        
    }
    
    if (renderBufferID == self.depthRenderBufferID) {
        
        glRenderbufferStorage(GL_RENDERBUFFER,
                              GL_DEPTH_COMPONENT16,
                              self.renderBufferSize.width,
                              self.renderBufferSize.height);
        
    }
    
}

核心函數(shù):存儲(chǔ)渲染信息

glRenderbufferStorage
void glRenderbufferStorage(GLenum target,GLenum internalformat,GLsizei width, GLsizei height)
target 只能是 GL_RENDERBUFFER
internalformat 可用選項(xiàng)見(jiàn)下表
width 渲染緩存的寬度(像素單位)
height 渲染緩存的高度(像素單位)
internalformat 存儲(chǔ)格式(位 = bit)
顏色方面 GL_RGB565(5 + 6 + 5 = 16位)、GL_RGBA4(4 x 4 = 16)绊寻、GL_RGB5_A1(5 + 5 + 5 + 1 = 16)丙者、GL_RGB8_OES(3 x 8 = 24 )复斥、GL_RGBA8_OES(4 x 8 = 32)
深度方面 GL_DEPTH_COMPONENT16(16位)、GL_DEPTH_COMPONENT24_OES(24位)械媒、GL_DEPTH_COMPONENT32_OES(32位)
模板方面 GL_STENCIL_INDEX8目锭、GL_STENCIL_INDEX1_OES、GL_STENCIL_INDEX4_OES
深度與模板 GL_DEPTH24_STENCIL8_OES

第三步纷捞,裝載渲染緩存到幀緩存中痢虹,對(duì)應(yīng)程序代碼為:

[self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferID
                                             attachment:GL_DEPTH_ATTACHMENT];
- (void)attachRenderBufferToFrameBufferWithRenderBufferID:(GLuint)renderBufferID attachment:(GLenum)attachment {

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderBufferID);
    
}
2. 數(shù)據(jù)源的編寫與綁定

數(shù)據(jù)源的書寫
從 2D 到 3D :


右下方,線框正方體的 8 個(gè)頂點(diǎn)坐標(biāo)分布主儡,其實(shí) 0~7 的編號(hào)是你決定的奖唯,也就是說(shuō) 0 放在那里開(kāi)始都是可以的,只要是 8 個(gè)點(diǎn)即可糜值;
Cube

static const VFVertex vertices[] = {
    // Front
    // 0 [UIColor colorWithRed:0.438 green:0.786 blue:1.000 alpha:1.000]
    {{ 1.0, -1.0,  1.0}, {0.438, 0.786, 1.000, 1.000}}, // 淡(藍(lán)) -- 0
    
    // 1 [UIColor colorWithRed:1.000 green:0.557 blue:0.246 alpha:1.000]
    {{ 1.0,  1.0,  1.0}, {1.000, 0.557, 0.246, 1.000}}, // 淡(橙) -- 1
    
    // 2 [UIColor colorWithRed:0.357 green:0.927 blue:0.690 alpha:1.000]
    {{-1.0,  1.0,  1.0}, {0.357, 0.927, 0.690, 1.000}}, // 藍(lán)(綠) -- 2
    
    // 3 [UIColor colorWithRed:0.860 green:0.890 blue:0.897 alpha:1.000]
    {{-1.0, -1.0,  1.0}, {0.860, 0.890, 0.897, 1.000}}, // 超淡藍(lán) 偏(白) -- 3
    
    // Back
    // 4 [UIColor colorWithRed:0.860 green:0.890 blue:0.897 alpha:1.000]
    {{-1.0, -1.0, -1.0}, {0.860, 0.890, 0.897, 1.000}}, // 超淡藍(lán) 偏(白) -- 4
    
    // 5 [UIColor colorWithRed:0.357 green:0.927 blue:0.690 alpha:1.000]
    {{-1.0,  1.0, -1.0}, {0.357, 0.927, 0.690, 1.000}}, // 藍(lán)(綠) -- 5
    
    // 6 [UIColor colorWithRed:1.000 green:0.557 blue:0.246 alpha:1.000]
    {{ 1.0,  1.0, -1.0}, {1.000, 0.557, 0.246, 1.000}}, // 淡(橙) -- 6
    
    // 7 [UIColor colorWithRed:0.438 green:0.786 blue:1.000 alpha:1.000]
    {{ 1.0, -1.0, -1.0}, {0.438, 0.786, 1.000, 1.000}}, // 淡(藍(lán)) -- 7
};

只要你空間想像不是特別差丰捷,估計(jì)能看出每個(gè)點(diǎn)的坐標(biāo)吧!你可以把這樣的點(diǎn) { 1.0, -1.0, -1.0} 改成你喜歡的數(shù)值亦可寂汇,只要最終是正方體即可病往;

真正重要的數(shù)據(jù)其實(shí)是下標(biāo)數(shù)據(jù):

static const GLubyte indices[] = {
    // Front  ------------- 藍(lán)橙綠白 中間線(藍(lán)綠)
    0, 1, 2, // 藍(lán)橙綠
    2, 3, 0, // 綠白藍(lán)
    // Back   ------------- 藍(lán)橙綠白 中間線(白橙)
    4, 5, 6, // 白綠橙
    6, 7, 4, // 橙藍(lán)白
    // Left   ------------- 白綠
    3, 2, 5, // 白綠綠
    5, 4, 3, // 綠白白
    // Right  ------------- 藍(lán)橙
    7, 6, 1, // 藍(lán)橙橙
    1, 0, 7, // 橙藍(lán)藍(lán)
    // Top    ------------- 橙綠
    1, 6, 5, // 橙橙綠
    5, 2, 1, // 綠綠橙
    // Bottom ------------- 白藍(lán)
    3, 4, 7, // 白白藍(lán)
    7, 0, 3  // 藍(lán)藍(lán)白
};

這些下標(biāo)的值由兩個(gè)因素決定,第一個(gè)因素是上面 8 個(gè)頂點(diǎn)數(shù)據(jù)的下標(biāo)骄瓣;第二個(gè)因素是時(shí)鐘方向停巷;

現(xiàn)在看看時(shí)鐘方向:



有沒(méi)有發(fā)現(xiàn),每一個(gè)正方形的兩個(gè)小三角,都是逆時(shí)針?lè)较虻呐锨冢划?dāng)然你也可以換成順時(shí)針?lè)较蚶俑鳎鄳?yīng)的下標(biāo)數(shù)據(jù)就要發(fā)生改變;

EP: 如 Front 這個(gè)面硼被,如果使用順時(shí)針來(lái)寫數(shù)據(jù)為:

    // Front  ------------- 白綠橙藍(lán) 中間線(白橙)
    3, 2, 1, // 白綠橙
    1, 0, 2, // 橙藍(lán)綠

你也可以從 2 或 1 開(kāi)始示损,看你的喜好咯;

方向只有兩個(gè):


資源綁定
這里主要是 VBO 的數(shù)據(jù)綁定嚷硫,增加 Element 的支持而已检访;

    [self bindVertexDatasWithVertexBufferID:kInvaildBufferID
                               bufferTarget:GL_ELEMENT_ARRAY_BUFFER
                                   dataSize:sizeof(indices)
                                       data:indices
                                   elements:YES];
- (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID bufferTarget:(GLenum)target dataSize:(GLsizeiptr)size data:(const GLvoid *)data elements:(BOOL)isElement {
    
    if ( ! isElement) {
        glBindBuffer(target, vertexBufferID);
    }
    
    // 創(chuàng)建 資源 ( context )
    glBufferData(target,            // 緩存塊 類型
                 size,              // 創(chuàng)建的 緩存塊 尺寸
                 data,              // 要綁定的頂點(diǎn)數(shù)據(jù)
                 GL_STATIC_DRAW);   // 緩存塊 用途
    
}

此處不再贅述;
如果實(shí)在不懂仔掸,請(qǐng)移步至
《OpenGL ES 2.0 (iOS)[03]:熟練圖元繪制脆贵,玩轉(zhuǎn)二維圖形》練習(xí)練習(xí);

3. 深度測(cè)試與繪制

Step Two

清除舊的深度緩存信息

[self clearColorRenderBuffer:YES depth:YES stencil:NO];
- (void)clearColorRenderBuffer:(BOOL)color depth:(BOOL)depth stencil:(BOOL)stencil {
    
    GLbitfield colorBit     = 0;
    GLbitfield depthBit     = 0;
    GLbitfield stencilBit   = 0;
    
    if (color)      { colorBit      = GL_COLOR_BUFFER_BIT;     }
    if (depth)      { depthBit      = GL_DEPTH_BUFFER_BIT;     }
    if (stencil)    { stencilBit    = GL_STENCIL_BUFFER_BIT;   }
    
    glClear(colorBit | depthBit | stencilBit);
    
}

啟用深度測(cè)試

[self enableDepthTesting];
- (void)enableDepthTesting {
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    
}

這里多了一個(gè) GL_CULL_FACE 的啟用起暮,它的意思就是卖氨,把看不見(jiàn)的像素信息剔除掉,只保留能看見(jiàn)的信息(留前去后)负懦;

如果沒(méi)有啟用 GL_DEPTH_TEST 程序運(yùn)行后是這樣的:


關(guān)掉 GL_DEPTH_TEST.gif

很明顯圖形是有穿透性的筒捺,如果去掉 GL_DEPTH_TEST 就不是實(shí)體的正方體了;當(dāng)然如果你喜歡這種效果纸厉,也可以關(guān)掉 GL_DEPTH_TEST (反正我個(gè)人覺(jué)得關(guān)掉也蠻好看的)系吭;

重新綁定 Color Render Buffer
原因,因?yàn)楫?dāng)綁定 Depth Render Buffer 之后颗品,渲染管線從原來(lái)的綁定(激活)的 Color Render Buffer 切換成了肯尺,綁定(激活)Depth Render Buffer ,從而導(dǎo)致渲染出來(lái)的結(jié)果躯枢,不是期望中的那樣则吟;所以在繪制前要重新綁定(激活)Color Render Buffer .

Step Three
- (void)drawCube {
    
    // 失敗的核心原因
    // 因?yàn)?depth buffer 是最后一個(gè)綁定的,所以當(dāng)前渲染的 buffer 變成了 depth 而不是 color
    // 所以 渲染的圖形沒(méi)有任何變化锄蹂,無(wú)法產(chǎn)生深度效果
    // Make the Color Render Buffer the current buffer for display
    [self rebindRenderBuffer:@[@(self.colorRenderBufferID)]];
    
    [self rebindVertexBuffer:@[@(self.vboBufferID)]];
    
    glDrawElements(GL_TRIANGLES,
                   sizeof(indices) / sizeof(indices[0]),
                   GL_UNSIGNED_BYTE,
                   indices);
    
}

這是注釋了代碼中氓仲,[self rebindRenderBuffer:@[@(self.colorRenderBufferID)]]; 的運(yùn)行結(jié)果;

4. 讓正方體動(dòng)起來(lái)

ViewController 的調(diào)度
其實(shí)就是得糜,view 顯示的時(shí)候更新敬扛,不顯示的時(shí)候停止更新;

- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];
    
    [self.cubeView update];
    
}

- (void)viewWillDisappear:(BOOL)animated {
    
    [super viewWillDisappear:animated];
    
    [self.cubeView pauseUpdate];
    
}

CubeView 的應(yīng)用

#pragma mark - DisplayLink Update

- (void)preferTransformsWithTimes:(NSTimeInterval)time {
    
    GLfloat rotateX = self.modelRotate.x;
//    rotateX += M_PI_4 * time;
    
    GLfloat rotateY = self.modelRotate.y;
    rotateY += M_PI_2 * time;
    
    GLfloat rotateZ = self.modelRotate.z;
    rotateZ += M_PI * time;
    
    self.modelRotate = GLKVector3Make(rotateX, rotateY, rotateZ);
    
}

本類提供的改變參數(shù)有:

@property (assign, nonatomic) GLKVector3 modelPosition, modelRotate, modelScale;
@property (assign, nonatomic) GLKVector3 viewPosition , viewRotate , viewScale ;
@property (assign, nonatomic) GLfloat projectionFov, projectionScaleFix, projectionNearZ, projectionFarZ;

已經(jīng)包含了所有的變換操作掀亩;

以下的幾個(gè)方法均是處理 VFRedisplay 類的實(shí)時(shí)更新問(wèn)題;

// <VFRedisplayDelegate>
- (void)updateContentsWithTimes:(NSTimeInterval)times {
    
    [self preferTransformsWithTimes:times];
    [self drawAndRender];
    
}

#pragma mark - Update

- (void)update {
    
    self.displayUpdate = [[VFRedisplay alloc] init];
    self.displayUpdate.delegate = self;
    self.displayUpdate.preferredFramesPerSecond = 25;
    self.displayUpdate.updateContentTimes = arc4random_uniform(650) / 10000.0;
    [self.displayUpdate startUpdate];
    
}

- (void)pauseUpdate {
    
    [self.displayUpdate pauseUpdate];
    
}

#pragma mark - Dealloc

- (void)dealloc {
    
    [self.displayUpdate endUpdate];

}
    self.displayUpdate.preferredFramesPerSecond = 25; //更新頻率
    self.displayUpdate.updateContentTimes = arc4random_uniform(650) / 10000.0; // 控制變化率(快慢)

核心是 - (void)updateContentsWithTimes:(NSTimeInterval)times 方法欢顷,這個(gè)方法是用于更新時(shí)槽棍,實(shí)時(shí)調(diào)用的方法;由VFRedisplay 類提供的協(xié)議 @interface VFGLCubeView ()<VFRedisplayDelegate> 方法;

VFRedisplay.h 主要內(nèi)容

@protocol VFRedisplayDelegate <NSObject>

- (void)updateContentsWithTimes:(NSTimeInterval)times;

@end

......

- (void)startUpdate;
- (void)pauseUpdate;
- (void)endUpdate;

VFRedisplay.m 主要內(nèi)容
開(kāi)始更新的方法:

- (void)startUpdate {
    
    if ( ! self.delegate ) {
        return;
    }
    
    self.displayLink = [CADisplayLink displayLinkWithTarget:self
                                                   selector:@selector(displayContents:)];
    
    self.displayLink.frameInterval = (NSUInteger)MAX(kLeastSeconds,
                                                     (kTotalSeconds / self.preferredFramesPerSecond));
    
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSDefaultRunLoopMode];
    
    self.displayPause = kDefaultDisplayPause;
    
}

- (void)displayContents:(CADisplayLink *)sender {
    
    if ([self.delegate respondsToSelector:@selector(updateContentsWithTimes:)]) {
        
        [self.delegate updateContentsWithTimes:self.updateContentTimes];
        
    }
    
}

四步走:

第一步,創(chuàng)建相應(yīng)的更新調(diào)度方法- (void)displayContents:(CADisplayLink *)sender炼七,這個(gè)方法必須是- (void)selector:(CADisplayLink *)sender這種類型的缆巧;

第二步,指定一個(gè)更新頻率(就是一秒更新多少次)frameInterval 一般是 24豌拙、25陕悬、30,默認(rèn)是 30 的按傅;

第三步捉超,把 CADisplayLink 的子類添加到當(dāng)前的 RunLoop [NSRunLoop currentRunLoop] 上,不然程序是無(wú)法調(diào)度指定的方法的唯绍;

第四步拼岳,啟動(dòng)更新 static const BOOL kDefaultDisplayPause = NO;

displayPause 屬性

@property (assign, nonatomic) BOOL displayPause;
@dynamic displayPause;
- (void)setDisplayPause:(BOOL)displayPause {
   self.displayLink.paused = displayPause;
}
- (BOOL)displayPause {
   return self.displayLink.paused;
}

停止更新的方法:

- (void)pauseUpdate {
    
    self.displayPause = YES;
    
}

結(jié)束更新的方法:

- (void)endUpdate {
    
    self.displayPause = YES;
    [self.displayLink invalidate];
    [self.displayLink removeFromRunLoop:[NSRunLoop currentRunLoop]
                                forMode:NSDefaultRunLoopMode];
    
    
}

不用的時(shí)候况芒,當(dāng)然要先停止更新惜纸,再關(guān)掉時(shí)鐘(CADisplayLink 就是一個(gè)時(shí)鐘類),最后要從當(dāng)前 RunLoop 中移除绝骚;

5. 工程文件

Github: DrawCube

Github:DrawCube_Onestep

增加魔方色開(kāi)關(guān)耐版,RubikCubeColor 宏定義;

開(kāi)關(guān)
數(shù)據(jù)源
正方體_魔方色.gif

三压汪、參考書籍粪牲、文章

《OpenGL ES 2 Programming Guide》
《OpenGL Programming Guide》8th
《Learning OpenGL ES For iOS》
RW.OpenGLES2.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蛾魄,隨后出現(xiàn)的幾起案子虑瀑,更是在濱河造成了極大的恐慌,老刑警劉巖滴须,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌狗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扔水,警方通過(guò)查閱死者的電腦和手機(jī)痛侍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)魔市,“玉大人主届,你說(shuō)我怎么就攤上這事〈拢” “怎么了君丁?”我有些...
    開(kāi)封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)将宪。 經(jīng)常有香客問(wèn)我绘闷,道長(zhǎng)橡庞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任印蔗,我火速辦了婚禮扒最,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘华嘹。我一直安慰自己吧趣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布耙厚。 她就那樣靜靜地躺著强挫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颜曾。 梳的紋絲不亂的頭發(fā)上纠拔,一...
    開(kāi)封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音泛豪,去河邊找鬼稠诲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诡曙,可吹牛的內(nèi)容都是我干的臀叙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼价卤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼劝萤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起慎璧,我...
    開(kāi)封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤床嫌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后胸私,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厌处,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年岁疼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阔涉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捷绒,死狀恐怖瑰排,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暖侨,我是刑警寧澤椭住,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站字逗,受9級(jí)特大地震影響京郑,放射性物質(zhì)發(fā)生泄漏显押。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一傻挂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挖息,春花似錦金拒、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至电禀,卻和暖如春幢码,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尖飞。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工症副, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人政基。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓贞铣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親沮明。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辕坝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容