你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS OpenGL ES2.0 開發實例

iOS OpenGL ES2.0 開發實例

編輯:IOS開發綜合

OpenGL ES 是可以在iphone上實現2D和3D圖形編程的低級API。

如果你之前接觸過 cocos2d,sparrow,corona,unity 這些框架,你會發現其實它們都是基於OpenGL上創建的。

多數程序員選擇使用這些框架,而不是直接調用OpenGL,因為OpenGL實在是太難用了。

而這篇教程,就是為了讓大家更好地入門而寫的。

在這個系列的文章中,你可以通過一些實用又容易上手的實驗,創建類似hello world的APP。例如顯示一些簡單的立體圖形。

流程大致如下:

·創建一個簡單的OpenGL app

·編譯並運行 vertex & fragment shaders

·通過vertex buffer,在屏幕上渲染一個簡單矩形

·使用投影和 model-view 變形。

·渲染一個可以 depth testing的3D對象。

說明:

我並非OpenGL的專家,這些完全是通過自學得來的。如果大家發現哪些不對的地方,歡迎指出。

OpenGL ES1.0 和 OpenGL ES2.0

第一件你需要搞清楚的事,是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,新建項目。

點擊下一步,把這個項目命名為HelloOpenGL,點擊下一步,選擇存放目錄,點擊“創建”。

接下來,你要在這個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,創建一些後面會用到的實例變量。

  1. #import
  2. #import
  3. #include
  4. #include
  5.  
  6. @interfaceOpenGLView:UIView{
  7. CAEAGLLayer*_eaglLayer;
  8. EAGLContext*_context;
  9. GLuint_colorRenderBuffer;
  10. }
  11.  
  12. @end

3)設置layer class 為 CAEAGLLayer

  1. +(Class)layerClass{
  2. return[CAEAGLLayerclass];
  3. }

想要顯示OpenGL的內容,你需要把它缺省的layer設置為一個特殊的layer。(CAEAGLLayer)。這裡通過直接復寫layerClass的方法。

4)設置layer為不透明(Opaque)

  1. -(void)setupLayer{
  2. _eaglLayer=(CAEAGLLayer*)self.layer;
  3. _eaglLayer.opaque=YES;
  4. }

因為缺省的話,CALayer是透明的。而透明的層對性能負荷很大,特別是OpenGL的層。

(如果可能,盡量都把層設置為不透明。另一個比較明顯的例子是自定義tableview cell)

5)創建OpenGL context

  1. -(void)setupContext{
  2. EAGLRenderingAPIapi=kEAGLRenderingAPIOpenGLES2;
  3. _context=[[EAGLContextalloc]initWithAPI:api];
  4. if(!_context){
  5. NSLog(@"FailedtoinitializeOpenGLES2.0context");
  6. exit(1);
  7. }
  8.  
  9. if(![EAGLContextsetCurrentContext:_context]){
  10. NSLog(@"FailedtosetcurrentOpenGLcontext");
  11. exit(1);
  12. }
  13. }

無論你要OpenGL幫你實現什麼,總需要這個EAGLContext。

EAGLContext管理所有通過OpenGL進行draw的信息。這個與Core Graphics context類似。

當你創建一個context,你要聲明你要用哪個version的API。這裡,我們選擇OpenGL ES 2.0.

(容錯處理,如果創建失敗了,我們的程序會退出)

6)創建render buffer (渲染緩沖區)

  1. -(void)setupRenderBuffer{
  2. glGenRenderbuffers(1,&_colorRenderBuffer);
  3. glBindRenderbuffer(GL_RENDERBUFFER,_colorRenderBuffer);
  4. [_contextrenderbufferStorage:GL_RENDERBUFFERfromDrawable:_eaglLayer];
  5. }

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 (幀緩沖區)

  1. -(void)setupFrameBuffer{
  2. GLuintframebuffer;
  3. glGenFramebuffers(1,&framebuffer);
  4. glBindFramebuffer(GL_FRAMEBUFFER,framebuffer);
  5. glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
  6. GL_RENDERBUFFER,_colorRenderBuffer);
  7. }

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)清理屏幕

  1. -(void)render{
  2. glClearColor(0,104.0/255.0,55.0/255.0,1.0);
  3. glClear(GL_COLOR_BUFFER_BIT);
  4. [_contextpresentRenderbuffer:GL_RENDERBUFFER];
  5. }

