zoukankan      html  css  js  c++  java
  • 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 小结
    本章介绍的只是一小点皮毛。

  • 相关阅读:
    MVC模式-----struts2框架(2)
    MVC模式-----struts2框架
    html的<h>标签
    jsp脚本元素
    LeetCode "Paint House"
    LeetCode "Longest Substring with At Most Two Distinct Characters"
    LeetCode "Graph Valid Tree"
    LeetCode "Shortest Word Distance"
    LeetCode "Verify Preorder Sequence in Binary Search Tree"
    LeetCode "Binary Tree Upside Down"
  • 原文地址:https://www.cnblogs.com/itlover2013/p/5673973.html
Copyright © 2011-2022 走看看