0%

OpenGL绘图实例四之区域颜色填充

综述

上一节我们利用种子填充算法实现了机器人的区域填充,我们可以发现,种子填充需要一定是时间,并不能在第一时间填充完毕。这里我们介绍一种更加简单的方法,我们利用 glPolygonMode 来绘制机器人,感受一下。

注意

我们曾经分别使用过类库和直线圆弧生成算法来绘制过机器人的线条。 类库绘制法:OpenGL 绘图实例一之机器人的绘制 算法绘制法:OpenGL 绘图实例二之直线和圆弧的绘制 填充算法也有两种,一种是种子填充算法,另一种就是本篇说的方法。 在这里我们可以归一下类,算法绘制线条方法和种子填充算法中的每一个点都是我们一点点绘制的,都是我们利用算法一点点计算的坐标来绘制,因此,可以把他俩归为一对。而类库绘制线条方法和本篇说的填充方法都是指定一部分点,类库为我们实现了填充,因此可以把他俩归为一对。下面就让我们来感受一下类库填充的魅力吧!

函数说明

1.函数原型

函数原型:void glPolygonMode(GLenum face, GLenum mode); 说明:控制一个多边形的正面和背面的绘图模式。表示多边形应该被画成点、轮廓还是填充形式。在默认情况下,多边形的正面和背面都被画成填充形式。 函数 glPolygonMode()用于改变当前的多边形绘制模式,它有两个参数。第一个参数指定哪些面将被这个调用,可能的值为 GL_FRONT、GL_BACK 或 GL_FRONT_AND_BACK,分别对应于前向多边形、后向多边形或两者。第二个参数指定将要使用的模式,可以为 GL_POINT、GL_LINE 或 GL_FILL。 下面我们来用一个小 Demo 来感受一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBegin(GL_TRIANGLES);
glVertex3f(‐0.8f,0.0f,0.0f);
glVertex3f(‐0.6f,0.0f,0.0f);
glVertex3f(‐0.7f,0.2f,0.0f);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_TRIANGLES);
glVertex3f(0.1f,0.0f,0.0f);
glVertex3f(‐0.1f,0.0f,0.0f);
glVertex3f(0.0f,0.2f,0.0f);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
glBegin(GL_TRIANGLES);
glVertex3f(0.6f,0.0f,0.0f);
glVertex3f(0.8f,0.0f,0.0f);
glVertex3f(0.7f,0.2f,0.0f);
glEnd();

以上三种模式分别是绘制了填充的三角形,三角形的边界,三角形的三个点。

2.画多边形

在 glBegin 的参数传入 GL_POLYGON,绘制的时候所有的点就会连接而成一个多边形。

3.线条的粗细

1
glLineWidth(lineWidth);

利用这个方法,我们可以传入像素值大小,比如传入 2,画线的时候就是 2 像素的粗细。

绘制机器人

接下来我们利用这个方法来绘制机器人吧!我们可以注意到,定义了 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)之后, 如果 glBegin 传入的是 GL_LINE_LOOP,绘制的仍然是封闭的曲线,如果 glBegin 传入的是 GL_POLYGON,绘制的则是封闭的填充的图形。 图片1 如果要实现上图的机器人,我们既要绘制黑色的边线,又要绘制带颜色的区块。所以我们就需要分别调用两种绘制模式,绘制的过程是完全相同的。所以,我们可以把模式当做一个变量来传入参数中改变绘制模式。比如之前的绘制三角形的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3){
//画封闭线
glBegin(GL_LINE_LOOP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

就可以改写为

1
2
3
4
5
6
7
8
9
10
11
12
13
//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3,int MODE){
//画封闭线
glBegin(MODE);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

在调用的时候我们只需要分别传入 GL_LINE_LOOP 和 GL_POLYGON 就可以了。 所有之前的方法改写如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY,int MODE){
//画封闭曲线
glBegin(MODE);
//左下角
glVertex2d(leftX,leftY);
//右下角
glVertex2d(rightX,leftY);
//右上角
glVertex2d(rightX,rightY);
//左上角
glVertex2d(leftX,rightY);
//结束画线
glEnd();
}

//画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标
void glRoundRec(int centerX,int centerY,int width,int height,float cirR,int MODE){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(MODE);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius,int MODE)
{
//开始绘制曲线
glBegin(MODE);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}


//画圆
void glCircle(double x, double y, double radius,int MODE)
{
//画全圆
glArc(x,y,0,2*PI,radius,MODE);
}

//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3,int MODE){
//画封闭线
glBegin(MODE);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

//画线,传入两点坐标
void glLine(int x1,int y1,int x2,int y2){
//画封闭线
glBegin(GL_LINE_STRIP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//结束画线
glEnd();
}

那么我们实现了上面的方法之后。比如我现在要画一个矩形,就要传入 GL_LINE_LOOP 绘制一个边缘,然后再传入 GL_POLYGON 绘制一个填充的矩形,其中这个矩形的长宽等于边缘的长宽减去线的宽度(经过探索发现,应该减去线宽的一半才对,因为线的基准点是在线宽的中心的) 所以,我们可以定义一个画带边矩形的方法,比如

1
2
3
4
5
6
7
//画填充的矩形,传入左上角和右下角的坐标
void glFillRect(int leftX,int leftY,int rightX,int rightY,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glRect(leftX,leftY,rightX,rightY,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glRect(leftX+lineWidth/2,leftY-lineWidth/2,rightX-lineWidth/2,rightY+lineWidth/2,GL_POLYGON);
}

我们先定义了边界颜色绘制了边界,然后定义了图形的颜色来绘制填充的图形。其中 color 作为参数,可以传入不同的 RGB 值。 定义颜色 RGB 数组即可

1
2
3
4
5
6
int border[3]={0,0,0};
int grey[3]={195,195,195};
int yellow[3]={255,243,0};
int red[3]={237,28,36};
int darkGrey[3]={126,126,126};
int white[3]={255,255,255};

调用的时候我们只需要

1
glFillRect(-108,45,-81,0,darkGrey);

就可以绘制一个黑色边界的矩形了,填充色为暗灰色。 其他类似的方法实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//画填充的三角形,传值为逆时针,此法针对于机器人的正三角形
void glFillTri(int x1,int y1,int x2,int y2,int x3,int y3,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glTri(x1,y1,x2,y2,x3,y3,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glTri(x1+lineWidth/2,y1+lineWidth/2,x2-lineWidth/2,y2+lineWidth/2,x3,y3-lineWidth/2,GL_POLYGON);
}

//画填充的圆角矩形
void glFillRoundRec(int centerX,int centerY,int width,int height,float cirR,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glRoundRec(centerX,centerY,width,height,cirR,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glRoundRec(centerX,centerY,width-lineWidth/2,height-lineWidth/2,cirR,GL_POLYGON);
}

//画填充的圆形
void glFillCircle(double x, double y, double radius,int color[3]){
glColor3ub(border[0],border[1],border[2]);
glCircle(x,y,radius,GL_LINE_LOOP);
glColor3ub(color[0],color[1],color[2]);
glCircle(x,y,radius-lineWidth/2,GL_POLYGON);
}

恩,万事俱备只欠东风了,下面我们调用一下这几个函数直接进行绘制就好啦。 display 中的绘图调用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(lineWidth);

//设置画线颜色
glColor3ub(0,0,0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//画圆角矩形,大脸
glFillRoundRec(0,113,128,74,10,grey);
//两个眼睛
glFillCircle(-30,111,10,white);
glFillCircle(30,111,10,white);
//黑色线
glColor3ub(0,0,0);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45,GL_LINE_STRIP);
//画矩形,脖子
glFillRect(-25,76,25,60,grey);
//圆角矩形,两个耳朵
glFillRoundRec(81,115,20,34,5,darkGrey);
glFillRoundRec(-81,115,20,34,5,darkGrey);
//画圆角矩形,大肚子
glFillRoundRec(0,0,146,120,15,grey);
//画三角,肚子里的三角
glFillTri(-30,-15,30,-15,0,28,yellow);
//画圆,中间小圈
glFillCircle(0,0,10,red);
//画矩形,胳膊连接处
glFillRect(-81,43,-73,25,grey);
glFillRect(73,43,81,25,grey);
//画矩形,上臂
glFillRect(-108,45,-81,0,darkGrey);
glFillRect(81,45,108,0,darkGrey);
//画矩形,中臂
glFillRect(-101,0,-88,-4,grey);
glFillRect(88,0,101,-4,grey);
//画矩形,下臂
glFillRect(-108,-4,-81,-37,darkGrey);
glFillRect(81,-4,108,-37,darkGrey);
//画圆形,手掌
glFillCircle(-95,-47,10,grey);
glFillCircle(95,-47,10,grey);
//画腿连接处
glFillRect(-41,-62,-21,-66,grey);
glFillRect(21,-62,41,-66,grey);
//画圆角矩形,大长腿
glFillRoundRec(-32,-92,38,52,10,darkGrey);
glFillRoundRec(32,-92,38,52,10,darkGrey);
//画矩形,脚踝
glFillRect(-41,-117,-21,-125,grey);
glFillRect(21,-117,41,-125,grey);
//画矩形,大脚掌
glFillRect(-59,-125,-8,-137,darkGrey);
glFillRect(8,-125,59,-137,darkGrey);
glFlush();
}

恩,这样就大功告成啦,是不是比种子填充算法简单多了?

运行结果

QQ截图20150421000536 恩,那些小白点就忽略吧!!这叫不拘小节!!哈哈哈!!

总结

本节我们利用了类库的方法来实现了颜色填充,是不是很简单?