0%

OpenGL绘图实例一之机器人的绘制

综述

计算机图形学教材中有多种绘图方法,如直线的 DDA 算法、正负法、Bresenham 算法和画圆弧的正负法和 Bresenham 算法。 同样,OpenGL 类库也为我们提供了多种绘图方法,比如 glVertex2d,在这里我们用类库的方法来实现一个机器人的绘制。DDA 等算法实现之后我们再替换类库的 glVertex2d 方法。

绘制要求

利用 glVertex2d 和 glVertex2f 在二维平面上绘制如下的机器人。所以我们现在不需要三维的绘图方法,仅在平面绘制即可。 20150410115231

问题分析

经过观察我们发现,图中包含了圆角矩形,矩形,直线,圆形,弧形,三角形,而对于 glVertex2d 的方法,只是定位好坐标点,然后利用坐标点连线或者形成封闭图形来绘制。所以,对于圆角矩形,弧形等,我们可以利用三角函数来求取坐标,并连线即可。

1.圆角矩形的解决方案

对于圆角矩形,顾名思义每个角是由一个四分之一圆弧组成的。我们定义这个圆弧的半径为 cirR,整个圆角矩形的宽度为 width,高度为 height,那么抛出四个角的圆弧,就会在圆角矩形内部形成一个小的矩形,我们定义它的宽高分别为 w,h。另外,圆角矩形的中心点为(centerX,centerY),如下图所示 (博主原创图,盗图必究) 在 OpenGL 中,可以定义一个绘图模式

1
glBegin(GL_LINE_LOOP);

可以绘制封闭曲线,也就是说,我们所有调用的 glVertex2f 函数绘制的点均可以自动连接成一个封闭曲线。 所以,我们要做的就是找出圆角的坐标,利用 C++ 中的三角函数来计算右上角小圆所在的路径,我们设角度为 X,所以圆弧的 x 方向延伸长度为 cirRcosX,在 y 方向延伸长度为 cirRsinX 对于右上角的四分之一圆弧,它所在的路径 x 坐标便是 centerX+w/2+cirRcosX = centerX+width/2-cirR+cirRcosX,同理,y 路径坐标便是 centerY+h/2+cirRsinX = centerY+height/2-cirR+cirRsinX 当然对于其他的角,是加 w/2 还是减 w/2 就要看它所在的象限了。 另外对于精确度的问题,我们可以定义一个 divide 变量,将四分之一圆弧分成若干个点来绘制,当然分的份数越多,越精细,分的份数的值就是 divide,所以每次增加的弧度便是 PI/(2*divide) 方法最终实现如下,我们传入矩形的宽高,矩形中心的坐标,圆角半径即可进行绘制。

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
//画圆角矩形,传入矩形中心点坐标,矩形宽高,角半径
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
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();
}

例如我们可以调用

1
glRoundRec(0,0,146,120,15);

可以绘制如下的圆角矩形

2.圆弧的解决方案

对于圆弧的画法,我们也可以利用三角函数来解决,所以我们需要知道的参数就有半径,起始角度,还有圆弧的中心点。 代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
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();
}

至于画圆,我们只需要画一个弧度为 0 至 2PI 的圆弧即可。

1
2
3
4
5
6
//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

3.其他图形的解决方案

对于直线,三角形,矩形等等,就没有那么复杂了,只需要绘制几个端点即可完成绘制。在此不再赘述。

4.坐标的解决方案

要画图,最重要的便是坐标点,在此博主对图上的某些坐标进行了测量,假设总宽度为 300,对应的坐标值标注如下图所示,当然比例不一样的话,坐标会有成比例的变化,在此仅作参考。 robot 在图中,某些点的坐标已做好标注,仅供参考。

完整程序

完整的画机器人的程序实现如下

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <glut.h>
#include <math.h>
#define PI 3.1415926

//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY){
//画封闭曲线
glBegin(GL_LINE_LOOP);
//左下角
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){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
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)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
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)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

//画三角形,传入三个点的坐标
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();
}

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

//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
//设置画线颜色
glColor3f(0.5,0.5,0.5);
//画点大小
glPointSize(2);
//画圆角矩形,大肚子
glRoundRec(0,0,146,120,15);
//画圆,中间小圈
glCircle(0,0,10);
//画矩形,脖子
glRect(-25,60,25,76);
//画圆角矩形,大脸
glRoundRec(0,113,128,74,10);
//两个眼睛
glCircle(-30,111,10);
glCircle(30,111,10);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆角矩形,两个耳朵
glRoundRec(81,115,20,34,5);
glRoundRec(-81,115,20,34,5);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45);
//画三角,肚子里的三角
glTri(-30,-15,30,-15,0,28);
//画矩形,胳膊连接处
glRect(-81,43,-73,25);
glRect(81,43,73,25);
//画矩形,上臂
glRect(-108,45,-81,0);
glRect(108,45,81,0);
//画矩形,中臂
glRect(-101,0,-88,-4);
glRect(101,0,88,-4);
//画矩形,下臂
glRect(-108,-4,-81,-37);
glRect(108,-4,81,-37);
//画圆形,手掌
glCircle(-95,-47,10);
glCircle(95,-47,10);
//画腿连接处
glRect(-41,-62,-21,-66);
glRect(41,-62,21,-66);
//画圆角矩形,大长腿
glRoundRec(-32,-92,38,52,10);
glRoundRec(32,-92,38,52,10);
//画矩形,脚踝
glRect(-41,-125,-21,-117);
glRect(41,-125,21,-117);
//画矩形,大脚掌
glRect(-59,-125,-8,-137);
glRect(59,-125,8,-137);

//保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待
glFlush();
}


//窗口大小变化时调用的函数
void ChangeSize(GLsizei w,GLsizei h)
{
//避免高度为0
if(h==0) {
h=1;
}
//定义视口大小,宽高一致
glViewport(0,0,w,h);
int half = 200;
//重置坐标系统,使投影变换复位
glMatrixMode(GL_PROJECTION);
//将当前的用户坐标系的原点移到了屏幕中心
glLoadIdentity();
//定义正交视域体
if(w<h) {
//如果高度大于宽度,则将高度视角扩大,图形显示居中
glOrtho(-half,half,-half*h/w,half*h/w,-half,half);
} else {
//如果宽度大于高度,则将宽度视角扩大,图形显示居中
glOrtho(-half*w/h,half*w/h,-half,half,-half,half);
}

}

//程序入口
int main(int argc, char *argv[]){
//对GLUT进行初始化,并处理所有的命令行参数
glutInit(&argc, argv);
//指定RGB颜色模式和单缓冲窗口
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
//定义窗口的位置
glutInitWindowPosition(100, 100);
//定义窗口的大小
glutInitWindowSize(400, 400);
//创建窗口,同时为之命名
glutCreateWindow("OpenGL");
//设置窗口清除颜色为白色
glClearColor(1.0f,1.0f,1.0f,1.0f);
//参数为一个函数,绘图时这个函数就会被调用
glutDisplayFunc(&display);
//参数为一个函数,当窗口大小改变时会被调用
glutReshapeFunc(ChangeSize);
//该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止
glutMainLoop();
//程序返回
return 0;
}

运行效果如下图所示 20150410152854

综述

当然,这里直接调用了类库中的画线方法,而且用的是三角函数定位坐标。对于 DDA 算法等实现还没有进行替换,等实现 DDA 算法之后,再将绘制直线,圆弧等等的算法替换掉。同样可以实现机器人的绘制。 希望对大家有帮助!