OpenGL ES 2.0 (iOS)[05-1]:進入 3D 世界,從正方體開始
目錄
一、目標
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 類的實時更新問題;
// <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: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
相關文章
- OpenGL ES 2 0 (iOS)[05 1]:進入 3D 世界,從正方體開始iOS3D
- OpenGL ES on iOS — 光照進階iOS
- OpenGL ES on iOS --- 光照進階iOS
- 從顯示一張圖片開始學習OpenGL ES
- OpenGL ES 2 0 (iOS)[01]: 一步從一個小三角開始iOS
- OpenGL ES on iOS — AssimpiOS
- OpenGL ES on iOS --- AssimpiOS
- OpenGL ES 2.0學習(一)入門知識點
- OpenGL ES 入門
- OpenGL ES繪製3D圖形3D
- Android OpenGL ES 開發(二): OpenGL ES 環境搭建Android
- OpenGL/OpenGL ES 初探
- Android 3D遊戲開發——Opengl ES遊戲引擎實現Android3D遊戲開發遊戲引擎
- OpenGL/OpenGL ES入門:紋理初探 - 常用API解析API
- 使用 iOS OpenGL ES 實現長腿功能iOS
- OpenGL ES 2 0 (iOS) 筆記大綱iOS筆記
- 從零開始學習OpenGL-14複習光照
- 3D開發和OpenGL3D
- iOS-零基礎學習OpenGL-ES入門教程(一)iOS
- OpenGL 學習 08 - 球體世界
- OpenGL/OpenGL ES入門: 影象渲染實現以及渲染問題
- OpenGL/OpenGL ES入門: 渲染流程以及固定儲存著色器
- OpenGL基礎圖形程式設計(一)OpenGL與3D圖形世界程式設計3D
- Opengl ES 3.0 on iOS--- HelloWord(繪製彩色矩形)iOS
- OpenGL 3D 模型載入和渲染3D模型
- 軟體測試入門,從這裡開始
- OpenGL ES 框架詳細解析(三) —— 構建用於iOS的OpenGL ES應用程式的清單框架iOS
- Android OpenGL ES 2.0 手把手教學(1)- Hello World!Android
- Android OpenGL ES 2.0 手把手教學(6)- 紋理Android
- OpenGL/OpenGL ES 入門:基礎變換 - 初識向量/矩陣矩陣
- Android 3D遊戲開發(高階篇)——Opengl ES遊戲引擎實現Android3D遊戲開發遊戲引擎
- OpenGL ES 高階進階:紋理陣列陣列
- OpenGL ES 2 0 (iOS)[06 1]:基礎紋理iOS
- [OpenGL ES 03]3D變換:模型,檢視,投影與Viewport3D模型View
- 從零開始打造一個iOS圖片載入框架(三)iOS框架
- 從零開始打造一個iOS圖片載入框架(二)iOS框架
- 從零開始打造一個iOS圖片載入框架(一)iOS框架
- 從零開始打造一個iOS圖片載入框架(四)iOS框架