為了盡快在屏幕上顯示一些什麼,在我們和那些 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

  1. //ReplaceinitWithFramewiththis
  2. -(id)initWithFrame:(CGRect)frame
  3. {
  4. self=[superinitWithFrame:frame];
  5. if(self){
  6. [selfsetupLayer];
  7. [selfsetupContext];
  8. [selfsetupRenderBuffer];
  9. [selfsetupFrameBuffer];
  10. [selfrender];
  11. }
  12. returnself;
  13. }
  14.  
  15. //Replacedeallocmethodwiththis
  16. -(void)dealloc
  17. {
  18. [_contextrelease];
  19. _context=nil;
  20. [superdealloc];
  21. }

10)把App Delegate和OpenGLView 連接起來

在HelloOpenGLAppDelegate.h 中修改一下:

  1. //Attopoffile
  2. #import"OpenGLView.h"
  3.  
  4. //Inside@interface
  5. OpenGLView*_glView;
  6.  
  7. //After@interface
  8. @property(nonatomic,retain)IBOutletOpenGLView*glView;

接下來修改.m文件:

  1. //Attopoffile
  2. @synthesizeglView=_glView;
  3.  
  4. //Attopofapplication:didFinishLaunchingWithOptions
  5. CGRectscreenBounds=[[UIScreenmainScreen]bounds];
  6. self.glView=[[[OpenGLViewalloc]initWithFrame:screenBounds]autorelease];
  7. [self.windowaddSubview:_glView];
  8.  
  9. //Indealloc
  10. [_glViewrelease];

一切順利的話,你就能看到一個新的view在屏幕上顯示。

這裡是OpenGL的世界。

\

添加shaders:頂點著色器和片段著色器

在OpenGL ES2.0 的世界,在場景中渲染任何一種幾何圖形,你都需要創建兩個稱之為“著色器”的小程序。

著色器由一個類似C的語言編寫- GLSL。知道就好了,我們不深究。

這個世界有兩種著色器(Shader):

·Vertex shaders – 在你的場景中,每個頂點都需要調用的程序,稱為“頂點著色器”。假如你在渲染一個簡單的場景:一個長方形,每個角只有一個頂點。於是vertex shader 會被調用四次。它負責執行:諸如燈光、幾何變換等等的計算。得出最終的頂點位置後,為下面的片段著色器提供必須的數據。

·Fragment shaders – 在你的場景中,大概每個像素都會調用的程序,稱為“片段著色器”。在一個簡單的場景,也是剛剛說到的長方形。這個長方形所覆蓋到的每一個像素,都會調用一次fragment shader。片段著色器的責任是計算燈光,以及更重要的是計算出每個像素的最終顏色。

下面我們通過簡單的例子來說明。

打開你的xcode,File\New\New File… 選擇iOS\Other\Empty, 點擊下一步。命名為:

SimpleVertex.glsl 點擊保存。

打開這個文件,加入下面的代碼:

  1. attributevec4Position;//1
  2. attributevec4SourceColor;//2
  3.  
  4. varyingvec4DestinationColor;//3
  5.  
  6. voidmain(void){//4
  7. DestinationColor=SourceColor;//5
  8. gl_Position=Position;//6
  9. }

我們一行一行解析:

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 保存。

打開這個文件,加入以下代碼:

  1. varyinglowpvec4DestinationColor;//1
  2.  
  3. voidmain(void){//2
  4. gl_FragColor=DestinationColor;//3
  5. }

下面解析:

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。

編譯 Vertex shader 和 Fragment shader

目前為止,xcode僅僅會把這兩個文件copy到application bundle中。我們還需要在運行時編譯和運行這些shader。

你可能會感到詫異。為什麼要在app運行時編譯代碼?

這樣做的好處是,我們的著色器不用依賴於某種圖形芯片。(這樣才可以跨平台嘛)

下面開始加入動態編譯的代碼,打開OpenGLView.m

