nsis error(nsis error opening zip file)

教程截图:OpenGL ES是一个底层API,可以在iphone上实现2D和3D图形编程。如果你之前接触过cocos2d、sparrow、corona、unit

教程截图:

nsis error(nsis error opening zip file)插图

OpenGL ES是一个底层API,可以在iphone上实现2D和3D图形编程。

如果你之前接触过cocos2d、sparrow、corona、unity这些框架,你会发现它们都是基于OpenGL创建的。

大多数程序员选择使用这些框架,而不是直接调用OpenGL,因为OpenGL太难用了。

而这篇教程是为了让你更好的入门而写的。

在这一系列文章中,你可以通过一些实用易用的实验,创建一个类似hello world的APP。比如显示一些简单的三维图形。

流程大致如下:

创建一个简单的OpenGL应用

编译和运行顶点和片段着色器

通过顶点缓冲在屏幕上呈现一个简单的矩形。

使用投影和模型视图变形。

渲染可以进行深度测试的3D对象。

描述:

我不是OpenGL专家,但都是自学的。如果你发现有错,请指出来。

OpenGL ES1.0和OpenGL ES2.0

你首先要搞清楚的是OpenGL ES 1.0和2.0的区别。

他们有多大不同?我只能说他们很不一样。

OpenGL ES1.0:

对于固定的流水线硬件,可以通过它的内置函数来设置灯光、顶点、颜色、摄像头等东西。

OpenGL ES2.0:

对于可编程流水线硬件来说,内置函数可以基于这种设计去死,但同时,任何函数都得自己写。

“TMD”,你可能会这么想。有没有可能我还想用2.0?

但是2.0确实可以做一些1.0做不到的很酷的事情,比如卡通着色器。

使用opengles2.0,您甚至可以创建以下酷炫的灯光和阴影效果:

OpenGL ES2.0只能在iphone 3GS+、iPod Touch 3G+和所有版本的ipad上运行。好在现在大部分用户都在这个范围内。

我们开始吧

虽然Xcode自带OpenGL ES项目模板,但是这个模板自己创建了很多代码,会让初学者很困惑。

所以,我们自己写就行了。一步一步写下来,可以更清楚的了解它的工作机理。

启动Xcode,新建一个项目——选择基于窗口的应用,让我们从头开始。

单击Next,将这个项目命名为HelloOpenGL,单击Next,选择存储目录,然后单击Create。

CMD+R,构建并运行.您将看到一个空白屏。

如您所见,基于窗口的模板创建了一个没有视图、没有视图控制器或其他任何东西的项目。它只包含一个必需的UIWindow。

文件/新建文件,新建一个文件:选择iOS \ Cocoa Touch \ Objective-C Class,点击下一步。

选择子类UIView,单击next,将其命名为OpenGLView.m,然后单击save。

接下来,你要在这个OpenGLView.m文件下添加很多代码。

1)添加必要的框架。

add:opengles . framework和QuartzCore.framework

在groups & Under目录下,选择目标“HelloOpenGL”并展开Link Binary with Libraries部分。这是项目中使用的框架。

“+”添加,选择OpenGLES.framework,再次添加QuartzCore.framework。

2)修改OpenGLView.h

如下:介绍OpenGL的头文件,创建一些后面会用到的实例变量。

#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)将图层类别设置为CAEAGLLayer

+ (Class)layerClass { return [CAEAGLLayer class];}

4)设置图层为不透明。

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

5)创建OpenGL上下文

- (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绘制的信息。这类似于核心图形上下文。

当您创建一个上下文时,您必须声明您想要使用哪个版本的API。在这里,我们选择OpenGL ES 2.0。

(容错处理,如果创建失败,我们的程序将退出)

6)创建渲染缓冲区(render buffer)

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

渲染缓冲区是OpenGL的一个对象,用来存储渲染图像。

有时你会发现渲染缓冲区会被引用为颜色缓冲区,因为本质上它是为显示而存储的颜色。

创建渲染缓冲区的三个步骤:

1.调用glGenRenderbuffers创建新的渲染缓冲区对象。这里,返回一个唯一的整数来标记渲染缓冲区(这里,这个唯一的值被赋给_ colorrendbuffer)。有时你会发现这个唯一的值被用作程序中一个OpenGL的名称。(反正就这一个)

