OpenGL ES 2 0 (iOS)[05 1]:进入 3D 世界,从正方体开始
目录
一、目标
- 基础知识准备
- 图形分析
二、编写程序
- 工程结构与整体渲染管线
- Depth Render Buffer
- 数据源的编写与绑定
- 深度测试与绘制
- 让正方体动起来
三、参考书籍、文章
一、目标>
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 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 世界,从正方体开始相关推荐
- OpenGL ES 2 0 (iOS)[06 1]:基础纹理
前言:如果你没有 OpenGL ES 2 的基础知识,请先移步 <OpenGL ES 2.0 (iOS) 笔记大纲> 学习一下基础的知识. 目录 一.软件运行效果演示 (一).最终效果 ( ...
- Android使用OpenGL ES 3.0实现随手指旋转3D立方体
OpenGL ES在做普通应用方面3D使用的不多,但有时候实现一些有趣的功能也是蛮不错的.画立方体的的demo网上已经很多了,这次我们就实现一个随手指旋转的立方体,这个demo基本可以了解各个坐标系转 ...
- 《OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例》一第6章 让场景更逼真——光照效果...
本节书摘来异步社区<OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例>一书中的第6章,第6.1节,作者: 吴亚峰 责编: 张涛,更多章节内容可以访问云栖社区"异步社 ...
- 【我的OpenGL学习进阶之旅】OpenGL ES 3.0新功能
目录 1.1 纹理 1.2 着色器 1.3 几何形状 1.4 缓冲区对象 1.5 帧缓冲区 OpenGL ES 2.0 开创了手持设备可编程着色器的时代,在驱动大量设备的游戏.应用程序和用户接口中获得 ...
- NDK OpenGL ES 3.0 开发(一):绘制一个三角形
该原创文章首发于微信公众号:字节流动 什么是 OpenGLES OpenGLES 全称 OpenGL for Embedded Systems ,是三维图形应用程序接口 OpenGL 的子集,本质上是 ...
- [jimmyzhouj 翻译] Nehe iOS OpenGL ES 2.0教程 --Lesson 02
http://jimmyzhouj.blog.51cto.com/2317513/883520 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任 ...
- 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)
0x00 - 前言 之前做一些移动端的AR应用以及目前看到的一些AR应用,基本上都是这样一个套路:手机背景显示现实场景,然后在该背景上进行图形学绘制.至于图形学绘制时,相机外参的解算使用的是V-SLA ...
- 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 ...
- 借助 OpenGL* ES 2.0 实现动态分辨率渲染
作者:omar-a-rodrigue 下载 借助 OpenGL* ES 2.0 实现动态分辨率渲染[PDF 677KB] 代码样本: dynamic-resolution.zip[ZIP 4MB] 像 ...
- Android opengl es 3.0 + ndk 绘画涂鸦项目
前言 写一个opengl es 3.0 + ndk 的绘画涂鸦项目,命名为白板哈哈哈,记录自己遇到的问题,顺便学到的知识整合一遍,算是对自己一段时间的总结. 项目地址:Whiteboard 如果对你有 ...
最新文章
- linux下使用NetBeans调试libevent库
- 参数验证 @Validated 和 @Valid 的区别,Java Web 开发必备。
- numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 f
- C语言 链表实现学生管理系统(含文件读写操作)
- 利用微软平台生成报表,线性图,柱形图
- leetcode374. 猜数字大小(二分法)
- 天逸ad一66da_深入解析天逸ad66d与ad66a哪个好?区别是?内幕评测吐槽
- spring-retry小结
- 5 shell命令之tr
- SQlite数据库的C编程接口(六) 返回值和错误码(Result Codes and Error Codes) ——《Using SQlite》读书笔记
- 迅雷 android通用版本下载地址,迅雷5下载|迅雷5安卓旧版本-520下载站
- matlab抗混叠滤波器,抗混叠滤波器讲解.doc
- Jmeter 线程数、Ramp-Up、循环次数 详解
- 2018年8月PMI全球认证人士及《项目管理知识体系指南(PMBOK® 指南)》发行量统计数据公布
- ESP8266 AP模式建立服务器
- SQLiteSpy介绍和使用
- Android实习周记:第八周,职场里有真感情吗?我的回答是T_T
- 教你怎么煲耳机 让声音更美妙!
- ExecuteNonQuery()返回值
- 在Windows中编辑好的汉字文档,上传到Linux下打开乱码问题