目录

一、目标

  1. 基础知识准备
  2. 图形分析

二、编写程序

  1. 工程结构与整体渲染管线
  2. Depth Render Buffer
  3. 数据源的编写与绑定
  4. 深度测试与绘制
  5. 让正方体动起来

三、参考书籍、文章


一、目标>

1. 基础知识准备

a. 渲染管线的基础知识 《OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始》

b. 3D 变换 《OpenGL ES 2.0 (iOS)[04]:坐标空间 与 OpenGL ES 2 3D空间》 #####2. 图形分析

a. 它是一个正方体,由六个正方形面组成,有 8 个顶点;

b. 正方体并不是二维图形,而是三维图形,即顶点坐标应为{x, y, z},而且 z 不可能一直为 0;

c. 若由 OpenGL ES 绘制,z 坐标表示深度(depth)信息;

d. 六个面均有不一样的颜色,即 8 个顶点都带有颜色信息,即渲染的顶点要提供相应的颜色信息;

e. 六个正方形面,若由 OpenGL ES 绘制,需要由两个三角面组合而成,即绘制模式为 GL_TRIANGLE*;

f. 正方体的每一个顶点都包含在三个面中,即一个顶点都会被使用多次,即绘制的时候应该使用 glDrawElements 方法而不是 glDrawArrays 方法,所以除 8 个顶点的数据外还需增加下标数据才有可能高效地绘制出正方体;

g. 正方体在不断地旋转运动,即可能要实时改变顶点的信息并进行重新绘制以达到运动的效果(思路:动图就是静态图的快速连续变化,只要变化的速度大于人眼可以辨别的速度,就会产生自然流畅的动图)

分析可程序化:

  1. 结合 a、b、c、d 四点可以知道,顶点的数据格式可以为:
#define PositionCoordinateCount         (3)
#define ColorCoordinateCount            (4)
typedef struct {GLfloat position[PositionCoordinateCount];GLfloat color[ColorCoordinateCount];
} VFVertex;
复制代码
static const VFVertex vertices[] = {{{...}, {...}}......
};
复制代码

当然你也可以把 position 和 color 分开来,只不过我认为放在一起更好管理罢了。

  1. 从 e、f 两点可以知道,增加的数据及绘制的方式:

因为使用 element 方式,所以增加下标信息;

static const GLubyte indices[] = {......
};
复制代码
    glDrawElements(GL_TRIANGLES,sizeof(indices) / sizeof(indices[0]),GL_UNSIGNED_BYTE,indices);
复制代码
  1. 从 g 点可以知道:

图形的运动,表明图形在一定时间内不断地进行更新(重新绘制并渲染),即只要使用具有定时功能的方法即可处理图形的运动,NSTimer 就可以胜任这个工作,不过 iOS 提供了一个 CADisplayLink 类来专门做定时更新的工作,所以可以选用它进行运动更新;


二、编写程序

0. 工程结构与整体渲染管线

结构目录简述

  1. 蓝框是包含 CADisplayLink 子类的类,用于更新渲染,就是让图形动起来;

  2. 红框就是整体的渲染管线,所有的绘制渲染工作均在此处;

渲染管线 + Depth Render Buffer 有三种缓存,Color 、Depth 、Stencil 三种;而单纯绘制 2D 图形的时候因为没有引入 z 坐标(z != 0)而只使用了 Render Buffer 的 Color Render Buffer ; 而如今要进行渲染的正方体,是带有 z 坐标,即深度信息,所以自然要引入 Depth Render Buffer 了; 引入 Depth Render Buffer 并使其工作的步骤:

ViewController 的程序调度

#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
复制代码

内容并不复杂,所以此处不进行赘述;

渲染管线

prepareDisplay + drawAndRender
复制代码

prepareDisplay 渲染管线的准备部分


