目录
一、目标
1. 基础知识准备
2. 图形分析
二、编写程序
0. 工程结构与整体渲染管线
1. Depth Render Buffer
2. 数据源的编写与绑定
3. 深度测试与绘制
4. 让正方体动起来
三、参考书籍、文章
一、目标
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. 正方体在不断地旋转运动,即可能要实时改变顶点的信息并进行重新绘制以达到运动的效果(思路:动图就是静态图的快速连续变化,只要变化的速度大于人眼可以辨别的速度,就会产生自然流畅的动图)
分析可程序化:
- 结合 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 分开来,只不过我认为放在一起更好管理罢了。
- 从 e、f 两点可以知道,增加的数据及绘制的方式:
因为使用 element 方式,所以增加下标信息;
static const GLubyte indices[] = {
......
};
glDrawElements(GL_TRIANGLES,
sizeof(indices) / sizeof(indices[0]),
GL_UNSIGNED_BYTE,
indices);
- 从 g 点可以知道:
图形的运动,表明图形在一定时间内不断地进行更新(重新绘制并渲染),即只要使用具有定时功能的方法即可处理图形的运动,NSTimer 就可以胜任这个工作,不过 iOS 提供了一个 CADisplayLink 类来专门做定时更新的工作,所以可以选用它进行运动更新;
二、编写程序
0. 工程结构与整体渲染管线
结构目录简述
蓝框是包含 CADisplayLink 子类的类,用于更新渲染,就是让图形动起来;
红框就是整体的渲染管线,所有的绘制渲染工作均在此处;
渲染管线 + 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 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];
}
基于这部分,本文的工作在以下两处进行:
// 1. Context
[self settingContext];
它负责确定渲染上下文,以及 Render Buffer 与 Frame Buffer 的资源绑定处理;
[self settingContext];
详见 本章 1.Depth Render Buffer 一节
// 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];
它是处理顶点缓存数据的;
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.colorRenderBufferID
attachment: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.depthRenderBufferID
attachment: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.depthRenderBufferID
attachment:GL_DEPTH_ATTACHMENT];
}
步骤分解:
第一步,创建并绑定深度渲染缓存,对应程序代码为:
self.depthRenderBufferID = [self createRenderBuffer];
- (GLuint)createRenderBuffer {
GLuint ID = kInvaildBufferID;
glGenRenderbuffers(RenderMemoryBlock, &ID); // 申请 Render Buffer
glBindRenderbuffer(GL_RENDERBUFFER, ID); // 创建 Render Buffer
return 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.depthRenderBufferID
attachment: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: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);
}
// 创建 资源 ( 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 类的实时更新问题;
//
- (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 ()
方法;
VFRedisplay.h 主要内容
@protocol VFRedisplayDelegate
- (void)updateContentsWithTimes:(NSTimeInterval)times;
@end
......
- (void)startUpdate;
- (void)pauseUpdate;
- (void)endUpdate;
VFRedisplay.m 主要内容
开始更新的方法:
- (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];
}
}
四步走:
第一步,创建相应的更新调度方法- (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