1、在窗口中绘制一个矩形。分别实现键盘和鼠标对矩形的控制,例如:拖动,缩放。并添加菜单进行颜色修改,图元设置等属性操作。
#include <GL/glut.h>
// 矩形的位置和大小
GLfloat rectX = 100.0f;
GLfloat rectY = 100.0f;
GLfloat rectWidth = 200.0f;
GLfloat rectHeight = 100.0f;
// 鼠标拖动相关变量
bool isDragging = false;
int dragStartX = 0;
int dragStartY = 0;
// 键盘平移和缩放相关变量
GLfloat translateX = 0.0f;
GLfloat translateY = 0.0f;
GLfloat scale = 1.0f;
// 矩形颜色
GLfloat red = 0.0f;
GLfloat green = 0.0f;
GLfloat blue = 1.0f;
// 图元设置
GLint drawMode = GL_FILL; // 默认填充模式
// 绘制矩形
void drawRectangle()
{
glPolygonMode(GL_FRONT_AND_BACK, drawMode); // 设置图元绘制模式
glPushMatrix();
glTranslatef(rectX + translateX, rectY + translateY, 0.0f);
glScalef(scale, scale, 1.0f);
glBegin(GL_QUADS);
glVertex2f(0.0f, 0.0f);//左下角
glVertex2f(rectWidth, 0.0f);//右下角
glVertex2f(rectWidth, rectHeight);//右上角
glVertex2f(0.0f, rectHeight);//左上角
glEnd();
glPopMatrix();
}
// 处理键盘事件
void keyboardFunc(unsigned char key, int x, int y) {
if (key == '+') {
scale += 0.1f; // 放大矩形
}
else if (key == '-') {
scale -= 0.1f; // 缩小矩形
}
else if (key == 'w') {
translateY += 10.0f; // 上移矩形
}
else if (key == 's') {
translateY -= 10.0f; // 下移矩形
}
else if (key == 'a') {
translateX -= 10.0f; // 左移矩形
}
else if (key == 'd') {
translateX += 10.0f; // 右移矩形
}
else if (key == 'f') {
drawMode = GL_FILL; // 填充模式
}
else if (key == 'l') {
drawMode = GL_LINE; // 线框模式
}
else if (key == 'p') {
drawMode = GL_POINT; // 点模式
}
switch (key)
{
case 'R' : // 红色加深
red += 0.1f; break;
case 'r': // 红色变浅
red -= 0.1f; break;
case 'G':// 绿色加深
green += 0.1f; break;
case 'g':// 绿色变浅
green -= 0.1f; break;
case 'B':// 绿色变浅
blue += 0.1f; break;
case 'b':// 绿色变浅
blue -= 0.1f; break;
case 'q':// 绿色变浅
{ red = green = 0.0f; blue = 1.0f; }
break;
}
glutPostRedisplay(); // 重新绘制
}
// 处理鼠标点击事件
void mouseFunc(int button, int state, int x, int y) {
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
isDragging = true;
dragStartX = x;
dragStartY = y;
}
else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
isDragging = false;
}
else if (button == 3) { // 滚轮向前滚动
scale += 0.1f; // 放大矩形
glutPostRedisplay(); // 重新绘制
}
else if (button == 4) { // 滚轮向后滚动
scale -= 0.1f; // 缩小矩形
glutPostRedisplay(); // 重新绘制
}
}
// 处理鼠标拖动事件
void motionFunc(int x, int y) {
if (isDragging) {
int deltaX = x - dragStartX;
int deltaY = y - dragStartY;
translateX += deltaX;
translateY -= deltaY; // 注意坐标系差异,鼠标向上拖动实际上是y减小
/* 鼠标向上拖动导致 y 坐标减小的原因是OpenGL中的窗口坐标系与屏幕坐标系的差异。
在屏幕坐标系中,通常原点位于左上角,向下移动会使 y 坐标增大,而向上移动会使 y 坐标减小。
而在OpenGL中,默认情况下使用的窗口坐标系,原点位于左下角,向上移动会使 y 坐标增大,而向下移动会使 y 坐标减小。
当处理鼠标拖动事件时,鼠标的移动距离(`deltaX` 和 `deltaY`)是基于屏幕坐标系计算的。因此,为了将鼠标的移动与窗口坐标系保持一致,我们需要将 y 坐标的变化取反,即 `translateY -= deltaY`。
这样做可以确保在OpenGL窗口中,当鼠标向上拖动时,矩形会相应地向上移动。*/
dragStartX = x;
dragStartY = y;
glutPostRedisplay(); // 重新绘制
}
}
// 处理菜单选择事件
void menuFunc(int value) {
switch (value) {
case 1: // 设置矩形颜色为红色加深
red += 0.1f;
break;
case 2: // 设置矩形颜色为红色变浅
red -= 0.1f;
break;
case 3: // 设置矩形颜色为绿色加深
green += 0.1f;
break;
case 4: // 设置矩形颜色为绿色变浅
green -= 0.1f;
break;
case 5: // 设置矩形颜色为蓝色加深
blue += 0.1f;
break;
case 6: // 设置矩形颜色为蓝色变浅
blue -= 0.1f;
break;
case 7: // 恢复初始颜色为蓝色
red = 0.0f;
green = 0.0f;
blue = 1.0f;
break;
}
glutPostRedisplay(); // 重新绘制
}
// 显示回调函数
void displayFunc() {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 设置清屏颜色为黑色
glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲区
glColor3f(red, green, blue); // 设置绘制颜色为红色
drawRectangle(); // 绘制矩形
glFlush(); // 刷新缓冲区,将绘制内容显示到屏幕上
}
int main(int argc, char** argv) {
glutInit(&argc, argv); // 初始化GLUT
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // 设置显示模式
glutInitWindowSize(500, 500); // 设置窗口尺寸
glutCreateWindow("OpenGL Rectangle"); // 创建窗口,标题为"OpenGL Rectangle"
glMatrixMode(GL_PROJECTION); // 设置投影矩阵模式
glLoadIdentity(); // 装载单位矩阵
gluOrtho2D(0.0, 500.0, 0.0, 500.0); // 设置正交投影
glutDisplayFunc(displayFunc); // 注册显示回调函数
glutMouseFunc(mouseFunc); // 注册鼠标点击回调函数
glutMotionFunc(motionFunc); // 注册鼠标拖动回调函数
glutKeyboardFunc(keyboardFunc); // 注册键盘回调函数
GLint menuColor = glutCreateMenu(menuFunc);
glutAddMenuEntry("Red++", 1);
glutAddMenuEntry("Red--", 2);
glutAddMenuEntry("Green++", 3);
glutAddMenuEntry("Green--", 4);
glutAddMenuEntry("Blue++", 5);
glutAddMenuEntry("Blue--", 6);
GLint menuDrawMode = glutCreateMenu(menuFunc);
glutAddMenuEntry("Fill", 7);
glutAddMenuEntry("Wireframe", 8);
glutAddMenuEntry("Points", 9);
GLint mainMenu = glutCreateMenu(menuFunc);
glutAddSubMenu("Color", menuColor);
glutAddSubMenu("Draw Mode", menuDrawMode);
glutAddMenuEntry("Reset", 10);
glutAttachMenu(GLUT_RIGHT_BUTTON); // 将菜单绑定到右键
glutMainLoop(); // 进入主循环,开始事件处理
return 0;
}
初始状态是蓝色的矩形
鼠标点击拖动矩形,中键滚轮前后缩放矩形,
键盘按wasd键实现矩形向上、左、下、右平移,
按“+”键放大,按“-”键缩小,
按 q键恢复矩形颜色为蓝色,
R键是红色加深,r键是红色变浅
G键是绿色加深,g键是绿色变浅
B键是蓝色加深,b键是蓝色变浅
鼠标右键点击出现菜单,Draw Mode是图元操作,依次为填充模式,线框模式,点模式
2、编写一个时钟绘制程序。
glFlush()函数和glClear(GL_COLOR_BUFFER_BIT)函数的作用分别是什么?
(可将这两个函数注释掉,和注释前的结果对比)
(1)glFlush() 函数用于强制执行所有待处理的OpenGL命令,并将它们发送到渲染管线进行处理。它会等待所有先前的绘图命令执行完成,然后将绘图结果立即输出到屏幕上。
在双缓冲机制下,glFlush() 会将绘制命令刷新到后备缓冲区,但不会立即将后备缓冲区的内容显示在屏幕上。相反,它只是确保所有绘制命令都已提交到渲染管线,并等待渲染完成。
glFlush() 通常在需要强制立即显示绘制结果时使用,例如在交互式绘图或实时渲染应用中。
注释掉后:
(2)glClear(GL_COLOR_BUFFER_BIT) 函数用于清除颜色缓冲区的内容。它将指定的颜色值应用于整个颜色缓冲区,使其完全填充为指定的颜色。
颜色缓冲区是用于存储绘制图形的像素颜色值的内存区域。调用glClear(GL_COLOR_BUFFER_BIT) 会清除颜色缓冲区的内容,将其重置为初始状态。通常,这个函数会在每次绘制新帧之前调用,以确保在绘制新图形之前清除之前的内容。GL_COLOR_BUFFER_BIT 是一个标志位,用于指示需要清除颜色缓冲区。
注释掉后:
(1)尝试直线与圆的绘制。
(2)尝试旋转与移动等功能。
#include <GL/glut.h>
#include <math.h>
#define PI 3.14159265358979323846
float angle_hour = 90.0;
float angle_minute = 90.0;
float angle_second = 90.0;
void init()
{
glClearColor(1.0f, 1.0f, 1.0f, 0.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
}
void drawClock()
{
glClear(GL_COLOR_BUFFER_BIT);
// 设置圆环
glColor3f(1.0f, 0.0f, 1.0f);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i++)//从0开始迭代到359,总共循环360次。在圆形边界上绘制足够多的点,使得最终看起来像一个圆。
{
float angle = float(2 * PI * i / 360);//计算当前循环迭代的角度。我们将角度范围从0到360度映射到0到2π(弧度制)范围。PI是圆周率,即3.14159265358979323846。
float x = float(0.8 * cos(angle));//计算当前角度下的x坐标。通过使用三角函数cos(),将角度转换为x轴上的坐标值。这里将x轴的长度缩放为0.8,使得圆的半径为0.8。
float y = float(0.8 * sin(angle));//计算当前角度下的y坐标。通过使用三角函数sin(),将角度转换为y轴上的坐标值。同样,y轴的长度也缩放为0.8。
glVertex2f(x, y);
}
glEnd();
//画时针
glPushMatrix();
glRotatef(angle_hour, 0.0, 0.0, 1.0);
glColor3f(1.0, 1.0, 0.0); //黄色
glBegin(GL_LINES);
glVertex2f(0.0, 0.0);
glVertex2f(0.2, 0.0);
glEnd();
glPopMatrix();
// 画分针
glPushMatrix();
glRotatef(angle_minute, 0.0, 0.0, 1.0);
glColor3f(0.0, 1.0, 1.0); //青色
glBegin(GL_LINES);
glVertex2f(0.0, 0.0);
glVertex2f(0.4, 0.0);
glEnd();
glPopMatrix();
// 画秒针
glPushMatrix();
glRotatef(angle_second, 0.0, 0.0, 1.0);
glColor3f(1.0, 0.0, 1.0);//紫色
glBegin(GL_LINES);
glVertex2f(0.0, 0.0);
glVertex2f(0.6, 0.0);
glEnd();
glPopMatrix();
glFlush();
}
void update(int value)
{
angle_second -= 6.0;
/*每次调用时,秒针的旋转角度减去6度。这里的6.0是指秒针每秒钟旋转的角度,通过将整个圆的360度除以60秒,得到每秒钟旋转的角度为6度。*/
angle_minute -= 0.1;
//每次调用时,分针的旋转角度减去0.1度。这里的0.1是指分针每秒钟旋转的角度,通过将整个圆的360度除以60分钟,得到每秒钟旋转的角度为0.1度。
angle_hour -= 1.0 / 120;
// 每次调用时,时针的旋转角度减去1/120度。这里的1.0 / 120是指时针每秒钟旋转的角度,360/12/60/60=1/120通过将整个圆的360度除以7200秒,得到每秒钟旋转的角度为1/120度。
glutPostRedisplay();
glutTimerFunc(1000, update, 0);//注册一个定时器,每隔1000毫秒(即1秒)调用一次 update() 函数,以便更新时钟指针的旋转角度。
//在update函数的末尾,要再次调用 glutTimerFunc 来注册下一次定时器,以便创建循环的定时器效果。
}
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
case 'a':
angle_hour += 30.0; // 时针逆时针转一个数字转30度
break;
case 's':
angle_hour -= 30.0; // 时针顺时针转一个数字转30度
break;
case 'd':
angle_minute += 6.0; // 分针逆时针转一个数字转6度
break;
case 'f':
angle_minute -= 6.0; // 分针顺时针转一个数字转6度
break;
case 'g':
angle_second += 6.0; // 秒针逆时针转一个数字转6度
break;
case 'h':
angle_second -= 6.0; //秒针顺时针转一个数字转6度
break;
}
glutPostRedisplay();
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("Clock");
init();
glutDisplayFunc(drawClock);
glutTimerFunc(1000, update, 0);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
以上程序绘制一个简单的时钟,时针、分针和秒针分别通过键盘控制顺时针和逆时针旋转。按下 'a' 键时,时针顺时针旋转30度;按下 's' 键时,时针逆时针旋转30度;按下 'd' 键时,分针顺时针旋转6度;按下 'f' 键时,分针逆时针旋转6度;按下 'g' 键时,秒针顺时针旋转6度;按下 'h' 键时,秒针逆时针旋转6度。