偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

OpenGL ES2.0 iPhone開發(fā)指引

移動(dòng)開發(fā) 移動(dòng)應(yīng)用
OpenGL ES 是可以在iphone上實(shí)現(xiàn)2D和3D圖形編程的低級(jí)API。如果你之前接觸過(guò) cocos2d,sparrow,corona,unity 這些框架,你會(huì)發(fā)現(xiàn)其實(shí)它們都是基于OpenGL上創(chuàng)建的。多數(shù)程序員選擇使用這些框架,而不是直接調(diào)用OpenGL,因?yàn)镺penGL實(shí)在是太難用了。而這篇教程,就是為了讓大家更好地入門而寫的。

OpenGL ES 是可以在iphone上實(shí)現(xiàn)2D和3D圖形編程的低級(jí)API。

如果你之前接觸過(guò) cocos2d,sparrow,corona,unity 這些框架,你會(huì)發(fā)現(xiàn)其實(shí)它們都是基于OpenGL上創(chuàng)建的。

多數(shù)程序員選擇使用這些框架,而不是直接調(diào)用OpenGL,因?yàn)镺penGL實(shí)在是太難用了。

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

在這個(gè)系列的文章中,你可以通過(guò)一些實(shí)用又容易上手的實(shí)驗(yàn),創(chuàng)建類似hello world的APP。例如顯示一些簡(jiǎn)單的立體圖形。

流程大致如下:

·創(chuàng)建一個(gè)簡(jiǎn)單的OpenGL app

·編譯并運(yùn)行 vertex & fragment shaders

·通過(guò)vertex buffer,在屏幕上渲染一個(gè)簡(jiǎn)單矩形

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

·渲染一個(gè)可以 depth testing的3D對(duì)象。

說(shuō)明:

我并非OpenGL的專家,這些完全是通過(guò)自學(xué)得來(lái)的。如果大家發(fā)現(xiàn)哪些不對(duì)的地方,歡迎指出。

OpenGL ES1.0 和 OpenGL ES2.0

第一件你需要搞清楚的事,是OpenGL ES 1.0 和 2.0的區(qū)別。

他們有多不一樣?我只能說(shuō)他們很不一樣。

[[112154]]

OpenGL ES1.0:

針對(duì)固定管線硬件(fixed pipeline),通過(guò)它內(nèi)建的functions來(lái)設(shè)置諸如燈光、,vertexes(圖形的頂點(diǎn)數(shù)),顏色、camera等等的東西。

OpenGL ES2.0:

針對(duì)可編程管線硬件(programmable pipeline),基于這個(gè)設(shè)計(jì)可以讓內(nèi)建函數(shù)見鬼去吧,但同時(shí),你得自己動(dòng)手編寫任何功能。

“TMD”,你可能會(huì)這么想。這樣子我還可能想用2.0么?

但2.0確實(shí)能做一些很cool而1.0不能做的事情,譬如:toon shader(貼材質(zhì)).

利用opengles2.0,甚至還能創(chuàng)建下面的這種很酷的燈光和陰影效果:

OpenGL ES2.0只能夠在iphone 3GS+、iPod Touch 3G+ 和所有版本的ipad上運(yùn)行。慶幸現(xiàn)在大多數(shù)用戶都在這個(gè)范圍。

開始吧

盡管Xcode自帶了OpenGL ES的項(xiàng)目模板,但這個(gè)模板自行創(chuàng)建了大量的代碼,這樣會(huì)讓初學(xué)者感到迷惘。

因此我們通過(guò)自行編寫的方式來(lái)進(jìn)行,通過(guò)一步一步編寫,你能更清楚它的工作機(jī)制。

啟動(dòng)Xcode,新建項(xiàng)目-選擇Window-based Application, 讓我們從零開始。

點(diǎn)擊下一步,把這個(gè)項(xiàng)目命名為HelloOpenGL,點(diǎn)擊下一步,選擇存放目錄,點(diǎn)擊“創(chuàng)建”。

CMD+R,build and run。你會(huì)看到一個(gè)空白的屏幕。

如你所見的,Window-based 模板創(chuàng)建了一個(gè)沒(méi)有view、沒(méi)有view controller或者其它東西的項(xiàng)目。它只包含了一個(gè)必須的UIWindow。

File/New File,新建文件:選擇iOS\Cocoa Touch\Objective-c Class, 點(diǎn)擊下一步。

選擇subclass UIView,點(diǎn)擊下一步,命名為 OpenGLView.m., 點(diǎn)擊保存。

接下來(lái),你要在這個(gè)OpenGLView.m 文件下加入很多代碼。

1)  添加必須的framework (框架)

加入:OpenGLES.frameworks 和 QuartzCore.framework

在項(xiàng)目的Groups&Files 目錄下,選擇target “HelloOpenGL”,展開Link Binary with Libraries部分。這里是項(xiàng)目用到的框架。

“+”添加,選擇OpenGLES.framework, 重復(fù)一次把QuartzCore.framework也添加進(jìn)來(lái)。

2)修改OpenGLView.h

