• OpenGL ES2.0 入门经典例子


    原文链接地址:http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial

      免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

      

      ps:非常感谢skingTree为我们提供的这篇翻译教程,感谢yy、小狼、北方加入我的教程翻译团队,谢谢你们!

    教程截图:

      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,新建项目-选择Window-based Application, 让我们从零开始。

      点击下一步,把这个项目命名为 HelloOpenGL,点击下一步,选择存放目录,点击“创建”。

      CMD+R,build and run。你会看到一个空白的屏幕。

      如你所见的,Window-based 模板创建了一个没有view、没有view controller或者其它东西的项目。它只包含了一个必须的UIWindow。

      File/New File,新建文件:选择iOSCocoa TouchObjective-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>

     

        CAEAGLLayer* _eaglLayer;

        EAGLContext* _context;

        GLuint _colorRenderBuffer;

     

    3)设置layer class  CAEAGLLayer

        return [CAEAGLLayer class];

      想要显示OpenGL的内容,你需要把它缺省的layer设置为一个特殊的layer。(CAEAGLLayer)。这里通过直接复写layerClass的方法。

    4) 设置layer为不透明(Opaque


        _eaglLayer = (CAEAGLLayer*) self.layer;

        _eaglLayer.opaque = YES;


      因为缺省的话,CALayer是透明的。而透明的层对性能负荷很大,特别是OpenGL的层。

      (如果可能,尽量都把层设置为不透明。另一个比较明显的例子是自定义tableview cell)

    5)创建OpenGL context

    - (void)setupContext {   

        EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;

        _context = [[EAGLContext alloc] initWithAPI:api];

    if (!_context) {

    );

    1);

        }

     

    if (![EAGLContext setCurrentContext:_context]) {

    );

    1);

        }

      无论你要OpenGL帮你实现什么,总需要这个 EAGLContext

      EAGLContext管理所有通过OpenGL进行draw的信息。这个与Core Graphics context类似。

      当你创建一个context,你要声明你要用哪个version的API。这里,我们选择OpenGL ES 2.0.

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

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

        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;

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


        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

    - (id)initWithFrame:(CGRect)frame

        self = [super initWithFrame:frame];

    if (self) {        

            [self setupLayer];        

            [self setupContext];                

            [self setupRenderBuffer];        

            [self setupFrameBuffer];                

            [self render];        

        }

    return self;

     

    - (void)dealloc

        [_context release];

        _context = nil;

        [super dealloc];

    10)把App DelegateOpenGLView 连接起来

      在 HelloOpenGLAppDelegate.h 中修改一下:

    #import "OpenGLView.h"

     

    OpenGLView* _glView;

     

    @property (nonatomic, retain) IBOutlet OpenGLView *glView;

      接下来修改.m文件:

    @synthesize glView=_glView;

     

    CGRect screenBounds = [[UIScreen mainScreen] bounds];    

    self.glView = [[[OpenGLView alloc] initWithFrame:screenBounds] autorelease];

    [self.window addSubview:_glView];

     

    [_glView release];

      一切顺利的话,你就能看到一个新的view在屏幕上显示。

      这里是OpenGL的世界。

    添加shaders 顶点着色器  片段着色器

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

      着色器由一个类似C的语言编写- GLSL。知道就好了,我们不深究。

      这个世界有两种着色器(Shader):

      ·Vertex shaders – 在你的场景中,每个顶点都需要调用的程序,称为“顶点着色器”。假如你在渲染一个简单的场景:一个长方形,每个角只有一个顶点。于是vertex shader 会被调用四次。它负责执行:诸如灯光、几何变换等等的计算。得出最终的顶点位置后,为下面的片段着色器提供必须的数据。

      ·Fragment shaders – 在你的场景中,大概每个像素都会调用的程序,称为“片段着色器”。在一个简单的场景,也是刚刚说到的长方形。这个长方形所覆盖到的每一个像素,都会调用一次fragment shader。片段着色器的责任是计算灯光,以及更重要的是计算出每个像素的最终颜色。

      下面我们通过简单的例子来说明。

      打开你的xcode,FileNewNew File… 选择 iOSOtherEmpty, 点击下一步。命名为:

      SimpleVertex.glsl 点击保存。

      打开这个文件,加入下面的代码:

    // 1

    // 2

     

    // 3

     

        DestinationColor = SourceColor; // 5

    // 6

      我们一行一行解析:

      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

      新建一个空白文件:

      FileNewNew File… 选择 iOSOtherEmpty

      命名为:SimpleFragment.glsl 保存。

      打开这个文件,加入以下代码:

    // 1

     

        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。

    编译 Vertex shader  Fragment shader

      目前为止,xcode仅仅会把这两个文件copy到application bundle中。我们还需要在运行时编译和运行这些shader。

      你可能会感到诧异。为什么要在app运行时编译代码?

      这样做的好处是,我们的着色器不用依赖于某种图形芯片。(这样才可以跨平台嘛)

      下面开始加入动态编译的代码,打开 OpenGLView.m

      在 initWithFrame: 方法上方加入:

     

        // 1

        NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName 

    @"glsl"];

        NSError* error;

        NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath 

            encoding:NSUTF8StringEncoding error:&error];

    if (!shaderString) {

    @"Error loading shader: %@", error.localizedDescription);

    1);

        }

     

        // 2

        GLuint shaderHandle = glCreateShader(shaderType);    

     

        // 3

    const char * shaderStringUTF8 = [shaderString UTF8String];    

    int shaderStringLength = [shaderString length];

    1, &shaderStringUTF8, &shaderStringLength);

     

        // 4

        glCompileShader(shaderHandle);

     

        // 5

        GLint compileSuccess;

        glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);

    if (compileSuccess == GL_FALSE) {

    256];

    sizeof(messages), 0, &messages[0]);

            NSString *messageString = [NSString stringWithUTF8String:messages];

    @"%@", messageString);

    1);

        }

     

    return shaderHandle;

     

      下面解析:



      1 这是一个UIKit编程的标准用法,就是在NSBundle中查找某个文件。大家应该熟悉了吧。

      2 调用 glCreateShader来创建一个代表shader 的OpenGL对象。这时你必须告诉OpenGL,你想创建 fragment shader还是vertex shader所以便有了这个参数:shaderType

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

      最后,调用glCompileShader 在运行时编译shader

      5 大家都是程序员,有程序的地方就会有fail。有程序员的地方必然会有debug。如果编译失败了,我们必须一些信息来找出问题原因。 glGetShaderiv 和 glGetShaderInfoLog  会把error信息输出到屏幕。(然后退出)

      我们还需要一些步骤来编译vertex shader 和frament shader

    - 把它们俩关联起来

    - 告诉OpenGL来调用这个程序,还需要一些指针什么的。

      在compileShader: 方法下方,加入这些代码

     

        // 1

    @"SimpleVertex" 

            withType:GL_VERTEX_SHADER];

    @"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) {

    256];

    sizeof(messages), 0, &messages[0]);

            NSString *messageString = [NSString stringWithUTF8String:messages];

    @"%@", messageString);

    1);

        }

     

        // 4

        glUseProgram(programHandle);

     

        // 5

    "Position");

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

      编译!运行!



      如果你仍能正常地看到之前那个绿色的屏幕,就证明你前面写的代码都很好地工作了。

    为这个简单的长方形创建 Vertex Data

      在这里,我们打算在屏幕上渲染一个正方形,如下图:

      在你用OpenGL渲染图形的时候,时刻要记住一点,你只能直接渲染三角形,而不是其它诸如矩形的图形。所以,一个正方形需要分开成两个三角形来渲染。

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

      OpenGL ES2.0的一个好处是,你可以按你的风格来管理顶点。

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

        float Position[3];

    float Color[4];

    } Vertex;

     

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

    };

     

         0, 1, 2,

    2, 3, 0

    };

      这段代码的作用是:

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

      2 定义了以上面这个Vertex结构为类型的array。

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

      数据准备好了,我们来开始把数据传入OpenGL

    创建Vertex Buffer 对象

      传数据到OpenGL的话,最好的方式就是用Vertex Buffer对象。

      基本上,它们就是用于缓存顶点数据的OpenGL对象。通过调用一些function来把数据发送到OpenGL-land。(是指OpenGL的画面?)

    这里有两种顶点缓存类型 – 一种是用于跟踪每个顶点信息的(正如我们的Vertices array),另一种是用于跟踪组成每个三角形的索引信息(我们的Indices array)。

      下面我们在initWithFrame中,加入一些代码:

    [self setupVBOs];

      下面是定义这个setupVBOs:

     

        GLuint vertexBuffer;

    1, &vertexBuffer);

        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

    sizeof(Vertices), Vertices, GL_STATIC_DRAW);

     

        GLuint indexBuffer;

    1, &indexBuffer);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

    sizeof(Indices), Indices, GL_STATIC_DRAW);

     

      如你所见,其实很简单的。这其实是一种之前也用过的模式(pattern)。

      glGenBuffers - 创建一个Vertex Buffer 对象

      glBindBuffer – 告诉OpenGL我们的vertexBuffer 是指GL_ARRAY_BUFFER

      glBufferData – 把数据传到OpenGL-land

      想起哪里用过这个模式吗?要不再回去看看frame buffer那一段? 

      万事俱备,我们可以通过新的shader,用新的渲染方法来把顶点数据画到屏幕上。

      用这段代码替换掉之前的render:

        glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);

        glClear(GL_COLOR_BUFFER_BIT);

     

        // 1

    0, 0, self.frame.size.width, self.frame.size.height);

     

        // 2

    3, GL_FLOAT, GL_FALSE, 

    sizeof(Vertex), 0);

    4, GL_FLOAT, GL_FALSE, 

    sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));

     

        // 3

    sizeof(Indices)/sizeof(Indices[0]), 

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

    uniform mat4 Projection;

     

    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文件中加上:

    #import "CC3GLMatrix.h"

     

    _projectionUniform = glGetUniformLocation(programHandle, "Projection");

     

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

     

    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

    uniform mat4 Modelview;

     

    gl_Position = Projection * Modelview * Position;

      就是又加了一个 Uniform的矩阵而已。顺便把它应用到gl_Position当中。

      然后到 OpenGLView.h中加上一个变量:

    GLuint _modelViewUniform;

      到OpenGLView.m中修改:



    _modelViewUniform = glGetUniformLocation(programHandle, "Modelview");

     

    CC3GLMatrix *modelView = [CC3GLMatrix matrix];

    [modelView populateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()), 0, -7)];

    glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix);

     

    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方法。

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

    渲染  CADisplayLink

      理想状态下,我们希望OpenGL的渲染频率跟屏幕的刷新频率一致。

      幸运的是,Apple为我们提供了一个CADisplayLink的类。这个很好用的,马上就用吧。

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



    - (void)setupDisplayLink {

        CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];

        [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];    

     

    - (void)render:(CADisplayLink*)displayLink {

     

    [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效果。




    不费功夫地变成3D方块?

      之前的只能算是2.5D,因为它还只是一个会旋转的面而已。现在我们把它改造成3D的。

      把之前的vertices、indices数组注释掉吧。

      然后加上新的:

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

    };

     

        // 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:

    - (void)setupDepthBuffer {

    1, &_depthRenderBuffer);

        glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);

        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height);    

     

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);

     

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);

     

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

    何去何从?

      这里有本教程的完整源代码

      这只是OpenGL的一篇引导教程,希望能让你轻松地入门。

      对了,我写这篇教程的原因是它在过去的数周中得票最高。谢谢大家的关注,并继续在今后为自己感兴趣的题目投上一票 ---- 我们每周都有一个新教程的。

  • 相关阅读:
    jQuery
    基于Js实现的UrlEncode和UrlDecode函数代码
    利用缓存、Timer间隔时间发送微信的实例,很有用的例子
    VisualStudio 自动排版等 快捷键
    正则表达式判断手机号码属于哪个运营商
    .Net常用方法汇总
    .NET中的三种Timer的区别和用法
    C# List和String互相转换
    Tempdb--TempDB Basic
    Tempdb--查看tempdb使用的脚本
  • 原文地址:https://www.cnblogs.com/stephen-init/p/4314700.html
Copyright © 2020-2023  润新知