Win32 OpenGL 编程 ( 3 ) 基本图元(点,直线,多边形)的绘制
write by 九天雁翎 (JTianLing) -- blog.csdn.net/vagrxie
讨论新闻组及文件
在前面两篇相关文章
《 Win32 OpenGL 编程(1)Win32下的OpenGL编程必须步骤 》
《 Win32 OpenGL编程(2) 寻找缺失的OpenGL函数 》
中,我们已经建立了一个较为全面的 Win32 OpenGL 编程环境及一个简单的框架,并且,实际上掌握了 OpenGL 在 Windows 下与 Win32 窗口交互的方法,在此基础上,总算是可以正式进行一些 OpenGL 相关知识的学习,前面的那些也就算是热身。本文的目的是将 OpenGL 中基本图元(点,线,多边形)的绘制大概的讲解一遍,最后可以组合的使用这些技术用 OpenGL 完成较为复杂的 2D 图形。
首先讲讲相关的概念,在 OpenGL 中,即使是复杂的图形,实际上也是由一些非常基本的图元组成,即点,直线,多边形,多边形中用的较多的又是三角形和矩形。在数学中,两点确定一条直线,三点确定一个三角形和一个面,同一个面上的四个点确定一个四边形。。。。。。在 OpenGL 中也大致的利用此方式来确定直线和多边形,也就是说,当你想画一个直线或者一个多边形的时候,只需要告诉 OpenGL 能确定此直线或者多边形的点即可。用参考 2 中的描述是:“在 OpenGL 中,所有的几何物体最终都描述成一组有序的顶点”。有此基本的概念后就可以看下面的例子了。
这里的 Hello World 程序指的是一个利用 OpenGL 完成的矩形绘制程序,相对于在系列文章 1 中的 Win32 OpenGL 编程框架,简化了很多东西,只剩下最最基本的 OpenGL 元素,但也是一个完善的 OpenGL 示例了。此示例显示的是一个白色的矩形,运行效果如附图 1 ,完整代码见我博客代码的 2009-10-12/SimpleRectangle 工程,具体下载及查看方法见本文最后的说明。
此示例 OpenGL 相关的主要就是两部分。
//OpenGL 初始化开始
void SceneInit ( int w , int h )
{
}
// 这里进行所有的绘图工作
void SceneShow ( GLvoid )
{
glClear ( GL_COLOR_BUFFER_BIT );
glBegin ( GL_QUADS );
glVertex3f (-0.5, -0.5, 0.0);
glVertex3f (0.5, -0.5, 0.0);
glVertex3f (0.5, 0.5, 0.0);
glVertex3f (-0.5, 0.5, 0.0);
glEnd ();
glFlush ();
}
我们下面从 OpenGL 的角度来分析此程序。作为 HelloWorld 级的程序,我简化了很多东西,首先 OpenGL 的初始化省略了,用的都是 OpenGL 的默认值,具体有哪些,后面一步一步说。以下是按步骤说明每个 OpenGL 函数。
OpenGL 参考手册 :
glClear takes a single argument that is the bitwise OR of several values indicating which buffer is to be cleared.
GL_COLOR_BUFFER_BIT Indicates the buffers currently enabled for color writing.
作用是清除颜色缓冲区。类似于我们使用一块新申请的内存时先用 memset/ZeroMemory 去清零一下,用这样的 Clear 操作为我们需要使用的颜色缓冲区 (Color Buffer) 清零一下。假如没有这样的操作,以前留在显存 / 内存 ( 不确定 ) 中的值会影响我们的操作,并且,这样的问题往往是非常难以调试和发现的,这一点大家可以尝试一下。
OpenGL 参考手册 :
glBegin, glEnd - delimit the vertices of a primitive or a group of like primitives
OpenGL 代码的主体部分是:
glBegin ( GL_QUADS );
glVertex3f (-0.5, -0.5, 0.0);
glVertex3f (0.5, -0.5, 0.0);
glVertex3f (0.5, 0.5, 0.0);
glVertex3f (-0.5, 0.5, 0.0);
glEnd ();
glBegin 与 glEnd 很明显是一对, 标志着一组 OpenGL 操作的开始和结束。 并且在参数中告诉了 OpenGL 下面的操作是针对什么图形进行的,此例中 GL_QUADS 是表示四边形。事实上还有很多其他的参数来表示各类图形,在《 OpenGL Programming Guide 》的 此页 中 Figure 2-7 : Geometric Primitive Types 一图形象的说明了各个参数的作用。
glVertex3f 就是在 基本图元相关概念 一节提到的 OpenGL 中确定顶点的函数。简而言之,上面的 4 句 glVertex3f 确定了矩形的 4 个顶点。(注意顺序)然后, OpenGL 就会自动根据 glBegin 指定的参数去完成相关的绘制任务了, 此例中 GL_QUADS 是表示四边形,所以最后的效果是一个矩形,实际的其他参数读者可以自己尝试一下。
我们再看一下 glVertex* 指定顶点的代码:
glVertex3f (-0.5, -0.5, 0.0);
glVertex3f (0.5, -0.5, 0.0);
glVertex3f (0.5, 0.5, 0.0);
glVertex3f (-0.5, 0.5, 0.0);
为什么上述就指定了一个矩形的四个顶点呢?需要说明的是,在 OpenGL 中默认坐标体系与 Windows 中常用的不同, Windows 中常用的坐标体系(仅 2D )是用户区的左上角为坐标原点,即 (0.0,0.0) 点,右为坐标轴的 X 轴正方向,下为 Y 轴正方向, OpenGL 中的坐标轴( 3D )默认以客户区中心点为坐标原点( 0.0,0.0,0.0 ),右为坐标轴的 X 轴正方向,上为 Y 轴正方向,垂直指出屏幕的方向为 Z 轴正方向。长度定义是将客户区范围为按单位长度定义,即整个客户区恰好是 (-1,-1) (左下)到 (1,1) (右上)。附图 2 是一个上述示例程序附上 OpenGL 的平面坐标系的图,也可以作为 OpenGL 默认坐标系的参考图。
这里顺面介绍一下 OpenGL 函数的命名规范,因为 C 语言天生的弱点及丰富的数据类型,在 OpenGL 中凡是牵涉到与参数数量和数据类型相关的函数,一般的命名方式都是 xxxx[n][t] 。
xxxx 表示函数的意义, [t] 用于表示此函数对应的类型。一般用单个的字母表示参数的类型, s 表示 16 位整数( OpenGL 中将这个类型定义为 GLshort ), i 表示 32 位整数( OpenGL 中将这个类型定义为 GLint 和 GLsizei ), f 表示 32 位浮点数( OpenGL 中将这个类型定义为 GLfloat ), d 表示 64 位浮点数( OpenGL 中将这个类型定义为 GLdouble )。此例中使用的是 32 位浮点数,所以是 f 。这是 C 语言没有函数重载机制的天生弱点导致的扭曲应对方案。(用 C++ 就不需要这么麻烦了)
然后是数字,因为同样的原因,在 C 语言中一个同样意义的函数不能同时有不同个数的参数,所以 OpenGL 用一个数字来表示参数的个数,此例中是 3 ,表示以 3 个参数(即点的 X,Y,Z 坐标)来表示顶点。(事实上还有 glVertex2* , glVertex4* )
比如此例中,用如下代码效果是一样的:
glVertex2f (-0.5, -0.5);
glVertex2f (0.5, -0.5);
glVertex2f (0.5, 0.5) ;
glVertex2f (-0.5, 0.5);
在 glVertex2f 中, Z 轴默认为 0.
这里我依照参考 2 的用法,以 * 作为通配符来表示一组函数, * 既可以表示代表参数数量的数字也可以表示代表类型的字母。
OpenGL 参考手册 :
glFlush - force execution of GL commands in finite time
说白了就是强制执行已经指定的 OpenGL 命令,与 fflush 命名类似作用也类似。
以上的 5 个 OpenGL 函数就构成了一个基本的 OpenGL 程序(不包括模板中使用的那些),由 glClear 清空颜色缓冲区获得干净的环境,由 glBegin 指定开始一组顶点操作的开始,并确认绘制图形,由 glVertex* 指定顶点,由 glEnd 表示操作结束,由 glFlush 强制开始绘图。运行效果如附图 1 。
事实上,上一节已经包含了所有的知识,各个图元在 OpenGL 中绘制方式的不同仅仅在于 glBegin 的参数不同而已,这里将基本图元简单介绍一下。
点是最常用的图元之一了,而且所有的图像都可以看做是由点构成的,事实上屏幕也就是由一个一个像素点构成了图像:)
画点的方式是使用 GL_POINTS 为参数调用 glBegin 。那么,每一个 glVertex* 指定顶点就会绘制成一个单独的点。点的默认大小为 1 个像素,可以通过 glPointSize 函数来改变点的大小,在点比较大并且没有开启抗锯齿时,是按照一个正方形来绘制的。比如上例中,仅仅将 GL_QUADS 换成 GL_POINTS ,将是绘制上述矩形的 4 个顶点,为了截图效果显著,调用 glPointSize 将点的大小改为 20 。如下代码:
// 这里进行所有的绘图工作
void SceneShow ( GLvoid )
{
glClear ( GL_COLOR_BUFFER_BIT );
glPointSize (20);
glBegin ( GL_POINTS );
glVertex3f (-0.5, -0.5, 0.0);
glVertex3f (0.5, -0.5, 0.0);
glVertex3f (0.5, 0.5, 0.0);
glVertex3f (-0.5, 0.5, 0.0);
glEnd ();
glFlush ();
}
效果如附图 3 所示。
光是点也可以做很多有趣的应用, Windows 的屏保模拟星空即是其一,事实上,在原来学习简单的图形编程时,我用多种语言尝试了用点做一些有意思的东西。见简单图形编程学习系列:
《 简单图形编程的学习( 2) ---点 (Qt实现 ) 》
《 简单图形编程的学习( 2) ---点 (Windows GDI实现 ) 》
《 简单图形编程的学习( 2) ---点 (small basic实现 ) 》
其中点与文字结合形成的那个星空文字效果我是印象深刻,我一直将其作为“简单的编程技术 + 创意”也能很强大的例子。
直线也是很基础的东西了,但是 OpenGL 中的直线与数学概念上有些区别,不知道外国的那些专家在命名时为啥乱了,其实 OpenGL 中的直线与数学中的线段概念一致,有两个端点确认长度。事实上,在 OpenGL 绘制中,指定的也就是线段的两个端点。用 GL_LINES 调用 glBegin 时,表示绘制的是直线。默认情况下宽度为一个像素,同样, OpenGL 也提供了函数 glLineWidth 用以改变直线的宽度。比如上例中,仅仅将 GL_QUADS 换成 GL_LINES 将是两条平行的直线,并用 glLineWidth 将直线宽度改为 5 ,如下代码:
// 这里进行所有的绘图工作
void SceneShow ( GLvoid )
{
glClear ( GL_COLOR_BUFFER_BIT );
glLineWidth (5);
glBegin ( GL_LINES );
glVertex3f (-0.5, -0.5, 0.0);
glVertex3f (0.5, -0.5, 0.0);
glVertex3f (0.5, 0.5, 0.0);
glVertex3f (-0.5, 0.5, 0.0);
glEnd ();
glFlush ();
}
运行效果如附图 4 所示。
直线的应用也是非常广泛的,还记得 Windows 的屏保中的变幻线吗?千变万变,无论多么绚烂的效果,其实也仅仅是一条条变化的线段而已。
其实可以将多边形看成是从点到线到面的一种扩展,这里的面自然也是有范围的,那么就成了多边形了,在绘制图形中用的最多的是三角形,因为三角形肯定在同一个面上,这样可以简化很多计算的处理。 GL_TRIANGLES 调用 glBegin 表示开始绘制三角形。然后还有 GL_QUADS 表示四边形(上例中的用法), GL_POLYGON 表示多边形(必须是凸的)。
其实除了上面讲到的那些参数,还有一些额外的参数,比如 GL_LINE_STRIP,GL_TRIANGLES_STRIP 等,表示绘制的时候绘制一些列连续的图形,这些参数用文字解释起来不够形象,还是推荐参考 《 OpenGL Programming Guide 》的 此页 中 Figure 2-7 : Geometric Primitive Types 一图,此图形象的展示了各个参数时对点的解释方式和顺序。在此图的下面,还有个表总结了一下各个参数的作用。
在一个 glBegin 和 glEnd 对中可以连续的制定多个顶点,甚至超出你指定的图形的数目,比如绘制三角形时可以指定 6 个点,那么此时,将会绘制两个三角形而不是一个,这样而当指定 4 , 5 个点时用 GL_TRIANGLES 参数时将会丢弃,用 GL_TRIANGLE_STRIP 参数时将会连续绘制两个三角形,具体的解释方法也是要看 glBegin 参数的,建议还是参考上述图片。
1. 《 OpenGL Reference Manual 》, OpenGL 参考手册
2. 《 OpenGL 编程指南》(《 OpenGL Programming Guide 》), Dave Shreiner , Mason Woo , Jackie Neider , Tom Davis 著,徐波译,机械工业出版社
3. 《 Nehe OpenGL Tutorials 》, Nehe 著,在 http://nehe.gamedev.net/ 上可以找到教程及相关的代码下载,(有 PDF 版本教程下载) Nehe 自己还做了一个面向对象的框架,作为演示程序来说,这样的框架非常合适。也有 中文版 ,各取所需吧。
4. 《 OpenGL 入门学习》 , eastcowboy 著,这是我在网上找到的一个比较好的教程,较为完善,而且非常通俗。这是第一篇的地址: http://bbs.pfan.cn/post-184355.html
1. 《 Win32 OpenGL 编程(1)Win32下的OpenGL编程必须步骤 》
2. 《 Win32 OpenGL编程(2) 寻找缺失的OpenGL函数 》
本文中所有代码(如果有的话)都能用 Mercurial 在 Google Code 中下载。
文章以博文发表的日期分目录存放,下载地址见:
http://code.google.com/p/jtianling/source/checkout?repo=blog-sample-code
或者直接使用 Mercurial 克隆下列库:
https://blog-sample-code.jtianling.googlecode.com/hg/
Mercurial 使用方法见《 分布式的,新一代版本控制系统 Mercurial的介绍及简要入门 》
write by 九天雁翎 (JTianLing) -- blog.csdn.net/vagrxie