- (void)prepareDisplay {// 1. Context[self settingContext];// 2 要在 Render Context setCurrent 后, 再进行 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 Objectself.vboBufferID = [self createVBO];[self bindVertexDatasWithVertexBufferID:_vboBufferIDbufferTarget:GL_ARRAY_BUFFERdataSize:sizeof(vertices)data:verticeselements:NO];[self bindVertexDatasWithVertexBufferID:kInvaildBufferIDbufferTarget:GL_ELEMENT_ARRAY_BUFFERdataSize:sizeof(indices)data:indiceselements:YES];// 3. ShaderGLuint 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:_programIDvertextShader:vertexShaderIDfragmentShader:fragmentShaderID];[self linkProgramWithProgramID:_programID];[self updateUniformsLocationsWithProgramID:_programID];// 4. Attach VBOs[self attachCubeVertexArrays];}
复制代码

基于这部分,本文的工作在以下两处进行:

    // 1. Context[self settingContext];
复制代码

它负责确定渲染上下文,以及 Render Buffer 与 Frame Buffer 的资源绑定处理; [self settingContext]; 详见 本章 1.Depth Render Buffer 一节

    // 2.? Vertex Buffer Objectself.vboBufferID = [self createVBO];[self bindVertexDatasWithVertexBufferID:_vboBufferIDbufferTarget:GL_ARRAY_BUFFERdataSize:sizeof(vertices)data:verticeselements:NO];[self bindVertexDatasWithVertexBufferID:kInvaildBufferIDbufferTarget:GL_ELEMENT_ARRAY_BUFFERdataSize:sizeof(indices)data:indiceselements:YES];
复制代码

它是处理顶点缓存数据的; VBO 与 数据源 详见 本章 2. 数据源的编写与绑定

drawAndRender 渲染管线的余下部分

- (void)drawAndRender {// 5. Draw Cube// 5.0 使用 Shader[self userShaderWithProgramID:_programID];// 5.1 应用 3D 变换self.modelPosition = GLKVector3Make(0, -0.5, -5);[self transforms];// 5.2 清除旧渲染缓存[self clearColorRenderBuffer:YES depth:YES stencil:NO];// 5.3 开启深度测试[self enableDepthTesting];// 5.4 绘制图形[self drawCube];// 5.5 渲染图形[self render];}
复制代码

基于这部分,本文的工作在此处进行:

    // 5.2 清除旧渲染缓存[self clearColorRenderBuffer:YES depth:YES stencil:NO];// 5.3 开启深度测试[self enableDepthTesting];// 5.4 绘制图形[self drawCube];
复制代码

详见 本章 3. 深度测试与绘制 一节

关于实时更新的内容

    [self.cubeView update];[self.cubeView pauseUpdate];
复制代码

详见 本章 4. 让正方体动起来

1. Depth Render Buffer

[self settingContext];

它的内容为:

- (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 创建 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.colorRenderBufferIDattachment:GL_COLOR_ATTACHMENT0];// 2.2 检查 Frame 装载 Render Buffer 的问题[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.depthRenderBufferIDattachment:GL_DEPTH_ATTACHMENT];}// 2.4 检查 Frame 装载 Render Buffer 的问题[self checkFrameBufferStatus];}}}- (void)settingContext {self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];}
复制代码

这里重写了 setContext: 方法,核心内容是 // 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.depthRenderBufferIDattachment:GL_DEPTH_ATTACHMENT];}
复制代码

步骤分解:

第一步,创建并绑定深度渲染缓存,对应程序代码为:

self.depthRenderBufferID = [self createRenderBuffer];
复制代码
- (GLuint)createRenderBuffer {GLuint ID = kInvaildBufferID;glGenRenderbuffers(RenderMemoryBlock, &ID);  // 申请 Render BufferglBindRenderbuffer(GL_RENDERBUFFER, ID); // 创建 Render Bufferreturn ID;}
复制代码

第二步,存储新创建的渲染缓存,对应程序代码为:

[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);}}
复制代码

核心函数:存储渲染信息

glRenderbufferStorage
void glRenderbufferStorage(GLenum target,GLenum internalformat,GLsizei width, GLsizei height)
target 只能是 GL_RENDERBUFFER
internalformat 可用选项见下表
width 渲染缓存的宽度(像素单位)
height 渲染缓存的高度(像素单位)
internalformat 存储格式(位 = 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

第三步,装载渲染缓存到帧缓存中,对应程序代码为:

[self attachRenderBufferToFrameBufferWithRenderBufferID:self.depthRenderBufferIDattachment:GL_DEPTH_ATTACHMENT];
复制代码
- (void)attachRenderBufferToFrameBufferWithRenderBufferID:(GLuint)renderBufferID attachment:(GLenum)attachment {glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderBufferID);}
复制代码
2. 数据源的编写与绑定

数据源的书写 从 2D 到 3D :

右下方,线框正方体的 8 个顶点坐标分布,其实 0~7 的编号是你决定的,也就是说 0 放在那里开始都是可以的,只要是 8 个点即可;

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}}, // 淡(蓝) -- 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}}, // 蓝(绿) -- 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}}, // 超淡蓝 偏(白) -- 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}}, // 超淡蓝 偏(白) -- 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}}, // 蓝(绿) -- 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}}, // 淡(蓝) -- 7
};
复制代码

