转贴:从零开始学习OpenGL ES之二 - 简单绘图概述
luyued 发布于 2011-06-25 11:48 浏览 N 次还有许多理论知识需要讨论,但与其花许多时间在复杂的数学公式或难以理解的概念上,还不如让我们开始熟悉OpenGL ES的基本绘图功能。
请下载OpenGL Xcode项目模板。我们使用此模板而不是Apple提供的模板。你可以解压到下面目录来安装它:
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/
此模板用于全屏OpenGL程序,它具有一个OpenGL视图以及相应的视图控制器。 大部分时候你不需要动到此视图。此视图用于处理一些诸如缓存切换之类的事物,但在两处调用了其控制器类。
首先,当设定视图时,调用了一次控制器。调用视图控制器的 setupView: 方法使控制器有机会增加所需的设定工作。这里是你设定视口,添加光源以及进行其他项目相关设定的地方。现在我们将忽略此方法。此方法中已经有非常基本的设定以允许你进行简单地绘图。
控制器的 drawView: 方法根据常数kRenderingFrequency的值定期地被调用。kRenderingFrequency的初始值为15.0,表示 drawView: 方法每秒钟被调用15次。如果你希望改变渲染的频率,你可以在ConstantsAndMacros.h中找到此常数的定义。
首先加入下列代码到GLViewController.m的drawView: 方法中:
- (void)drawView:(GLView*)view; { Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0); Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0); Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0); Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3); glLoadIdentity(); glClearColor(0.7, 0.7, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnableClientState(GL_VERTEX_ARRAY); glColor4f(1.0, 0.0, 0.0, 1.0); glVertexPointer(3, GL_FLOAT, 0, &triangle); glDrawArrays(GL_TRIANGLES, 0, 9); glDisableClientState(GL_VERTEX_ARRAY); }
在讨论我们到底做了什么之前,先运行一下,你应该看到以下画面:
这是个简单的方法;如果你试过了,你可能已经知道我们到底做了什么,但这里我们还是一起过一遍。因为我们的任务是画三角形,所以需要三个顶点,因此我们创建三个上一篇文章中讨论过的Vertex3D对象:
Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0); Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0); Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
你应该注意到了三个顶点的z值是一样的,其值(-3.0)是处于原点 “之后”的。因为我们还没有做任何改变,所以我们是站在原点上观察虚拟世界的,这是默认的起点位置。将三角形放置在z值为-3处,可以保证我们可以在屏幕上看到它。
随后,我们创建一个由这三个顶点构成的三角形。
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);
这些代码很容易理解,对吗?但是,在幕后,电脑是将其视为一个包含9个 GLfloat 的数组。如下:
GLfloat triangle[] = {0.0, 1.0, -3.0, 1.0, 0.0, -3.0, -1.0, 0.0, -3.0};
并不是完全相同 – 这里有一个很小但很重要的区别。在我们的示例中,我们传递给OpenGL的是 Triangle3D 对象的地址(即 &triangle ),但在第二个使用数组的示例中,由于C数组是指针,我们传递的是数组。现在不需要考虑太多,因为这将是最后一次我用这种方式(第二种方法)定义一个 Triangle3D 对象。等一下我将解释原因,但现在让我们先过一遍代码。下一步我们做的是加载单位矩阵。我将花至少一整篇文章讨论变换矩阵。我们暂且将其视为OpenGL的“复位开关”。它将清除虚拟世界中的一切旋转,移动或其他变化并将观察者置于原点。
glLoadIdentity();
之后,我们告诉OpenGL所有的绘制工作是在一个灰色背景上进行的。OpenGL通常需要用四个钳位值来定义颜色。上一篇文章中有提过,钳位浮点数是0.0 到 1.0之间的浮点数。我们通过定义红,绿,蓝以及alpha元素来定义颜色, alpha值定义了颜色之后物体的透视程度。现在暂时不用管它,将其设为1.0,代表完全不透明。
glClearColor(0.7, 0.7, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
在OpenGL中要定义白色,我们需要将四个元素全部设为1.0。要定义不透明的黑色,则定义红,绿,蓝为0.0,alpha为1.0。上例代码的第二行是通知OpenGL清除以前的一切图形并将其设为clear颜色。
你可能想知道 glClear()的两个参数是什么意思。简单地说,它们是存储与位域中的常量。OpenGL 保存了一系列 缓存(buffers),即用于绘图各方面的内存块。将这两个值进行逻辑或是通知OpenGL清除两个不同的缓存 – 颜色缓存(color buffer) 和 深度缓存(depth buffer)。 颜色缓存保存当前帧各像素的颜色。基本上就是你在屏幕上看到的。深度缓存(有时也称为“z-buffer”)保存每个潜在像素离观察者距离的信息。使用此信息可以确定一个像素是否需要被绘制出来。这两个缓存是OpenGL中最常见的缓存。还有其他一些缓存,如模板缓存(stencil buffer)和累积缓存(accumulation buffer),但现在我们暂时不讨论这些。我们现在只需记住在绘制一帧之前,必须清除这两个缓存以保证不会和以前的内容混杂。
然后,我们要启动OpenGL的一项称为vertex arrays(顶点数组)的特性。此特性可能只需要setupView:方法中启动一次,但作为基本准则,我喜欢启动和禁止我使用的功能。你永远也不会知道是否另一段代码会做不同处理。如果你打开你需要的功能然后关闭它,产生问题的几率将大为减小。就本例来说,如果另一个类不使用顶点数组而使用顶点缓存的话,任何一段代码遗留了启动了的特性或没有显性启动其需要的特性,这一段或两段代码都会导致不可预知的结果。
glEnableClientState(GL_VERTEX_ARRAY);
接下来我们设置了绘图时所需的颜色。此行代码将绘图颜色设为鲜艳的红色。
glColor4f(1.0, 0.0, 0.0, 1.0);
现在,直到下次调用 glColor4f()前所有的图形都是以红色绘制。有一些例外的情况,例如绘制纹理形状时,但基本上,这样设定颜色可以使颜色保持。
由于我们使用顶点数组,我们必须通知OpenGL顶点的数组在什么地方。记住,顶点数组只是一个GLfloat的C数组,每三个值代表一个顶点。我们创建了Triangle3D 对象,但在内存中,它完全等同于9个连续的GLfloat, 所以我们可以传递此三角形对象的地址。
glVertexPointer(3, GL_FLOAT, 0, &triangle);
glVertexPointer() 的第一个参数指示了多少个GLfloat代表一个顶点。根据你是在进行二维或三维绘图,你可以传递2或者3。尽管我们的物体是存在于一个平面的,我们仍然将其绘制在三维虚拟世界中,因此每个顶点用三个数值表示,所以我们传递3给函数。然后,我们传递一个枚举值告诉OpenGL顶点是由GLfloat构成。OpenGL ES允许你在当地数组中使用大部分的数据类型,但除GL_FLOAT外,其他都很少见。下一个参数… 现在不需要考虑下一个参数。那是以后讨论的主题。现在,它始终为0。在以后的文章中,我将讨论怎样使用此参数将同一对象以不同的数据类型混杂在一个数据结构中。
随后,我们通知OpenGL通过刚才提交的顶点数组来绘制三角形。
glDrawArrays(GL_TRIANGLES, 0, 9);
你可能已经可以猜到,第一个枚举值是告诉OpenGL绘制什么。尽管OpenGL ES不支持绘制三角形之外的四边形或其他多边形,但它仍然支持一些其他绘图模式,如绘制点,线,线回路,三角形条和三角形扇。稍后我们将讨论这些绘图模式。
最后,我们要禁止先前启动了的特性以保证不会被其他地方的代码弄混。本例中没有其他的代码了,但通常你可以使用OpenGL绘制多个物体。
glDisableClientState(GL_VERTEX_ARRAY);
好了,我们的代码可以工作了尽管它不是那么引人入胜而且不是十分高效,但它确确实实可以工作。每秒钟我们的代码被调用数次。不相信?加入下列黑体代码再次运行:
- (void)drawView:(GLView*)view; { static GLfloat rotation = 0.0; Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0); Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0); Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0); Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3); glLoadIdentity(); glRotatef(rotation, 0.0, 0.0, 1.0); glClearColor(0.7, 0.7, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnableClientState(GL_VERTEX_ARRAY); glColor4f(1.0, 0.0, 0.0, 1.0); glVertexPointer(3, GL_FLOAT, 0, &triangle); glDrawArrays(GL_TRIANGLES, 0, 9); glDisableClientState(GL_VERTEX_ARRAY); rotation+= 0.5; }
当你再次运行时,三角形将沿着原点缓缓转动。先不需要关注太多旋转的逻辑。我只是想告诉你我们的代码每秒钟被调用了多次。
如果你想画正方形怎么办?OpenGL ES并不支持正方形,所以我们只能通过三角形来定义正方形。这很简单 – 一个正方形可以通过两个三角形构成。我们要怎样调整上叙代码来绘制两个三角形?是不是可以创建两个Triangle3D?是的,你可以这样做,但那没有效率。我们最好将两个三角形置入同一个顶点数组中。我们可以通过定义一个包含两个Triangle3D对象的数组,或分配大小等于两个Triangle3D对象或18个GLfloat的内存.
这是一种方法:
- (void)drawView:(GLView*)view; { Triangle3D triangle[2]; triangle[0].v1 = Vertex3DMake(0.0, 1.0, -3.0); triangle[0].v2 = Vertex3DMake(1.0, 0.0, -3.0); triangle[0].v3 = Vertex3DMake(-1.0, 0.0, -3.0); triangle[1].v1 = Vertex3DMake(-1.0, 0.0, -3.0); triangle[1].v2 = Vertex3DMake(1.0, 0.0, -3.0); triangle[1].v3 = Vertex3DMake(0.0, -1.0, -3.0); glLoadIdentity(); glClearColor(0.7, 0.7, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnableClientState(GL_VERTEX_ARRAY); glColor4f(1.0, 0.0, 0.0, 1.0); glVertexPointer(3, GL_FLOAT, 0, &triangle); glDrawArrays(GL_TRIANGLES, 0, 18); glDisableClientState(GL_VERTEX_ARRAY); }
运行,你将看到如下屏幕:
由于Vertex3DMake()方法在堆中创建新的Vertex3D然后复制其值到数组中而造成额外的内存被占用,因此上述代码并不理想。
The Unreal Universe
A Book on Physics and Philosophy
Pages: 292
(282 in eBook)
Trimsize: 6" x 9"
Illustrations: 34
(9 in color in eBook)
Tables: 8
Bibliography: Yes
Index: Yes
ISBN:
9789810575946
对于这样简单的情况是没有什么问题的,但是在一个更为复杂的情况下,如果定义的3D物体很大时,那么你不会希望将其分配在堆中而且你不会希望对一个顶点不止一次地进行内存分配,所以最好是养成习惯通过我们的老朋友 malloc() (我更喜欢用 calloc() ,因为它会将所有值设为0,比较易于查找错误)将顶点分配在栈中。 首先我们需要一个函数设定现存顶点的值而不是像Vertex3DMake()一样创建一个新对象。如下:
static inline void Vertex3DSet(Vertex3D *vertex, CGFloat inX, CGFloat inY, CGFloat inZ) { vertex->x = inX; vertex->y = inY; vertex->z = inZ; }
现在,我们使用新方法将两个三角形分配在栈中,重写代码:
- (void)drawView:(GLView*)view; { Triangle3D *triangles = malloc(sizeof(Triangle3D) * 2); Vertex3DSet(&triangles[0].v1, 0.0, 1.0, -3.0); Vertex3DSet(&triangles[0].v2, 1.0, 0.0, -3.0); Vertex3DSet(&triangles[0].v3, -1.0, 0.0, -3.0); Vertex3DSet(&triangles[1].v1, -1.0, 0.0, -3.0); Vertex3DSet(&triangles[1].v2, 1.0, 0.0, -3.0); Vertex3DSet(&triangles[1].v3, 0.0, -1.0, -3.0); glLoadIdentity(); glClearColor(0.7, 0.7, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnableClientState(GL_VERTEX_ARRAY); glColor4f(1.0, 0.0, 0.0, 1.0); glVertexPointer(3, GL_FLOAT, 0, triangles); glDrawArrays(GL_TRIANGLES, 0, 18); glDisableClientState(GL_VERTEX_ARRAY); if (triangles != NULL) free(triangles); }
好了,我们已经讨论了许多基础知识,我们现在更深入一点。记住我说过OpenGL ES不止一种绘图方式吗?现在正方形需要6个顶点(18个 GLfloat),实际上我们可以使用 triangle strips (GL_TRIANGLE_STRIP)方法通过四个顶点(12个 GLfloat)来绘制正方形。
这里是三角形条的基本概念:第一个三角形条是由前三个顶点构成(索引0, 1, 2)。第二个三角形条是由前一个三角形的两个顶点加上数组中的下一个顶点构成,继续直到整个数组结束。看下图更清楚 – 第一个三角形由顶点 1, 2, 3构成,下一个三角形由顶点 2, 3, 4构成,等等:
所以,我们的正方形是这样构成的:
- 07-01· 禁教唐诗算术能还幼儿快
- 07-01· 2011年06月17日
- 07-01· 唐诗宋词英译:李商隐 筹
- 07-01· 仿评《唐诗1000首》第186首
- 07-01· 没事干的时候背背唐诗吧
- 07-01· [转载]唐诗中“斜”字该读
- 07-01· 湖南醴陵瓷业转型升级
- 07-01· 奇瑞风云2两厢黑色|2010款
- 07-01· 摩根士丹利华鑫摩根士丹
- 07-01· 摩根士丹利华鑫近期优选
- 07-01· 中金投行部大摩出售中金
- 07-01· 摩根士丹利招聘6月2日【实
- 07-01· 营养防病圣典
- 07-01· 《博伽梵歌原意》之第十
- 07-01· [不错]斑斓圣典---减肥中常
- 07-01· 武乐圣典《太极武当》:武
- 07-01· 铁血英雄-现阶段战功牌兑
- 07-01· 2011年06月10日【原创】南歌
- 07-01· 【淘宝网信息】- 2010年的
- 07-01· 深圳品牌女装有哪些?