zoukankan      html  css  js  c++  java
  • [ios][opengles]GLKit如何搭一个app的框架

    一个外文对GLKit的讲解:

    Beginning OpenGL ES 2.0 with GLKit Part 1

       英文原文链接:http://www.raywenderlich.com/5223/beginning-opengl-es-2-0-with-glkit-part-1

    Beginning OpenGL ES 2.0 with GLKit Part 2
        英文原文链接:http://www.raywenderlich.com/5235/beginning-opengl-es-2-0-with-glkit-part-2

    对应图片参照这个链接地址吧。


    翻译原地址:

    第一部分:http://site.douban.com/129642/widget/notes/5513129/note/197166612/


    Beginning OpenGL ES 2.0 with GLKit Part 1

       英文原文链接:http://www.raywenderlich.com/5223/beginning-opengl-es-2-0-with-glkit-part-1

     iOS 5提供了一系列新的API,简化了OpenGL的使用。
        新的API集合为GLKit。包括4个部分:
            -GLKView/GLKViewController:这些类抽取出大量的样板(boilerplate)代码,这些代码完成了OpenGL ES 项目的基本配置。
            -GLKEffects:这些类实现了OpenGL ES 1.0公共(common)的shading行为,简化(从1.0)到OpenGL ES 2.0的转化。它们也提供了让光和纹理(lighting and texturing)工作的简单方法。
            -GLMath:在iOS 5之前,每个游戏都需要它们自己的数学库,处理公共的向量和矩阵操作方法。现在有了GLMath,大多数的公共方法都可以从GLMath中获得。
            -GLKTextureLoader:这个类使得加载图像作为OpenGL使用的纹理更加简单。和写一个复杂的方法来处理大量不同的图像格式比,现在加载一个纹理只需要一个简单的方法调用。

         这篇导引的目标是在假定您没有任何相关经验的前提下,让您尽快学会通过GLKit使用OpenGL技术。我们将建立一个简单的app,在屏幕中绘制一个简单的方块,并让它旋转。
         在此过程中,你将学会使用这些新API的基础知识。无论你过去是否曾经使用过OpenGL,甚至你是一个完全的初学者,这都是一篇优秀的GLKit的介绍,
         注意这篇导引和其他的OpenGL ES 2.0导引有重复的部分。这篇导引假设你没有读过其他的导引,但是如果你已经读过,可跳过对应部分。

    OpenGL ES 1.0 vs OpenGL ES 2.0

        在我们开始之前,我必须声明这篇导引将重点讲述OpenGL ES 2.0。
        如果你刚刚接触OpenGL ES 编程,下面是OpenGL ES 1.0 和 OpenGL ES 2.0的差别:
            -OpenGL ES 1.0使用一个固定的管线,这是一个好方法(fancy way),说明你在使用内建函数设置光、顶点、颜镜头,和其他。
            -OpenGL ES 2.0使用一个可编程的管线,这是一个好方法,说明你没有使用任何内建方法,你需要自己写全部的东西。
        “OMG!”你可能会想“如果只是增加了额外的工作量,我为啥要还要用OpenGL ES 2.0?!”,尽管确实增加了额外的工作量,使用OpenGL ES 2.0你可以做出一些非常酷的效果。
         效果:见原文吧。


         OpenGL ES 2.0只有iPhone 3GS+,iPod Touch 3G+,和所有的iPad支持。但是这些设备的用户现在已经是主流了,所以值得使用。
         OpenGL ES 2.0和OpenGL ES 1.0比有更高的学习曲线,但是现在使用GLKit学习曲线将更加简单,因为GLKeffects和GLKMath APIs使你更容易做许多OpenGL ES 1.0支持的事情。
         如果你是OpenGL 编程新手,最好是直接学习OpenGL ES 2.0,而不必尝试先学习1.0,再升级到2.0,特别是现在有了GLKit。这篇导引将帮助你开始学习基础知识。 

    Getting Started

        好,让我们开始!
        在Xcode中创建一个新的项目,并选择iOSApplicationEmpty Application Template。我们选择Empty Application template(不是OpenGL Game template), so you can put everything together from scratch and get a better idea how everything fits together.(直译没想好怎么译,把原文贴着,这是我的理解:这样你可以一步一步地学习每样东西,更好地理解它们是怎样协同工作的。)
        设置产品名称HelloGLKit,确保Device Family被设置为iPhone,确保"Use Automatic Reference Counting"被选中,其他选项不选,然后点Next按钮。选择一个文件夹保存你的项目并点Create按钮。
        如果你现在运行app,你将看到一个空的window:
        
        项目此时基本不包含任何代码,但是让我们快速的看一下,how it all fits together(它们是如何协同工作的)。如果你学习过Storyboard导引,这里将是一个好的回顾。
        打开main.m,你将看到app开始运行的时候,第一个函数被调用的函数:
    int main(int argc, char *argv[])
    {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
        最后一个参数告诉UIApplication创建这个类的实例并用它作为代理,在本例中是AppDelegate。
        切换到唯一的类,AppDelegate.m,看一下app启动时调用的方法:
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds];
        // Override point for customization after application launch.
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        return YES;
    }
         通过编程的方式创建app的主window,并使其可见。好,就这样!About as “from scratch” as you can get.(这句不会译,大概是:让我们从头开始做起)

         Introducing GLKView

        开始学习OpenGL ES 2.0,第一件事我们需要做的是为window增加一个subview,该subview通过OpenGL绘制。如果你曾经编写过OpenGL ES 2.0代码,你知道有很多样板文件代码,使其工作-比如创建渲染缓冲区和帧缓冲区,等。
        但是现在可以非常方便地使用新的GLKit类调用GLKView!任何时候你想在一个view中使用OpenGL进行渲染,你只须简单的增加一个GLKView(一个普通的UIView子类)并配置一些属性。
        之后你可以设置一个类作为GLKView的代理,当需要绘制时,将会调用代理类的方法。在该方法中你可以加入你的OpenGL命令!
        让我们看一下这是如何工作的。首先第一件事-你需要增加一些框架到你的项目,才可以使用GLKit。在项目导航器中选择你的HelloGLKit项目,选择HelloGLKit目标,选择Build Phases,展开Link Binary With Libraries section,并点+按钮。从列表中,选择如下的框架,并点Add:
    QuartzCore.framework
    OpenGLES.framework
    GLKit.framework
    原文此处有截图,可参考
     
        
        切换到AppDelegate.h,在顶部import GLKit的头文件:
    #import <GLKit/GLKit.h>
        接下来切换到AppDelegate.m,并修改application:didFinishLaunchingWithOptions以增加一个GLKView作为main window的subview:
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds];

        EAGLContext * context = [EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; // 1
        GLKView *view = [GLKView alloc] initWithFrame:[UIScreen mainScreen] bounds]; // 2
        view.context = context; // 3
        view.delegate = self; // 4
        [self.window addSubview:view]; // 5

        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        return YES;
    }
        增加了标记注释的行是新增加的代码行-让我们逐行看一下。
    1. Create a OpenGL context. 创建一个OpenGL 上下文。
        使用OpenGL做任何事前,你都需要先创建一个EAGLContext。
        iOS使用OpenGL进行绘制时需要一些信息,这些信息都由EAGLContext管理。这和你使用一个Core Graphics上下文差不多。
        当你创建一个上下文时,你需要定义要使用的API版本。这里你定义成使用OpenGL ES 2.0(比如程序运行在iPhone3G上的时候)。如果获取不到,app将退出。
    2. Create a GLKView.创建一个GLKView。
        这创建了一个新的GLKView的实例,并使它和整个window一样大。
    3. Set the GLKView’s context.设置GLKView上下文。
        当你创建一个GLKView的时候,你需要告诉它要使用的OpenGL 上下文,所以配置成我们刚刚创建的context(注释1)。
    4. Set the GLKView’s delegate.设置GLKView的代理。
        这设置当前类(AppDelegate)作为GLKView的代理。这意味着无论何时该视图需要重绘时,都会调用代理的glkView:drawInRect方法。我们将在AppDelegate中马上实现这些,增加一些基本的OpenGL命令把屏幕绘制成红色。
    5. Add the GLKView as a subview.把GLKView作为subview添加。
        这行代码把GLKView作为main window的subview添加。
        
        因为我们把AppDelegate设置为GLKView的代理,所以我们需要让它实现GLKiewDelegate协议。让我们切换到AppDelegate.h并修改@interface 行如下:
    @interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate>
        还差一步!切换回AppDelegate.m,在@end之前增加如下的代码:
    #pragma mark - GLKViewDelegate

    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);

    }

        第一行调用glClearColor定义用来清理屏幕的RGB颜色和alpha(透明度)值。这里我们设置红色。
        第二行调用glClear实际执行清理操作。记住缓冲区有不同的类型,比如渲染/颜色缓冲区,以及其他我们现在还没有使用过的缓冲区,像深度或者模板缓冲区。
        就这样!编译并运行,只使用了7行代码我们就使用OpenGL对屏幕进行了渲染。
        
        如果你完全是OpenGL的新手,可能会没什么感觉,但是之前用过的,将非常高兴地发现其简单性。

    GLKView Properties and Methods

        我们这里只设置了GLKView一些属性(上下文和代理),但是我要提一下GLKView的其他属性和方法,它们后续将对你非常有用。
        这是一个可选的参考部分,对相关属性和方法进行说明。如果你想继续编码,可以跳到下一段。
        context and delegate
        我们已经在上一段中进行了讲述,这里不再重复。
        drawableColorFormat
        你的OpenGL上下文有一个缓冲区,它用以存储将在屏幕中显示的颜色。你可以使用其属性来设置缓冲区中每个像素的颜色格式。
        缺省值是GLKViewDrawableColorFormatRGBA8888,即缓冲区的每个像素使用8个bit(所以每个像素4个字节)。这非常好,因为它给了你提供了最广泛的颜色范围,让你的app看起来更好。
        但是如果你的app允许更小范围的颜色,你可以设置为GLKViewDrawableColorFormatRGB565,从而使你的app消耗更少的资源(内存和处理时间)。
        drawableDepthFormat
        你的OpenGL上下文还可以(可选地)有另一个缓冲区,称为深度缓冲区。这帮助我们确保更接近观察者的对象显示在远一些的对象的前面(意思就是离观察者近一些的对象会挡住在它后面的对象)。
        其缺省的工作方式是:OpenGL把接近观察者的对象的所有像素存储到深度缓冲区,当开始绘制一个像素时,它(OpenGL)首先检查深度缓冲区,看是否已经绘制了更接近观察者的什么东西,如果是则忽略它(要绘制的像素,就是说,在绘制一个像素之前,看看前面有没有挡着它的东西,如果有那就不用绘制了)。否则,把它增加到深度缓冲区和颜色缓冲区。
        你可以设置这个属性,以选择深度缓冲区的格式。缺省值是GLKViewDrawableDepthFormatNone,意味着完全没有深度缓冲区。
        但是如果你要使用这个属性(一般用于3D游戏),你应该选择GLKViewDrawableDepthFormat16或GLKViewDrawableDepthFormat24。这里的差别是使用GLKViewDrawableDepthFormat16将消耗更少的资源,但是当对象非常接近彼此时,你可能存在渲染问题()。
       drawableStencilFormat
       你的OpenGL上下文的另一个可选的缓冲区是stencil(模板)缓冲区。它帮助你把绘制区域限定到屏幕的一个特定部分。它还用于像影子一类的事物=比如你可以使用stencil缓冲区确保影子投射到地板。
       缺省值是GLKViewDrawableStencilFormatNone,意思是没有stencil缓冲区,但是你可以通过设置其值为GLKViewDrawableStencilFormat8(唯一的其他选项)使能它。
        drawableMultisample
        这是你可以设置的最后一个可选缓冲区,对应的GLKView属性是multisampling。如果你曾经尝试过使用OpenGL画线并关注过"锯齿壮线",multisampling就可以帮助你处理。
        Basically what it does is instead of calling the fragment shader one time per pixel, it divides up the pixel into smaller units and calls the fragment shader multiple times at smaller levels of detail. It then merges the colors returned, which often results in a much smoother look around edges of geometry.
        以前对于每个像素,都会调用一次fragment shader(片段着色器),drawableMultisample基本上替代了这个工作,它将一个像素分成更小的单元,并在更细微的层面上多次调用fragment shader。之后它将返回的颜色合并,生成更光滑的几何边缘效果。
        要小心此操作,因为它需要占用你的app的更多的处理时间和内存。缺省值是GLKViewDrawableMultisampleNone,但是你可以通过设置其值GLKViewDrawableMultisample4X为来使能它。
        drawableHeight/drawableWidth
        这些“只读”属性代表各种各样的缓冲区的整形的高度和宽度值(高度还理解,宽度啥意思,不明白。。。)。根据视图的边界和contentSize的变化-缓冲区自动调整这些值大小。
        snapshot
        这是一种从视图当前上下文中获得UIImage的便捷方法。
        bindDrawable
        OpenGL还有另一个缓冲区-帧缓冲区,这基本上是前面提到的其他缓冲区的集合(颜色,深度,stencil等缓冲区)。
        在你的glkView:drawInRect方法被调用之前,GLKit将在幕后绑定到为你配置好的帧缓冲区。但是如果你的游戏需要转到一个不同的帧缓冲区去执行其他的渲染操作(比如,你要渲染另一个纹理),你可以使用方法bindDrawable通知GLKit重新绑定回为你配置好的帧缓冲区。
        deleteDrawable
        GLKView和OpenGL为这些缓冲区使用大量的内存,如果你的GLKView不可见,你会发现在其可见之前临时释放这些内存是很有用的。如果你要这么做,就使用这个方法!
        下次视图被绘制时,GLKView将在幕后自动重新分配内存。很方便不是吗?
        enableSetNeedsDisplay and display
        我不想破坏惊喜-我们将在下一段解释这些。

    Updating the GLKView

        让我们试着定期更新我们的GLKView,就像我们在游戏中一样。我们把屏幕从红色跳转到黑色怎么样,就像红色警报一样!
        到AppDelegate.m的顶部,修改@implementation行,增加2个私有变量:
      @implementation AppDelegate {
        float _curRed;
        BOOL _increasing;
    }
        在application:didFinishLaunchingWithOptions中初始化这些变量:
    _increasing = YES;
    _curRed = 0.0;
       之后回到glkView:drawInRect方法,按照下面这样进行更新:
    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

        if (_increasing) {
            _curRed += 0.01;
        } else {
            _curRed -= 0.01;
        }
        if (_curRed >= 1.0) {
            _curRed = 1.0;
            _increasing = NO;
        }
        if (_curRed <= 0.0) {
            _curRed = 0.0;
            _increasing = YES;
        }

        glClearColor(_curRed, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);

    }
        每次glkView:drawInRect被调用时,它都根据是_increasing增加还是减少(YES/NO)来更新一点(0.01)_curRed的值。注意这段代码不是完美的,因为它没考虑调用glkView:drawInRect的时间间隔。这意味着根据调用glkView:drawInRect的速度(即多长时间调用一次),动画会时快时慢。我们后面再讨论解决这一问题的方法。
        编译并运行,等待一分钟,什么都没发生!
        缺省情况下,GLKView只在需要的时候才更行自己,比如当视图第一次显示,大小改变,或者类似的场景。然而对于游戏编程,我们经常需要重新绘制每一
        我们可以将enableSetNeedsDisplay设置为false,来关闭GLKView的这一缺省行为。之后我们可以调用GLKView的display方法来控制什么时候重新绘制。
        理想情况下我们更希望同步屏幕的刷新率和OpenGL的渲染频率。
        幸运的是,苹果提供了一种简单方法来完成这项工作,使用CADisplayLink!它非常容易使用,让我们来看看。首先增加这个import在AppDelegate.m的顶部:
        #import <QuartzCore/QuartzCore.h>
        之后,增加这些行代码到application:didFinishLaunchingWithOptions:
    view.enableSetNeedsDisplay = NO;
    CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // 加入循环
        然后增加一个新的渲染函数:
    - (void)render:(CADisplayLink*)displayLink {
        GLKView * view = [self.window.subviews objectAtIndex:0];
        [view display]; // 间接调用drawInRect

        编译并运行,你将看到一个很酷的闪动的红色警报效果!

    Introducing GLKViewController

        你理解我们前面刚刚写的代码吗?你可以忘记它了,因为有更简单的方法-使用GLKViewController。
        我先给你展示怎么使用普通的GLKView的原因是让你在使用(文艺的?哈~)GLKViewController前,先理解其工作原理。GLKViewController使你不必再写那些代码了,另外,它还增加了一些新的特性,不过这些特性必须编码才能使用。
        所以让我们来尝试一下GLKViewController吧,像这样修改你的application:didFinishLaunchingWithOptions方法:
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds];

        EAGLContext * context = [EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        GLKView *view = [GLKView alloc] initWithFrame:[UIScreen mainScreen] bounds];
        view.context = context;
        view.delegate = self;
        //[self.window addSubview:view];

        _increasing = YES;
        _curRed = 0.0;

        //view.enableSetNeedsDisplay = NO;
        //CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
        //[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

        GLKViewController * viewController = [GLKViewController alloc] initWithNibName:nil bundle:nil]; // 1
        viewController.view = view; // 2
        viewController.delegate = self; // 3
        viewController.preferredFramesPerSecond = 60; // 4
        self.window.rootViewController = viewController; // 5

        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        return YES;
    }

        您可以直接删除注释掉的行,我刚把他们注释掉,这样更容易看出哪些代码是不再需要的。这里还有4行新代码(用注视标记了):
    1. Create a GLKViewController.
        这编程创建了一个新的 GLKViewController实例。在这种情况下,没有关联XIB文件。
    2. Set the GLKViewController’s view.
         GLKViewController的root view应该是一个GLKView,所以我们把它设置成前面刚刚创建的那个。
    3. Set the GLKViewController’s delegate.
        我们设置当前类(AppDelegate)作为GLKViewController的代理,这样每一帧GLKViewController都会通知我们(游戏停止时也会通知我们),让我们有机会执行游戏逻辑(这是GLKViewController的一个不错的内建属性,我们稍后会介绍)。
        原文:We set the current class (AppDelegate) as the delegate of the GLKViewController. This means that the GLKViewController will notify us each frame so we can run game logic, or when the game pauses (a nice built-in feature of GLKViewController we’ll demonstrate later).

    4. Set the preferred FPS.
        GLKViewController在1秒内会多次调用你的draw方法,设置调用的次数来让GLKViewController知道你期待被调用的频率。当然,如果你的游戏花了很多时间对帧进行渲染,实际的调用次数将你设置的值。
        缺省值是30FPS。苹果的指导意见是把这个值设置成你的app能够稳定支持的帧率,以保持一致,看起来不卡。这个app非常简单,可以按照60FPS运行,所以我们设置成60FPS。
        和FYI一样(FYI是神马啊?),如果你想知道OS实际尝试调用你的update/draw方法的次数,可以检查只读属性framesPerSecond。
    5. Set the rootViewController. 
        我们让这个试图控制器作为第一个要显示的东西,所以我们将其作为window的rootViewController加进来。注意我们不再需要手工为window增加subview了,因为它是GLKViewController的root view。
        注意我们不再需要运行循环渲染的代码来告诉GLView刷新每一帧了-GLKViewController在后台为我们做了这一切。所以接下来要把render方法注释掉。
        别忘了我们把GLKViewController’s的代理设置为当前的类(AppDelegate),所以要实现GLKViewControllerDelegate协议。切换到AppDelegate.h文件并替换如下:
    @interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate, GLKViewControllerDelegate>
        最后一步是更新方法glkView:drawInRect,并增加GLKViewController的回调实现glkViewControllerUpdate:
    #pragma mark - GLKViewDelegate

    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

        glClearColor(_curRed, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);

    }

    #pragma mark - GLKViewControllerDelegate

    - (void)glkViewControllerUpdate:(GLKViewController *)controller {
        if (_increasing) {
            _curRed += 1.0 * controller.timeSinceLastUpdate;
        } else {
            _curRed -= 1.0 * controller.timeSinceLastUpdate;
        }
        if (_curRed >= 1.0) {
            _curRed = 1.0;
            _increasing = NO;
        }
        if (_curRed <= 0.0) {
            _curRed = 0.0;
            _increasing = YES;
        }
    }

        注意,我们把代码从draw方法移到update方法中。
        还要注意,我们把颜色的计数值由硬编码的值改为计算出的值-基于上次更新时的时间进行计算。这样可以很好地保证动画被匀速地处理,不必关心帧率的问题。(个人理解:原来是基于次数的,现在是基于时间的,基于次数的调用时间间隔是不确定的,因此颜色变化并不流畅,而基于时间的则是根据逝去时间来计算当前应显示的颜色,因此是流畅的)。
        这是GLKViewController为你做的另一件方便的事情!我们不必写特殊的代码去保存上次更新时的次数-GLKViewController已经为你保存了。还有一些基于时间的属性,我们后面再讨论。
        (先调用代理的glkViewControllerUpdate方法,然后调用drawInRect。调用频率由系统参考viewController.preferredFramesPerSecond,结合当前运行情况决定。)
    GLKViewController and Storyboards

        到目前为止,我们手工创建了GLKViewController和GLKView,因为这是向您介绍它们工作原理的简单方法。但是在实际的app中你可能不想这样做,更好的办法是利用Storyboards的力量,这样你就可以在你app体系结构的任何地方包含这个视图控制器。
        所以让我们做一点重构来完成它。首先,让我们创建一个GLKViewController的子类,来包含我们app的逻辑。所以使用 iOSCocoa TouchUIViewController子类模板创建一个新的文件,命名为HelloGLKitViewController,作为GLKViewController的子类(you can type this in even though it’s not in the dropdown-虽然下拉菜单里没有,但是你可以手工输入GLKViewController。学了个单词dropdown,即下拉菜单,呵呵)。确保Targeted和iPad and With XIB都没有选中,然后创建文件。
        打开HelloGLKitViewController.m,增加类的私有成员,包含我们需要的实例变量,增加一个存储上下文的属性。
    @interface HelloGLKitViewController () {
        float _curRed;
        BOOL _increasing;

    }
    @property (strong, nonatomic) EAGLContext *context;

    @end

    @implementation HelloGLKitViewController 
    @synthesize context = _context;
        之后实现viewDidLoad 和 viewDidUnload方法:
    - (void)viewDidLoad
    {
        [super viewDidLoad];

        self.context = [EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

        if (!self.context) {
            NSLog(@"Failed to create ES context");
        }

        GLKView *view = (GLKView *)self.view;
        view.context = self.context;
    }

    - (void)viewDidUnload
    {
        [super viewDidUnload];

        if ([EAGLContext currentContext] == self.context) {
            [EAGLContext setCurrentContext:nil];
        }
        self.context = nil;
    }
        在viewDidLoad中,我们创建了OpenGL ES 2.0上下文(和上次在App Delegate做的一样)并保存。我们的root view是一个GLKView(我们知道是因为我们在Storyboard中做了配置-就是我们需要在Storyboard中把view的类名称设置成GLKView),所以我们先做一下类型转化(cast)。之后我们将其上下文设置为OpenGL上下文。
        注意我们不必把视图控制器设置为视图的代理-GLKViewController在幕后自动完成这项工作。
        在viewDidUnload中,我们只需做反向清理工作。我们必须确保这里没有遗漏的对上下文的引用,所以我们先检查当前上下文是不是我们的上下文,如果是的话,将其设置为nil。我们还将我们的引用也设置为nil。
        在文件的底部,增加glkView:drawInRect和update回调的视线,和前面相似:
    #pragma mark - GLKViewDelegate

    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

        glClearColor(_curRed, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);

    }

    #pragma mark - GLKViewControllerDelegate

    - (void)update {
        if (_increasing) {
            _curRed += 1.0 * self.timeSinceLastUpdate;
        } else {
            _curRed -= 1.0 * self.timeSinceLastUpdate;
        }
        if (_curRed >= 1.0) {
            _curRed = 1.0;
            _increasing = NO;
        }
        if (_curRed <= 0.0) {
            _curRed = 0.0;
            _increasing = YES;
        }
    }

        注意update方法只是被命名为update,因为现在我们在GLKViewCotroller子类内部,我们只覆盖这个方法,不必再实现前面提到的GLKViewCotroller代理方法。timeSinceLastUpdate也可以直接通过self访问,不需要使用视图控制器。
        做好这些后,让我们创建Storyboard。使用iOSUser InterfaceStoryboard模板创建一个新的文件,设备族选择iPhone,并保存为MainStoryboard.storyboard。
        打开MainStoryboard.storyboard,从对象面板拽一个试图控制器到网格区。选择试图控制器,在Identity Inspector中把类设置为HelloGLKitViewController:
     此处有图,参考原文。 
        还有,在视图控制器中选择视图,在Identity Inspector中把类设置为GLKView。
        为了在启动的时候运行此Storyboard,打开HelloGLKit-Info.plist,按住ctrl,点空白区域,选择增加行。从下拉菜单中选择Main storyboard file base name,并输入MainStoryboard。
        这基本上完成了我们要做的所有事情,但是我们仍有一些就代码在AppDelegate中需要清理掉。先从AppDelegate.m中删除_curRed和_increasing实例变量。还要删除glkView:drawInRect和glkViewControllerUpdate方法。
        然后删除application:didFinishLaunchingWithOptions的所有东西如下:
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        return YES;
    }
        修改AppDelegate @interface,去掉GLKit的两个代理,因为我们不再使用他们了。
    @interface AppDelegate : UIResponder <UIApplicationDelegate>
        好了!编译,运行,你将看到红色警报的效果依然。
         到这里,你已经非常接近了使用Storyboard创建OpenGL Game 模板(except it has a lot of other code in there you can just delete if you don’t need it)。以后当你创建一个新的OpenGL项目的时候,你可以自由选择那种方式来节省一点时间,但是现在你知道它是如何从基础开始工作的了。
        
    GLKViewController and Pausing

        现在我们已经完美地配置了一个定制GLKViewController子类,让我们来搞一下GLKViewController的新特性-停止!
        为了看它如何工作的,增加下面的代码到HelloGLKitViewController.m的底部:
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        self.paused = !self.paused;
    }
        编译并运行app,现在任何时候你的点击都会停止动画!在幕后,GLKViewController停止调用你的update方法和你的draw方法。这是在游戏中实现暂停按钮的一个非常简单的方法。
        除此之外,GLKViewController有一个pauseOnWillResignActive属性,缺省被设置为YES。这表示当用户按home键的时候或者被中断的情况,比如接到电话,你的游戏将自动暂停!类似的,还有一个resumeOnDidBecomeActive属性缺省被设置为YES,当用户返回到你的app时,会自动结束暂停状态。非常简单!
        至此我们差不多介绍了GLKViewController的全部属性,除了我们前面讨论过的额外的时间信息属性:
        timeSinceLastDraw:给你提供上次调用draw方法后的逝去时间。注意这和timeSinceLastUpdate不同,因为你的update方法会花费时间!
        timeSinceFirstResume:给你提供从GLKViewController第一次恢复(resumed)发送更新(update)消息后的逝去时间。如果GLKViewController是第一个显示的东西,这通常是你的app启动时间。
        timeSinceLastResume:给你提供从GLKViewController最后一次恢复发送更新消息后的逝去时间。这通常是你的游戏上次结束暂停状态的时间。

        让我们增加一些代码来试试它们。增加如下的代码到touchesBegan方法的顶部:
    NSLog(@"timeSinceLastUpdate: %f", self.timeSinceLastUpdate);
    NSLog(@"timeSinceLastDraw: %f", self.timeSinceLastDraw);
    NSLog(@"timeSinceFirstResume: %f", self.timeSinceFirstResume);
    NSLog(@"timeSinceLastResume: %f", self.timeSinceLastResume);

        多多尝试,这样你就会熟悉它们是如何工作的。就像你看到的,这些非常方便。


    第二部分:
     http://site.douban.com/129642/widget/notes/5513129/note/197203045/


    Beginning OpenGL ES 2.0 with GLKit Part 2
        英文原文链接:http://www.raywenderlich.com/5235/beginning-opengl-es-2-0-with-glkit-part-2对应图片参照这个链接地址吧。

    Welcome back to our Beginning OpenGL ES 2.0 with GLKit series!
        欢迎回到我们的Beginning OpenGL ES 2.0 with GLKit系列~
        在本系列的第一部分,我们从头为您展示了如何创建一个简单的GLKView 和GLKViewController 项目。在那个过程中,您学会了哪些类可以为你做的全部事情,以及为什么你要使用它们。
        在此第二部分,也是导引的最后一部分,由于您已经具备了一定的基础,我们可以开始做一些有趣的东西了!我们将使用一些OpenGl命令,和一个新的GLKit类,GLKBaseEffect,在屏幕中渲染一个方块。之后我们将使用GLKMath库提供的新的便捷函数来让方块旋转!
        这篇导读接续上一篇的讲述,所以如果你还没有看完上一篇,请先看完。
        好,到了用OpenGL向屏幕画些什么的时候了!
    Creating Vertex Data for a Simple Square
        让我们先做简单和有趣的部分,把一个方块渲染到屏幕中。方块向下面这样配置:
         此处有图,坐标的那张。
        当你使用OpenGL渲染几何图形时,记住它无法渲染方形-它只能渲染三角形。但是我们可以用2个三角形创造一个方形,就像上面图片那样:一个三角形的顶点是(V0, V1, V2),一个是(V2, V3, V0)。
        OpenGL ES 2.0的一个好处是,你可以用任何你喜欢的方式保存你的顶点数据。打开HelloGLKitViewController.m,创建一个C-结构体和一些数组来保存你的方块顶点信息,如下:
    typedef struct {
        float Position[3]; // 坐标:x, y, z
        float Color[4]; // 颜色
    } Vertex;
     
    const Vertex Vertices[] = { // 正方形4个顶点坐标及颜色
        {{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[] = { // 数组元素值对应的是顶点在Vertices数组中的下标。
        0, 1, 2, // 每一个行对应组成三角形的3个顶点
        2, 3, 0
    }; 

        我们基本上创造了:
         [1] 一个结构体,保存每个顶点的信息(目前只有颜色和位置);
         [2] 一个数组,保存正方形的所有顶点;
         [3] 一个数组,提供要创建的一系列三角形,通过定义组成三角形的顶点来保存三角形信息;
        我们现在具备了我们需要的全部信息,我们只需要把它们传给OpenGL!

    Creating Vertex Buffer Objects

        向OpenGL发送数据的最好的方法是通过Vertex Buffer Objects。这些基本上就是OpenGL用来缓存顶点数据的对象。
        有两种顶点缓冲区对象-一种保存每个顶点的数据(像前面代码中的Vertices数组),另一种保存构成三角形的顶点(像前面代码中的Indices数组)。
        首先把如下的实例变量增加到你的 私有HelloGLKitViewController 类:
    GLuint _vertexBuffer;
    GLuint _indexBuffer; 
        然后在viewDidLoad上面增加一个方法来创建这些东西:
    - (void)setupGL {
     
        [EAGLContext setCurrentContext:self.context];
     
        glGenBuffers(1, &_vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
     
        glGenBuffers(1, &_indexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
     


        可以看到这非常简单。它做的第一件事是设置当前的OpenGL上下文。当其他代码已经改变了全局上下文的情况下,这件事情是非常重要的。
        然后它调用glGenBuffers创建一个新的顶点缓冲区对象,glBindBuffer函数告诉OpenGL:“嘿,当我说GL_ARRAY_BUFFER的时候,我得意思是vertexBuffer”,glBufferData函数把数据发送到OpenGL的领地。

       还要增加一个新的方法来删除顶点和索引缓冲区:
    - (void)tearDownGL {
     
        [EAGLContext setCurrentContext:self.context];
     
        glDeleteBuffers(1, &_vertexBuffer);
        glDeleteBuffers(1, &_indexBuffer);
     


        在我们忘记之前,把这句代码加到viewDidLoad方法的底部:
    [self setupGL]; 

        这段加到viewDidUnload方法中,放在[super viewDidUnload]后面:
    [self tearDownGL]; 

    Introducing GLKBaseEffect
        
        在OpenGL ES 2.0中,无论你要向场景(scene)中渲染什么样的几何图形,都必须创建2个小程序,称为着色器(shaders)。
        着色器是用类C语言-GLSL写的。不用太担心要去学习相关的参考资料-在这篇导引中你不需要他们,原因马上就能看到!
        有两种类型的着色器:
            [1] 顶点着色器(Vertex shaders),你的每个场景的每个顶点都要调用一次这个程序。所以如果你正在一个简单的场景中渲染一个简单的方块,每个角的顶点都要调用一次,一共要调用4次。它的工作是执行一些计算,比如光,几何转化,等等,计算顶点的最终位置,还要传送一些数据给片段着色器。
            [2] 片段着色器(Fragment shaders),你的场景中的每个像素都要调用一次这个程序。所以如果你正在渲染相同场景中的一个单独的方块,方块所覆盖的每个像素都会调用一次。片段着色器也可以执行光([1]中提到的),等计算,但是他们的最重要的任务是设置每个像素的最终颜色。

        GLKBaseEffect是一个辅助类,维尼实现一些通用的着色器。GLKBaseEffect的目标是提供OpenGL ES 1.0中提供的大多数函数,使从1.0向2.0移植更加简单。
            
        要使用GLKBaseEffect,你需要这样做:
            [1] 创建一个GLKBaseEffect。通常当你创建OpenGL上下文的时候,你都会创建一个。对于不同的几何通行,你可以(或者说应该)重用相同的GLKBaseEffect,只是要重新设置其属性。在后台,GLKBaseEffect只会向它的着色器传播刚刚变化的属性。
            [2] 设置GLKBaseEffect的属性。这里你可以配置光,转化,和其他GLKBaseEffect的着色器用来渲染几何图形的属性。
            [3] 调用GLKBaseEffect的prepareToDraw方法。任何时候如果你改变了GLKBaseEffect的一个属性,你都要先调用prepareToDraw方法让着色器得到正确的配置。这也让GLKBaseEffect的着色器作为“当前”的着色器程序(就是让GLKBaseEffect的着色器起作用)。
            [4] 使能预定义属性。通常当你使用自定义着色器时,他们会使用称为属性的参数,你要写代码获得它们的ID。对于GLKBaseEffect的内建着色器,这些属性已经用常量值预先定义好了,比如GLKVertexAttribPosition或者GLKVertexAttribColor。所以你要使能所有需要传给着色器的参数,并保证他们指向了数据。
            [5] 绘制你的几何图形。一旦你东西要配置,你都可以使用普通的OpenGL绘图指令,比如glDrawArrays或者glDrawElements,它会使用你刚刚配置好的效果进行渲染!
        GLKBaseEffect的好处是如果你使用它们,你根本就不需要去写任何的着色器!当然如果你喜欢,你还是可以像原来那样做,并且你可以使用GLKBaseEffect混合、匹配并渲染一些东西,还可以使用你自定义的着色器。如果你观察一下OpenGL template project,你会发现一些这样的例子!
        在这篇导引中,我们重点学习使用GLKBaseEffect,因为最终目的是让您快速的掌握GLKit的功能-而且它还特别简单!
        所以让我们在代码中一步一步的过一遍。
        [1] 创建一个GLKBaseEffect
          第一步只是简单的创建一个GLKBaseEffect。在你的私有HelloGLKitViewController 类中,增加一个GLKBaseEffect属性。
    @property (strong, nonatomic) GLKBaseEffect *effect; 

          然后在@implementation后面synthesize它:
    @synthesize effect = _effect; 

          之后在中setupGL,调用[EAGLContext setCurrentContext:...]后初始化它:
    self.effect = [GLKBaseEffect alloc] init]; 
          在tearDownGL的最后将其设置为nil:
    self.effect = nil; 
        现在我们必须创建效果,让我们结合我们的顶点和索引缓冲区来使用它去渲染方块。第一步是设置我们的投影矩阵(projection matrix)!
        [2] 设置GLKBaseEffect属性
        我们需要设置的第一个属性是投影矩阵(projection matrix)。通过投影矩阵,你能够告诉CPU如何在2D平面上渲染3D几何图形。想象一下,从你眼中画出一束线通过屏幕上得每个像素。然而(从眼中画出的)每条线和最前面的3D对象交汇的地方决定了要向屏幕中绘制的像素。
        GLKit提供了一些方便的函数来设置投影矩阵。我们马上要使用的这个(函数)允许你定义沿y轴的显示区域,方向间的比例(aspect ratio),和近与远的平面:
    [Illustration from Vicki]

        显示区域(field of view)与照相机的镜头比较像。一个小的可视区域(比如10)就像一个长焦镜头-通过把远距离图像拉近来放大图像。一个大的显示区域(比如100)像广角镜头-它使得一切看起来都很远。一个常用的典型值是65-75。
        方向比例是你要渲染的方向比例(比如视图的方向比例,其实就是x轴和y轴的比例),还是看原文吧:The aspect ratio is the aspect ratio you want to render to (i.e. the aspect ratio of the view). 。方向比例与可视区域(y轴)相结合来决定x轴显示区域。
        近平面和远平面都是限定盒(bounding boxes),限定了场景的可视范围。所以如果某些东西离眼睛比近平面还近,或者比远平面还远,那么就不会被渲染出来。这是一个很常见的问题-你尝试要渲染一些东西,但是它却显示不出来。你要检查一下它实际上是否处于近平面和远平面之间。
        让我们尝试一下-在update方法下面增加如下代码:
    float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 4.0f, 10.0f); 
    self.effect.transform.projectionMatrix = projectionMatrix; 
        
        第一行,我们得到GLKView的方向比例。
        第二行,我们使用一个GLKit数学库中内建的辅助函数来简化透视矩阵(perspective matrix)的创建-我们所要做的只是将上面讨论过的参数传入。我们设置近平面为距离眼睛4单位,远平面10单位。(第一个参数就是前面提到的镜头的视角,第二个是方向比例,第三个是近平面距离,第四个是远平面距离)。
        第三行,我们刚刚设置了效果转化(effect.transform)属性的投影矩阵!
        我们现在需要设置更多的属性-modelViewMatrix。modelViewMatrix是会应用到effect渲染的所有几何图形的转化矩阵。
        GLKit数学库再次提供了一些非常方便的函数使演示转译(performing translations), 旋转(rotations),和缩放(scales)非常简单,即使你不了解矩阵数学也一样可以使用。为了看下效果,在update中增加如下的代码行:
    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -6.0f); 
    _rotation += 90 * self.timeSinceLastUpdate;
    modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(_rotation), 0, 0, 1);
    self.effect.transform.modelviewMatrix = modelViewMatrix; 
        如果你还记得我们设置的方块的顶点,想想顶点的z轴坐标都是0。如果我们尝试用透视矩阵渲染它,它会显示不出来,因为它比近平面还要靠近眼睛。
        所以我们要做的第一件事是向后移动(沿z轴)。所以第一行,我们使用GLKMatrix4MakeTranslation 函数创建一个矩阵,向后平移6单位。
        接下来,我们要让立方体旋转起来,以增加乐趣。所以我们增加一个实例变量来保存当前旋转(我们马上会增加),使用GLKMatrix4Rotate 方法通过旋转它来改变当前的转化矩阵(transformation)。它使用弧度做为参数,所以我们使用GLKMathDegreesToRadians 方法做转化。使得,这个数学库包含了几乎所有我们需要的矩阵和顶点的数学方法!
        最后,我们设置模型视图矩阵到effect的transform属性。
        别忘了,还要在HelloGLKitViewController的私有定义中增加这个旋转实例变量:
    float _rotation; 
        稍后我们将看到更多的GLKBaseEffect 属性,因为还有大量的很酷的东西,我们现在只涉及到很少的部分。但是现在让我们继续,我们可以最终让某些东西渲染出来!
        
         [3] 调用GLKBaseEffect的prepareToDraw方法
            这步非常简单。在glkView:drawInRect中增加下面的代码:
    [self.effect prepareToDraw];
           记住每次你改变了GLKBaseEffect属性后,在绘制之前都要调用它。
        [4] 使能预定义的属性
            接下来在glkView:drawInRect中增加如下代码: 
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
        [5] 绘制你的几何图形
    glEnableVertexAttribArray(GLKVertexAttribPosition); 
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Position));
    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Color));
        如果你之前使用过OpenGL ES 2.0编程,你会感觉非常熟悉,如果你没有,让我来解释一下。
        每次你绘制之前,你必须告诉OpenGl你要使用哪个顶点缓冲区。所以这里我们绑定前面创建的顶点和索引缓冲区。准确地讲,对于这个app我们没必要做这些(因为前面已经绑定过了)但是通常我们必须这么做,因为在大多数游戏中,我们有很多不同的顶点缓冲区对象。
        接下来,我们必须使能预定义的,要让GLKBaseEffect 使用的顶点属性。我们使用glEnableVertexAttribArray 来使能2个属性-一个是定线位置,一个是顶点颜色。GLKit提供了预定的常量供我们使用-GLKVertexAttribPosition 和GLKVertexAttribColor。
        然后,我们调用glVertexAttribPointer 来给这两个顶点着色器的输入变量设置正确的值。
       这是一个非常重要的函数,所以让我们仔细看看它是怎么工作的。
       - 第一个参数定义要设置的属性名。我们就使用预定义的GLKit常量。
       - 第二个参数定义了每个顶点有多少个值。如果你往回看看顶点的结构,你会看到对于位置,有3个浮点值(x, y, z),对于颜色有4个浮点值(r, g, b, a)。
        - 第三个参数定义了每个值的类型-对于位置和颜色都是浮点型。
        - 第四个参数通常都是false。
        - 第五个参数是跨度(stride)的大小,简单点说就是包含每个顶点的数据结构的大小。所以我们可以简单地传进sizeof(Vertex),让编译器帮助我们计算它。
       - 最后一个参数是在数据结构中要获得此数据的偏移量。我们使用方便的offsetof操作来找到结构体中一个具体属性(就是从Vertex数据结构中,找到“位置”信息的偏移量)。
        所以现在我们为GLKBaseEffect传递了位置和颜色数据,还剩下一步了:

        [5] 绘制你的几何图形
    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0);
        这还是一个重要的函数,所以让我们讨论一下每个参数。
        - 第一个参数定义了绘制顶点的方法。在其他的导引中你会看到,有不同的选项,像GL_LINE_STRIP,GL_TRIANGLE_FAN,但是GL_TRIANGLES 是最通用的(特别是与VBOs结合的时候),所以我们这里使用它。
       - 第二个参数是要渲染的顶点的数量。我们使用一个C的小戏法来计算数组中元素的数量,用整个数组的大小,除以数组元素的大小就可以得到了。
       - 第三个参数是所以数组中每个索引的数据类型。我们使用unsigned byte进行的定义,所以我们在这里也这样定义它。
       从文档中可以看出最后一个参数应该是一个纸箱索引的指针。但是由于我们使用了VBOs,情况特殊,它将使用我们已经传送给OpenGL的GL_ELEMENT_ARRAY_BUFFER中得索引矩阵。
       您猜怎么着?-我们做完了!编译并运行这个app,你会看到一个美丽的方块在屏幕中旋转!
       此处有图,可以在原文中看看~~~

  • 相关阅读:
    Coding 账户与 本地 Git 客户端的配置
    leetcode_sort-list
    leetcode_insertion-sort-list
    leetcode_move-zeroes
    search-insert-position
    leetcode_remove-nth-node-from-end-of-list
    leetcode_queue-reconstruction-by-height
    leetcode_valid-parentheses
    leetcode_swap-nodes-in-pairs
    20201115-东北师范大学-助教-周总结-第9次
  • 原文地址:https://www.cnblogs.com/lyggqm/p/5217224.html
Copyright © 2011-2022 走看看