如下:引入OpenGL的Header,創(chuàng)建一些后面會(huì)用到的實(shí)例變量。

  1. #import <UIKit/UIKit.h> 
  2. #import <QuartzCore/QuartzCore.h> 
  3. #include <OpenGLES/ES2/gl.h> 
  4. #include <OpenGLES/ES2/glext.h> 
  5.   
  6. @interface OpenGLView : UIView { 
  7.     CAEAGLLayer* _eaglLayer; 
  8.     EAGLContext* _context; 
  9.     GLuint _colorRenderBuffer; 
  10.   
  11. @end 

3)設(shè)置layer class 為 CAEAGLLayer

  1. + (Class)layerClass { 
  2.     return [CAEAGLLayer class]; 

想要顯示OpenGL的內(nèi)容,你需要把它缺省的layer設(shè)置為一個(gè)特殊的layer。(CAEAGLLayer)。這里通過(guò)直接復(fù)寫layerClass的方法。

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

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

因?yàn)槿笔〉脑?,CALayer是透明的。而透明的層對(duì)性能負(fù)荷很大,特別是OpenGL的層。

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

5)創(chuàng)建OpenGL context

  1. - (void)setupContext {    
  2.     EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; 
  3.     _context = [[EAGLContext alloc] initWithAPI:api]; 
  4.     if (!_context) { 
  5.         NSLog(@"Failed to initialize OpenGLES 2.0 context"); 
  6.         exit(1); 
  7.     } 
  8.   
  9.     if (![EAGLContext setCurrentContext:_context]) { 
  10.         NSLog(@"Failed to set current OpenGL context"); 
  11.         exit(1); 
  12.     } 

無(wú)論你要OpenGL幫你實(shí)現(xiàn)什么,總需要這個(gè) EAGLContext。

EAGLContext管理所有通過(guò)OpenGL進(jìn)行draw的信息。這個(gè)與Core Graphics context類似。

當(dāng)你創(chuàng)建一個(gè)context,你要聲明你要用哪個(gè)version的API。這里,我們選擇OpenGL ES 2.0.

(容錯(cuò)處理,如果創(chuàng)建失敗了,我們的程序會(huì)退出)

6)創(chuàng)建render buffer (渲染緩沖區(qū))

  1. - (void)setupRenderBuffer { 
  2.     glGenRenderbuffers(1, &_colorRenderBuffer); 
  3.     glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);         
  4.     [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];     

Render buffer 是OpenGL的一個(gè)對(duì)象,用于存放渲染過(guò)的圖像。

有時(shí)候你會(huì)發(fā)現(xiàn)render buffer會(huì)作為一個(gè)color buffer被引用,因?yàn)楸举|(zhì)上它就是存放用于顯示的顏色。

創(chuàng)建render buffer的三步:

1.     調(diào)用glGenRenderbuffers來(lái)創(chuàng)建一個(gè)新的render buffer object。這里返回一個(gè)唯一的integer來(lái)標(biāo)記render buffer(這里把這個(gè)唯一值賦值到_colorRenderBuffer)。有時(shí)候你會(huì)發(fā)現(xiàn)這個(gè)唯一值被用來(lái)作為程序內(nèi)的一個(gè)OpenGL 的名稱。(反正它唯一嘛)

2.     調(diào)用glBindRenderbuffer ,告訴這個(gè)OpenGL:我在后面引用GL_RENDERBUFFER的地方,其實(shí)是想用_colorRenderBuffer。其實(shí)就是告訴OpenGL,我們定義的buffer對(duì)象是屬于哪一種OpenGL對(duì)象

3.     最后,為render buffer分配空間。renderbufferStorage

7)創(chuàng)建一個(gè) frame buffer (幀緩沖區(qū))

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

Frame buffer也是OpenGL的對(duì)象,它包含了前面提到的render buffer,以及其它后面會(huì)講到的諸如:depth buffer、stencil buffer 和 accumulation buffer。

前兩步創(chuàng)建frame buffer的動(dòng)作跟創(chuàng)建render buffer的動(dòng)作很類似。(反正也是用一個(gè)glBind什么的)

而最后一步  glFramebufferRenderbuffer 這個(gè)才有點(diǎn)新意。它讓你把前面創(chuàng)建的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.     [_context presentRenderbuffer:GL_RENDERBUFFER]; 

為了盡快在屏幕上顯示一些什么,在我們和那些 vertexes、shaders打交道之前,把屏幕清理一下,顯示另一個(gè)顏色吧。(RGB 0, 104, 55,綠色吧)

這里每個(gè)RGB色的范圍是0~1,所以每個(gè)要除一下255.

下面解析一下每一步動(dòng)作:

1.      調(diào)用glClearColor ,設(shè)置一個(gè)RGB顏色和透明度,接下來(lái)會(huì)用這個(gè)顏色涂滿全屏。

2.      調(diào)用glClear來(lái)進(jìn)行這個(gè)“填色”的動(dòng)作(大概就是photoshop那個(gè)油桶嘛)。還記得前面說(shuō)過(guò)有很多buffer的話,這里我們要用到GL_COLOR_BUFFER_BIT來(lái)聲明要清理哪一個(gè)緩沖區(qū)。