2.调用glBinderBuffer,告诉这个OpenGL:我后面引用GL_RENDERBUFFER的地方,其实是想用_ colorrendBuffer。其实就是告诉OpenGL我们定义的缓冲对象属于哪个OpenGL对象。

3.最后,为渲染缓冲区分配空空间。渲染缓冲存储

7)创建一个帧缓冲器。

- (void)setupFrameBuffer { GLuint framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer); }

帧缓冲区也是OpenGL的一个对象,它包含前面提到的渲染缓冲区,以及后面将要讨论的其他缓冲区,如深度缓冲区、模板缓冲区和累积缓冲区。

前两步中创建帧缓冲区的操作与创建渲染缓冲区的操作非常相似。(反正是glBind什么的)

最后一步,glFramebufferRenderbuffer,是新的。它允许您将先前创建的缓冲区渲染附加到帧缓冲区的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];}

为了尽快在屏幕上显示一些东西,在我们处理那些顶点和着色器之前,清理屏幕并显示另一种颜色。(RGB 0,104,55,绿色条)

这里每种RGB颜色的范围是0~1,所以每种都要除以255。

下面我们来分析一下每个动作:

1.调用glClearColor并设置RGB颜色和透明度。接下来,整个屏幕将充满这种颜色。

2.调用glClear来执行这个“填色”动作(很可能是photoshop的油桶)。记住前面提到的缓冲区有很多。这里我们需要使用GL_COLOR_BUFFER_BIT来声明要清理哪个缓冲区。

3.调用OpenGL上下文的presentRenderbuffer方法,在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 @interfaceOpenGLView* _glView; // After @interface@property (nonatomic, retain) IBOutlet OpenGLView *glView;

接下来,修改。m文件:

// At top of file@synthesize glView=_glView; // At top of application:didFinishLaunchingWithOptionsCGRect screenBounds = [[UIScreen mainScreen] bounds]; self.glView = [[[OpenGLView alloc] initWithFrame:screenBounds] autorelease];[self.window addSubview:_glView]; // In dealloc[_glView release];

如果一切顺利,您可以在屏幕上看到一个新的视图。

这就是OpenGL的世界。

添加着色器:顶点着色器和片段着色器

在OpenGL ES2.0的世界中,要渲染场景中任何种类的几何图形,都需要创建两个名为“着色器”的小程序。

着色器是用类似C语言的GLSL编写的。很高兴知道。我们就不深究了。

这个世界上有两种着色器:

顶点着色器–在你的场景中,每个顶点都需要调用一个名为“顶点着色器”的程序。假设您正在渲染一个简单的场景:一个每个角只有一个顶点的矩形。因此顶点着色器将被调用四次。它负责执行:计算,如照明,几何变换等。获得最终顶点位置后,为下面的片段着色器提供必要的数据。

片段着色器-在你的场景中,每个像素调用的程序称为“片段着色器”。简单场景下,也是刚才说的长方形。这个矩形覆盖的每个像素将调用片段着色器一次。片段着色器的职责是计算光线,更重要的是,计算每个像素的最终颜色。

我们用一个简单的例子来说明。

打开您的xcode,File\New\New File…选择iOS\Other\Empty,然后点击Next。命名为:

单击保存。

打开该文件并添加以下代码:

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

我们逐行分析:

1“属性”声明该着色器将接受一个名为“位置”的传入变量。在后面的代码中,您将使用它来传入顶点的位置数据。这个变量的类型是“vec4”,这意味着它是一个4部分向量。

和上面一样,这里是传入顶点的颜色变量。

3该变量没有“属性”关键字。指示它是一个传出变量,该变量是将传入片段着色器的参数。“varying”关键字是指根据顶点的颜色平滑计算顶点之间每个像素的颜色。

文字难懂,我们有一张图抵得上千言万语:

图形中的像素,位于红色和绿色顶点之间。准确的说是离顶顶点55/100,离底顶点45/100的点。因此,可以通过过渡来确定该像素的颜色。

4每个着色器都从main开始,就像c一样。

5设置目标颜色=传入变量:SourceColor

GL _ position是一个内置的输出变量。这是一个必须在顶点着色器中设置的变量。这里我们直接放gl _ Position = Position没有任何逻辑运算。

简单的顶点着色器就是这种情况。接下来,让我们创建一个简单的片段着色器。