在initWithFrame: 方法上方加入:

  1. -(GLuint)compileShader:(NSString*)shaderNamewithType:(GLenum)shaderType{
  2.  
  3. //1
  4. NSString*shaderPath=[[NSBundlemainBundle]pathForResource:shaderName
  5. ofType:@"glsl"];
  6. NSError*error;
  7. NSString*shaderString=[NSStringstringWithContentsOfFile:shaderPath
  8. encoding:NSUTF8StringEncodingerror:&error];
  9. if(!shaderString){
  10. NSLog(@"Errorloadingshader:%@",error.localizedDescription);
  11. exit(1);
  12. }
  13.  
  14. //2
  15. GLuintshaderHandle=glCreateShader(shaderType);
  16.  
  17. //3
  18. constchar*shaderStringUTF8=[shaderStringUTF8String];
  19. intshaderStringLength=[shaderStringlength];
  20. glShaderSource(shaderHandle,1,&shaderStringUTF8,&shaderStringLength);
  21.  
  22. //4
  23. glCompileShader(shaderHandle);
  24.  
  25. //5
  26. GLintcompileSuccess;
  27. glGetShaderiv(shaderHandle,GL_COMPILE_STATUS,&compileSuccess);
  28. if(compileSuccess==GL_FALSE){
  29. GLcharmessages[256];
  30. glGetShaderInfoLog(shaderHandle,sizeof(messages),0,&messages[0]);
  31. NSString*messageString=[NSStringstringWithUTF8String:messages];
  32. NSLog(@"%@",messageString);
  33. exit(1);
  34. }
  35.  
  36. returnshaderHandle;
  37.  
  38. }

下面解析:

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: 方法下方,加入這些代碼

  1. -(void)compileShaders{
  2.  
  3. //1
  4. GLuintvertexShader=[selfcompileShader:@"SimpleVertex"
  5. withType:GL_VERTEX_SHADER];
  6. GLuintfragmentShader=[selfcompileShader:@"SimpleFragment"
  7. withType:GL_FRAGMENT_SHADER];
  8.  
  9. //2
  10. GLuintprogramHandle=glCreateProgram();
  11. glAttachShader(programHandle,vertexShader);
  12. glAttachShader(programHandle,fragmentShader);
  13. glLinkProgram(programHandle);
  14.  
  15. //3
  16. GLintlinkSuccess;
  17. glGetProgramiv(programHandle,GL_LINK_STATUS,&linkSuccess);
  18. if(linkSuccess==GL_FALSE){
  19. GLcharmessages[256];
  20. glGetProgramInfoLog(programHandle,sizeof(messages),0,&messages[0]);
  21. NSString*messageString=[NSStringstringWithUTF8String:messages];
  22. NSLog(@"%@",messageString);
  23. exit(1);
  24. }
  25.  
  26. //4
  27. glUseProgram(programHandle);
  28.  
  29. //5
  30. _positionSlot=glGetAttribLocation(programHandle,"Position");
  31. _colorSlot=glGetAttribLocation(programHandle,"SourceColor");
  32. glEnableVertexAttribArray(_positionSlot);
  33. glEnableVertexAttribArray(_colorSlot);
  34. }

下面是解析:

1 用來調用你剛剛寫的動態編譯方法,分別編譯了vertex shader 和 fragment shader

2 調用了glCreateProgramglAttachShaderglLinkProgram連接 vertex 和 fragment shader成一個完整的program。

3 調用glGetProgramivlglGetProgramInfoLog來檢查是否有error,並輸出信息。

4 調用glUseProgram讓OpenGL真正執行你的program

5 最後,調用glGetAttribLocation來獲取指向 vertex shader傳入變量的指針。以後就可以通過這寫指針來使用了。還有調用glEnableVertexAttribArray來啟用這些數據。(因為默認是 disabled的。)

最後還有兩步:

1 在 initWithFrame方法裡,在調用render之前要加入這個:

  1. [selfcompileShaders];

2 在@interface in OpenGLView.h 中添加兩個變量:

  1. GLuint_positionSlot;
  2. GLuint_colorSlot;

編譯!運行!

如果你仍能正常地看到之前那個綠色的屏幕,就證明你前面寫的代碼都很好地工作了。

為這個簡單的長方形創建 Vertex Data!

在這裡,我們打算在屏幕上渲染一個正方形,如下圖:

\