只要你空间想像不是特别差,估计能看出每个点的坐标吧!你可以把这样的点 { 1.0, -1.0, -1.0} 改成你喜欢的数值亦可,只要最终是正方体即可;

真正重要的数据其实是下标数据:

static const GLubyte indices[] = {// Front  ------------- 蓝橙绿白 中间线(蓝绿)0, 1, 2, // 蓝橙绿2, 3, 0, // 绿白蓝// Back   ------------- 蓝橙绿白 中间线(白橙)4, 5, 6, // 白绿橙6, 7, 4, // 橙蓝白// Left   ------------- 白绿3, 2, 5, // 白绿绿5, 4, 3, // 绿白白// Right  ------------- 蓝橙7, 6, 1, // 蓝橙橙1, 0, 7, // 橙蓝蓝// Top    ------------- 橙绿1, 6, 5, // 橙橙绿5, 2, 1, // 绿绿橙// Bottom ------------- 白蓝3, 4, 7, // 白白蓝7, 0, 3  // 蓝蓝白
};
复制代码

这些下标的值由两个因素决定,第一个因素是上面 8 个顶点数据的下标;第二个因素是时钟方向;

现在看看时钟方向:

有没有发现,每一个正方形的两个小三角,都是逆时针方向的;当然你也可以换成顺时针方向,相应的下标数据就要发生改变;

EP: 如 Front 这个面,如果使用顺时针来写数据为:

    // Front  ------------- 白绿橙蓝 中间线(白橙)3, 2, 1, // 白绿橙1, 0, 2, // 橙蓝绿
复制代码

你也可以从 2 或 1 开始,看你的喜好咯;

方向只有两个:

资源绑定 这里主要是 VBO 的数据绑定,增加 Element 的支持而已;

    [self bindVertexDatasWithVertexBufferID:kInvaildBufferIDbufferTarget:GL_ELEMENT_ARRAY_BUFFERdataSize:sizeof(indices)data:indiceselements:YES];