创建新的空白色文件:

文件\新建\新建文件…选择iOS \其他\空

将其另存为:SimpleFragment.glsl..

打开该文件并添加以下代码:

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

以下分析:

1是从顶点着色器传入的变量,与顶点着色器的定义一致。还有一个额外的关键词:lowp。在片段着色器中,必须给出计算精度。出于性能原因,始终使用最低精度是一个好习惯。这里它被设置为最低精度。如果愿意,也可以将其设置为medp或highp。

2它也从main开始。

3正如你必须在顶点着色器中设置gl_Position一样,你也必须在片段着色器中设置gl_FragColor。

这里,这些值也是直接从顶点着色器获取的,没有做任何更改。

可以吗?接下来,我们开始使用这些着色器来创建我们的应用程序。

编译顶点着色器和片段着色器

到目前为止,xcode只将这两个文件复制到应用程序包中。我们还需要在运行时编译和运行这些着色器。

你可能会感到惊讶。为什么要在应用程序运行时编译代码?

这样做的好处是我们的着色器不需要依赖某种图形芯片。(只有这样才能跨平台)

我们开始添加动态编译的代码,打开OpenGLView.m

Add:高于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); // 3constchar* 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; glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } return shaderHandle; }

以下分析:

1是UIKit编程的一个标准用法,就是在NSBundle中找一个文件。你应该很熟悉。

2调用glCreateShader创建一个代表着色器的OpenGL对象。这时候你必须告诉OpenGL你是要创建片段着色器还是顶点着色器。所以有这个参数:shaderType

3调用glShaderSource,让OpenGL获取这个着色器的源代码。(就是我们写的那个)这里我们也把NSString转换成C-string。

最后,调用glCompileShader在运行时编译着色器。

每个人都是程序员,哪里有程序,哪里就会有失败。哪里有程序员,哪里就有调试。如果编译失败,我们需要一些信息来找出问题的原因。GlGetShaderiv和glGetShaderInfoLog会将错误信息输出到屏幕上。(然后退出)

我们仍然需要一些步骤来编译顶点着色器和帧着色器。

把它们俩关联起来告诉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用来调用你刚写的动态编译方法,分别编译顶点着色器和片段着色器。

2调用GLCreateProgram GlattachShader gllink program将顶点和片段着色器连接成一个完整的程序。

3调用glgetProgramiv lglggetPrograminBlog检查是否有错误,并输出信息。

调用glUseProgram让OpenGL真正执行你的程序

5.最后,调用glGetAttribLocation来获取由顶点着色器传入的变量的指针。以后可以用这个书写指针。还有一个对glEnableVertexAttribArray的调用来启用这些数据。(因为默认是禁用的。)

还有最后两个步骤:

1在initWithFrame方法中,在调用render之前添加以下内容:

[self compileShaders];

在OpenGLView.h的2 @interface中添加两个变量:

GLuint _positionSlot;GLuint _colorSlot;

编译!快跑!

如果还能正常看到之前的绿屏,证明你之前写的代码运行良好。

为这个简单的矩形创建顶点数据!

这里,我们打算在屏幕上呈现一个正方形,如下所示:

当你使用OpenGL渲染图形时,一定要记住你只能直接渲染三角形,而不能渲染矩形等其他图形。所以一个正方形需要分成两个三角形进行渲染。

在图中,顶点(0,1,2)和顶点(0,2,3)分别是三角形。

OpenGL ES2.0的一个优点是你可以根据自己的风格管理顶点。

打开OpenGLView.m文件,创建一个纯C结构以及一些数组来跟踪我们的矩形信息,如下所示:

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};

这段代码的作用是:

一个用于跟踪所有顶点信息的结构顶点(目前只包含位置和颜色。)

2以顶点结构作为类型定义数组。

用于表示三角形顶点的3个数组。

当数据准备好了,让我们开始将数据传输到OpenGL中。

创建顶点缓冲区对象

将数据传输到OpenGL的最好方法是使用顶点缓冲对象。

基本上,它们是用来缓存顶点数据的OpenGL对象。通过调用一些函数向OpenGL-land发送数据。(指OpenGL屏幕?)

这里有两种类型的顶点缓存——一种用于跟踪每个顶点的信息(就像我们的顶点数组),另一种用于跟踪每个三角形的索引信息(我们的索引数组)。

