这一小节主要描述点、直线、多边形的属性,通过这些属性,控制点、直线、多边形的显示方式。内容有点多。
点的细节
这里只描述了点的一个属性,点的大小,可以通过glPointSize函数来控制。然后通过glGetFloatv来进行查询。其中点的大小,还有抗锯齿和非抗锯齿之分。
void glPointSize(GLfloat size);
这个函数的参数,表示点的大小,单位是像素。size必须大于0.0,默认情况下为1.0。由于像素是最小的可显示单位,不能显示0.1个像素,所以,OpenGL对于浮点数采用了四舍五入,比如size = 1.1则只绘制一个像素乘以一个像素的方块,size=1.6则绘制两个像素乘以两个像素的方块。
如果启用了抗锯齿或多重采样,则绘制的点,不再是单纯的像素方块了。而是一个圆形的像素区域。一般情况下,位于边界的像素所使用的颜色强度较小,使边缘具有更平滑的外观。
这个点的大小,应该有一个取值范围,这个范围是多少呢?
这个可以通过函数进行查询。glGetFloatv()函数,以GL_ALIASED_POINT_RANGE为参数,可以查询在未进行抗锯齿处理的情况下的最小和最大的点,其中单词aliased表示 锯齿的。
那在抗锯齿情况下,如何查询点的大小范围呢?通过glGetFloatv()函数,参数使用GL_SMOOTH_POINT_SIZE_RANGE进行查询即可。除了可以查询范围之外,还可以查询特定抗锯齿点大小的精度。以GL_SMOOTH_POINT_SIZE_GRANULARITY为参数,调用glGetFloatv()即可。其中单词 granularity,意思是 间隔尺寸、粒度。
直线的细节
直线有两个属性,线宽 和 线型。 线宽是指线段的宽度,宽度越大,线段越粗。 线型是指这个线是实线,还是虚线,或者点线。
线宽
直线的宽度,使用glLineWidth来控制。
void glLineWidth(GLfloat width);
这个函数的参数width,也是以像素为单位。width参数也必须大于0.0,默认情况下为1.0。这个和点的控制函数glPointSize的参数是一样的。
这个线宽和点的大小在很多地方都有相同之处。这个也分抗锯齿和非抗锯齿。不使用抗锯齿,那么指定线宽为2,就是单纯的使用2个像素绘制直线。而如果使用了抗锯齿,那么,直线边界处的像素会画得淡一些。
同样,线宽范围,也可以通过函数查询。传递GL_ALIASED_LINE_WIDTH_RANGE给glGetFloatv()函数,可以查询非抗锯齿情况下,线宽范围。传递GL_SMOOTH_LINE_WITH_RANGE和GL_SMOOTH_LINE_WIDTH_GRANULARITY可以查询抗锯齿情况下,线的宽度范围和精度。
线型
书中管这个叫点画线。使用函数glLineStipple()进行控制直线的线型。stipple 这个单词,就是 点画,点刻 的意思。设置了线型以后,还需要开启直线点画功能。如下所示:
glLineStipple(1, 0x3F07);
glEnable(GL_LINE_STIPPLE);
下面glLineStipple的两个参数。第一个参数 GLint factor,表示重复因子。什么的重复呢?重复第二个参数的。那就必须先了解第二个参数。
第二个参数 GLushort pattern,是无符号,短整型的。这个数字,比如0x3F07可以表示成二进制 0011111100000111,二进制中的1表示绘制像素,0表示不绘制像素,需要从右往左解读。先绘制3个像素,因为右边有3个1,中间5个像素不绘制,再绘制6个像素,然后2个像素不绘制。这就形成了一定的线型。然后按照这个线型,重复的去绘制直线。
第一个参数factor这个重复因子,就表示第二个参数pattern的每一个二进制位,重复多少次。比如如果factor为2,那么0x3F07这个数字的二进制位,每一位重复2次,那就是先绘制6个像素,再空10个像素,再绘制12个像素,最后空4个像素。
然后书中给出了一个具体的例子,用来说明直线的点画模式。
#include <GL/glut.h>
#include <stdlib.h>
#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \
glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();
void init(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
}
void display(void)
{
int i;
glClear (GL_COLOR_BUFFER_BIT);
/* select white for all lines */
glColor3f (1.0, 1.0, 1.0);
/* in 1st row, 3 lines, each with a different stipple */
glEnable (GL_LINE_STIPPLE);
glLineStipple (1, 0x0101); /* dotted */
drawOneLine (50.0, 125.0, 150.0, 125.0);
glLineStipple (1, 0x00FF); /* dashed */
drawOneLine (150.0, 125.0, 250.0, 125.0);
glLineStipple (1, 0x1C47); /* dash/dot/dash */
drawOneLine (250.0, 125.0, 350.0, 125.0);
/* in 2nd row, 3 wide lines, each with different stipple */
glLineWidth (5.0);
glLineStipple (1, 0x0101); /* dotted */
drawOneLine (50.0, 100.0, 150.0, 100.0);
glLineStipple (1, 0x00FF); /* dashed */
drawOneLine (150.0, 100.0, 250.0, 100.0);
glLineStipple (1, 0x1C47); /* dash/dot/dash */
drawOneLine (250.0, 100.0, 350.0, 100.0);
glLineWidth (1.0);
/* in 3rd row, 6 lines, with dash/dot/dash stipple */
/* as part of a single connected line strip */
glLineStipple (1, 0x1C47); /* dash/dot/dash */
glBegin (GL_LINE_STRIP);
for (i = 0; i < 7; i++)
glVertex2f (50.0 + ((GLfloat) i * 50.0), 75.0);
glEnd ();
/* in 4th row, 6 independent lines with same stipple */
for (i = 0; i < 6; i++) {
drawOneLine (50.0 + ((GLfloat) i * 50.0), 50.0,
50.0 + ((GLfloat)(i+1) * 50.0), 50.0);
}
/* in 5th row, 1 line, with dash/dot/dash stipple */
/* and a stipple repeat factor of 5 */
glLineStipple (5, 0x1C47); /* dash/dot/dash */
drawOneLine (50.0, 25.0, 350.0, 25.0);
glDisable (GL_LINE_STIPPLE);
glFlush ();
}
void reshape (int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
}
/* ARGSUSED1 */
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (400, 150);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
init ();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
运行结果如下所示。
第一行,切换了3种线型。
第二行,比第一行的线的宽度增大了5倍,线型还是一样的。
第三行,使用一种线型,但是几何图元是GL_LINE_STRIP,而不再是GL_LINES。
第四行,同样的线型,画了6条相互连接的直线,发现和第三行的效果是一样的。
第五行,使用一种线型,绘制了一条直线,但是线型的重复因子,设置成了5。发现和第四行相比,单个模式的线,宽了5倍。原来看起来的一个点,现在像一条小短线。
多边形的细节
多边形的细节,我首先想到的是多边形的边,多边形的填充区域,它们两个的属性。
但是这个不按规则出牌啊,给我搞出了什么多边形的正反面、剔除面、点画多边形、还有点、轮廓或实心形式的多边形。
其实也还可以理解,正常的多边形,有边和填充区域。如果我不想绘制多边形的填充区域,那就是只绘制多边形轮廓。如果连轮廓也不想绘制,那就只绘制多边形的顶点。 如果我觉得多边形的填充区域太单调,就可以使用多边形的 点画模型,正如直线的点画模式那样。唯一不好理解的就是多边形的正反面 和 剔除面。
点、轮廓或实心形式的多边形
这个是通过函数glPolygonMode来实现的。
glPolygonMode(GLenum face, GLenum mode);
第一个参数,表示这个作用于哪个面,一共可以有三个可选的值,正面GL_FRONT,反面GL_BACK,正反面GL_FRONT_AND_BACK。第二个参数,表示这个面的多边形绘制模式,是只绘制顶点GL_POINT,还是绘制轮廓GL_LINE,或者绘制填充区域GL_FILL, 这三者其实就是点、线、面。
正反面
那OpenGL的正反面如何定义的呢?
我们在纸上画一个正方形,我们画画时,朝上的那一面,叫正面,朝下的那一面,叫反面。
OpenGL中的正反面,是以顶点出现在屏幕上的顺序来定义的。
例如:
glBegin(GL_POLYGON);
glVertex2f(0.0, 0.0);//顶点A
glVertex2f(0.0, 3.0);//B
glVertex2f(4.0, 3.0);//C
glVertex2f(4.0, 0.0);//D
glEnd();
上面的代码,指定了一个四边形,这个四边形位于z=0平面上。OpenGL采用的右手坐标系,当我们从z轴正半轴看时,看到ABCD四个点,成顺时针方向。当我们从Z轴负半轴看时, ABCD四个点成逆时针方向。默认情况下,顶点以逆时针方向出现在屏幕上时,这个面为“正面”。以这个四边形为例,默认情况下,Z轴负半轴为这个四边形的正面,Z轴正半轴为这个四边形的反面。不过,这个正反面可以调换过来,通过调用函数glFrontFace()。
glFrontFace(GLenum mode);
这个函数的参数,可以使GL_CCW,CCW全称 counter clockwise,逆时针。或者GL_CW,CW全称clockwise,顺时针。默认值为GL_CCW。
反转和剔除多边形表面
反转多边形表面,上面已经描述了,通过调用glFrontFace。
剔除多边形表面
为什么要剔除多边形表面?因为我们通常只能看到多边形的一面,看不到的另一面,没必要绘制,所以通过剔除多边形表面,可以减少没必要的渲染。
通过函数glCullFace来剔除。
void glCullFace(GLenum mode);
mode参数可以是GL_FRONT, GL_BACK, GL_FRONT_AND_BACK。为了使剔除生效,必须以GL_CULL_FACE为参数调用glEnable()函数来启用剔除功能。当然也可以使用glDisable()函数禁用剔除功能。
点画多边形
这个可以参照点画直线来进行。
在点画直线中,先调用glLineStipple()设置线型,再调用glEnable(GL_LINE_STIPPLE)启用直线的点画模式,然后开始画线就可以了。
在点画多边形中,也是先指定点画模式,然后启用多边形点画模式功能,最后开始画多边形即可。只是指定多边形点画模式的函数,叫glPolygonStipple()。然后启用多边形点画功能,是通过调用glEnable(GL_POLYGON_STIPPLE)。
void glPolygonStipple(const GLubyte *mask);
这个函数的参数mask,是一个指向32X32位图的指针,后者被解释为0和1的掩码。如果模式中出现的是1,那么多边形中对应的像素就被绘制,如果出现的是0,多边形中对应的像素就不会被绘制。这个和glLineStipple函数是一致的。
下面是多边形点画模式的代码:
#include <stdlib.h>
#include <GL/glut.h>
void display(void)
{
GLubyte fly[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60, 0x04, 0x60, 0x06, 0x20,
0x04, 0x30, 0x0C, 0x20, 0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22, 0x44, 0x01, 0x80, 0x22,
0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x66, 0x01, 0x80, 0x66,
0x33, 0x01, 0x80, 0xCC, 0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0, 0x03, 0x31, 0x8c, 0xc0,
0x03, 0x33, 0xcc, 0xc0, 0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08, 0x10, 0x63, 0xC6, 0x08,
0x10, 0x30, 0x0c, 0x08, 0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08};
GLubyte halftone[] = {
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55};
glClear (GL_COLOR_BUFFER_BIT);
/* draw all polygons in white */
glColor3f (1.0, 1.0, 1.0);
/* draw one solid, unstippled rectangle, */
/* then two stippled rectangles */
glRectf (25.0, 25.0, 125.0, 125.0);
glEnable (GL_POLYGON_STIPPLE);
glPolygonStipple (fly);
glRectf (125.0, 25.0, 225.0, 125.0);
glPolygonStipple (halftone);
glRectf (225.0, 25.0, 325.0, 125.0);
glDisable (GL_POLYGON_STIPPLE);
glFlush ();
}
void myinit (void)
{
/* clear background to black */
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
}
static void reshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble)w, 0.0, (GLdouble)h, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
/* Main Loop
* Open window with initial window size, title bar,
* RGBA display mode, and handle input events.
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (350, 150);
glutCreateWindow (argv[0]);
myinit ();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
运行结果如下所示:
标记多边形的边界边
边界边,就是位于多边形边界处的边,这个和内部边对立。
标记边界边干什么?当使用GL_LINE模式绘制多边形时,为了将内部边和边界边区分开。
如何标记边界边?通过函数glEdgeFlag()函数来进行标记。
glEdgeFlag(GLboolean flag);
使用起来比较简便,在glBegin和glEnd函数之间调用。调用完了之后,后面创建的点都受这个标记的影响,直到再次标记。
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_POLYGON);
glEdgeFlag(GL_TRUE);
glVertex3fv(v0);
glEdgeFlag(GL_FALSE);
glVertex3fv(v1);
glEdgeFlag(GL_TRUE);
glVertex3fv(v2);
glEnd();
如上所示,上面的v0和v2被标记为边界点,而v1被标记为非边界点。
正常情况下,OpenGL绘制的线, v0->v1, v1->v2, v2->v0,这三条线,但是由于v1被标记为非边界点,所以v1->v2这条边就是非边界边,结果就不画出来了。
啊,这一小节终于学完啦。靠!