IOS-OpenGLES 基础教程(一)

http://www.helmsmansoft.com/index.php/archives/1417

今天起本站将推出IOS-OpenGLES基础教程,当然本人也是一面自学一面将学习中所用到的和学到的东西一并分享给大家,在本教程中,本人会对代码进行逐行的注释讲解,资源来自于网络和书籍的整合,如在教程中有错误的地方,希望大家及时指正,可随时发送邮件到[email protected] 与本人取得联系,多谢。

下面进入正题:

OpenGL 数据类型
首先我们要讨论的是OpenGL的数据类型。因为OpenGL是一个跨平台的API,数据类 型的大小会随使用的编程语言以及处理器(64位,32 位,16位)等的不同而不同,所 以OpenGL定义了自己的数据类型。当传递数据到OpenGL时,你应该坚持使用这些 OpenGL的数据类型,从而保证 传递数据的尺寸和精度正确。不这样做的后果是可能 会导致无法预料的结果或由于运行时的数据转换造成效率低下。不论平台或语言实现 的OpenGL都采用这种 方式定义数据类型以保证在各平台上数据的尺寸一致,并使平 台间OpenGL代码移植更为容易。
下面是OpenGL的各种数据类型:
GLenum:用于GL枚举的无符号整型。通常用于通知OpenGL由指针传递的存储 于数组中数据的类型(例如,GL_FLOA T用于指示数组由GLfloat组成)。
GLboolean:用于单布尔值。OpenGLES还定义了其自己的“真”和“假”值 (GL_TRUE和GL_FALSE)以避免平台和语言的差别。当向OpenGL传递布尔 值时,请使用这些值而不是 使用YES或NO(尽管由于它们的定义实际没有区 别,即使你不小心使用了YES或NO。但是,使用GL-定义值是一个好的习 惯。)
GLbitfield:用于将多个布尔值(最多32个)打包到单个使用位操作变量的四 字节整型。我们将在第一次使用位域变量时详细介绍,请参阅 wikipedia
GLbyte:有符号单字节整型,包含数值从-128到127
GLshort:有符号双字节整型,包含数值从−32,768到32,767
GLint:有符号四字节整型,包含数值从−2,147,483,648到2,147,483,647
GLsizei:有符号四字节整型,用于代表数据的尺寸(字节),类似于C中的
size_t
GLubyte:无符号单字节整型,包含数值从0到255。
GLushort:无符号双字节整型,包含数值从0到65,535
GLuint:无符号四字节整型,包含数值从0到4,294,967,295
GLfloat:四字节精度IEEE754-1985浮点数
GLclampf:这也是四字节精度浮点数,但OpenGL使用GLclampf特别表示数值
为0.0 到 1.0
GLvoid:void值用于指示一个函数没有返回值,或没有参数
GLfixed:定点数使用整型数存储实数。由于大部分计算机处理器在处理整型
数比处理浮点数快很多,这通常是对3D系统的优化方式。但因为iPhone具有用
于浮点运算的矢量处理器,我们将不讨论定点运算或GLfixed数据类型。
GLclampx:另一种定点型,用于使用定点运算来表示0.0到1.0之间的实数。正
如GLfixed,我们不会讨论或使用它。
OpenGL ES (至少iPhone目前所使用的版本)不支持8字节(64位)数据类型,如long 或double。OpenGL 其实具有这些大型数据类型,但考虑到大部分嵌入式设备屏幕尺寸 以及可能为它们所写的程序类型而且使用它们有可能对性能造成不利的影响,最后的 决定是在 OpenGL ES中排除这些数据类型。
点或顶点
3D图像的最小单位称为 点(point) 或者 顶点vertex。它们代表三维空间中的一个点 并用来建造更复杂的物体。多边形就是由点构成,而物体是由多个多边形组成。尽管 通常OpenGL支持多种多边形,但OpenGL ES只支持三边形(即三角形)。
如果你回忆高中学过的几何学,你可能会记得所谓笛卡尔坐标。 基本概念是在空间中 任选一点,称作原点。 然后你可以通过参照原点并使用三个代表三维的数值指定空间 中的任意一点,坐标是由三个想象的通过原点线表示的。从左至右的想象直线叫x- 轴。沿着x-轴从 左至右数值变大,向左移动数值变小。原点左方x为负值,右边为正 值。另外两轴同理。沿y轴向上,y值增加,向下y值减小。原点上方y为正,原点下方 为负。 对于z轴,当物体离开观察者,数值变小,向观察者移动(或超出观察者), 数值变大。原点前方z值为正,原点之后为负。下图帮助说明了这一点:

Note: iPhone上另一种绘图框架Core Graphics使用了稍微不同的坐标系统,当向屏幕上 方移动时y值减小,而向下移动y值增加。
沿各轴增加或减小的数值是以任意刻度进行的 – 它们不代表任何真实单位,如英尺, 英寸或米等。你可以选择任何对你的程序有意义的刻度。如果你想设计的游戏以英尺 为单位,你可以那样做。如果你希望单位为 毫米,同样可行。OpenGL不管它对最终 用户代表什么,只是将它作为单位处理,保证它们具有相同的距离。
由于任何物体在三维空间中的方位可以由三个数值表示,物体的位置通常在OpenGL中 由使用一个三维数组的三个GLfloat变量表示,数组中的第 一项(索引0)为x位置,第 二项(索引1)为y位置,第三项(索引2)为z位置。下面是一个创建OpenGL ES顶点 的简单例子:
GLfloat vertex[3]; vertex[0] = 10.0; vertex[1] = 23.75; vertex[2] = -12.532;
// x // y // z
在OpenGL ES中,通常将场景中所有构成所有或部分物体的提交为顶点数组。一个顶 点数组是包括场景中部分 或所有顶点数据的简单数组。我将在系列的下一篇教程中讨 论,有关顶点数组要记住的是它们的大小是基于呈现的顶点数乘以三(三维空间绘 图)或二(二维空间绘 图)。所以一个包含六个三维空间中的三角形的顶点数组由54 个GLfloat组成,因为每个三角形有三个顶点,而每个顶点有三个坐标,即6 x 3 x 3 = 54。
处理所有这些GLfloat是很痛苦的事情。幸运的是,有一个容易的方法。我们可以定义一个数据结构了保存多个顶点,像这样:
typedef struct { GLfloat x; GLfloat y; GLfloat z;
} Vertex3D;
通过这样做,我们的代码可读性更强:
Vertex3D vertex;
vertex.x = 10.0;
vertex.y = 23.75;
vertex.z = -12.532; 现在由于Vertex3D由三个GLfloat组成,向Vertex3D传递指针与向数组传递一个包 含三个GLfloat的 数组的指针完全一样。对于电脑而言毫无分别;两者具有同样的尺 寸和同样的字节数以及OpenGL需要的同样的顺序。将数据分组到数据结构只是让程序 员感到 更容易,处理起来更方便。如果你下载了文章开头处的Xcode模板,你会发现 此数据结构以及我后面将讨论的各种函数都定义在文件OpenGLCommon.h中。还有一 个内联函数用于创建单个顶点:
static inline Vertex3D Vertex3DMake(CGFloat inX, CGFloat inY, CGFloat inZ)
{
Vertex3D ret; ret.x = inX; ret.y = inY; ret.z = inZ; return ret;
}
如果你回忆起几何学(如果不记得也不要紧)的内容,你会知道空间中两点间的距离
是使用下面公式计算的:
我们可以在一个简单的内联函数中实现这个公式来计算三维空间中任何两点间的直线
距离:
static inline GLfloat Vertex3DCalculateDistanceBetweenVertices (Vertex3D first, Vertex3D second)
{
GLfloat deltaX = second.x – first.x; GLfloat deltaY = second.y – first.y; GLfloat deltaZ = second.z – first.z;
return sqrtf(deltaX*deltaX + deltaY*deltaY +
deltaZ*deltaZ ); };
三角形
由于OpenGL ES仅支持三角形,因此我们可以通过创建一个数据结构将三个顶点组合 成一个三角形物体。

typedef struct { Vertex3D v1; Vertex3D v2; Vertex3D v3;
} Triangle3D;
一个 Triangle3D实际上与一个九个GLfloat构成的数组是完全一样的,因为我们通 过顶点和三角形而不是GLfloat数组来构建物体,所以它能帮助我们更容易地处理我 们的代码。
然而关于三角形你需要知道更多的事情。在OpenGL中有一个概念叫卷绕(winding), 它表示顶点绘制的次序是重要的。不像真实世界中的物体,OpenGL中的多边形通常都 不会有两面。它们只有一面,被当做front face(前面), 三角形只有其front face面对 观察者时才可见。可以设置OpenGL将多边形作为两面处理,但默认状态下,三角形只 有一个可见面。通过知道哪一个面是多边形的前面或可见面,才能使OpenGL只做一半 的计算。
尽管有时多边形也可以独立存在,需要绘制其背面,但通常三角形是一个大物体的一 部分,其面对物体内部的一面永远也不可见。不被绘制的一面称为backface(背 面),OpenGL是通过观察顶点的绘制次序来确定front face和backface的。以反时针次 序绘制顶点的构成的面是frontface(默认,可以改变)。由于OpenGL可以很容易确定 哪个三角形对用户可见,所以它使用了一种称为Backface Culling(隐面消除) 的技术 来避免绘制视窗中多边形的不可见面。下一篇文章将讨论视窗,现在你可将其想象成 一个虚拟摄像或观察OpenGL世界的虚拟窗口。