3.      調(diào)用OpenGL context的presentRenderbuffer方法,把緩沖區(qū)(render buffer和color buffer)的顏色呈現(xiàn)到UIView上。

9)把前面的動(dòng)作串起來(lái)修改一下OpenGLView.m

  1. // Replace initWithFrame with this 
  2. - (id)initWithFrame:(CGRect)frame 
  3.     self = [super initWithFrame:frame]; 
  4.     if (self) {         
  5.         [self setupLayer];         
  6.         [self setupContext];                 
  7.         [self setupRenderBuffer];         
  8.         [self setupFrameBuffer];                 
  9.         [self render];         
  10.     } 
  11.     return self; 
  12.   
  13. // Replace dealloc method with this 
  14. - (void)dealloc 
  15.     [_context release]; 
  16.     _context = nil; 
  17.     [super dealloc]; 

10)把App Delegate和OpenGLView 連接起來(lái)

在HelloOpenGLAppDelegate.h 中修改一下:

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

接下來(lái)修改.m文件:

  1. // At top of file 
  2. @synthesize glView=_glView; 
  3.   
  4. // At top of application:didFinishLaunchingWithOptions 
  5. CGRect screenBounds = [[UIScreen mainScreen] bounds];     
  6. self.glView = [[[OpenGLView alloc] initWithFrame:screenBounds] autorelease]; 
  7. [self.window addSubview:_glView]; 
  8.   
  9. // In dealloc 
  10. [_glView release]; 

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

這里是OpenGL的世界。

添加shaders:頂點(diǎn)著色器和片段著色器

在OpenGL ES2.0 的世界,在場(chǎng)景中渲染任何一種幾何圖形,你都需要?jiǎng)?chuàng)建兩個(gè)稱之為“著色器”的小程序。

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

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

·Vertex shaders – 在你的場(chǎng)景中,每個(gè)頂點(diǎn)都需要調(diào)用的程序,稱為“頂點(diǎn)著色器”。假如你在渲染一個(gè)簡(jiǎn)單的場(chǎng)景:一個(gè)長(zhǎng)方形,每個(gè)角只有一個(gè)頂點(diǎn)。于是vertex shader 會(huì)被調(diào)用四次。它負(fù)責(zé)執(zhí)行:諸如燈光、幾何變換等等的計(jì)算。得出最終的頂點(diǎn)位置后,為下面的片段著色器提供必須的數(shù)據(jù)。

·Fragment shaders – 在你的場(chǎng)景中,大概每個(gè)像素都會(huì)調(diào)用的程序,稱為“片段著色器”。在一個(gè)簡(jiǎn)單的場(chǎng)景,也是剛剛說(shuō)到的長(zhǎng)方形。這個(gè)長(zhǎng)方形所覆蓋到的每一個(gè)像素,都會(huì)調(diào)用一次fragment shader。片段著色器的責(zé)任是計(jì)算燈光,以及更重要的是計(jì)算出每個(gè)像素的最終顏色。

下面我們通過(guò)簡(jiǎn)單的例子來(lái)說(shuō)明。

打開你的xcode,F(xiàn)ile\New\New File… 選擇iOS\Other\Empty, 點(diǎn)擊下一步。命名為:

SimpleVertex.glsl 點(diǎn)擊保存。

打開這個(gè)文件,加入下面的代碼:

  1. attribute vec4 Position; // 1 
  2. attribute vec4 SourceColor; // 2 
  3.   
  4. varying vec4 DestinationColor; // 3 
  5.   
  6. void main(void) { // 4 
  7.     DestinationColor = SourceColor; // 5 
  8.     gl_Position = Position; // 6 

我們一行一行解析:

1 “attribute”聲明了這個(gè)shader會(huì)接受一個(gè)傳入變量,這個(gè)變量名為“Position”。在后面的代碼中,你會(huì)用它來(lái)傳入頂點(diǎn)的位置數(shù)據(jù)。這個(gè)變量的類型是“vec4”,表示這是一個(gè)由4部分組成的矢量。

2 與上面同理,這里是傳入頂點(diǎn)的顏色變量。

3 這個(gè)變量沒(méi)有“attribute”的關(guān)鍵字。表明它是一個(gè)傳出變量,它就是會(huì)傳入片段著色器的參數(shù)。“varying”關(guān)鍵字表示,依據(jù)頂點(diǎn)的顏色,平滑計(jì)算出頂點(diǎn)之間每個(gè)像素的顏色。

文字比較難懂,我們一圖勝千言:

圖中的一個(gè)像素,它位于紅色和綠色的頂點(diǎn)之間,準(zhǔn)確地說(shuō),這是一個(gè)距離上面頂點(diǎn)55/100,距離下面頂點(diǎn)45/100的點(diǎn)。所以通過(guò)過(guò)渡,能確定這個(gè)像素的顏色。

4 每個(gè)shader都從main開始– 跟C一樣嘛。

5 設(shè)置目標(biāo)顏色 = 傳入變量:SourceColor

6 gl_Position 是一個(gè)內(nèi)建的傳出變量。這是一個(gè)在 vertex shader中必須設(shè)置的變量。這里我們直接把gl_Position = Position; 沒(méi)有做任何邏輯運(yùn)算。

一個(gè)簡(jiǎn)單的vertex shader 就是這樣了,接下來(lái)我們?cè)賱?chuàng)建一個(gè)簡(jiǎn)單的fragment shader。