让我们在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); }

如你所见,这其实很简单。这其实是以前用过的模式。

创建一个顶点缓冲对象。

glbindbuffer–告诉OpenGL我们的vertexBuffer指的是GL_ARRAY_BUFFER。

glbufferdata–将数据传输到OpenGL-land

你记得这个图案用在哪里吗?你不想回去看看帧缓冲区吗?

一切准备就绪,我们可以用新的着色器和新的渲染方法在屏幕上绘制顶点数据。

用以下代码替换以前的渲染:

- (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为顶点着色器的两个输入参数配置两个合适的值。

这里的第二段是一个很重要的方法。让我们认真看看它是如何工作的:

第一个参数声明了该属性的名称,以前称为glGetAttribLocation。

第二个参数定义了这个属性包含多少个值。比方说位置由三个浮点数(x,y,z)组成,颜色由四个浮点数(r,g,b,a)组成。

第三,声明每个值是什么类型。(在这个例子中,我们对位置和颜色都使用GL_FLOAT)

第四个,嗯...如果一直是假的就好了。

第五是指步幅的大小。这是描述每个顶点的数据大小的一种方式。所以我们可以简单的传入sizeof(Vertex)让编译器计算。

最好的是这个数据结构的偏移量。指示在这个结构中从哪里开始获取我们的值。Position的值在前面,传0进去就行了。颜色是紧跟在position后面的数据,position的大小是3个float的大小,所以从3 * sizeof(float)开始。

回到代码,第三点:

3调用glDrawElements,最后会调用我们每个顶点上的顶点着色器和每个像素上的片段着色器,最后画出我们的矩形。

也是一种重要的方法。让我们仔细研究一下:

第一个参数,说明哪个特性用于渲染图形。有GL_LINE_STRIP和GL_TRIANGLE_FAN。然而,GL_TRIANGLE是最常用的,尤其是当它与VBO相关联时。

其次,告诉渲染器要渲染多少个图形。我们用C代码算出有多少。这是通过将数组的字节大小除以索引类型的大小得到的。

第三个是指每个指数中的指数类型。

最后一个,官方文件里说的,是index的指针。但是这里我们用的是VBO,所以可以通过index的数组(传入GL_ELEMENT_ARRAY_BUFFER)来访问,所以这里不需要。

如果是编译的,可以看到这个图。

你可能想知道为什么这个矩形刚好占据了整个屏幕。默认情况下,OpenGL的“相机”位于(0,0,0)位置,面向Z轴的正方向。

当然,我们后面会谈到投影和如何控制摄像机。

添加投影。

为了在2D屏幕上显示三维图像,我们需要对图形进行一些投影变换。所谓投影就是下图:

基本上是为了模仿人类眼球的原理。我们设置了一个远平面和一个近平面。在两个平面之前,靠近近平面的图像会显得更小,因为它已经被缩小了。而靠近远平面的图像也会变大。

打开SimpleVertex.glsl并进行一些修改:

// Add right before the mainuniform mat4 Projection; // Modify gl_Position line as followsgl_Position = Projection * Position;

我们在这里添加了一个名为projection的输入变量。uniform关键字表示这将是一个适用于所有顶点的常数,而不是一个随顶点不同而变化的值。

Mat4表示4X4矩阵。但是,矩阵数学是一门大学科,我们无法在这里分析。所以在这里,你只要认为它是用来放大缩小,旋转变形的就可以了。

将位置乘以投影矩阵,我们得到最终的位置值。

没看错,这就是所谓“线性代数”的东西。上了大学之后,大部分都忘了。

其实数学只是一个工具,这个工具已经被之前的天才学者解决了。我们知道如何使用它是件好事。

《cocos3d》的作者比尔·霍林斯。他写了一个完整的3D特征框架,集成到cocos2d中。(作者:也许有一天我也会得到一个3D教程)无论如何,Cocos3d包含了Objective-C的向量和矩阵库,所以我们可以很好地将其应用到这个项目中。

这里 xzuxjlafny 7 l . cloudfront . net/downloads/cocos 3d mathlib . zip

有一个zip文件,(作者:我删除了一些不必要的依赖项)下载并复制到您的项目中。请记住选择“将项目拷贝到目的群组的文件夹中(如果需要)”,然后点按“完成”。

在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 glViewportCC3GLMatrix *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 planesconst 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获取顶点着色器中的投影输入变量。

然后,使用数学库创建投影矩阵。这样,您可以指定坐标以及远近屏幕的位置来创建一个矩阵,这将使事情变得更容易。

用于将数据传输到顶点着色器的方法称为glUniformMatrix4fv。这个CC3glMatrix类有一个非常方便的方法glMatrix把矩阵转换成OpenGL的数组格式。

最后,修改前面的顶点数据,使Z坐标为-7。

编译运行后,稍微远一点应该能看到一个正方形。

试着移动和旋转。

如果你总是要修改那个顶点数组来改变图形,那就太烦人了。

这正是变换矩阵应该做的(同样,线性代数)

之前,我们修改了应用于投影矩阵的顶点数组来移动图形。为什么不试试,做一个变形、放大、缩小、旋转的矩阵来应用呢?我们称之为“模型-视图”转换。

返回到SimpleVertex.glsl

// Add right after the Projection uniformuniform mat4 Modelview; // Modify the gl_Position linegl_Position = Projection * Modelview * Position;

只不过是另一个均匀矩阵。顺便套用到gl_Position上。

然后向OpenGLView.h添加一个变量:

GLuint _modelViewUniform;

转到OpenGLView.m进行修改:

// Add to end of compileShaders_modelViewUniform = glGetUniformLocation(programHandle, "Modelview"); // Add to render, right before call to glViewportCC3GLMatrix *modelView = [CC3GLMatrix matrix];[modelView populateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()), 0, -7)];glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix); // Revert vertices back to z-value 0const 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}}};

获取模型视图统一的输入变量。

使用cocos3d数学库创建一个新矩阵,并在转换中加载该矩阵。

变换是在Z轴上移动-7,但为什么是sin(当前时间)?

哈哈,如果你还记得高中的三角函数。Sin()是从-1到1的函数。圆周率(3.14)是一个循环。如果这样做,这个函数将大约每3.14秒从-1循环到1。

将顶点结构改回来,并将z坐标设回0。

跑,就算我们把z设回0,也能看到中间这个方块。

什么?不动了?

当然,我们只调用了一次render方法。

接下来,我们每帧调用一次。

和渲染CADisplayLink。

理想情况下,我们希望OpenGL的渲染频率与屏幕刷新频率保持一致。

好在苹果给我们提供了一类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的浮动,每秒会增加90度。

通过修改模型视图矩阵(这里相当于变量矩阵),增加旋转。

旋转作用于x和y轴,但不作用于z轴。

运行,你会看到一个非常时尚的翻转3D效果。

不费吹灰之力变成3D方块?

前面那个只能算2.5D,因为只是旋转面。现在让我们把它转换成3D。

注释掉前面的顶点和索引数组。

然后添加一个新的:

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[] = { // Front0, 1, 2, 2, 3, 0, // Back4, 6, 5, 4, 7, 6, // Left2, 7, 3, 7, 6, 2, // Right0, 4, 1, 4, 1, 5, // Top6, 2, 1, 1, 6, 5, // Bottom0, 3, 7, 0, 7, 4 };

跑,你会看到一个广场。

但是这个广场有时候让人觉得很假,因为你可以看到里面。

还有一个功能叫深度测试。当你启动它时,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 setupFrameBufferglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer); // In the render method, replace the call to glClear with the followingglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glEnable(GL_DEPTH_TEST); // Add to initWithFrame, right before call to setupRenderBuffer[self setupDepthBuffer];

setupDepthBuffer方法创建深度缓冲区。这个和前面的渲染/颜色缓冲区差不多,就不赘述了。值得注意的是,这里使用的是glRenderbufferStorage,但不是context的RenderbufferStorage(这是专门为OpenGL视图中的颜色渲染缓冲区设置的)。

然后,我们调用glFramebufferRenderbuffer来关联深度缓冲和渲染缓冲。还记得我说过帧缓冲区中存储了很多不同的缓冲区吗?这是一个新的缓冲器。

在render方法中,我们在每次更新时清除深度缓冲区,并启用深度测试。

运行并查看本教程的最终效果。

使用OpenGL ES2.0的选定立方体

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

作者:美站资讯,如若转载,请注明出处:https://www.meizw.com/n/147662.html

发表回复

登录后才能评论