复制代码
- (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID bufferTarget:(GLenum)target dataSize:(GLsizeiptr)size data:(const GLvoid *)data elements:(BOOL)isElement {if ( ! isElement) {glBindBuffer(target, vertexBufferID);}// 创建 资源 ( context )glBufferData(target,            // 缓存块 类型size,              // 创建的 缓存块 尺寸data,              // 要绑定的顶点数据GL_STATIC_DRAW);   // 缓存块 用途}
复制代码

此处不再赘述; 如果实在不懂,请移步至 《OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形》练习练习;

3. 深度测试与绘制

清除旧的深度缓存信息

[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);}
复制代码

启用深度测试

[self enableDepthTesting];
复制代码
- (void)enableDepthTesting {glEnable(GL_DEPTH_TEST);glEnable(GL_CULL_FACE);}
复制代码

这里多了一个 GL_CULL_FACE 的启用,它的意思就是,把看不见的像素信息剔除掉,只保留能看见的信息(留前去后); 如果没有启用 GL_DEPTH_TEST 程序运行后是这样的:

很明显图形是有穿透性的,如果去掉 GL_DEPTH_TEST 就不是实体的正方体了;当然如果你喜欢这种效果,也可以关掉 GL_DEPTH_TEST (反正我个人觉得关掉也蛮好看的);

重新绑定 Color Render Buffer 原因,因为当绑定 Depth Render Buffer 之后,渲染管线从原来的绑定(激活)的 Color Render Buffer 切换成了,绑定(激活)Depth Render Buffer ,从而导致渲染出来的结果,不是期望中的那样;所以在绘制前要重新绑定(激活)Color Render Buffer .

- (void)drawCube {// 失败的核心原因// 因为 depth buffer 是最后一个绑定的,所以当前渲染的 buffer 变成了 depth 而不是 color// 所以 渲染的图形没有任何变化,无法产生深度效果// 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)]]; 的运行结果;

#####4. 让正方体动起来

ViewController 的调度 其实就是,view 显示的时候更新,不显示的时候停止更新;

- (void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];[self.cubeView update];}- (void)viewWillDisappear:(BOOL)animated {[super viewWillDisappear:animated];[self.cubeView pauseUpdate];}
复制代码

CubeView 的应用

#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);}
复制代码

本类提供的改变参数有:

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

已经包含了所有的变换操作;

以下的几个方法均是处理 VFRedisplay 类的实时更新问题;

// <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 方法,这个方法是用于更新时,实时调用的方法;由VFRedisplay 类提供的协议 @interface VFGLCubeView ()<VFRedisplayDelegate> 方法;

VFRedisplay.h 主要内容

@protocol VFRedisplayDelegate <NSObject>- (void)updateContentsWithTimes:(NSTimeInterval)times;@end......- (void)startUpdate;
- (void)pauseUpdate;
- (void)endUpdate;
复制代码

VFRedisplay.m 主要内容 开始更新的方法:

- (void)startUpdate {if ( ! self.delegate ) {return;}self.displayLink = [CADisplayLink displayLinkWithTarget:selfselector:@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];}}
复制代码

四步走: 第一步,创建相应的更新调度方法 - (void)displayContents:(CADisplayLink *)sender,这个方法必须是- (void)selector:(CADisplayLink *)sender这种类型的; 第二步,指定一个更新频率(就是一秒更新多少次)frameInterval 一般是 24、25、30,默认是 30 的; 第三步,把 CADisplayLink 的子类添加到当前的 RunLoop [NSRunLoop currentRunLoop] 上,不然程序是无法调度指定的方法的; 第四步,启动更新 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;}
复制代码

结束更新的方法:

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

不用的时候,当然要先停止更新,再关掉时钟(CADisplayLink 就是一个时钟类),最后要从当前 RunLoop 中移除;

5. 工程文件

Github: DrawCube

Github:DrawCube_Onestep

增加魔方色开关,RubikCubeColor 宏定义;


三、参考书籍、文章

《OpenGL ES 2 Programming Guide》
《OpenGL Programming Guide》8th
《Learning OpenGL ES For iOS》

RW.OpenGLES2.0 视频