在你用OpenGL渲染圖形的時候,時刻要記住一點,你只能直接渲染三角形,而不是其它諸如矩形的圖形。所以,一個正方形需要分開成兩個三角形來渲染。

圖中分別是頂點(0,1,2)和頂點(0,2,3)構成的三角形。

OpenGL ES2.0的一個好處是,你可以按你的風格來管理頂點。

打開OpenGLView.m文件,創建一個純粹的C結構以及一些array來跟蹤我們的矩形信息,如下:

  1. typedefstruct{
  2. floatPosition[3];
  3. floatColor[4];
  4. }Vertex;
  5.  
  6. constVertexVertices[]={
  7. {{1,-1,0},{1,0,0,1}},
  8. {{1,1,0},{0,1,0,1}},
  9. {{-1,1,0},{0,0,1,1}},
  10. {{-1,-1,0},{0,0,0,1}}
  11. };
  12.  
  13. constGLubyteIndices[]={
  14. 0,1,2,
  15. 2,3,0
  16. };

這段代碼的作用是:

1 一個用於跟蹤所有頂點信息的結構Vertex (目前只包含位置和顏色。)

2 定義了以上面這個Vertex結構為類型的array。

3 一個用於表示三角形頂點的數組。

數據准備好了,我們來開始把數據傳入OpenGL

創建Vertex Buffer 對象

傳數據到OpenGL的話,最好的方式就是用Vertex Buffer對象。

基本上,它們就是用於緩存頂點數據的OpenGL對象。通過調用一些function來把數據發送到OpenGL-land。(是指OpenGL的畫面?)

這裡有兩種頂點緩存類型– 一種是用於跟蹤每個頂點信息的(正如我們的Vertices array),另一種是用於跟蹤組成每個三角形的索引信息(我們的Indices array)。

下面我們在initWithFrame中,加入一些代碼:

  1. [selfsetupVBOs];

下面是定義這個setupVBOs:

  1. -(void)setupVBOs{
  2.  
  3. GLuintvertexBuffer;
  4. glGenBuffers(1,&vertexBuffer);
  5. glBindBuffer(GL_ARRAY_BUFFER,vertexBuffer);
  6. glBufferData(GL_ARRAY_BUFFER,sizeof(Vertices),Vertices,GL_STATIC_DRAW);
  7.  
  8. GLuintindexBuffer;
  9. glGenBuffers(1,&indexBuffer);
  10. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,indexBuffer);
  11. glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(Indices),Indices,GL_STATIC_DRAW);
  12.  
  13. }

如你所見,其實很簡單的。這其實是一種之前也用過的模式(pattern)。

glGenBuffers - 創建一個Vertex Buffer 對象

  1. glBindBuffer–告訴OpenGL我們的vertexBuffer是指GL_ARRAY_BUFFER

glBufferData – 把數據傳到OpenGL-land

想起哪裡用過這個模式嗎?要不再回去看看frame buffer那一段?

萬事俱備,我們可以通過新的shader,用新的渲染方法來把頂點數據畫到屏幕上。

用這段代碼替換掉之前的render:

  1. -(void)render{
  2. glClearColor(0,104.0/255.0,55.0/255.0,1.0);
  3. glClear(GL_COLOR_BUFFER_BIT);
  4.  
  5. //1
  6. glViewport(0,0,self.frame.size.width,self.frame.size.height);
  7.  
  8. //2
  9. glVertexAttribPointer(_positionSlot,3,GL_FLOAT,GL_FALSE,
  10. sizeof(Vertex),0);
  11. glVertexAttribPointer(_colorSlot,4,GL_FLOAT,GL_FALSE,
  12. sizeof(Vertex),(GLvoid*)(sizeof(float)*3));
  13.  
  14. //3
  15. glDrawElements(GL_TRIANGLES,sizeof(Indices)/sizeof(Indices[0]),
  16. GL_UNSIGNED_BYTE,0);
  17.  
  18. [_contextpresentRenderbuffer:GL_RENDERBUFFER];
  19. }

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,做一下修改:

  1. //Addrightbeforethemain
  2. uniformmat4Projection;
  3.  
  4. //Modifygl_Positionlineasfollows
  5. 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 中加入一個實例變量:

  1. GLuint_projectionUniform;