上面使摘自网络的一些文档,下面说下在ios中的具体使用方法;

笔者使用的xcode版本为4.3.2

新建工程,选择”Single View Application”模板,工程创建好以后,添加QuartzCore.framework和OpenGLES.framework框架,新建UIView,起名为OpenGLRenderView.

OpenGLRenderView.h中的代码如下:

//
//  OpenGLRenderView.h
//  TheGameTwo
//
//  Created by helmsman soft on 12-6-7.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//
 
#import 
#import 
#import 
#import 
 
@interface OpenGLRenderView : UIView{
 
    CAEAGLLayer * _eaglLayer;
    EAGLContext * _context;
    GLuint colorRenderBuffer;
    GLuint positionSlot;
    GLuint colorSlot;
 
}
 
@property (nonatomic, retain) CAEAGLLayer *eaglLayer;
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) GLuint colorRenderBuffer;
@property (nonatomic, assign) GLuint positionSlot;
@property (nonatomic, assign) GLuint colorSlot;
 
@end

OpenGLRenderView.m中的代码如下:

//
//  OpenGLRenderView.m
//  TheGameTwo
//
//  Created by helmsman soft on 12-6-7.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//
 
#import "OpenGLRenderView.h"
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
 
        [self setupLayer];
        [self setupContext];
        [self setupRenderBuffer];
        [self setupFrameBuffer];
        [self render];
 
    }
    return self;
}
 
/*重写layerClass方法,默认位[CALayer class],重写后此方法告诉UIView这个视图的主要层是CAEAGLLayer,CAEAGLLayer是最终渲染opengl的地方,如果没有此层就无法渲染opengl,此层非常重要。*/
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}
 
- (void)setupLayer
{
    _eaglLayer = (CAEAGLLayer *)self.layer;   //获取层引用
    _eaglLayer.opaque = YES;//设置层不透明,无特别好的理由,不要在层中使用透明,此方法默认为透明,在此设置其不透明,如果透明会严重影响性能,特别实在opengl中。如果允许CAEAGLLayer透明,显示图层后的内容需要使用大量混色才能显示。混色是一个很费资源的处理过程,可能会带来性能问题。
}
 
//初始化上下文环境
- (void)setupContext
{
    EAGLRenderingAPI renderAPI = kEAGLRenderingAPIOpenGLES2;   //OpenGLES Version 2.0
    _context = [[EAGLContext alloc] initWithAPI:renderAPI];   //EAGLContext负责管理状态信息,命令和OpenGLES绘制时使用的资源等
 
    if(!_context){
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        [self release];     //如果失败释放自己,因为self已经被分配并占用内存
 
    }
 
    if(![EAGLContext setCurrentContext:_context]){
        NSLog(@"Failed setContext");
        [self release];
 
    };
 
}
 
/*创建渲染缓冲区*/
- (void)setupRenderBuffer
{
    glGenRenderbuffers(1, &colorRenderBuffer);  //创建渲染缓冲区对象,两个参数:第一个参数为生成渲染缓冲区的数目,第二个存储缓冲区,如果缓冲区数目大于1,则第二个参数应设置成数组,以保存所有返回缓冲区的名称;
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderBuffer);  //绑定缓冲区到GL_RENDERBUFFER,
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];//在渲染缓冲区绑定完成之后,为EAGLContext分配存储区,此命令将会绑定layer的可绘制对象到刚才绑定的OpenGLES的渲染缓冲区.
}
 
/*创建帧缓冲区*/
- (void)setupFrameBuffer
{
    GLuint frameBuffer;
    glGenFramebuffers(1, &frameBuffer);  //创建帧缓冲区,两个参数:第一个参数为生成帧缓冲区的数目,第二个存储缓冲区,同上.
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); //绑定帧缓冲区到GL_FRAMEBUFFER
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderBuffer);//此处,命令GL_COLOR_ATTACHMENT0,渲染缓冲区被关联到帧缓冲区,多个缓冲区关联到同一帧缓冲区的情况是可能的,这里,用glFramebufferRenderbuffer将渲染缓冲区作为一个特殊附件附加到帧缓冲区.
}
 
- (void)render
{
    glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);  //设定清屏操作使用的颜色值,此处数值应全为浮点型
    glClear(GL_COLOR_BUFFER_BIT);  //清屏,使用了逐位OR的掩码来指示将被清楚的缓冲区 
    [_context presentRenderbuffer:GL_RENDERBUFFER];//请求语境将渲染缓冲区中的内容呈现到屏幕上
}

上面的代码中已经加了详细的注释说明,这里就不多介绍了。代码大部分类源于网络,这里加了详细的注释,希望对学习者有所帮助。

上面的代码实现了在屏幕上渲染出单一颜色的功能,主要是清屏代码实现。


你可能感兴趣的:(IOS,Opengl)