本文已有最新Swift版html
https://www.raywenderlich.com/5146-glkit-tutorial-for-ios-getting-started-with-opengl-esios
教程截圖:程序員
OpenGL ES 是能夠在iphone上實現2D和3D圖形編程的低級API。編程
若是你以前接觸過 cocos2d,sparrow,corona,unity 這些框架,你會發現其實它們都是基於OpenGL上建立的。小程序
多數程序員選擇使用這些框架,而不是直接調用OpenGL,由於OpenGL實在是太難用了。api
而這篇教程,就是爲了讓你們更好地入門而寫的。數組
在這個系列的文章中,你能夠經過一些實用又容易上手的實驗,建立相似hello world的APP。例如顯示一些簡單的立體圖形。xcode
流程大體以下:緩存
·建立一個簡單的OpenGL app數據結構
·編譯並運行 vertex & fragment shaders
·經過vertex buffer,在屏幕上渲染一個簡單矩形
·使用投影和 model-view 變形。
·渲染一個能夠 depth testing的3D對象。
說明:
我並不是OpenGL的專家,這些徹底是經過自學得來的。若是你們發現哪些不對的地方,歡迎指出。
第一件你須要搞清楚的事,是OpenGL ES 1.0 和 2.0的區別。
他們有多不同?我只能說他們很不同。
OpenGL ES1.0:
針對固定管線硬件(fixed pipeline),經過它內建的functions來設置諸如燈光、,vertexes(圖形的頂點數),顏色、camera等等的東西。
OpenGL ES2.0:
針對可編程管線硬件(programmable pipeline),基於這個設計可讓內建函數見鬼去吧,但同時,你得本身動手編寫任何功能。
「TMD」,你可能會這麼想。這樣子我還可能想用2.0麼?
但2.0確實能作一些很cool而1.0不能作的事情,譬如:toon shader(貼材質).
利用opengles2.0,甚至還能建立下面的這種很酷的燈光和陰影效果:
OpenGL ES2.0只可以在iphone 3GS+、iPod Touch 3G+ 和全部版本的ipad上運行。慶幸如今大多數用戶都在這個範圍。
儘管Xcode自帶了OpenGL ES的項目模板,但這個模板自行建立了大量的代碼,這樣會讓初學者感到迷惘。
所以咱們經過自行編寫的方式來進行,經過一步一步編寫,你能更清楚它的工做機制。
啓動Xcode,新建項目-選擇Window-based Application, 讓咱們從零開始。
點擊下一步,把這個項目命名爲HelloOpenGL,點擊下一步,選擇存放目錄,點擊「建立」。
CMD+R,build and run。你會看到一個空白的屏幕。
如你所見的,Window-based 模板建立了一個沒有view、沒有view controller或者其它東西的項目。它只包含了一個必須的UIWindow。
File/New File,新建文件:選擇iOS\Cocoa Touch\Objective-c Class, 點擊下一步。
選擇subclass UIView,點擊下一步,命名爲 OpenGLView.m., 點擊保存。
接下來,你要在這個OpenGLView.m 文件下加入不少代碼。
1) 添加必須的framework (框架)
加入:OpenGLES.frameworks 和 QuartzCore.framework
在項目的Groups&Files 目錄下,選擇target 「HelloOpenGL」,展開Link Binary with Libraries部分。這裏是項目用到的框架。
「+」添加,選擇OpenGLES.framework, 重複一次把QuartzCore.framework也添加進來。
2)修改OpenGLView.h
以下:引入OpenGL的Header,建立一些後面會用到的實例變量。
#import <UIKit/UIKit.h> #import <QuartzCore/QuartzCore.h> #include <OpenGLES/ES2/gl.h> #include <OpenGLES/ES2/glext.h> @interface OpenGLView : UIView { CAEAGLLayer* _eaglLayer; EAGLContext* _context; GLuint _colorRenderBuffer; } @end
3)設置layer class 爲 CAEAGLLayer
+ (Class)layerClass { return [CAEAGLLayer class]; }
4) 設置layer爲不透明(Opaque)
- (void)setupLayer { _eaglLayer = (CAEAGLLayer*) self.layer; _eaglLayer.opaque = YES; }
5)建立OpenGL context
- (void)setupContext { EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; _context = [[EAGLContext alloc] initWithAPI:api]; if (!_context) { NSLog(@"Failed to initialize OpenGLES 2.0 context"); exit(1); } if (![EAGLContext setCurrentContext:_context]) { NSLog(@"Failed to set current OpenGL context"); exit(1); } }
不管你要OpenGL幫你實現什麼,總須要這個 EAGLContext。
EAGLContext管理全部經過OpenGL進行draw的信息。這個與Core Graphics context相似。
當你建立一個context,你要聲明你要用哪一個version的API。這裏,咱們選擇OpenGL ES 2.0.
(容錯處理,若是建立失敗了,咱們的程序會退出)
6)建立render buffer (渲染緩衝區)
- (void)setupRenderBuffer { glGenRenderbuffers(1, &_colorRenderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer); [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer]; }
Render buffer 是OpenGL的一個對象,用於存放渲染過的圖像。
有時候你會發現render buffer會做爲一個color buffer被引用,由於本質上它就是存放用於顯示的顏色。
建立render buffer的三步:
1. 調用glGenRenderbuffers來建立一個新的render buffer object。這裏返回一個惟一的integer來標記render buffer(這裏把這個惟一值賦值到_colorRenderBuffer)。有時候你會發現這個惟一值被用來做爲程序內的一個OpenGL 的名稱。(反正它惟一嘛)
2. 調用glBindRenderbuffer ,告訴這個OpenGL:我在後面引用GL_RENDERBUFFER的地方,實際上是想用_colorRenderBuffer。其實就是告訴OpenGL,咱們定義的buffer對象是屬於哪種OpenGL對象
3. 最後,爲render buffer分配空間。renderbufferStorage
7)建立一個 frame buffer (幀緩衝區)
- (void)setupFrameBuffer { GLuint framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer); }
Frame buffer也是OpenGL的對象,它包含了前面提到的render buffer,以及其它後面會講到的諸如:depth buffer、stencil buffer 和 accumulation buffer。
前兩步建立frame buffer的動做跟建立render buffer的動做很相似。(反正也是用一個glBind什麼的)
而最後一步 glFramebufferRenderbuffer 這個纔有點新意。它讓你把前面建立的buffer render依附在frame buffer的GL_COLOR_ATTACHMENT0位置上。
8)清理屏幕
- (void)render { glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); [_context presentRenderbuffer:GL_RENDERBUFFER]; }
爲了儘快在屏幕上顯示一些什麼,在咱們和那些 vertexes、shaders打交道以前,把屏幕清理一下,顯示另外一個顏色吧。(RGB 0, 104, 55,綠色吧)
這裏每一個RGB色的範圍是0~1,因此每一個要除一下255.
下面解析一下每一步動做:
1. 調用glClearColor ,設置一個RGB顏色和透明度,接下來會用這個顏色塗滿全屏。
2. 調用glClear來進行這個「填色」的動做(大概就是photoshop那個油桶嘛)。還記得前面說過有不少buffer的話,這裏咱們要用到GL_COLOR_BUFFER_BIT來聲明要清理哪個緩衝區。
3. 調用OpenGL context的presentRenderbuffer方法,把緩衝區(render buffer和color buffer)的顏色呈現到UIView上。
9)把前面的動做串起來修改一下OpenGLView.m
// Replace initWithFrame with this - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupLayer]; [self setupContext]; [self setupRenderBuffer]; [self setupFrameBuffer]; [self render]; } return self; } // Replace dealloc method with this - (void)dealloc { [_context release]; _context = nil; [super dealloc]; }
10)把App Delegate和OpenGLView 鏈接起來
在HelloOpenGLAppDelegate.h 中修改一下:
// At top of file #import "OpenGLView.h" // Inside @interface OpenGLView* _glView; // After @interface @property (nonatomic, retain) IBOutlet OpenGLView *glView;
接下來修改.m文件:
// At top of file @synthesize glView=_glView; // At top of application:didFinishLaunchingWithOptions CGRect screenBounds = [[UIScreen mainScreen] bounds]; self.glView = [[[OpenGLView alloc] initWithFrame:screenBounds] autorelease]; [self.window addSubview:_glView]; // In dealloc [_glView release];
一切順利的話,你就能看到一個新的view在屏幕上顯示。
這裏是OpenGL的世界。
在OpenGL ES2.0 的世界,在場景中渲染任何一種幾何圖形,你都須要建立兩個稱之爲「着色器」的小程序。
着色器由一個相似C的語言編寫- GLSL。知道就行了,咱們不深究。
這個世界有兩種着色器(Shader):
·Vertex shaders – 在你的場景中,每一個頂點都須要調用的程序,稱爲「頂點着色器」。假如你在渲染一個簡單的場景:一個長方形,每一個角只有一個頂點。因而vertex shader 會被調用四次。它負責執行:諸如燈光、幾何變換等等的計算。得出最終的頂點位置後,爲下面的片斷着色器提供必須的數據。
·Fragment shaders – 在你的場景中,大概每一個像素都會調用的程序,稱爲「片斷着色器」。在一個簡單的場景,也是剛剛說到的長方形。這個長方形所覆蓋到的每個像素,都會調用一次fragment shader。片斷着色器的責任是計算燈光,以及更重要的是計算出每一個像素的最終顏色。
下面咱們經過簡單的例子來講明。
打開你的xcode,File\New\New File… 選擇iOS\Other\Empty, 點擊下一步。命名爲:
SimpleVertex.glsl 點擊保存。
打開這個文件,加入下面的代碼:
attribute vec4 Position; // 1 attribute vec4 SourceColor; // 2 varying vec4 DestinationColor; // 3 void main(void) { // 4 DestinationColor = SourceColor; // 5 gl_Position = Position; // 6 }
咱們一行一行解析:
1 「attribute」聲明瞭這個shader會接受一個傳入變量,這個變量名爲「Position」。在後面的代碼中,你會用它來傳入頂點的位置數據。這個變量的類型是「vec4」,表示這是一個由4部分組成的矢量。
2 與上面同理,這裏是傳入頂點的顏色變量。
3 這個變量沒有「attribute」的關鍵字。代表它是一個傳出變量,它就是會傳入片斷着色器的參數。「varying」關鍵字表示,依據頂點的顏色,平滑計算出頂點之間每一個像素的顏色。
文字比較難懂,咱們一圖勝千言:
圖中的一個像素,它位於紅色和綠色的頂點之間,準確地說,這是一個距離上面頂點55/100,距離下面頂點45/100的點。因此經過過渡,能肯定這個像素的顏色。
4 每一個shader都從main開始– 跟C同樣嘛。
5 設置目標顏色 = 傳入變量:SourceColor
6 gl_Position 是一個內建的傳出變量。這是一個在 vertex shader中必須設置的變量。這裏咱們直接把gl_Position = Position; 沒有作任何邏輯運算。
一個簡單的vertex shader 就是這樣了,接下來咱們再建立一個簡單的fragment shader。
新建一個空白文件:
File\New\New File… 選擇iOS\Other\Empty
命名爲:SimpleFragment.glsl 保存。
打開這個文件,加入如下代碼:
varying lowp vec4 DestinationColor; // 1 void main(void) { // 2 gl_FragColor = DestinationColor; // 3 }
下面解析:
1 這是從vertex shader中傳入的變量,這裏和vertex shader定義的一致。而額外加了一個關鍵字:lowp。在fragment shader中,必須給出一個計算的精度。出於性能考慮,總使用最低精度是一個好習慣。這裏就是設置成最低的精度。若是你須要,也能夠設置成medp或者highp.
2 也是從main開始嘛
3 正如你在vertex shader中必須設置gl_Position, 在fragment shader中必須設置gl_FragColor.
這裏也是直接從 vertex shader中取值,先不作任何改變。
還能夠吧?接下來咱們開始運用這些shader來建立咱們的app。
目前爲止,xcode僅僅會把這兩個文件copy到application bundle中。咱們還須要在運行時編譯和運行這些shader。
你可能會感到詫異。爲何要在app運行時編譯代碼?
這樣作的好處是,咱們的着色器不用依賴於某種圖形芯片。(這樣才能夠跨平臺嘛)
下面開始加入動態編譯的代碼,打開OpenGLView.m
在initWithFrame: 方法上方加入:
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType { // 1 NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"glsl"]; NSError* error; NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error]; if (!shaderString) { NSLog(@"Error loading shader: %@", error.localizedDescription); exit(1); } // 2 GLuint shaderHandle = glCreateShader(shaderType); // 3 constchar* shaderStringUTF8 = [shaderString UTF8String]; int shaderStringLength = [shaderString length]; glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength); // 4 glCompileShader(shaderHandle); // 5 GLint compileSuccess; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); if (compileSuccess == GL_FALSE) { GLchar messages[256]; glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } return shaderHandle; }
下面解析:
1 這是一個UIKit編程的標準用法,就是在NSBundle中查找某個文件。你們應該熟悉了吧。
2 調用 glCreateShader來建立一個表明shader 的OpenGL對象。這時你必須告訴OpenGL,你想建立 fragment shader仍是vertex shader。因此便有了這個參數:shaderType
3 調用glShaderSource ,讓OpenGL獲取到這個shader的源代碼。(就是咱們寫的那個)這裏咱們還把NSString轉換成C-string
4 最後,調用glCompileShader 在運行時編譯shader
5 你們都是程序員,有程序的地方就會有fail。有程序員的地方必然會有debug。若是編譯失敗了,咱們必須一些信息來找出問題緣由。 glGetShaderiv 和 glGetShaderInfoLog 會把error信息輸出到屏幕。(而後退出)
咱們還須要一些步驟來編譯vertex shader 和frament shader。
把它們倆關聯起來
告訴OpenGL來調用這個程序,還須要一些指針什麼的。
在compileShader: 方法下方,加入這些代碼
- (void)compileShaders { // 1 GLuint vertexShader = [self compileShader:@"SimpleVertex" withType:GL_VERTEX_SHADER]; GLuint fragmentShader = [self compileShader:@"SimpleFragment" withType:GL_FRAGMENT_SHADER]; // 2 GLuint programHandle = glCreateProgram(); glAttachShader(programHandle, vertexShader); glAttachShader(programHandle, fragmentShader); glLinkProgram(programHandle); // 3 GLint linkSuccess; glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess); if (linkSuccess == GL_FALSE) { GLchar messages[256]; glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } // 4 glUseProgram(programHandle); // 5 _positionSlot = glGetAttribLocation(programHandle, "Position"); _colorSlot = glGetAttribLocation(programHandle, "SourceColor"); glEnableVertexAttribArray(_positionSlot); glEnableVertexAttribArray(_colorSlot); }
下面是解析:
1 用來調用你剛剛寫的動態編譯方法,分別編譯了vertex shader 和 fragment shader
2 調用了glCreateProgram glAttachShader glLinkProgram 鏈接 vertex 和 fragment shader成一個完整的program。
3 調用 glGetProgramiv lglGetProgramInfoLog 來檢查是否有error,並輸出信息。
4 調用 glUseProgram 讓OpenGL真正執行你的program
5 最後,調用 glGetAttribLocation 來獲取指向 vertex shader傳入變量的指針。之後就能夠經過這寫指針來使用了。還有調用 glEnableVertexAttribArray來啓用這些數據。(由於默認是 disabled的。)
最後還有兩步:
1 在 initWithFrame方法裏,在調用render以前要加入這個:
[self compileShaders];
2 在@interface in OpenGLView.h 中添加兩個變量:
GLuint _positionSlot; GLuint _colorSlot;
編譯!運行!
若是你仍能正常地看到以前那個綠色的屏幕,就證實你前面寫的代碼都很好地工做了。
在這裏,咱們打算在屏幕上渲染一個正方形,以下圖:
在你用OpenGL渲染圖形的時候,時刻要記住一點,你只能直接渲染三角形,而不是其它諸如矩形的圖形。因此,一個正方形須要分開成兩個三角形來渲染。
圖中分別是頂點(0,1,2)和頂點(0,2,3)構成的三角形。
OpenGL ES2.0的一個好處是,你能夠按你的風格來管理頂點。
打開OpenGLView.m文件,建立一個純粹的C結構以及一些array來跟蹤咱們的矩形信息,以下:
typedef struct { float Position[3]; float Color[4]; } Vertex; const Vertex Vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {0, 1, 0, 1}}, {{-1, 1, 0}, {0, 0, 1, 1}}, {{-1, -1, 0}, {0, 0, 0, 1}} }; const GLubyte Indices[] = { 0, 1, 2, 2, 3, 0 };
這段代碼的做用是:
1 一個用於跟蹤全部頂點信息的結構Vertex (目前只包含位置和顏色。)
2 定義了以上面這個Vertex結構爲類型的array。
3 一個用於表示三角形頂點的數組。
數據準備好了,咱們來開始把數據傳入OpenGL
傳數據到OpenGL的話,最好的方式就是用Vertex Buffer對象。
基本上,它們就是用於緩存頂點數據的OpenGL對象。經過調用一些function來把數據發送到OpenGL-land。(是指OpenGL的畫面?)
這裏有兩種頂點緩存類型– 一種是用於跟蹤每一個頂點信息的(正如咱們的Vertices array),另外一種是用於跟蹤組成每一個三角形的索引信息(咱們的Indices array)。
下面咱們在initWithFrame中,加入一些代碼:
[self setupVBOs];
下面是定義這個setupVBOs:
- (void)setupVBOs { GLuint vertexBuffer; glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW); GLuint indexBuffer; glGenBuffers(1, &indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW); }
如你所見,其實很簡單的。這實際上是一種以前也用過的模式(pattern)。
glGenBuffers - 建立一個Vertex Buffer 對象
glBindBuffer – 告訴OpenGL咱們的vertexBuffer 是指GL_ARRAY_BUFFER
glBufferData – 把數據傳到OpenGL-land
想起哪裏用過這個模式嗎?要再也不回去看看frame buffer那一段?
萬事俱備,咱們能夠經過新的shader,用新的渲染方法來把頂點數據畫到屏幕上。
用這段代碼替換掉以前的render:
- (void)render { glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); // 1 glViewport(0, 0, self.frame.size.width, self.frame.size.height); // 2 glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) *3)); // 3 glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0); [_context presentRenderbuffer:GL_RENDERBUFFER]; }
1 調用glViewport 設置UIView中用於渲染的部分。這個例子中指定了整個屏幕。但若是你但願用更小的部分,你能夠更變這些參數。
2 調用glVertexAttribPointer來爲vertex shader的兩個輸入參數配置兩個合適的值。
第二段這裏,是一個很重要的方法,讓咱們來認真地看看它是如何工做的:
·第一個參數,聲明這個屬性的名稱,以前咱們稱之爲glGetAttribLocation
·第二個參數,定義這個屬性由多少個值組成。譬如說position是由3個float(x,y,z)組成,而顏色是4個float(r,g,b,a)
·第三個,聲明每個值是什麼類型。(這例子中不管是位置仍是顏色,咱們都用了GL_FLOAT)
·第四個,嗯……它老是false就行了。
·第五個,指 stride 的大小。這是一個種描述每一個 vertex數據大小的方式。因此咱們能夠簡單地傳入 sizeof(Vertex),讓編譯器計算出來就好。
·最好一個,是這個數據結構的偏移量。表示在這個結構中,從哪裏開始獲取咱們的值。Position的值在前面,因此傳0進去就能夠了。而顏色是緊接着位置的數據,而position的大小是3個float的大小,因此是從 3 * sizeof(float) 開始的。
回來繼續說代碼,第三點:
3 調用glDrawElements ,它最後會在每一個vertex上調用咱們的vertex shader,以及每一個像素調用fragment shader,最終畫出咱們的矩形。
它也是一個重要的方法,咱們來仔細研究一下:
·第一個參數,聲明用哪一種特性來渲染圖形。有GL_LINE_STRIP 和 GL_TRIANGLE_FAN。然而GL_TRIANGLE是最經常使用的,特別是與VBO 關聯的時候。
·第二個,告訴渲染器有多少個圖形要渲染。咱們用到C的代碼來計算出有多少個。這裏是經過個 array的byte大小除以一個Indice類型的大小獲得的。
·第三個,指每一個indices中的index類型
·最後一個,在官方文檔中說,它是一個指向index的指針。但在這裏,咱們用的是VBO,因此經過index的array就能夠訪問到了(在GL_ELEMENT_ARRAY_BUFFER傳過了),因此這裏不須要.
編譯運行的話,你就能夠看到這個畫面喇。
你可能會疑惑,爲何這個長方形恰好佔滿整個屏幕。在缺省狀態下,OpenGL的「camera」位於(0,0,0)位置,朝z軸的正方向。
固然,後面咱們會講到projection(投影)以及如何控制camera。
爲了在2D屏幕上顯示3D畫面,咱們須要在圖形上作一些投影變換,所謂投影就是下圖這個意思:
基本上,爲了模仿人類的眼球原理。咱們設置一個遠平面和一個近平面,在兩個平面以前,離近平面近的圖像,會由於被縮小了而顯得變小;而離遠平面近的圖像,也會所以而變大。
打開SimpleVertex.glsl,作一下修改:
// Add right before the main uniform mat4 Projection; // Modify gl_Position line as follows gl_Position = Projection * Position;
這裏咱們增長了一個叫作projection的傳入變量。uniform 關鍵字表示,這會是一個應用於全部頂點的常量,而不是會由於頂點不一樣而不一樣的值。
mat4 是 4X4矩陣的意思。然而,Matrix math是一個很大的課題,咱們不可能在這裏解析。因此在這裏,你只要認爲它是用於放大縮小、旋轉、變形就行了。
Position位置乘以Projection矩陣,咱們就獲得最終的位置數值。
無錯,這就是一種被稱之「線性代數」的東西。我在大學時期後,早就忘大部分了。
其實數學也只是一種工具,而這種工具已經由前面的才子解決了,咱們知道怎麼用就好。
Bill Hollings,cocos3d的做者。他編寫了一個完整的3D特性框架,並整合到cocos2d中。(做者:可能有一天我也會弄一個3D的教程)不管任何,Cocos3d包含了Objective-C的向量和矩陣庫,因此咱們能夠很好地應用到這個項目中。
這裏,http://d1xzuxjlafny7l.cloudfront.net/downloads/Cocos3DMathLib.zip
有一個zip文件,(做者:我移除了一些沒必要要的依賴)下載並copy到你的項目中。記得選上:「Copy items into destination group’s folder (if needed)」 點擊Finish。
在OpenGLView.h 中加入一個實例變量:
GLuint _projectionUniform;
而後到OpenGLView.m文件中加上:
// Add to top of file #import "CC3GLMatrix.h" // Add to bottom of compileShaders _projectionUniform = glGetUniformLocation(programHandle, "Projection"); // Add to render, right before the call to glViewport CC3GLMatrix *projection = [CC3GLMatrix matrix]; float h =4.0f* self.frame.size.height / self.frame.size.width; [projection populateFromFrustumLeft:-2 andRight:2 andBottom:-h/2 andTop:h/2 andNear:4 andFar:10]; glUniformMatrix4fv(_projectionUniform, 1, 0, projection.glMatrix); // Modify vertices so they are within projection near/far planes const Vertex Vertices[] = { {{1, -1, -7}, {1, 0, 0, 1}}, {{1, 1, -7}, {0, 1, 0, 1}}, {{-1, 1, -7}, {0, 0, 1, 1}}, {{-1, -1, -7}, {0, 0, 0, 1}} };
·經過調用 glGetUniformLocation 來獲取在vertex shader中的Projection輸入變量
·而後,使用math library來建立投影矩陣。經過這個讓你指定座標,以及遠近屏位置的方式,來建立矩陣,會讓事情比較簡單。
·你用來把數據傳入到vertex shader的方式,叫作 glUniformMatrix4fv. 這個CC3GLMatrix類有一個很方便的方法 glMatrix,來把矩陣轉換成OpenGL的array格式。
·最後,把以前的vertices數據修改一下,讓z座標爲-7.
編譯後運行,你應該能夠看到一個稍稍有點距離的正方形了。
若是老是要修改那個vertex array才能改變圖形,這就太煩人了。
而這正是變換矩陣該作的事(又來了,線性代數)
在前面,咱們修改了應用到投影矩陣的vertex array來達到移動圖形的目的。何不試一下,作一個變形、放大縮小、旋轉的矩陣來應用?咱們稱之爲「model-view」變換。
再回到 SimpleVertex.glsl
// Add right after the Projection uniform uniform mat4 Modelview; // Modify the gl_Position line gl_Position = Projection * Modelview * Position;
就是又加了一個 Uniform的矩陣而已。順便把它應用到gl_Position當中。
而後到 OpenGLView.h中加上一個變量:
GLuint _modelViewUniform;
到OpenGLView.m中修改:
// Add to end of compileShaders _modelViewUniform = glGetUniformLocation(programHandle, "Modelview"); // Add to render, right before call to glViewport CC3GLMatrix *modelView = [CC3GLMatrix matrix]; [modelView populateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()), 0, -7)]; glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix); // Revert vertices back to z-value 0 const Vertex Vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {0, 1, 0, 1}}, {{-1, 1, 0}, {0, 0, 1, 1}}, {{-1, -1, 0}, {0, 0, 0, 1}} };
·獲取那個model view uniform的傳入變量
·使用cocos3d math庫來建立一個新的矩陣,在變換中裝入矩陣。
·變換是在z軸上移動-7,而爲何sin(當前時間) 呢?
哈哈,若是你還記得高中時候的三角函數。sin()是一個從-1到1的函數。已PI(3.14)爲一個週期。這樣作的話,約每3.14秒,這個函數會從-1到1循環一次。
·把vertex 結構改回去,把z座標設回0.
編譯運行,就算咱們把z設回0,也能夠看到這個位於中間的正方形了。
什麼?一動不動的?
固然了,咱們只是調用了一次render方法。
接下來,咱們在每一幀都調用一次看看。
理想狀態下,咱們但願OpenGL的渲染頻率跟屏幕的刷新頻率一致。
幸運的是,Apple爲咱們提供了一個CADisplayLink的類。這個很好用的,立刻就用吧。
在OpenGLView.m文件,修改以下:
// Add new method before init - (void)setupDisplayLink { CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } // Modify render method to take a parameter - (void)render:(CADisplayLink*)displayLink { // Remove call to render in initWithFrame and replace it with the following [self setupDisplayLink];
這就好了,有CADisplayLink在每一幀都調用你的render方法,咱們的圖形看起身就好似被sin()週期地變型了。如今這個方塊會前先後後地來回移動。
讓圖形旋轉起來,纔算得上有型。
再到OpenGLView.h 中,添加成員變量。
float _currentRotation;
在OpenGLView.m的render中,在populateFromTranslation的調用後面加上:
_currentRotation += displayLink.duration *90; [modelView rotateBy:CC3VectorMake(_currentRotation, _currentRotation, 0)];
·添加了一個叫_currentRotation的float,每秒會增長90度。
·經過修改那個model view矩陣(這裏至關於一個用於變型的矩陣),增長旋轉。
·旋轉在x、y軸上做用,沒有在z軸的。
編譯運行,你會看到一個頗有型的翻轉的3D效果。
以前的只能算是2.5D,由於它還只是一個會旋轉的面而已。如今咱們把它改形成3D的。
把以前的vertices、indices數組註釋掉吧。
而後加上新的:
const Vertex Vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {1, 0, 0, 1}}, {{-1, 1, 0}, {0, 1, 0, 1}}, {{-1, -1, 0}, {0, 1, 0, 1}}, {{1, -1, -1}, {1, 0, 0, 1}}, {{1, 1, -1}, {1, 0, 0, 1}}, {{-1, 1, -1}, {0, 1, 0, 1}}, {{-1, -1, -1}, {0, 1, 0, 1}} }; const GLubyte Indices[] = { // Front 0, 1, 2, 2, 3, 0, // Back 4, 6, 5, 4, 7, 6, // Left 2, 7, 3, 7, 6, 2, // Right 0, 4, 1, 4, 1, 5, // Top 6, 2, 1, 1, 6, 5, // Bottom 0, 3, 7, 0, 7, 4 };
編譯運行,你會看到一個方塊了。
但這個方塊有時候讓人以爲假,由於你能夠看到方塊裏面。
這裏還有一個叫作 depth testing(深度測試)的功能,啓動它,OpenGL就能夠跟蹤在z軸上的像素。這樣它只會在那個像素前方沒有東西時,纔會繪畫這個像素。
到OpenGLView.h中,添加成員變量。
GLuint _depthRenderBuffer;
在OpenGLView.m:
// Add new method right after setupRenderBuffer - (void)setupDepthBuffer { glGenRenderbuffers(1, &_depthRenderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height); } // Add to end of setupFrameBuffer glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer); // In the render method, replace the call to glClear with the following glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); // Add to initWithFrame, right before call to setupRenderBuffer [self setupDepthBuffer];
·setupDepthBuffer方法建立了一個depth buffer。這個與前面的render/color buffer相似,再也不重複了。值得注意的是,這裏使用了glRenderbufferStorage, 然不是context的renderBufferStorage(這個是在OpenGL的view中特別爲color render buffer而設的)。
·接着,咱們調用glFramebufferRenderbuffer,來關聯depth buffer和render buffer。還記得,我說過frame buffer中儲存着不少種不一樣的buffer?這正是一個新的buffer。
·在render方法中,咱們在每次update時都清除深度buffer,並啓用depth testing。
編譯運行,看看這個教程最後的效果。
一個選擇的立方塊,用到了OpenGL ES2.0。
這裏有本教程的完整源代碼。