然後到OpenGLView.m文件中加上:

  1. //Addtotopoffile
  2. #import"CC3GLMatrix.h"
  3.  
  4. //AddtobottomofcompileShaders
  5. _projectionUniform=glGetUniformLocation(programHandle,"Projection");
  6.  
  7. //Addtorender,rightbeforethecalltoglViewport
  8. CC3GLMatrix*projection=[CC3GLMatrixmatrix];
  9. floath=4.0f*self.frame.size.height/self.frame.size.width;
  10. [projectionpopulateFromFrustumLeft:-2andRight:2andBottom:-h/2andTop:h/2andNear:4andFar:10];
  11. glUniformMatrix4fv(_projectionUniform,1,0,projection.glMatrix);
  12.  
  13. //Modifyverticessotheyarewithinprojectionnear/farplanes
  14. constVertexVertices[]={
  15. {{1,-1,-7},{1,0,0,1}},
  16. {{1,1,-7},{0,1,0,1}},
  17. {{-1,1,-7},{0,0,1,1}},
  18. {{-1,-1,-7},{0,0,0,1}}
  19. };

·通過調用glGetUniformLocation來獲取在vertex shader中的Projection輸入變量

·然後,使用math library來創建投影矩陣。通過這個讓你指定坐標,以及遠近屏位置的方式,來創建矩陣,會讓事情比較簡單。

·你用來把數據傳入到vertex shader的方式,叫做glUniformMatrix4fv. 這個CC3GLMatrix類有一個很方便的方法 glMatrix,來把矩陣轉換成OpenGL的array格式。

·最後,把之前的vertices數據修改一下,讓z坐標為-7.

編譯後運行,你應該可以看到一個稍稍有點距離的正方形了。

\

嘗試移動和旋轉吧

如果總是要修改那個vertex array才能改變圖形,這就太煩人了。

而這正是變換矩陣該做的事(又來了,線性代數)

在前面,我們修改了應用到投影矩陣的vertex array來達到移動圖形的目的。何不試一下,做一個變形、放大縮小、旋轉的矩陣來應用?我們稱之為“model-view”變換。

再回到 SimpleVertex.glsl

  1. //AddrightaftertheProjectionuniform
  2. uniformmat4Modelview;
  3.  
  4. //Modifythegl_Positionline
  5. gl_Position=Projection*Modelview*Position;

就是又加了一個 Uniform的矩陣而已。順便把它應用到gl_Position當中。

然後到 OpenGLView.h中加上一個變量:

  1. GLuint_modelViewUniform;

到OpenGLView.m中修改:

  1. //AddtoendofcompileShaders
  2. _modelViewUniform=glGetUniformLocation(programHandle,"Modelview");
  3.  
  4. //Addtorender,rightbeforecalltoglViewport
  5. CC3GLMatrix*modelView=[CC3GLMatrixmatrix];
  6. [modelViewpopulateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()),0,-7)];
  7. glUniformMatrix4fv(_modelViewUniform,1,0,modelView.glMatrix);
  8.  
  9. //Revertverticesbacktoz-value0
  10. constVertexVertices[]={
  11. {{1,-1,0},{1,0,0,1}},
  12. {{1,1,0},{0,1,0,1}},
  13. {{-1,1,0},{0,0,1,1}},
  14. {{-1,-1,0},{0,0,0,1}}
  15. };

·獲取那個model view uniform的傳入變量

·使用cocos3d math庫來創建一個新的矩陣,在變換中裝入矩陣。

·變換是在z軸上移動-7,而為什麼sin(當前時間) 呢?

哈哈,如果你還記得高中時候的三角函數。sin()是一個從-1到1的函數。已PI(3.14)為一個周期。這樣做的話,約每3.14秒,這個函數會從-1到1循環一次。

·把vertex 結構改回去,把z坐標設回0.

編譯運行,就算我們把z設回0,也可以看到這個位於中間的正方形了。

\

什麼?一動不動的?

當然了,我們只是調用了一次render方法。

接下來,我們在每一幀都調用一次看看。

渲染和 CADisplayLink

理想狀態下,我們希望OpenGL的渲染頻率跟屏幕的刷新頻率一致。