OpenGL ES 2 0 (iOS)[05 1]:进入 3D 世界,从正方体开始相关推荐

  1. OpenGL ES 2 0 (iOS)[06 1]:基础纹理

    前言:如果你没有 OpenGL ES 2 的基础知识,请先移步 <OpenGL ES 2.0 (iOS) 笔记大纲> 学习一下基础的知识. 目录 一.软件运行效果演示 (一).最终效果 ( ...

  2. Android使用OpenGL ES 3.0实现随手指旋转3D立方体

    OpenGL ES在做普通应用方面3D使用的不多,但有时候实现一些有趣的功能也是蛮不错的.画立方体的的demo网上已经很多了,这次我们就实现一个随手指旋转的立方体,这个demo基本可以了解各个坐标系转 ...

  3. 《OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例》一第6章 让场景更逼真——光照效果...

    本节书摘来异步社区<OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例>一书中的第6章,第6.1节,作者: 吴亚峰 责编: 张涛,更多章节内容可以访问云栖社区"异步社 ...

  4. 【我的OpenGL学习进阶之旅】OpenGL ES 3.0新功能

    目录 1.1 纹理 1.2 着色器 1.3 几何形状 1.4 缓冲区对象 1.5 帧缓冲区 OpenGL ES 2.0 开创了手持设备可编程着色器的时代,在驱动大量设备的游戏.应用程序和用户接口中获得 ...

  5. NDK OpenGL ES 3.0 开发(一):绘制一个三角形

    该原创文章首发于微信公众号:字节流动 什么是 OpenGLES OpenGLES 全称 OpenGL for Embedded Systems ,是三维图形应用程序接口 OpenGL 的子集,本质上是 ...

  6. [jimmyzhouj 翻译] Nehe iOS OpenGL ES 2.0教程 --Lesson 02

    http://jimmyzhouj.blog.51cto.com/2317513/883520 原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 .作者信息和本声明.否则将追究法律责任 ...

  7. 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)

    0x00 - 前言 之前做一些移动端的AR应用以及目前看到的一些AR应用,基本上都是这样一个套路:手机背景显示现实场景,然后在该背景上进行图形学绘制.至于图形学绘制时,相机外参的解算使用的是V-SLA ...

  8. OpenGL ES 2.0 for iPhone Tutorial

    来源:http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial If you're new here, you may w ...

  9. 借助 OpenGL* ES 2.0 实现动态分辨率渲染

    作者:omar-a-rodrigue 下载 借助 OpenGL* ES 2.0 实现动态分辨率渲染[PDF 677KB] 代码样本: dynamic-resolution.zip[ZIP 4MB] 像 ...

  10. Android opengl es 3.0 + ndk 绘画涂鸦项目

    前言 写一个opengl es 3.0 + ndk 的绘画涂鸦项目,命名为白板哈哈哈,记录自己遇到的问题,顺便学到的知识整合一遍,算是对自己一段时间的总结. 项目地址:Whiteboard 如果对你有 ...

最新文章

  1. linux下使用NetBeans调试libevent库
  2. 参数验证 @Validated 和 @Valid 的区别,Java Web 开发必备。
  3. numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 f
  4. C语言 链表实现学生管理系统(含文件读写操作)
  5. 利用微软平台生成报表,线性图,柱形图
  6. leetcode374. 猜数字大小(二分法)
  7. 天逸ad一66da_深入解析天逸ad66d与ad66a哪个好?区别是?内幕评测吐槽
  8. spring-retry小结
  9. 5 shell命令之tr
  10. SQlite数据库的C编程接口(六) 返回值和错误码(Result Codes and Error Codes) ——《Using SQlite》读书笔记
  11. 迅雷 android通用版本下载地址,迅雷5下载|迅雷5安卓旧版本-520下载站
  12. matlab抗混叠滤波器,抗混叠滤波器讲解.doc
  13. Jmeter 线程数、Ramp-Up、循环次数 详解
  14. 2018年8月PMI全球认证人士及《项目管理知识体系指南(PMBOK® 指南)》发行量统计数据公布
  15. ESP8266 AP模式建立服务器
  16. SQLiteSpy介绍和使用
  17. Android实习周记:第八周,职场里有真感情吗?我的回答是T_T
  18. 教你怎么煲耳机 让声音更美妙!
  19. ExecuteNonQuery()返回值
  20. 在Windows中编辑好的汉字文档,上传到Linux下打开乱码问题

热门文章

  1. 【魔兽世界插件】魔兽世界插件实战笔记从入门到放弃的心理历程 第五节 窗体文字材质设置
  2. Best定理和MatrixTree定理 学习笔记
  3. 手机远程计算机桌面,远程桌面预览Windows10,您可以远程通过手机控制电脑
  4. Ubuntu 18安装搜狗拼音
  5. 电脑族:常点眼药水,还得干眼症?
  6. Science| 深度解析肠道菌群与肥胖
  7. 081-反射(Kind)
  8. 宁盾无线认证对接锐捷EG3220
  9. springboot2.0+springDataJPA报错Could not obtain identifier
  10. ModSecurity的规则