新建一個(gè)空白文件:

File\New\New File… 選擇iOS\Other\Empty

命名為:SimpleFragment.glsl 保存。

打開這個(gè)文件,加入以下代碼:

  1. varying lowp vec4 DestinationColor; // 1 
  2.   
  3. void main(void) { // 2 
  4.     gl_FragColor = DestinationColor; // 3 

下面解析:

1 這是從vertex shader中傳入的變量,這里和vertex shader定義的一致。而額外加了一個(gè)關(guān)鍵字:lowp。在fragment shader中,必須給出一個(gè)計(jì)算的精度。出于性能考慮,總使用最低精度是一個(gè)好習(xí)慣。這里就是設(shè)置成最低的精度。如果你需要,也可以設(shè)置成medp或者h(yuǎn)ighp.

2 也是從main開始嘛

3 正如你在vertex shader中必須設(shè)置gl_Position, 在fragment shader中必須設(shè)置gl_FragColor.

這里也是直接從 vertex shader中取值,先不做任何改變。

還可以吧?接下來(lái)我們開始運(yùn)用這些shader來(lái)創(chuàng)建我們的app。

編譯 Vertex shader 和 Fragment shader

目前為止,xcode僅僅會(huì)把這兩個(gè)文件copy到application bundle中。我們還需要在運(yùn)行時(shí)編譯和運(yùn)行這些shader。

你可能會(huì)感到詫異。為什么要在app運(yùn)行時(shí)編譯代碼?

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

下面開始加入動(dòng)態(tài)編譯的代碼,打開OpenGLView.m

在initWithFrame: 方法上方加入:

  1. - (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType { 
  2.   
  3.     // 1 
  4.     NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName  
  5.         ofType:@"glsl"]; 
  6.     NSError* error; 
  7.     NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath  
  8.         encoding:NSUTF8StringEncoding error:&error]; 
  9.     if (!shaderString) { 
  10.         NSLog(@"Error loading shader: %@", error.localizedDescription); 
  11.         exit(1); 
  12.     } 
  13.   
  14.     // 2 
  15.     GLuint shaderHandle = glCreateShader(shaderType);     
  16.   
  17.     // 3 
  18. constchar* shaderStringUTF8 = [shaderString UTF8String];     
  19.     int shaderStringLength = [shaderString length]; 
  20.     glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength); 
  21.   
  22.     // 4 
  23.     glCompileShader(shaderHandle); 
  24.   
  25.     // 5 
  26.     GLint compileSuccess; 
  27.     glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); 
  28.     if (compileSuccess == GL_FALSE) { 
  29.         GLchar messages[256]; 
  30.         glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); 
  31.         NSString *messageString = [NSString stringWithUTF8String:messages]; 
  32.         NSLog(@"%@", messageString); 
  33.         exit(1); 
  34.     } 
  35.   
  36.     return shaderHandle; 
  37.   

下面解析:

1 這是一個(gè)UIKit編程的標(biāo)準(zhǔn)用法,就是在NSBundle中查找某個(gè)文件。大家應(yīng)該熟悉了吧。

2 調(diào)用 glCreateShader來(lái)創(chuàng)建一個(gè)代表shader 的OpenGL對(duì)象。這時(shí)你必須告訴OpenGL,你想創(chuàng)建 fragment shader還是vertex shader。所以便有了這個(gè)參數(shù):shaderType

3 調(diào)用glShaderSource ,讓OpenGL獲取到這個(gè)shader的源代碼。(就是我們寫的那個(gè))這里我們還把NSString轉(zhuǎn)換成C-string

4 最后,調(diào)用glCompileShader 在運(yùn)行時(shí)編譯shader

5 大家都是程序員,有程序的地方就會(huì)有fail。有程序員的地方必然會(huì)有debug。如果編譯失敗了,我們必須一些信息來(lái)找出問(wèn)題原因。 glGetShaderiv 和 glGetShaderInfoLog  會(huì)把error信息輸出到屏幕。(然后退出)

我們還需要一些步驟來(lái)編譯vertex shader 和frament shader。

- 把它們倆關(guān)聯(lián)起來(lái)

- 告訴OpenGL來(lái)調(diào)用這個(gè)程序,還需要一些指針什么的。

在compileShader: 方法下方,加入這些代碼

  1. - (void)compileShaders { 
  2.   
  3.     // 1 
  4.     GLuint vertexShader = [self compileShader:@"SimpleVertex"  
  5.         withType:GL_VERTEX_SHADER]; 
  6.     GLuint fragmentShader = [self compileShader:@"SimpleFragment"  
  7.         withType:GL_FRAGMENT_SHADER]; 
  8.   
  9.     // 2 
  10.     GLuint programHandle = glCreateProgram(); 
  11.     glAttachShader(programHandle, vertexShader); 
  12.     glAttachShader(programHandle, fragmentShader); 
  13.     glLinkProgram(programHandle); 
  14.   
  15.     // 3 
  16.     GLint linkSuccess; 
  17.     glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess); 
  18.     if (linkSuccess == GL_FALSE) { 
  19.         GLchar messages[256]; 
  20.         glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]); 
  21.         NSString *messageString = [NSString stringWithUTF8String: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); 

下面是解析:

1       用來(lái)調(diào)用你剛剛寫的動(dòng)態(tài)編譯方法,分別編譯了vertex shader 和 fragment shader

2       調(diào)用了glCreateProgram glAttachShader  glLinkProgram 連接 vertex 和 fragment shader成一個(gè)完整的program。

3       調(diào)用 glGetProgramiv  lglGetProgramInfoLog 來(lái)檢查是否有error,并輸出信息。

4       調(diào)用 glUseProgram  讓OpenGL真正執(zhí)行你的program

5       最后,調(diào)用 glGetAttribLocation 來(lái)獲取指向 vertex shader傳入變量的指針。以后就可以通過(guò)這寫指針來(lái)使用了。還有調(diào)用 glEnableVertexAttribArray來(lái)啟用這些數(shù)據(jù)。(因?yàn)槟J(rèn)是 disabled的。)

最后還有兩步:

1 在 initWithFrame方法里,在調(diào)用render之前要加入這個(gè):

  1. [self compileShaders]; 

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

  1. GLuint _positionSlot; 
  2. GLuint _colorSlot; 

編譯!運(yùn)行!

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

為這個(gè)簡(jiǎn)單的長(zhǎng)方形創(chuàng)建 Vertex Data!

在這里,我們打算在屏幕上渲染一個(gè)正方形,如下圖:

在你用OpenGL渲染圖形的時(shí)候,時(shí)刻要記住一點(diǎn),你只能直接渲染三角形,而不是其它諸如矩形的圖形。所以,一個(gè)正方形需要分開成兩個(gè)三角形來(lái)渲染。

圖中分別是頂點(diǎn)(0,1,2)和頂點(diǎn)(0,2,3)構(gòu)成的三角形。

OpenGL ES2.0的一個(gè)好處是,你可以按你的風(fēng)格來(lái)管理頂點(diǎn)。

打開OpenGLView.m文件,創(chuàng)建一個(gè)純粹的C結(jié)構(gòu)以及一些array來(lái)跟蹤我們的矩形信息,如下:

  1. typedef struct { 
  2.     float Position[3]; 
  3.     float Color[4]; 
  4. } Vertex; 
  5.   
  6. const Vertex Vertices[] = { 
  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. const GLubyte Indices[] = { 
  14.      0, 1, 2, 
  15.      2, 3, 0 
  16. }; 

這段代碼的作用是:

1 一個(gè)用于跟蹤所有頂點(diǎn)信息的結(jié)構(gòu)Vertex (目前只包含位置和顏色。)

2 定義了以上面這個(gè)Vertex結(jié)構(gòu)為類型的array。

3 一個(gè)用于表示三角形頂點(diǎn)的數(shù)組。

數(shù)據(jù)準(zhǔn)備好了,我們來(lái)開始把數(shù)據(jù)傳入OpenGL

創(chuàng)建Vertex Buffer 對(duì)象

傳數(shù)據(jù)到OpenGL的話,最好的方式就是用Vertex Buffer對(duì)象。

基本上,它們就是用于緩存頂點(diǎn)數(shù)據(jù)的OpenGL對(duì)象。通過(guò)調(diào)用一些function來(lái)把數(shù)據(jù)發(fā)送到OpenGL-land。(是指OpenGL的畫面?)

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

下面我們?cè)趇nitWithFrame中,加入一些代碼:

  1. [self setupVBOs]; 

下面是定義這個(gè)setupVBOs:

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

如你所見,其實(shí)很簡(jiǎn)單的。這其實(shí)是一種之前也用過(guò)的模式(pattern)。

glGenBuffers - 創(chuàng)建一個(gè)Vertex Buffer 對(duì)象

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

glBufferData – 把數(shù)據(jù)傳到OpenGL-land

想起哪里用過(guò)這個(gè)模式嗎?要不再回去看看frame buffer那一段? 

萬(wàn)事俱備,我們可以通過(guò)新的shader,用新的渲染方法來(lái)把頂點(diǎn)數(shù)據(jù)畫到屏幕上。

用這段代碼替換掉之前的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.     [_context presentRenderbuffer:GL_RENDERBUFFER]; 

1       調(diào)用glViewport 設(shè)置UIView中用于渲染的部分。這個(gè)例子中指定了整個(gè)屏幕。但如果你希望用更小的部分,你可以更變這些參數(shù)。

2       調(diào)用glVertexAttribPointer來(lái)為vertex shader的兩個(gè)輸入?yún)?shù)配置兩個(gè)合適的值。

第二段這里,是一個(gè)很重要的方法,讓我們來(lái)認(rèn)真地看看它是如何工作的:

·第一個(gè)參數(shù),聲明這個(gè)屬性的名稱,之前我們稱之為glGetAttribLocation

·第二個(gè)參數(shù),定義這個(gè)屬性由多少個(gè)值組成。譬如說(shuō)position是由3個(gè)float(x,y,z)組成,而顏色是4個(gè)float(r,g,b,a)

·第三個(gè),聲明每一個(gè)值是什么類型。(這例子中無(wú)論是位置還是顏色,我們都用了GL_FLOAT)

·第四個(gè),嗯……它總是false就好了。

·第五個(gè),指 stride 的大小。這是一個(gè)種描述每個(gè) vertex數(shù)據(jù)大小的方式。所以我們可以簡(jiǎn)單地傳入 sizeof(Vertex),讓編譯器計(jì)算出來(lái)就好。

·最好一個(gè),是這個(gè)數(shù)據(jù)結(jié)構(gòu)的偏移量。表示在這個(gè)結(jié)構(gòu)中,從哪里開始獲取我們的值。Position的值在前面,所以傳0進(jìn)去就可以了。而顏色是緊接著位置的數(shù)據(jù),而position的大小是3個(gè)float的大小,所以是從 3 * sizeof(float) 開始的。

回來(lái)繼續(xù)說(shuō)代碼,第三點(diǎn):

3       調(diào)用glDrawElements ,它最后會(huì)在每個(gè)vertex上調(diào)用我們的vertex shader,以及每個(gè)像素調(diào)用fragment shader,最終畫出我們的矩形。

它也是一個(gè)重要的方法,我們來(lái)仔細(xì)研究一下:

·第一個(gè)參數(shù),聲明用哪種特性來(lái)渲染圖形。有GL_LINE_STRIP 和 GL_TRIANGLE_FAN。然而GL_TRIANGLE是最常用的,特別是與VBO 關(guān)聯(lián)的時(shí)候。

·第二個(gè),告訴渲染器有多少個(gè)圖形要渲染。我們用到C的代碼來(lái)計(jì)算出有多少個(gè)。這里是通過(guò)個(gè) array的byte大小除以一個(gè)Indice類型的大小得到的。

·第三個(gè),指每個(gè)indices中的index類型

·最后一個(gè),在官方文檔中說(shuō),它是一個(gè)指向index的指針。但在這里,我們用的是VBO,所以通過(guò)index的array就可以訪問(wèn)到了(在GL_ELEMENT_ARRAY_BUFFER傳過(guò)了),所以這里不需要.

編譯運(yùn)行的話,你就可以看到這個(gè)畫面喇。

你可能會(huì)疑惑,為什么這個(gè)長(zhǎng)方形剛好占滿整個(gè)屏幕。在缺省狀態(tài)下,OpenGL的“camera”位于(0,0,0)位置,朝z軸的正方向。

當(dāng)然,后面我們會(huì)講到projection(投影)以及如何控制camera。

增加一個(gè)投影

為了在2D屏幕上顯示3D畫面,我們需要在圖形上做一些投影變換,所謂投影就是下圖這個(gè)意思:

基本上,為了模仿人類的眼球原理。我們?cè)O(shè)置一個(gè)遠(yuǎn)平面和一個(gè)近平面,在兩個(gè)平面之前,離近平面近的圖像,會(huì)因?yàn)楸豢s小了而顯得變??;而離遠(yuǎn)平面近的圖像,也會(huì)因此而變大。

打開SimpleVertex.glsl,做一下修改:

  1. // Add right before the main 
  2. uniform mat4 Projection; 
  3.   
  4. // Modify gl_Position line as follows 
  5. gl_Position = Projection * Position; 

這里我們?cè)黾恿艘粋€(gè)叫做projection的傳入變量。uniform 關(guān)鍵字表示,這會(huì)是一個(gè)應(yīng)用于所有頂點(diǎn)的常量,而不是會(huì)因?yàn)轫旤c(diǎn)不同而不同的值。

mat4 是 4X4矩陣的意思。然而,Matrix math是一個(gè)很大的課題,我們不可能在這里解析。所以在這里,你只要認(rèn)為它是用于放大縮小、旋轉(zhuǎn)、變形就好了。

Position位置乘以Projection矩陣,我們就得到最終的位置數(shù)值。

無(wú)錯(cuò),這就是一種被稱之“線性代數(shù)”的東西。我在大學(xué)時(shí)期后,早就忘大部分了。

其實(shí)數(shù)學(xué)也只是一種工具,而這種工具已經(jīng)由前面的才子解決了,我們知道怎么用就好。

Bill Hollings,cocos3d的作者。他編寫了一個(gè)完整的3D特性框架,并整合到cocos2d中。(作者:可能有一天我也會(huì)弄一個(gè)3D的教程)無(wú)論任何,Cocos3d包含了Objective-C的向量和矩陣庫(kù),所以我們可以很好地應(yīng)用到這個(gè)項(xiàng)目中。