幸運的是,Apple為我們提供了一個CADisplayLink的類。這個很好用的,馬上就用吧。

在OpenGLView.m文件,修改如下:

  1. //Addnewmethodbeforeinit
  2. -(void)setupDisplayLink{
  3. CADisplayLink*displayLink=[CADisplayLinkdisplayLinkWithTarget:selfselector:@selector(render:)];
  4. [displayLinkaddToRunLoop:[NSRunLoopcurrentRunLoop]forMode:NSDefaultRunLoopMode];
  5. }
  6.  
  7. //Modifyrendermethodtotakeaparameter
  8. -(void)render:(CADisplayLink*)displayLink{
  9.  
  10. //RemovecalltorenderininitWithFrameandreplaceitwiththefollowing
  11. [selfsetupDisplayLink];

這就行了,有CADisplayLink在每一幀都調用你的render方法,我們的圖形看起身就好似被sin()周期地變型了。現在這個方塊會前前後後地來回移動。

\

不費功夫地旋轉

讓圖形旋轉起來,才算得上有型。

再到OpenGLView.h 中,添加成員變量。

  1. float_currentRotation;

在OpenGLView.m的render中,在populateFromTranslation的調用後面加上:

  1. _currentRotation+=displayLink.duration*90;
  2. [modelViewrotateBy:CC3VectorMake(_currentRotation,_currentRotation,0)];

·添加了一個叫_currentRotation的float,每秒會增加90度。

·通過修改那個model view矩陣(這裡相當於一個用於變型的矩陣),增加旋轉。

·旋轉在x、y軸上作用,沒有在z軸的。

編譯運行,你會看到一個很有型的翻轉的3D效果。

\

不費功夫地變成3D方塊?

之前的只能算是2.5D,因為它還只是一個會旋轉的面而已。現在我們把它改造成3D的。

把之前的vertices、indices數組注釋掉吧。

然後加上新的:

  1. constVertexVertices[]={
  2. {{1,-1,0},{1,0,0,1}},
  3. {{1,1,0},{1,0,0,1}},
  4. {{-1,1,0},{0,1,0,1}},
  5. {{-1,-1,0},{0,1,0,1}},
  6. {{1,-1,-1},{1,0,0,1}},
  7. {{1,1,-1},{1,0,0,1}},
  8. {{-1,1,-1},{0,1,0,1}},
  9. {{-1,-1,-1},{0,1,0,1}}
  10. };
  11.  
  12. constGLubyteIndices[]={
  13. //Front
  14. 0,1,2,
  15. 2,3,0,
  16. //Back
  17. 4,6,5,
  18. 4,7,6,
  19. //Left
  20. 2,7,3,
  21. 7,6,2,
  22. //Right
  23. 0,4,1,
  24. 4,1,5,
  25. //Top
  26. 6,2,1,
  27. 1,6,5,
  28. //Bottom
  29. 0,3,7,
  30. 0,7,4
  31. };

編譯運行,你會看到一個方塊了。

\

但這個方塊有時候讓人覺得假,因為你可以看到方塊裡面。

這裡還有一個叫做 depth testing(深度測試)的功能,啟動它,OpenGL就可以跟蹤在z軸上的像素。這樣它只會在那個像素前方沒有東西時,才會繪畫這個像素。

到OpenGLView.h中,添加成員變量。

  1. GLuint_depthRenderBuffer;

在OpenGLView.m:

  1. //AddnewmethodrightaftersetupRenderBuffer
  2. -(void)setupDepthBuffer{
  3. glGenRenderbuffers(1,&_depthRenderBuffer);
  4. glBindRenderbuffer(GL_RENDERBUFFER,_depthRenderBuffer);
  5. glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT16,self.frame.size.width,self.frame.size.height);
  6. }
  7.  
  8. //AddtoendofsetupFrameBuffer
  9. glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,_depthRenderBuffer);
  10.  
  11. //Intherendermethod,replacethecalltoglClearwiththefollowing
  12. glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  13. glEnable(GL_DEPTH_TEST);
  14.  
  15. //AddtoinitWithFrame,rightbeforecalltosetupRenderBuffer
  16. [selfsetupDepthBuffer];

·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。

\

本教程源碼地址下載:https://github.com/wanglixin1999/HelloGL

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved