计算机图形学 03 输出图元

引言:

    图形软件包中用来描述各种几何图形元素的函数成为图形输出原语(Graphics Output Primitive),或简称为图元(Primitive)。描述对象几何要素的输出图元一般称之为几何图元(Geometric Primitive)

一、坐标系统

    为了描述图形,首先必须确定一个称之为世界坐标系的适合的二维或三维笛卡尔坐标系。接着通过给出世界坐标系中的位置等几何描述来定义图形中的对象。这些坐标位置与对象的颜色坐标范围(Coordinate Extent)即对象坐标x、y、z的最小值和最大值等其他信息一起存储在该场景描述中。
    坐标范围也称为对象的包围盒(Bounding Box)。对于二维图形来说,坐标范围也称为对象的包围矩形(Bounding Rectangle)

1.1 屏幕坐标

    视频监视器上的位置使用与帧缓存中的像素位置相对应的整数屏幕坐标(Screen Coordinate)进行描述。像素的坐标值给出扫描行号(y值)和列号(x值)。屏幕刷新等硬件处理一般从屏幕左上角开始对像素编址。从屏幕最上面的0行到屏幕下面最下面的整数值ymax行,从最左边的0行到xmax行。
    但是,使用软件命令可以按照任何方式设定屏幕位置的参考系统。例如,我们可以设定屏幕左下角为原点,用整数坐标或非整数笛卡尔坐标来描述。描述场景几何要素的坐标值由观察函数转换为帧缓存中的整数像素位置。

    图元的扫描转换算法使用定义的坐标描述来确定显示像素的位置。例如,给定一直线段的两个端点,其显示函数必须计算出两点之间位于线段上所有像素的位置。由于一个像素的位置占有屏幕上一个有限范围,因此实现此算法必须考虑像素的有限大小。目前,假设每一个整数屏幕位置代表像素区域的中心。
    一旦确定了一个对象的像素位置,必须将合适的颜色的存入帧缓存。为此,我们要使用一个底层函数:

setPixel (x, y);
//颜色值存入帧缓存的xy处

    该函数将当前颜色设定值存入帧缓存的整数坐标位置(x, y)处,该位置相对于屏幕坐标的原点而选定。
    有时我们也希望获得一个像素位置的当前帧缓存设置。使用下列底层函数可以获取帧缓存的颜色值:

getPixel (x, y, color);
//从帧缓存的xy处读取颜色

    在这一函数中,参数color得到一个与储存位置(x, y)的像素中的红色、绿色和蓝色(RGB)组合对应的整数值。
    对于二维图形来说,仅需要在(x, y)位置指定颜色值;但是对于三维图像来说,还需要其他的平面坐标信息。这时,屏幕坐标值按三维值来储存,第三维表示对象位置相对于观察位置的深度。在二维常见中,深度均为0。

1.2 绝对和相对坐标描述

    到目前位置,我们讨论的坐标均为绝对坐标(absolute coordinate)值。这表示指定的值是坐在坐标系统中的真实位置。
    然而有些图形软件包还允许使用相对坐标(Relative Coordinate)来表述位置。(书P38)

二、OpenGL中指定二维世界坐标系统

    之前介绍了gluOrtho2D命令,我们可以利用该命令设定一个二维笛卡尔坐标系。该函数的变量是指定显示图形的x和y坐标范围的四个值。由于gluOrtho2D函数指定正交投影,因此我们也要确定坐标值放进了OpenGL投影矩阵中。此外,我们可以将世界坐标范围设定前的投影矩阵定义为一个单位矩阵。这样可以保证坐标值不会受到以前设置矩阵的影响。因此,对于最初的二维例子,我们可以通过以下语句定义屏幕显示窗口的坐标系统:

glMatrixMode (GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(xmin, xmax, ymin, ymax);


    如图所示,显示窗口被指定为其左下角位于坐标(xmin, ymin)处,右上角位于坐标(xmax, ymax)处。
    之后可以使用gluOrtho2D语句描述的坐标系统来指定一个或多个要显示的图元。如果一个图元的坐标范围完全在显示窗口的坐标范围内,则改图元将被完整的显示出来,否则只显示部分。同样,在建立图形的几何描述时,所有OpenGL图元的位置必须使用gluOrtho2D函数定义的坐标系统中的绝对坐标给出。

三、OpenGL画点函数

    默认的图颜色是白色,默认的点大小等于单一屏幕像素大小。
    使用下面的OpenGL函数可以指定一个点位置的坐标值:

glVertex* ();

    这里的星号(*)表示该函数还有后缀(例如2i, 2u, 3d之类)。这些后缀表明空间维度数。坐标变量的数据类型等。
    在glBegin函数和glEnd函数之间插入对glVertex函数的调用。glBegin函数的变量用来指定输出图元的类型,而glEnd函数没有变量。对于点的绘制,glBegin函数的变量是符号常量GL_POINTS。因此,一个点的位置OpenGL描述形式是:

glBegin(GL_POINTS)
glVertex * ();
glEnd();

    尽管术语定点(vertex)严格表示一个多边形的“角点”、一个角两边的交点,但是在OpenGL中glVertex函数可用于表述任意一点的位置。
    下面给出两个画点的实例:

glBegin(GL_POINTS);
glVertex2i(50, 100);
glVertex2i(100, 200);
glVertex2i(150, 300);
glEnd();

    或者以矩阵形式描述这些点:

int point1[] = { 50, 100 };
int point2[] = { 100, 200 };
int point3[] = { 150, 300 };

glBegin(GL_POINTS);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

    运行结果:

    完整程序:

#include <GL/glut.h>
#include <iostream>

const int winWidth = 400;
const int winHeight = 400;

void DrawLine();
void Reshape(int w, int h);

int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(100, 100);
glutInitWindowSize(winWidth, winHeight);
glutCreateWindow("Win_1");
//初始化相关

glClearColor(1.0, 1.0, 1.0, 0);
//设置背景窗口颜色

glutDisplayFunc(DrawLine);
glutReshapeFunc(Reshape);
//一定要有Reshape才看得见

glutMainLoop();
}

void DrawLine()
{
glColor3f(1.0, 0, 0);
//填充颜色

glClear(GL_COLOR_BUFFER_BIT);

int point1[] = { 50, 100 };
int point2[] = { 100, 200 };
int point3[] = { 150, 300 };

glPointSize(10.0);
glBegin(GL_POINTS);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

glFlush();
}

void Reshape(int w, int h)
{
// 投影矩阵设好后再将当前矩阵设置为模型矩阵
// 方便后续的图形绘制和图形变换
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 400, 0, 400);
}

四、OpenGL画线函数

    在OpenGL中,和选择一个点位置一样,使用glVertex函数选择单个端点的坐标位置。我们可以使用glBegin/glEnd的配对来引入一串端点位置。有三个OpenGL符号常量可以用于指定如何把这一串端点位置连接成一组直线段。默认情况下每一符号常量都显示白色直线。
    下面有三种基本图元:
1:GL_LINES:两点之间的线段;
2:GL_LINE_STRIP:连续折线;
3:GL_LINE_LOOP:连续封闭直线;
    下面一一介绍:

4.1 GL_LINES:

    使用图元GL_LINES可连接每一对相邻的端点而得到一条直线段。通常,由于OpenGL仅在线段共线一个顶点时承认其相连;交叉但不共享的点不被承认其相连,这会导致一组未相连的线段,除非某些坐标点是重复的。如果只描述了一个端点则什么也不会做,如果列出的端点数为奇数则最后一个端点不能被处理。
    下面通过一段代码来演示:

void DrawLine()
{
glColor3f(1.0, 0, 0);
//填充颜色

glClear(GL_COLOR_BUFFER_BIT);

int point1[] = { 50, 100 };
int point2[] = { 100, 200 };
int point3[] = { 150, 300 };

//画点
glPointSize(10.0);
glBegin(GL_POINTS);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

//画线
glColor3f(0, 1.0, 1.0);
glLineWidth(5);
glBegin(GL_LINES);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

glFlush();
}

    这里是三个点,让我们看一下画图的效果:

    这样,我们在第一和第二坐标之间得到一条线段,因为是奇数个坐标,所以最后一个坐标被忽略。

4.2 GL_LINE_STRIP

    使用OpenGL图元常量GL_LINE_STRIP可以或得折线(Ployline)。此时,第一个端点到最后一个端点首尾相连。第一条直线在第一和第二端点之间显示,第二条直线在第二和第三端点之间显示,以此类推。
    下面通过一段代码来演示:

void DrawLine()
{
glColor3f(1.0, 0, 0);
//填充颜色

glClear(GL_COLOR_BUFFER_BIT);

int point1[] = { 50, 100 };
int point2[] = { 100, 200 };
int point3[] = { 30, 300 };

//画点
glPointSize(10.0);
glBegin(GL_POINTS);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

//画线
glColor3f(0, 1.0, 1.0);
glLineWidth(5);
glBegin(GL_LINE_STRIP);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

glFlush();
}

4.3 GL_LINE_LOOP

    第三个OpenGL图元常量是生成封闭折线(Closed Polyline)的GL_LINE_LOOP。这和GL_LINE_STRIP一样,但是增加了一条线段:即第一端点和最末端点相连:
    下面通过一段代码来演示:

void DrawLine()
{
glColor3f(1.0, 0, 0);
//填充颜色

glClear(GL_COLOR_BUFFER_BIT);

int point1[] = { 50, 100 };
int point2[] = { 100, 200 };
int point3[] = { 30, 300 };

//画点
glPointSize(10.0);
glBegin(GL_POINTS);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

//画线
glColor3f(0, 1.0, 1.0);
glLineWidth(5);
glBegin(GL_LINE_LOOP);
glVertex2iv(point1);
glVertex2iv(point2);
glVertex2iv(point3);
glEnd();

glFlush();
}

五、OpenGL曲线函数

    书上的意思是:以直代曲。后面会有专门的画圆。

六、填充区图元

    除了点、直线、曲线之外,另外还有一种描述图形组成部分的结构是使用某种颜色或图案进行区域填充。这类型的图形部分一般被称为填充区(Fill Area)或填充的区域(Filled Area)。通常,填充区域用于描述实体表面,但在许多其他应用中也很有用。填充区常常是一个平面表面,主要是多边形。一般而言,图形中可能有多种形状的区域选用某种颜色填充。
    尽管有可能使用各中心在,但图形库一般不支持任意填充形状的描述。(书上的意思是以平面代替曲面P43)。

七、多边形填充区

    一个多边形(Ploygon)在数学上定义为由三个火更多称为顶点的坐标描述的平面几何图形,这些顶点有称之为多边形的边(Edge或Side)顺序连接。进一步看来,几何上要求多边形除了端点之外没有其他的公共点。因此,一个多边形的所有顶点必须在同一平面上,且所有边之间无交叉。有时,任意有封闭折线边界的平面图形暗指一个多边形,而没有交叉边则称为标准多边形(Standard Ploygon)或简单多边形(Simple Ploygon)。为了避免混淆,我们把术语“多边形”限定为那些有封闭折线且无交叉边的平面图形。

7.1 多边形分类

    多边形的一个内角(Interior Angle)是由两条相邻边组成的多边形边界之内的角。如果一个多边形的所有角均小于180°,则该多边形为凸(Convex)多边形。凸多边形的一个等价定义时它内部完全在他任意一边及其延长线的一侧。同样,如果两点位于凸多边形其内部,其连线也位于多边形内部。不是凸多边形的多边形成为凹多边形(存在内角大于180°)。
    退化多边形(Degenerate Polygon)常用来描述共线或重叠坐标的顶点集。共线顶点生成以线段。重叠顶点位置可以生成有多余线段、重叠边或长度为0的边的多边形。又是退化多边形也用于少于三个坐标位置的顶点队列。
    凹多边形也会有一些相关问题。对凹多边形的填充算法和其他子程序的实现比较复杂,因此在处理前常将凹多边形分割成一组凸多边形以提高效率。和其他的多边形预处理算法一样,凹多边形的分割一般也不包括在图形库函数中。

7.2 识别凹多边形

    I:凹多边形至少有一个内角大于180°。II:凹多边形某些边的延长线会与其他边相交并且有时一对内点之间的连线会与多边形边界相交。
·1:如果为每一条边奖励一个向量,则可以使用邻边的叉积来判断凹凸性。凸多边形向量叉积均为同号。因此,如果某些叉积取正值而另外一些为负值,则可以确定为凹多边形。
·2:识别凹多边形的另一个方法是观察多边形顶点位置与每条边延长线的关系。如果有些项的顶点在某一延长线的一侧而其他顶点在另一侧,则为凹多边形。
    下面通过一段代码来判断凹多边形:

#include <iostream>
#include <GL/glut.h>
#include <glm/glm.hpp>
#include <vector>

const int winWidth = 400;
const int winHeight = 400;

void DrawLine();
void Reshape(int w, int h);
bool IsConvex();

/*--测试点集---*/
int p1[] = { 20, 30 };
int p2[]{ 120, 70 };
int p3[]{ 200, 50 };
int p4[]{ 120, 300 };
int p5[]{ 200, 300 };
int p6[]{ 20, 340 };
int p7[]{ 220, 380 };
int p8[]{ 210, 30 };
/*--测试点集---*/

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(winWidth, winHeight);
    glutCreateWindow("Win_1");
    //初始化相关

    glClearColor(1.0, 1.0, 1.0, 0);
    //设置背景窗口颜色

    glutDisplayFunc(DrawLine);
    glutReshapeFunc(Reshape);
    //一定要有Reshape才看得见

    std::cout << std::boolalpha << IsConvex() << std::endl;

    glutMainLoop();
}

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glLineWidth(2);
    glBegin(GL_LINE_LOOP);
        glVertex2iv(p1);
        glVertex2iv(p2);
        glVertex2iv(p3);
        glVertex2iv(p4);
        glVertex2iv(p5);
        glVertex2iv(p6);
        glVertex2iv(p7);
        glVertex2iv(p8);
    glEnd();

    glFlush();
}

void Reshape(int w, int h)
{
    // 投影矩阵设好后再将当前矩阵设置为模型矩阵
    // 方便后续的图形绘制和图形变换
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, 400, 0, 400);
}

bool IsConvex()
{
    bool re(true);
    //默认为真,凸多边形

    glm::vec<3, double> v1 = { p1[0] - p2[0], p1[1] - p2[1], 0 };
    glm::vec<3, double> v2 = { p2[0] - p3[0], p2[1] - p3[1], 0 };
    glm::vec<3, double> v3 = { p3[0] - p4[0], p3[1] - p4[1], 0 };
    glm::vec<3, double> v4 = { p4[0] - p5[0], p4[1] - p5[1], 0 };
    glm::vec<3, double> v5 = { p5[0] - p6[0], p5[1] - p6[1], 0 };
    glm::vec<3, double> v6 = { p6[0] - p7[0], p6[1] - p7[1], 0 };
    glm::vec<3, double> v7 = { p7[0] - p8[0], p7[1] - p8[1], 0 };
    glm::vec<3, double> v8 = { p8[0] - p1[0], p8[1] - p1[1], 0 };

    std::vector<glm::vec<3, double>> ansList;

    ansList.push_back(glm::cross(v1, v2));
    ansList.push_back(glm::cross(v2, v3));
    ansList.push_back(glm::cross(v3, v4));
    ansList.push_back(glm::cross(v4, v5));
    ansList.push_back(glm::cross(v5, v6));
    ansList.push_back(glm::cross(v6, v7));
    ansList.push_back(glm::cross(v7, v8));
    ansList.push_back(glm::cross(v8, v1));

    bool f_pos(false), f_neg(false);
    //正负数判断

    for (auto i : ansList)
    {
        if (i[2] > 0)
        {
            f_pos = true;
        }
        else if (i[2] < 0)
        {
            f_neg = true;
        }
        else if (i[2] == 0)
        {
            //do nothing
            //这里要求多边形三点不共线,所以理论上不发生这种情况
        }
    }

    re = (f_pos && f_neg);
    return !re;
}

7.3 分割多边形

    一旦识别出多边形,我们就可以把它分割成多组凸多边形。我们可以使用向量的叉积来完成。
    对于分割凹多边形的向量方法,我们首先要给定形成边向量。给定相继的向量位置Vk和Vk+1来定义向量。
    例如在上面7.1中,我们判断出一个多边形为凹多边形,我们可以选择延长其为负z分量的向量。下面举一个例子:

E1 x E2 =(0, 0, 1)
E2 x E3 =(0, 0, -2)
E3 x E4 =(0, 0, 2)
E4 x E5 =(0, 0, 6)
E5 x E6 =(0, 0, 6)
E6 x E1 =(0, 0, 2)

    此处我们延长E2就可以将这个凹多边形分解为两个凸多边形。
    详情见书P45。

7.4 将凸多边形分割为三角集

P46

7.5 内-外测试

    各种图形处理常常需要鉴别对象的内部区域。识别简单对象往往很容易,但有时需要处理复杂的对象。
    下面有两种方法来判断图形的内外:奇偶规则和非零环绕数。

7.5.1 奇偶规则

    奇偶规则(odd-even rule)也称奇偶性规则(odd-parity rule)或偶奇规则(even-odd rule)。该规则从任意位置P到对象坐标范围以外的远点话一条概念上的直线(射线),并统计与各边交点的数目。如果为奇数,则P是内部(interior)点,否则P是外部(exterior)点。
    为了得到精确结果,应当确保该直线不会与多边形的任何顶点相交。

7.5.2 非零环绕数

    该方法统计多边形以逆时针方向环绕某一点的次数,这个数称为环绕数(Winding-number),对二维图像而言,内部点是指那些环绕数不为0的点。
    在对多边形应用非零环绕数规则时,将环绕数初始化为零。设想任意位置P到对象坐标范围外的远点画一条射线。该射线不能与多边形的顶点相交,统计穿过该射线的边数。如果多边形从右向左穿过射线,边数+1,从左向右穿过射线,边数-1。如果环绕数非0,则是内部点,为0则是外部点。
    这里值得一提的是,边穿过的方向是按照顶点顺序生成的向量来决定的。

7.5.3 总结

    对于多边形和简单图形对象,非零环绕数和奇偶规则给出了相同的结果;但是对于复杂的形状,两者则可能会给出不同的结果(如上图所示)。
·1:使用叉积判断非零环绕数:从P点发出一条射线向量u与穿过射线的每条边的向量E进行叉积运算。按照右手螺旋定则(叉乘求法):如果E从右穿过u,则z分量为正,环绕数+1;同理,z分量为负则环绕数-1。
·2:使用点积代替叉积:同样创建向量u(ux, uy),则该向量的垂直向量u’为(-uy, ux)。如果u’与边界向量E的点积为正,则表示从右向左穿越,环绕数+1;为负则环绕数-1。
    非零环绕数与奇偶规则的区别及应用见书P47。

7.6 多边形表

    场景中的对象一般用一组多边形面片来表示。在输入每个多边形的信息时,数据放进一些表格中等待后续处理、显示和场景的对象管理。这些多边形数据表分为两组:几何数据表属性数据表
·1:几何数据表包含顶点坐标和标识多边形面片空间方向的参数。
·2:对象的属性信息包含指定对象的透明度及其表面的反射性能和纹理特征。
    几何数据表可简单组织为三张表:
·1:顶点表
·2:边表
·3:面片表
    对象的每一顶点坐标存储在顶点表中;边表包含指向顶点表的指针移确定每一多边形的边的端点。而面片表包含指向边表的指针以确定每个多边形的边。
    同时可以在数据表中加入附加信息来提高信息的提取速度,如在边表中加入指向面片表的指针。
    数据表中包含的信息越多,错误的检查越容易。因此,当使用三个数据表(顶点、边和面片)时错误比较容易检查,因为这个方案提供了最多的信息。

7.7 平面方程

    平面的一般方程为:

Ax + By + Cz + D = 0

    其中(x, y, z)是平面中的任意一点,系数A、B、C、D称为平面参数(Plane Parameter)是描述平面空间特征的常熟。为此,可选择逆时针凸多边形的三个连续顶点(x1, y1, z1)、(x2, y2, z2)和(x3, y3, z3)并且联立来解下面的方程组。

(A/D)xk + (B/D)yk + (C/D)zk = -1
k = 1 2 3

    使用Cramer规则可列出行列式:

A = {1 y1 z1;
     1 y2 z2;
     1 y3 z3}

B = {x1 1 z1;
     x2 1 z2;
     x3 1 z3}

C = {x1 y1 1;
     x2 y2 1;
     x3 y3 1}

D = - {x1 y1 z1;
       x2 y2 z2;
       x3 y3 z3}

    展开行列式可得到平面系数的表达式:

A = y1(z2 - z3) + y2(z3 - z1) + y3(z1 - z2)
B = z1(x2 - x3) + z2(x3 - x1) + z3(x1 - x2)
C = x1(y2 - y3) + x2(y3 - y1) + x3(y1 - y2)
D = -x1(y2z3 - y3z2) - x2(y3z1 - y1z3) - x3(y1z2 - y2z1)

7.8 前向面与后向面

    向着对象内部的一侧称为后向面(Back Face),可见或朝向外面一侧称为前向面(Front Face)。
    我们可以使用平面一般方程来判断一个点所处的位置在面的前向面或后向面。
·1:如果Ax + By + Cz + D < 0,则点(x, y, z)在平面后方。 ·2:如果Ax + By + Cz + D > 0,则点(x, y, z)在平面前方。
    法向量的分量也可以通过叉积计算或得。假定我们有一个多边形面片和一个右手坐标系,在选择任意三个顶点:v1, v2和v3,满足从对象外部向内观察时的逆时针排序。形成两个向量,一个从v1到v2,一个从v1到v3,按向量叉积计算N:
N =(v2 – v1) x (v3 – v1)
    这样就生成了平面参数A、B和C,接下来将这些值和一个多边形顶点带入平面方程,可解出D。使平面法向量N,和平面上一点P可以给出向量形式的平面方程。
N · P = -D
    对于图多边形来说,我们也可以使用两个连续边的向量的叉积来得到平面参数。对于凹多边形,我们可以选择这样的三个顶点,使得用于叉积计算的两条边的夹角小于180°。否则,我们取叉积的反向量来获得正确的多边形向量方向。

八、OPenGL多边形填充区函数

    描述多边形的OpenGL过程与描述点和折线类似,但有一个例外。函数glVertex用来输入多边形的一个顶点坐标,而完整的多边形用用glBeging到glEnd之间的一组顶点来描述,但有另外一个函数可以用来显示具有不同形式的矩形。
    OpenGL中的填充区必须制定为凸多边形。我们描述的的每一个多边形有两个面:后向面和前向面。在OpenGL中,可以为每个面设定填充颜色和其他属性,并在二维和三位观察子程序中要求它有后向/前向标志。因此,多边形按“外部”来观察它时的逆时针方向描述。这标识了该多边形的前向面。
    因为图形显示中经常包含有矩阵的填充区,OpenGL提供了一个特殊的矩形函数,直接在xy平面中描述顶点。在有些OpenGL的实现中,下面函数比用glVertex描述的填充区有更高的效率:

glRect*(x1, y1, x2, y2);

    该矩形的一个角位于位置(x1, y1)出,与其相对一角位置位于(x2, y2)处。glRect的后缀码指出,该坐标的数据类型及是否用数组袁术来表示坐标。
    利用函数glRect生成矩形时,多边形的生成按照序列(x1, y1)、(x2, y1)、(x2, y2)、(x1, y2)然后回到(x1, y1)来生成。
    下面通过一段代码来演示:

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(50, 100);
    glm::vec2 point2(100, 200);

    glRectf(point1.x, point1.y, point2.x, point2.y);

    glFlush();
}

    这里要注意顶点的先后顺序,下面介绍另外6种填充图元,并用他们来绘制六边形。

8.1 GL_POLYGON


    这个填充图元按照逆时针顺序绘制一般概念上的多边形。

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(100, 300);
    glm::vec2 point2(200, 300);
    glm::vec2 point3(50, 200);
    glm::vec2 point4(250, 200);
    glm::vec2 point5(100, 100);
    glm::vec2 point6(200, 100);

    glBegin(GL_POLYGON);
        glVertex2i(point2.x, point2.y);
        glVertex2i(point1.x, point1.y);
        glVertex2i(point3.x, point3.y);
        glVertex2i(point5.x, point5.y);
        glVertex2i(point6.x, point6.y);
        glVertex2i(point4.x, point4.y);
    glEnd();
    //这里按逆时针顺序生成

    glFlush();
}

8.2 GL_TRIANGLES


    这个填充图元按照逆时针三个点为一个集合生成三角形。

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(100, 300);
    glm::vec2 point2(200, 300);
    glm::vec2 point3(50, 200);
    glm::vec2 point4(250, 200);
    glm::vec2 point5(100, 100);
    glm::vec2 point6(200, 100);

    glBegin(GL_TRIANGLES);
        glVertex2i(point2.x, point2.y);
        glVertex2i(point1.x, point1.y);
        glVertex2i(point3.x, point3.y);
        glVertex2i(point5.x, point5.y);
        glVertex2i(point6.x, point6.y);
        glVertex2i(point4.x, point4.y);
    glEnd();
    //这里按逆时针顺序生成

    glFlush();
}

8.3 GL_TRIANGLE_STRIP


    这个图元同样是生成三角形,但是不同于上面的GL_ANGLES,这里后继的一个三角形共享前一个三角形的一条边。

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(100, 300);
    glm::vec2 point2(200, 300);
    glm::vec2 point3(50, 200);
    glm::vec2 point4(250, 200);
    glm::vec2 point5(100, 100);
    glm::vec2 point6(200, 100);

    glBegin(GL_TRIANGLE_STRIP);
        glVertex2i(point3.x, point3.y);
        glVertex2i(point5.x, point5.y);
        glVertex2i(point1.x, point1.y);
        glVertex2i(point6.x, point6.y);
        glVertex2i(point2.x, point2.y);
        glVertex2i(point4.x, point4.y);
    glEnd();

    glFlush();
}

8.4 GL_TRIANGLE_FAN


    同样是构造三角形,但是不同于前者的是,这里指定了一个顶点,同时按照逆时针顺序。

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(100, 300);
    glm::vec2 point2(200, 300);
    glm::vec2 point3(50, 200);
    glm::vec2 point4(250, 200);
    glm::vec2 point5(100, 100);
    glm::vec2 point6(200, 100);

    glBegin(GL_TRIANGLE_FAN);
        glVertex2i(point1.x, point1.y);
        glVertex2i(point3.x, point3.y);
        glVertex2i(point5.x, point5.y);
        glVertex2i(point6.x, point6.y);
        glVertex2i(point4.x, point4.y);
        glVertex2i(point2.x, point2.y);
    glEnd();
    //这里按逆时针顺序生成
    glFlush();
}

8.4 GL_QUADS


    这个图元按照逆时针顺序生成不相邻的四边形。

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(100, 300);
    glm::vec2 point2(100, 100);
    glm::vec2 point3(200, 100);
    glm::vec2 point4(200, 300);
    glm::vec2 point5(250, 300);
    glm::vec2 point6(250, 100);
    glm::vec2 point7(350, 100);
    glm::vec2 point8(350, 300);

    glBegin(GL_QUADS);
        glVertex2i(point1.x, point1.y);
        glVertex2i(point2.x, point2.y);
        glVertex2i(point3.x, point3.y);
        glVertex2i(point4.x, point4.y);
        glVertex2i(point5.x, point5.y);
        glVertex2i(point6.x, point6.y);
        glVertex2i(point7.x, point7.y);
        glVertex2i(point8.x, point8.y);
    glEnd();
    //这里按逆时针顺序生成
    glFlush();
}

8.5 GL_QUAD_STRIP


    同三角形,这里生成了三个四边形。

void DrawLine()
{
    glColor3f(1.0, 0, 0);
    //填充颜色

    glClear(GL_COLOR_BUFFER_BIT);

    glm::vec2 point1(100, 300);
    glm::vec2 point2(100, 100);
    glm::vec2 point3(200, 100);
    glm::vec2 point4(200, 300);
    glm::vec2 point5(250, 300);
    glm::vec2 point6(250, 100);
    glm::vec2 point7(350, 100);
    glm::vec2 point8(350, 300);

    glBegin(GL_QUAD_STRIP);
        glVertex2i(point1.x, point1.y);
        glVertex2i(point2.x, point2.y);
        glVertex2i(point4.x, point4.y);
        glVertex2i(point3.x, point3.y);
        glVertex2i(point5.x, point5.y);
        glVertex2i(point6.x, point6.y);
        glVertex2i(point8.x, point8.y);
        glVertex2i(point7.x, point7.y);
    glEnd();
    //这里按逆时针顺序生成
    glFlush();
}

九、OpenGL顶点数组

    为了简化对象描述的问题,OpenGL提供了一种机制来减少处理坐标信息的函数调用数量。使用顶点数组(vertex array),可以利用很少的函数调用来安排常见描述。步骤如下:
·1:引用函数glEnableClientState(GL_VERTEX_ARRAY)激活OpenGL的顶点数组特性。
·2:实用函数glVertexPointer指定顶点坐标的位置和数据格式。
·3:使用子程序如glDrawElements显示场景,该子程序可以处理多个图元而仅需少量的函数调用。
    下面通过一段代码来演示:定义pt数组

int pt[8][3]{ {0, 0, 0}, {0, 1, 0}, {1, 0, 0}, {1, 1, 0},
            {0, 0, 1}, {0, 1, 1}, {1, 0, 1}, {1, 1, 1} };
//定义8个三维点来描述一个正方形
   
glEnableClientState(GL_VERTEX_ARRAY);
//激活顶点数组
glVertexPointer(3, GL_INT, 0, pt);
//(维度, 符号倡廉, 字节位移, 指向包含坐标值的顶点数组)

GLubyte vertIndex[]{ 6, 2, 3, 7,
                        5, 1, 0, 4,
                        7, 3, 1, 5,
                        4, 0, 2, 6,
                        2, 0, 1, 3,
                        7, 5, 4, 6 };
//逆时针方向表示每个平面
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, vertIndex);
//绘制正方形平面

十、像素阵列图元

    见书p57。

十一、OpenGL像素阵列函数

    OpenGL中有两个函数可以定义矩阵的形状或图案。一个函数定义位图,另一个函数定义像素图。

11.1 OpenGL位图函数

    下面的函数定义一个二值的阵列。

glBitmap (width, height, x0, y0, x0ffset, y0ffset, bitShape);
//(列, 行, 原点位置, 当前光栅位, Bitshape)

    Bitshape赋值0或1,赋值1表示对应像素用前面设定的颜色显示;否则对应像素不受位图影响。参数x0和y0定义了位图的原点位置所在,原点位置位于Bitshape的左下角,而x0和y0可正可负。另外,需要指定帧缓存中的应用图案位置。该位置称为当前光栅位,而位图将原地置于当前光栅位后显示。
    可以使用以下子程序来设置当前光栅位:

glRasterPos*()

    其参数后缀与glVertex一样。因此,当前光栅位在世界坐标中给出,有观察变换将其变换到屏幕坐标。在我们二维例子中,我们可以用整数坐标指定当前光栅位置的坐标。当前光栅位置的默认值是世界坐标系统中的(0, 0, 0)。
    位图使用glRasterPos被引用时的有效颜色,后来的任何颜色改变不会影响该位图。

11.2 OpenGL像素图函数

glDrawPixels (width, height, dataFormat, dataType, pixMap);
//(列, 行, 颜色指定方法, GL数据类型, pixMap)

    由于OpenGL提供了若干个缓存,将某缓存选贼glDrawPixels子程序的目标即可将一个阵列送进该缓存。有的缓存存放颜色值,有的缓存存放另外的像素数据。例如深度缓存(Depth BUffer)用来存放对象离开观察位置的距离,而模板缓存(Stencil Buffer)用来存放场景的边界图案。glDrawPixels函数中的dataFormat参数设定为GL_DEPTH_COMPONENT或GL_STENCIL_INDEX就可以在这两个缓存中选取一个。

11.3 OpenGL光栅操作

    一般情况下,光栅操作(Raster Operation)用于描述以某种方式处理一个像素阵列的任何功能。将一个像素阵列的值从一个位置移动到另一个位置的光栅操作也叫块操作(Block Transfer)或BitBlt移动(Bit-Block Transfer)。在多层次系统中,属于pixblt用于块移动。

11.3.1 读取矩阵块的像素值
glReadPixels (xmin, ymin, width, height, dataFormat, dataType, array);
//屏幕坐标位置左下角(xmin, ymin),其余与前面提到的相同。

    这里array传入的参数依赖于前面设置的参数。

glReadBuffer(buffer)

    用函数glReadBuffer()可以为glReadPixels子程序选择颜色或辅助缓存的特殊组合、
    

11.3.2 复制像素块
glCopyPixels(xmin, ymin, width, height, pixelValues);