這里,http://d1xzuxjlafny7l.cloudfront.net/downloads/Cocos3DMathLib.zip

有一個(gè)zip文件,(作者:我移除了一些不必要的依賴)下載并copy到你的項(xiàng)目中。記得選上:“Copy items into destination group’s folder (if needed)” 點(diǎn)擊Finish。

在OpenGLView.h 中加入一個(gè)實(shí)例變量:

  1. GLuint _projectionUniform; 

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

  1. // Add to top of file 
  2. #import "CC3GLMatrix.h" 
  3.   
  4. // Add to bottom of compileShaders 
  5. _projectionUniform = glGetUniformLocation(programHandle, "Projection"); 
  6.   
  7. // Add to render, right before the call to glViewport 
  8. CC3GLMatrix *projection = [CC3GLMatrix matrix]; 
  9. float h =4.0f* self.frame.size.height / self.frame.size.width; 
  10. [projection populateFromFrustumLeft:-2 andRight:2 andBottom:-h/2 andTop:h/2 andNear:4 andFar:10]; 
  11. glUniformMatrix4fv(_projectionUniform, 1, 0, projection.glMatrix); 
  12.   
  13. // Modify vertices so they are within projection near/far planes 
  14. const Vertex Vertices[] = { 
  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. }; 

·通過(guò)調(diào)用  glGetUniformLocation 來(lái)獲取在vertex shader中的Projection輸入變量

·然后,使用math library來(lái)創(chuàng)建投影矩陣。通過(guò)這個(gè)讓你指定坐標(biāo),以及遠(yuǎn)近屏位置的方式,來(lái)創(chuàng)建矩陣,會(huì)讓事情比較簡(jiǎn)單。

·你用來(lái)把數(shù)據(jù)傳入到vertex shader的方式,叫做 glUniformMatrix4fv. 這個(gè)CC3GLMatrix類有一個(gè)很方便的方法 glMatrix,來(lái)把矩陣轉(zhuǎn)換成OpenGL的array格式。

·最后,把之前的vertices數(shù)據(jù)修改一下,讓z坐標(biāo)為-7. 

編譯后運(yùn)行,你應(yīng)該可以看到一個(gè)稍稍有點(diǎn)距離的正方形了。

嘗試移動(dòng)和旋轉(zhuǎn)吧

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

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

在前面,我們修改了應(yīng)用到投影矩陣的vertex array來(lái)達(dá)到移動(dòng)圖形的目的。何不試一下,做一個(gè)變形、放大縮小、旋轉(zhuǎn)的矩陣來(lái)應(yīng)用?我們稱之為“model-view”變換。

再回到 SimpleVertex.glsl

  1. // Add right after the Projection uniform 
  2. uniform mat4 Modelview; 
  3.   
  4. // Modify the gl_Position line 
  5. gl_Position = Projection * Modelview * Position; 

就是又加了一個(gè) Uniform的矩陣而已。順便把它應(yīng)用到gl_Position當(dāng)中。

然后到 OpenGLView.h中加上一個(gè)變量:

  1. GLuint _modelViewUniform; 

到OpenGLView.m中修改:

  1. // Add to end of compileShaders 
  2. _modelViewUniform = glGetUniformLocation(programHandle, "Modelview"); 
  3.   
  4. // Add to render, right before call to glViewport 
  5. CC3GLMatrix *modelView = [CC3GLMatrix matrix]; 
  6. [modelView populateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()), 0, -7)]; 
  7. glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix); 
  8.   
  9. // Revert vertices back to z-value 0 
  10. const Vertex Vertices[] = { 
  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. }; 

·獲取那個(gè)model view uniform的傳入變量

·使用cocos3d math庫(kù)來(lái)創(chuàng)建一個(gè)新的矩陣,在變換中裝入矩陣。

·變換是在z軸上移動(dòng)-7,而為什么sin(當(dāng)前時(shí)間) 呢?

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

·把vertex 結(jié)構(gòu)改回去,把z坐標(biāo)設(shè)回0.

編譯運(yùn)行,就算我們把z設(shè)回0,也可以看到這個(gè)位于中間的正方形了。

什么?一動(dòng)不動(dòng)的?

當(dāng)然了,我們只是調(diào)用了一次render方法。

接下來(lái),我們?cè)诿恳粠颊{(diào)用一次看看。

渲染和 CADisplayLink

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

幸運(yùn)的是,Apple為我們提供了一個(gè)CADisplayLink的類。這個(gè)很好用的,馬上就用吧。

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

  1. // Add new method before init 
  2. - (void)setupDisplayLink { 
  3.     CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)]; 
  4.     [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];     
  5.   
  6. // Modify render method to take a parameter 
  7. - (void)render:(CADisplayLink*)displayLink { 
  8.   
  9. // Remove call to render in initWithFrame and replace it with the following 
  10. [self setupDisplayLink]; 

這就行了,有CADisplayLink在每一幀都調(diào)用你的render方法,我們的圖形看起身就好似被sin()周期地變型了?,F(xiàn)在這個(gè)方塊會(huì)前前后后地來(lái)回移動(dòng)。

不費(fèi)功夫地旋轉(zhuǎn)

讓圖形旋轉(zhuǎn)起來(lái),才算得上有型。

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

  1. float _currentRotation; 

在OpenGLView.m的render中,在populateFromTranslation的調(diào)用后面加上:

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

·添加了一個(gè)叫_currentRotation的float,每秒會(huì)增加90度。

·通過(guò)修改那個(gè)model view矩陣(這里相當(dāng)于一個(gè)用于變型的矩陣),增加旋轉(zhuǎn)。

·旋轉(zhuǎn)在x、y軸上作用,沒(méi)有在z軸的。

編譯運(yùn)行,你會(huì)看到一個(gè)很有型的翻轉(zhuǎn)的3D效果。

不費(fèi)功夫地變成3D方塊?

之前的只能算是2.5D,因?yàn)樗€只是一個(gè)會(huì)旋轉(zhuǎn)的面而已?,F(xiàn)在我們把它改造成3D的。

把之前的vertices、indices數(shù)組注釋掉吧。

然后加上新的:

  1. const Vertex Vertices[] = { 
  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. const GLubyte Indices[] = { 
  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. }; 

編譯運(yùn)行,你會(huì)看到一個(gè)方塊了。

 但這個(gè)方塊有時(shí)候讓人覺(jué)得假,因?yàn)槟憧梢钥吹椒綁K里面。

這里還有一個(gè)叫做 depth testing(深度測(cè)試)的功能,啟動(dòng)它,OpenGL就可以跟蹤在z軸上的像素。這樣它只會(huì)在那個(gè)像素前方?jīng)]有東西時(shí),才會(huì)繪畫這個(gè)像素。

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

  1. GLuint _depthRenderBuffer;

在OpenGLView.m:

  1. // Add new method right after setupRenderBuffer 
  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. // Add to end of setupFrameBuffer 
  8. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer); 
  9.   
  10. // In the render method, replace the call to glClear with the following 
  11. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
  12. glEnable(GL_DEPTH_TEST); 
  13.   
  14. // Add to initWithFrame, right before call to setupRenderBuffer 
  15. [self setupDepthBuffer]; 

·setupDepthBuffer方法創(chuàng)建了一個(gè)depth buffer。這個(gè)與前面的render/color buffer類似,不再重復(fù)了。值得注意的是,這里使用了glRenderbufferStorage, 然不是context的renderBufferStorage(這個(gè)是在OpenGL的view中特別為color render buffer而設(shè)的)。

·接著,我們調(diào)用glFramebufferRenderbuffer,來(lái)關(guān)聯(lián)depth buffer和render buffer。還記得,我說(shuō)過(guò)frame buffer中儲(chǔ)存著很多種不同的buffer?這正是一個(gè)新的buffer。

·在render方法中,我們?cè)诿看蝩pdate時(shí)都清除深度buffer,并啟用depth  testing。

編譯運(yùn)行,看看這個(gè)教程最后的效果。

一個(gè)選擇的立方塊,用到了OpenGL ES2.0。

何去何從?

這里有本教程的完整源代碼

這只是OpenGL的一篇引導(dǎo)教程,希望能讓你輕松地入門。

對(duì)了,我寫這篇教程的原因是它在過(guò)去的數(shù)周中得票最高。謝謝大家的關(guān)注,并繼續(xù)在今后為自己感興趣的題目投上一票 ---- 我們每周都有一個(gè)新教程的。

原文:http://www.cnblogs.com/andyque

責(zé)任編輯:閆佳明 來(lái)源: cnblogs
相關(guān)推薦

2014-04-29 14:16:54

2014-04-24 11:16:00

OpenGL ES 2入門

2014-04-29 14:27:59

OpenGL ES 2Android繪制紋理

2014-04-24 14:00:35

OpenGL ES 2編程

2010-02-14 15:27:25

2013-04-26 10:26:08

2011-08-02 17:58:09

iPhone開發(fā) 事件

2014-04-29 14:49:37

OpenGL ES 2Android應(yīng)用投影

2013-09-02 15:46:06

OpenGLWindows

2020-11-17 08:43:20

ElasticSear

2013-09-26 14:09:31

iOS開發(fā)OpenGL ES教程繪制矩形

2014-04-24 13:35:11

OpenGL ES2.iOSAndroid

2011-08-19 14:14:14

iPhone應(yīng)用

2013-07-05 14:45:05

AndroidOpenGL ES開發(fā)

2010-06-09 16:13:23

Windows Pho

2011-07-25 16:21:22

Sencha touc

2017-07-04 12:26:14

ARARKit

2014-04-29 14:05:02

OpenGL ESAndroid添加動(dòng)作

2017-07-19 15:25:16

iOS開發(fā)ARKitOpen GL

2022-08-02 08:01:09

開發(fā)插件Chrome前端技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)