• iOS 使用Quartz和OpenGL绘图


    http://blog.csdn.net/coder9999/article/details/7641701

    第十二章 使用Quartz和OpenGL绘图

    有时应用程序需要能够自定义绘图。一个库是Quartz 2D,她是Core Graphics框架的一部分;另一个库是OpenGL ES,她是跨平台的图形库。OpenGL ES是跨平台图形库OpenGL的简化版。OpenGL ES是OpenGL的一个子集,OpenGL ES是专门为iPhone之类的嵌入式系统(因此缩写字母为“ES”)设计的。

    12.1 图形世界的两个视图
    Quartz是一组函数、数据类型以及对象,专门设计用于直接在内存中对视图或图像进行控制。
    Quartz将正在绘制的视图或图像视为一个虚拟的画布,并遵循所谓的“绘画者模型”。如果绘画者将整个画布涂为红色,然后将画布的下半部分涂为蓝色,那么画布将变为一半红色、一半蓝色或紫色。如果颜料是不透明的,应该为蓝色,如果颜料是半透明的,应该为紫色。每个绘图操作都将被应用于画布,并且处于之前所有绘图操作之上。
    另一方面,OpenGL ES以状态机的形式实现。OpenGL ES不允许执行直接影响视图、窗口或图像的操作,他维护一个虚拟的三维世界。当向这个世界中添加对象时,OpenGL会跟踪所有对象的状态。虽然OpenGL ES没有提供虚拟画布,但是却提供了一个进入其世界的虚拟窗口。可以向该世界中添加对象并定义虚拟窗口相对于该世界的位置。然后,OpenGL根据配置方式以及各种对象彼此相对的位置绘制视图,并通过该窗口呈现给用户。这个概念有点抽象。感到迷惑的话,就通过后面的示例理解吧。

    Quartz相对比较容易使用。他提供了各种直线、形状以及图像绘制函数。但Quartz 2D仅限于二维绘图。尽管许多Quartz函数会在绘图时利用硬件加速,但无法保证在Quartz中执行的任何操作都得到了加速。
    OpenGL同时提供了二维和三维绘图。他经过专门设计,目的是为了充分利用硬件加速。由于他可以跟踪虚拟世界的状态,因此还非常适合用于编写游戏和其他复杂的、图形密集的程序。

    12.2 本章的绘图应用程序
    我们将分别使用Quartz 2D和OpenGL ES来构建应用程序。
    该应用程序的特点是顶部和底部各有一个工具栏,每个工具栏都有一个分段控件。顶部的控件用于更改图形颜色,底部的控件用于更改要绘制的形状。当用户触击和拖动对象时,程序将用所需颜色绘制所选形状。为了最大程度地降低应用程序的复杂性,一次只绘制一种形状。

    12.3 Quartz绘图方法
    使用Quartz绘制图形时,通常会向绘制图形的视图中添加绘图代码。例如,可能会创建UIView的子类,并向该类的drawRect:方法中添加Quartz函数调用。drawRect:方法是UIView类定义的一部分,并且每次需要重绘视图时都会调用该方法。如果在drawRect:中插入Quartz代码,则会先调用该代码,然后重绘视图。

    12.3.1 Quartz 2D的图形上下文
    在Quartz 2D中,和在其他Core Graphics中一样,绘图是在“图形上下文”中进行的,通常,只称为上下文。每个视图都有相关联的上下文。要在某个视图中绘图时,你将检索当前上下文,使用此上下文进行各种Quartz图形调用,并且让此上下文负责将图形呈现到视图上。

    下面这行代码将检索当前上下文:
    CGContextRef context=UIGraphicsGetCurrentContext();

    说明:我们使用Core Graphics C函数,而不是使用Obj-C对象来绘图。Core Graphics的API是基于C的,因此在本章的此部分中编写的大多数代码将由C函数调用组成。

    定义图形上下文之后,可以将其传递给各种Core Graphics函数来进行绘图。例如:以下代码将在上下文中绘制一条2像素宽的直线:

    CGContextSetLineWidth(context,2.0);
    CGContextSetStrokeColorWithColor(context,[UIColor redColor].CGColor);
    CGContextMoveToPoint(context,100.0f,100.0f);
    CGContextAddLineToPoint(context,200.0f,200.0f);
    //之前的操作就像用不可见的墨水在书写一样,下一步是告知Quartz使用CGContextStrokePath()绘制直线
    CGContextStrokePath(context);

    12.3.2 坐标系
    左上角为(0,0),向下移动时y增加,向右移动时x增加。
    在OpenGL Es中,(0,0)位于左下角,向上移动时y增加,向右移动时x增加。
    使用OpenGL时,必须将视图坐标系转换为OpenGL坐标系。这很容易。稍后讲解

    12.3.3 指定颜色
    UIKit提供了一个Obj-C类:UIColor。你不能在Core Graphics调用中直接使用UIColor对象,但可以使用他的CGColor属性从UIColor实例中检索CGColor引用。

    1,iPhone显示的颜色理论
    在现代计算机图形中,通常使用argb来表示颜色。在Quartz 2D中,这些值都是CGFloat类型(与iPhone上的float相同),并且只能在0和1中取值。

    2,比所看到的颜色还多
    除了rgb之外,还有一个a--Alpha,表示透明程度。

    对于大多数操作来说,我们不必担心所使用的颜色模型。我们只需从UIColor对象中传递CGColor,Core Graphics会处理任何所需的转换。在使用OpenGL ES时,记住由于OpenGL ES需要采用RGBA来指定颜色,因此Quartz支持其他颜色模型,这一点非常重要。
    UIColor的便利方法创建的实例都是使用RGBA颜色模型。
    如果不使用便利方法。下面代码:
    return [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f];

    12.3.4 在上下文中绘制图像

    使用Quartz 2D,可以直接在上下文中绘制图像。这是Obj-C类(UIImage)的另一个示例,你可以使用此类作为操作Core Graphics数据结构(CGImage)的备用选项。此UIImage类包含将图像绘制到当前上下文中的方法。你需要确定此图像出现在上下文中的位置,方法是:指定一个CGPoint来确定图像的左上角或者指定一个CGRect来框住图像,并根据需要调整图像大小使其适合该框。可以在当前上下文中绘制一个UIImage,如下所示:
    CGPoint drawPoint=CGPointMake(100.0f,100.0f);
    [image drawAtPoint:drawPoint];

    12.3.5 绘制形状:多边形、直线和曲线
    例如绘制一个椭圆:
    CGRect theRect=CGMakeRect(0,0,100,100);
    CGContextAddEllipseInRect(context,theRect);
    CGContextDrawPath(context,kCGPathFillStroke);

    12.3.6 Quartz 2D 工具示例:模式、梯度、虚线模式
    Quartz 2D不像OpenGL那么昂贵,却提供了许多吸引人的工具,尽管这些工具中的许多工具不在本书的讨论范围之内,但你应该知道他们的存在。例如,Quartz 2D支持用梯度填充多边形,而不只是用纯色,并且不仅仅支持实线,而且还支持虚线模式。

    12.4 构建QuartzFun应用程序
    在Xcode中,使用基于视图的应用程序模版创建一个新项目---QuartzFun。
    我们将在视图中执行自定义绘图,因此需要创建一个UIView子类。在该子类中,我们将通过覆盖drawRect:方法进行绘图。创建一个新的Cocoa Touch Classes文件,并选择UIView subclass模版。命名为QuartzFunView.m。

    与之前一样,我们将定义一些常量,但这次定义的常量是多个类所需要的,并且不是特定于某个类的。我们将只为常量创建头文件,因此通过以下访问创建一个新文件:从Other栏中选择Empty File 模版,命名为Constants.h。
    我们还需要创建两个文件,让UIColor类提供返回随机颜色的方法,即为UIColor创建一个类别。同样使用Empty File模版创建两个文件,将他们分别命名为UIColor-Random.h和.m。

    12.4.1 创建随机颜色
    UIColor-Random.h:

    #import <UIKit/UIKit.h>
    @interface UIColor(Random)
    + (UIColor *)randomColor;
    @end

    UIColor-Random.m:

    #import "UIColor-Random.h"

    @implementation UIColor(Random)
    + (UIColor *)randomColor
    {
     static BOOL seeded=NO;
     if (!seeded) {
      seeded=YES;
      srandom(time(NULL));
     }
     CGFloat red=(CGFloat)random()/(CGFloat)RAND_MAX;
     CGFloat blue=(CGFloat)random()/(CGFloat)RAND_MAX;
     CGFloat green=(CGFloat)random()/(CGFloat)RAND_MAX;
     return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
    }
    @end

    12.4.2 定义应用程序常量
    修改Constants.h:

    typedef enum {
     kLineShape=0,
     kRectShape
     kEllipseShape,
     kImageShape
    } ShapeType;

    typedef enum {
     kRedColorTab=0,
     kBlueColorTab,
     kYellowColorTab,
     kGreenColorTab,
     kRranomColorTab
    } ColorTabIndex;
    #define degreesToRadian(x) (3.14159265358979323846 * (x) / 180.0)

    12.4.3 实现QuartzFunView框架
    修改QuartzFunView.h:

    #import <UIKit/UIKit.h>
    #import "Constants.h"

    @interface QuartzFunView:UIView {
     CGPoint firstTouch;
     CGPoint lastTouch;
     UIColor *currentColor;
     ShapeType shapeType;
     UIImage *drawImage;
     BOOL useRandomColor;
    }
    @property ...
    @end

    切换到QuartzFunView.m:

    #import "QuartzFunView.h"
    #import "UIColor-Random.h"

    @implementation QuartzFunView

    @synthesize ...

    - (id)initWithCoder:(NSCoder *)coder
    {
     if ((self=[super initWithCoder:coder])) {
      self.currentColor=[UIColor redColor];
      self.useRandomColor=NO;
      if (drawImage==nil) 
        self.drawImage=[UIImage imageNamed:@"iphone.png"];
     }
     return self;
    }

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
    {
     if (useRandomColor)
       self.currentColor=[UIColor randomColor];
     UITouch *touch=[touches anyObject];
     firstTouch=[touch locationInView:self];
     lastTouch=[touch locationInView:self];
     [self setNeedsDisplay];
    }

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
     UITouch *touch=[touches anyObject];
     lastTouch=[touch locationInView:self];

     [self setNeedsDisplay];
    }

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
     UITouch *touch=[touches anyObject];
     lastTouch=[touch locationInView:self];

     [self setNeedsDisplay];
    }

    - (void)drawRect:(CGRect)rect {
     //Drawing code
    }
    ...
    @end

    由于该视图是从nib中加载的,因此,我们首先实现initWithCoder:。请记住nib中的对象实例将存储为归档对象,这与我们在上一章将对象归档和加载到磁盘所使用的机制完全相同。因此,从nib中加载对象实例时,init:或initWithFrame:都不会调用。而是使用initWithCoder:,因此,任何初始化代码都需要在这里添加。

    drawRect:方法是此应用程序的主体部分,目前仅包含一条注释。我们首先需要完成应用程序设置,然后再添加绘图代码。

    12.4.4 向视图控制器中添加输出口和操作

    单击QuartzFunViewController.h:

    #import <UIKit/UIKit.h>

    @interface QuartzFunViewController:UIViewController {
     IBOutlet UISegmentedControl *colorControl;
    }
    @property (nonatomic,retain) UISegmentedControl *colorControl;
    - (IBAction)changeColor:(id)sender;
    - (IBAction)changeShape:(id)sender;
    @end

    切换到QuartzFunViewController.m:

    #import "QuartzFunViewController.h"
    #import "QuartzFunView.h"
    #import "UIColor-Random.h"
    #import "Constants.h"

    @implementation QuartzFunViewController
    @synthesize colorControl;

    - (IBAction)changeColor:(id)sender {
     UISegmentedControl *control=sender;
     NSInteger index=[control selectedSegmentIndex];
      
     QuartzFunView *quartzView=(QuartzFunView *)self.view;
     
     switch (index) {
       case kReadColorTab:
         quartzView.currentColor=[UIColor redColor];
         quartzView.useRandomColor=NO;
         break;
      ...
      case kRandomColorTab:
        quartzView.useRandomColor=YES;
        break;
      default:
        break;
     }
    }

    - (IBAction)changeShape:(id)sender {
     UISegmentedControl *control=sender;
     [(QuartzView *)self.view setShapeType:[control selectedSegmentInedx]];
     
     if ([control selectedSegmentIndex]==kImageShape)
       colorControl.hidden=YES;
     else
       colorControl.hidden=NO;
    }

    ...
    @end


    12.4.5 更新QuartzFunViewController.xib
    打开QuartzFunViewController.xib,先更改视图的类,单击View图标,cmd+4打开身份检查器,将该类从UIView改为QuartzFunView。
    接下来,从库中找到Navigation Bar。确保你控制的是Navigation Bar,而非Navigation Controller。将其紧贴在视图窗口的顶部。接下来,从库中找到Segmented Control,并将该控件拖动到Navigation Bar的顶部。放下该控件之后,他应该仍然处于选中状态。捕捉此分段控件任何一侧的调整大小的点,调整他的大小,以便它占据导航栏的整个宽度。你不会看到任何蓝色引导线,但这种情况下,IB会限制该栏的最大大小,因此只需拖动他直到不能再进一步展开为止。
    按Cmd+1调出属性检查器,并将分段数量从2改为5.依次双击各分段,将他们的标签分别改为Red、Blue、Yellow、Green和Random。

    按住Ctrl,将File's Owner拖到分段控件上,选择colorControl输出口。接下来,选中分段控件,Cmd+2打开连接检查器,从Value Changed事件拖到File's Owner选择changeColor:操作。

    现在,从库中拖出Toolbar,放置在窗口底部。然后拖出另一个Segmented Control到工具栏上。
    结果是,分段控件在工具栏中居中有点困难,因此我们将提供一点帮助。将Flexible Space bar Button Item从库中拖到位于分段控件左侧的工具栏上。接下来,将另一个Flexible Space bar Button Item拖到位于分段控件右侧的工具栏上。当我们调整该工具栏的大小时,这些项目将使分段控件位于工具栏的中心。单击分段控件将其选中,并调整其大小以使他适合此工具栏,其中左侧两侧各留一点空间。
    接下来,Cmd+1打开属性检查器,并将分段从2改为4.标题分别改为Line、Rect、Ellipse和Image。将Value Changed事件连接到File's Owner的ChangeShape:方法。

    编译运行应用程序,确保一切正常。目前你还不能在屏幕上绘制形状,但分段控件可以工作。

    12.4.6 绘制直线

    返回Xcode,编辑QuartzFunView.m,并有以下代码替换空的drawRect:方法。

    - (void)drawRect:(CGRect)rect {
     if (currentColor==nil)
       self.currentColor=[UIColor redColor];

     CGContextRef context=UIGraphicsGetCurrentContext();
     
     CGContextSetLineWidth(context,2.0);
     CGCOntextSetStrokeColorWithColor(context,currentColor.CGColor);
     CGContextSetFillColorWithColor(context,currentColor.CGColor);

     CGRect currentRect=CGRectMake (
       (firstTouch.x>lastTouch.x)?lastTouch.x:firstTouch.x,
       (firstTouch.y>lastTouch.y)?lastTouch.y:firstTouch.y,
       fabsf(firstTouch.x-lastTouch.x),
       fabsf(firstTouch.y-lastTouch.y)
     );
           

     switch (shapeType) {
      case kLineShape:
       CGContextMoveToPoint(context,firstTouch.x,firstToch.y);
       CGContextAddLineToPoint(context,lastTouch.x,lastTouch.y);
       CGContextStrokePath(context);
       break;
      case kRectShape:
       CGContextAddRect(context,currentRect);
       CGContextDrawPath(context,kCGPathFillStroke);
       break;
      case kEllipseShape:
       CGContextAddEllipseInRect(context,currentRect);
       CGContextDrawPath(context,kCGPathFillStroke); 
       break;
      case kImageShape: {
       CGFloat horizontalOffset=drawImage.size.width /2;
       CGFloat verticalOffset=drawImage.size.height /2;
       CGPoint drawPoint=CGPointMake(lastTouch.x-horizontalOffset,lastTouch.y-verticalOffset);
       [drawImage drawAtPoint:drawPoint];
       break;
      }
      //注意,这里使用了花括号。GCC在case语句之后的第一行中声明变量时遇到了问题。这些花括号是我们告诉GCC停止抱怨的一种方式。
      default:
       break;
     }
    }

    由于Quartz 2D是Core Graphics的一部分。因此在构建和运行之前,需要将Core Graphics框架添加到项目中。

    在该应用程序中,你不会注意到速度减慢,但是在更复杂的应用程序中,你会看到某些延迟。该问题由QuartzFunView.m中的方法touchesMoved:和touchesEnded:引起。这两个方法都有下面这行代码:
    [self setNeedsDisplay];
    很明显,这是我们告知视图重新绘制自身的方式。为避免在拖动期间多次强制重新绘制整个视图,我们可以使用setNeedsDisplayInRect:方法。setNeedsDisplayInRect:是一个NSView方法,该方法会将视图区域的一个矩形部分标记为需要重新显示。我们需要重新绘制的不仅仅是firstTouch和lastTouch之间的矩形,还有当前拖动所包围的任何屏幕部分。如果用户触摸屏幕,然后在屏幕上到处乱画,则只需要重新绘制firstTouch和lastTouch之间的部分,将许多不需要的已绘制的内容留在屏幕上。
    答案是跟踪受CGRect实例变量中的特定拖动影响的整个区域。在touchesBegan:中,我们将该实例变量重置为仅用户触摸的点。然后在touchesMoved:和touchesEnded:中,使用一个Core Graphics函数获取当前矩形和存储的矩形的并集,然后存储所得到的矩形。此外,还使用该函数指定需要重新绘制的视图部分。该方法为我们提供了受当前拖动影响的正在运行的全部区域。
    我们立刻在drawRect:方法中计算当前矩形,以便绘制椭圆形和矩形形状。我们将该计算结果移动到新方法中,以便在所有3个额外i只中使用此新方法,而没有重复代码。准备好了吗?让我们开始吧!
    对QuartzFunView.h进行以下更改:

    ...
    {
    ...
    CGRect redrawRect;
    }
    ...
    @property (readonly) CGRect currentRect;
    @property CGRect redrawRect;
    @end

    切换到QuartzFunView.m:

    ...
    @synthesize redrawRect;
    @dynamic currentRect;

    - (CGRect)currentRect {
     return CGRectMake (
       (firstTouch.x>lastTouch.x)?lastTouch.x:firstTouch.x,
       (firstTouch.y>lastTouch.y)?lastTouch.y:firstTouch.y,
       fabsf(firstTouch.x-lastTouch.x),
       fabsf(firstTouch.y-lastTouch.y)
     );
    }

    - (void)drawRect:(CGRect)rect {
     ...
     switch (shapeType) {
      case kLineShape:
       ...
      case kRectShape:
       CGContextAddRect(context,self.currentRect);
       CGContextDrawPath(context,kCGPathFillStroke);
       break;
      case kEllipseShape:
       CGContextAddEllipseInRect(context,self.currentRect);
       CGContextDrawPath(context,kCGPathFillStroke); 
       break;
      case kImageShape: 
       ...
      default:
       break;
     }
    }

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
    {
     if (useRandomColor)
       self.currentColor=[UIColor randomColor];
     UITouch *touch=[touches anyObject];
     firstTouch=[touch locationInView:self];
     lastTouch=[touch locationInView:self];

     if (shapeType==kImageShape) {
       CGFloat horizontalOffset=drawImage.size.width/2;
       CGFloat verticalOffset=drawImage.size.height /2;
       redrawRect=CGRectMake(firstTouch.x-horizontalOffset,firstTouch.y-verticalOffset,drawImage.size.width,drawImage.size.height);
     }
     else
      redrawRect=CGRectMake(firstTouch.x,firstTouch.y,0,0);

     [self setNeedsDisplay];

    }

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
     UITouch *touch=[touches anyObject];
     lastTouch=[touch locationInView:self];

     if (shapeType==kImageShape) {
       CGFloat horizontalOffset=drawImage.size.width/2;
       CGFloat verticalOffset=drawImage.size.height /2;
       redrawRect=CGRectMake(firstTouch.x-horizontalOffset,firstTouch.y-verticalOffset,drawImage.size.width,drawImage.size.height);
     }
     else
      redrawRect=CGRectUnion(redrawRect,self.currentRect);
     redrawRect=CGRectInSet(redrawRect,-2.0,-2.0);

     [self setNeedsDisplayInRect:redrawRect];
    }

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
     UITouch *touch=[touches anyObject];
     lastTouch=[touch locationInView:self];

     if (shapeType==kImageShape) {
       CGFloat horizontalOffset=drawImage.size.width/2;
       CGFloat verticalOffset=drawImage.size.height /2;
       redrawRect=CGRectMake(firstTouch.x-horizontalOffset,firstTouch.y-verticalOffset,drawImage.size.width,drawImage.size.height);
     }
     
     redrawRect=CGRectUnion(redrawRect,self.currentRect);
     

     [self setNeedsDisplayInRect:redrawRect];
    }

    @end

    仅增加了几行代码,我们就减少了重新绘制视图所需的大量工作(不再需要擦除和重新绘制未受当前拖动影响的视图部分)。像这样妥善处理iPhone宝贵的处理器周期,可以在应用程序性能方面产生巨大差别,尤其是当应用程序变得更加复杂时。

    12.5 一些OpenGL ES基础知识
    对OpenGL ES的详细介绍本身就是一本书,因此我们在此不对其进行讨论。我们使用OpenGL ES重新创建我们的Quartz 2D应用程序,只是为了让你对其有个基本了解,并且向你提供一些示例代码。

    说明:准备在你自己的应用程序中添加OpenGL时,请顺便浏览一下http:www.khronos.org/opengles/,该网页是OpenGL ES标准组的主页。更好的做法是访问此页并搜索单词“tutorial”:http://www.khronos.org/developers/resources/opengles/。

    让我们开始创建应用程序吧

    构建GLFun应用程序
    在Xcode中创建一个基于视图的新应用程序,命名为GLFun。为了节省时间,将文件Constants.h、UIColor-Random.h、UIColor-Random.m和iPhone.png从Quartz-Fun项目复制到这个新项目中。
    打开GLFunViewController.h并进行和QuartzFunViewController.h相同的修改:
    声明一个IBOutlet UISegmentedControl *colorControl;
    - (IBAction)changeColor:(id)sender;
    - (IBAction)changeShape:(id)sender;

    切换到GLFunViewController.m并进行以下更改:

    - (IBAction)changeColor:(id)sender
    {
     UISegmentedControl *control=sender;
     NSInteger index=[control selectedSegmentIndex];

     GLFunView *glView=(GLFunView *)self.view;
     switch (index) {
      case kRedColorTab:
       glView.currentColor=[UIColor redColor];
       glView.useRandomColor=NO;
       break;
      ...
     }
    }

    - (IBAction)changeShape:(id)sender {
     UISegmentedControl *control=sender;
     [(GLFunView *)self.view setShapeType:[control selectedSegmentIndex]];
     if ([control selectedSegmentIndex]==kImageShape)
       [colorControl setHidden:YES];
     else
       [colorControl setHidden:NO];
    }

    这里唯一不同的是我们引用一个名为GLFunView的视图,而不是QuartzFunView的视图。进行绘图的代码包含在UIView的子类中。

    继续操作之前,你需要在项目中添加几个文件。在12 GLFun文件夹中,你可以找到4个文件,名称分别为Texture2D.h、Texture2D.m、OpenGLES2DView.h和OpenGLES2DView.m。这些文件中的代码是由苹果公司编写的,他们使得在OpenGL ES中绘制图像更容易。如果你愿意,可以在自己的程序中随意使用这些文件。
    OpenGL ES本身并没有sprite或图像;他具有纹理,但纹理必须绘制在对象上。在OpenGL ES中绘制图像的方法是绘制一个多边形,然后将纹理映射到该多边形上,以便他与多边形的大小完全匹配。Texture2D将相对比较复杂的过程封装到一个易于使用的类中。
    OpenGLES2DView是一个UIView子类,他使用OpenGL进行绘图。我们设置此视图的目的是便于在一对一的基础上映射OpenGL ES的坐标系和视图的坐标系。OpenGL Es是一个三维系统。OpenGLES2DView将OpenGL 3-D世界映射到2-D视图的像素。注意,尽管视图的OpenGL上下文之间是一对一的关系,但是y坐标仍然是翻转的,因此我们必须将y坐标从视图坐标系转换为OpenGL坐标系。
    若要使用OpenGLES2DView类,首先要将其子类化,然后实现draw方法进行实际绘图。还可以在视图中实现所需的任何其他方法,如与触摸有关的方法。

    使用UIView子类模版创建一个新文件,命名为GLFunView.m。现在,你可以双击GLFunViewController.xib,然后设计其界面。

    完成之后,保存返回Xcode。单击GLFunView.h:

    #import <UIKit/UIKit.h>
    #import "Constants.h"
    #import "Texture2D.h"
    #import "OpenGLES2DView.h"

    @interface GLFunView:OpenGLES2DView {
     CGPoint firstTouch;
     CGPoint lastTouch;
     UIColor *currentColor;
     BOOL useRandomColor;

     ShapeType shapeType;
     
     Texture2D *sprite;
    }
    @property ...
    @end

    此类与QuartzFunView.h非常相似,但他不使用UIImage来存放图像,我们使用Texture2D来简化将图像绘制到OpenGL ES上下文中的过程。我们还将超类从UIView改成OpenGLES2DView,以便视图支持使用OpenGL ES进行二维绘图。

    切换到GLFunView.m:

    - (id)initWithCoder:(NSCoder *)coder
    { if (self=[super initWithCoder:coder]) {
       self.currentColor=[UIColor redCorlo];
       self.useRandomColor=NO;
      }
      return self;
    }

    - (void)draw
    {
     glLoadIdentity();
     
     glClearColor(1.0f,1.0f,1.0f,1.0f);
     glClear(GL_COLOR_BUFFER_BIT);

     CGColorRef color=currentColor.CGColor;
     const CGFloat *components=CGColorGetComponents(color);
     CGFloat red=components[0];
     CGFloat green=components[1];
     CGFloat blue=components[2];

     glColor4f(red,green,blue,1.0);

     switch (shapeType) {
      case kLineShape:{
        if (sprite) {
          [sprite release];
          self.sprite=nil;
        }
        GLFloat vertices[4];

        //Convert coordinates
        vertices[0]=firstTouch.x;
        vertices[1]=self.frame.size.height-firstTouch.y;
        vertices[2]=lastTouch.x;
        vertices[3]=self.frame.size.height-lastTouch.y;
        glLineWidth(2.0);
        glVertexPointer(2,GL_FLOAT,0,vertices);
        glDrawArrays(GL_LINES,0,2);
        break;
      }
      case kRectShape:{
        if (sprite) {
          [sprite release];
          self.sprite=nil;
        }
        
        //Calculate bounding rect and store in vertices
        GLfloat vertices[8];
        GLfloat minX=(firstTouch.x>lastTouch.x)?lastTouch.x:firstTouch.x;
        GLfloat minY=(self.frame.size.height-firstTouch.y > self.frame.size.height-lastTouch.y ) ?
                      self.frame.size.height-lastTouch.y:self.frame.size.height-firstTouch.y;
        GLfloat maxX=(firstTouch.x>lastTouch.x)?firstTouch.x:lastTouch.x;
        GLfloat maxY=(self.frame.size.height-firstTouch.y > self.frame.size.height-lastTouch.y ) ?
                      self.frame.size.height-firstTouch.y:self.frame.size.height-lastTouch.y;

        vertices[0]=maxX;
        vertices[1]=maxY;
        vertices[2]=minX;
        vertices[3]=maxY;
        vertices[4]=minX;
        vertices[5]=minY;
        vertices[6]=maxX;
        vertices[7]=minY;

        glVertexPointer(2,GL_FLOAT,0,vertices);
        glDrawArrays(GL_TRIANGLE_FAN,0,4);
        break;
      }
      case kEllipseShape:{
        if (sprite) {
          [sprite release];
          self.sprite=nil;
        }
            
        GLfloat vertices[720];
        GLfloat xradius=(firstTouch.x>lastTouch.x)? (firstTouch.x-lastTouch.x)/2:(lastTouch.x-firstTouch.x)/2;
        GLfloat yradius=(self.frame.size.height-firstTouch.y>self.frame.size.height-lastTouch.y)?
                  ((self.frame.size.height-firstTouch.y)-(self.frame.size.height-lastTouch.y))/2:
                  ((self.frame.size.height-lastTouch.y)-(self.frame.size.height-firstTouch.y))/2;
        for (int i=0;i<=720;i+=2)
        {
          GLfloat xOffset=(firstTouch.x>lastTouch.x)?lastTouch.x+xradius:firstTouch.x+xradius;
          GLfloat yOffset=(self.frame.size.height-firstTouch.y>self.frame.size.height-lastTouch.y)?
                    self.frame.size.height-lastTouch.y+yradius:
                    self.frame.size.height-firstTouch.y+yradius;
          vertices[i]=(cos(degreesToRadian(i))*xradius)+xOffset;
          vertices[i+1]=(sin(degreesToRadian(i))*yradius)+yOffset;
        }
        glVertexPointer(2,GL_FLOAT,0,vertices);
        glDrawArrays(GL_TRIANGLE_FAN,0,360);
        break;
      }
      case kImageShape:
        if (sprite==nil) {      
          self.sprite=[[Texture2D alloc] initWithImage:[UIImage imageNamed:@"iphone.png"]];
          glBindTexture(GL_TEXTURE_2D,sprite.name);
        }
        [sprite drawAtPoin:CGPointMake(lastTouch.x,self.frame.size.height-lastTouch.y)];
        break;
      default:
        break;
     }
     glBindRenderbufferOES(GL_RENDERBUFFER_OES,viewRenderbuffer);
     [context presentRenderbuffer:GL_RENDERBUFFER_OES];
    }

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    { if (useRandomColor)
       self.currentColor=[UIColor randomColor];
      UITouch *touch=[[event touchesForView:self] anyObject];
      firstTouch=[touch locationInView:self];
      lastTouch=[touch locationInView:self];
      [self draw];
    }

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
     UITouch *touch=[touches anyObject];
     lastTouch=[touch locationInView:self];
     
     [self draw];
    }

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
     UITouch *touch=[touches anyObject];
     lastTouch=[touch locationInView:self];
     
     [self draw];
    }
    @end

    解析:
    在initWithCoder:方法中,我们没有创建Texture2D对象。由于我们绘制的形状没有纹理,因此不需要加载纹理。如果加载纹理,OpenGL ES将在绘制多边形时尝试使用纹理。因此,我们需要采取一些步骤以确保在绘制其他形状时不加载纹理。处理此问题的首选方法就是延迟加载纹理。

    下面是draw方法:
    首先,我们重置虚拟机世界以便删除任何旋转、转换或已经应用于他的其他变换:

     glLoadIdentity();

    接下来,我们将背景清除为白色: 

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

    之后,我们必须通过分割UIColor并从中拖出各个RGB组件来设置OpenGL绘图颜色。

     CGColorRef color=currentColor.CGColor;
     const CGFloat *components=CGColorGetComponents(color);
     CGFloat red=components[0];
     CGFloat green=components[1];
     CGFloat blue=components[2];
     glColor4f(red,green,blue,1.0);

    若要绘制直线,我们需要两个顶点,这意味着我们需要包含4个元素的数组。在Quartz中,我们使用CGPoint struct来存放这些点。在OpenGL中,点未嵌入到struct中,相反,我们用组成需要绘制的形状的所有点来填充数组。因此,若要在OpenGL ES中绘制一条从点(100,150)到点(200,250)的直线,我们将创建一个如下所示的顶点数组:

    vertex[0]=100;
    vertex[1]=150;
    vertex[2]=200;
    vertex[3]=250;

    我们的数组格式为{x1,y1,x2,y2,x3,y3}。
    该方法中的下一段代码将两个CGPoint结构转换为顶点数组:

        //Convert coordinates
        vertices[0]=firstTouch.x;
        vertices[1]=self.frame.size.height-firstTouch.y;
        vertices[2]=lastTouch.x;
        vertices[3]=self.frame.size.height-lastTouch.y;

    定义顶点数组之后,指定线宽,使用方法glVertextPointer()将该数组传递到OpenGL ES中,并通知OpenGL ES绘制数组:

        glLineWidth(2.0);
        glVertexPointer(2,GL_FLOAT,0,vertices);
        glDrawArrays(GL_LINES,0,2);

    在OpenGL ES中完成绘图之后,我们必须告诉他渲染其缓冲器,并且告诉我们的视图上下文显示新渲染的缓冲器:

     glBindRenderbufferOES(GL_RENDERBUFFER_OES,viewRenderbuffer);
     [context presentRenderbuffer:GL_RENDERBUFFER_OES];

    为了阐明在OpenGL中绘图的过程由3个步骤组成。首先,在上下文中绘图。其次,完成所有绘图之后,将上下文呈现到缓冲器中。第三,呈现渲染缓冲器,即当像素实际绘制到屏幕上时。

    正如你所见,OpenGL示例比较长,当查看绘制椭圆的过程时,Quartz 2D和OpenGL ES之间的差别变得更加明显。OpenGL ES不知道如何绘制椭圆。OpenGL是OpenGL ES的老大哥甚至前辈,他有许多生成常见的二维和三维形状的便利函数,而这些便利函数只是从OpenGL ES分离出来的一部分功能,这使得OpenGL更加精简并且更加适合在嵌入式设备中使用。因此,更多责任落在了开发人员的身上。

    为了绘制椭圆,我们将定义一个顶点数组,该数组存放720个GLfloat,这将存放360个点的x和y位置,围绕圆一度一个。我们可以更改点数来提高或降低此圆的平滑度。

        GLfloat vertices[720];

    接下来,我们将根据存储在firstTouch和lastTouch中的两个点计算此椭圆的水平半径和垂直半径。

        GLfloat xradius=(firstTouch.x>lastTouch.x)? (firstTouch.x-lastTouch.x)/2:(lastTouch.x-firstTouch.x)/2;
        GLfloat yradius=(self.frame.size.height-firstTouch.y>self.frame.size.height-lastTouch.y)?
                  ((self.frame.size.height-firstTouch.y)-(self.frame.size.height-lastTouch.y))/2:
                  ((self.frame.size.height-lastTouch.y)-(self.frame.size.height-firstTouch.y))/2;

    然后,我们将围绕圆进行循环,计算围绕圆的正确的点:

        for (int i=0;i<=720;i+=2)
        {
          GLfloat xOffset=(firstTouch.x>lastTouch.x)?lastTouch.x+xradius:firstTouch.x+xradius;
          GLfloat yOffset=(self.frame.size.height-firstTouch.y>self.frame.size.height-lastTouch.y)?
                    self.frame.size.height-lastTouch.y+yradius:
                    self.frame.size.height-firstTouch.y+yradius;
          vertices[i]=(cos(degreesToRadian(i))*xradius)+xOffset;
          vertices[i+1]=(sin(degreesToRadian(i))*yradius)+yOffset;
        }

    最后,我们将顶点数组提供给OpenGL ES,通知OpenGL ES绘制并渲染他,然后通知上下文呈现新渲染的图像:

        glVertexPointer(2,GL_FLOAT,0,vertices);
        glDrawArrays(GL_TRIANGLE_FAN,0,360);
     glBindRenderbufferOES(GL_RENDERBUFFER_OES,viewRenderbuffer);
     [context presentRenderbuffer:GL_RENDERBUFFER_OES];

    绘制矩形的方法就不再赘述了。
    绘制图像的方法也不再赘述。

    不需要告知OpenGL ES将更新屏幕的哪些部分。他会计算出来并且利用硬件加速以最高效的方式绘制。但是,在编译运行之前,需要就爱那个这两个框架链接到你的项目。按照第五章介绍的添加Core Graphics框架的说明,然后选择OpenGLES.framework和QuartzCore.framework。

    提示:如果你想创建一个全屏的OpenGL ES应用程序,不必手动构建他。Xcode为你提供了一个实用的模版。该模版为你设置了屏幕和缓冲器,甚至将一些示例绘图和动画代码放置到类中,以便你可以看到放置你的代码的位置。这个模版是OpenGL ES Application模版。

    12.6 小结
    本章介绍的只是一小点皮毛。

  • 相关阅读:
    Python shutil模块
    Flask 上传文件
    Flask DBUtils
    flash-session
    Flash 上下文管理
    python 栈
    python 偏函数
    threding.local
    next() 与 nextLine() 区别
    Thread.sleep(1000*3); // 休眠3秒
  • 原文地址:https://www.cnblogs.com/itlover2013/p/5673973.html
Copyright © 2020-2023  润新知