介绍
在这个项目中,我们将使用 OpenGL 和 GLUT(图形库实用工具包)创建一个简单的时钟动画。这个动画将显示一个带有移动指针的时钟,以表示当前时间。时钟将实时更新,模拟时针、分针和秒针的运动。我们将首先设置项目文件,然后编写必要的代码。
👀 预览

🎯 任务
在这个项目中,你将学习:
- 如何设置项目文件和库
- 如何创建窗口并初始化 OpenGL
- 如何绘制时钟背景和轮廓
- 如何旋转时钟,使 12 点位置位于顶部
- 如何获取当前时间并计算时钟指针的位置
- 如何在时钟上绘制时针、分针和秒针
- 如何调整窗口大小并实时显示时钟
🏆 成果
完成这个项目后,你将能够:
- 设置并初始化 OpenGL 和 GLUT
- 使用 OpenGL 绘制基本形状和线条
- 在 OpenGL 中旋转对象
- 获取当前时间并用于动画对象
- 处理窗口大小调整和图形的实时显示
创建项目文件
首先,在终端中运行以下命令来安装OpenGL和GLUT库:
sudo apt update
sudo apt-get install mesa-utils freeglut3-dev -y
这将安装 Mesa 3D 图形库以及 GLUT 库。之后,你可以使用gcc编译 OpenGL 程序。
接下来,创建一个名为clock_opengl.c的新文件,并在你喜欢的代码编辑器中打开它。
cd ~/project
touch clock_opengl.c
包含头文件并定义变量
接下来,我们将逐步编写代码来实现这个项目。
#include <GL/gl.h>
#include <GL/glut.h>
#include <time.h>
#include <math.h>
#include <stdio.h>
const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;
const int ROTATE_DEGREES = 180;
定义了三个整数常量,WINDOW_WIDTH、WINDOW_HEIGHT 和 ROTATE_DEGREES。这些常量用于定义窗口的宽度和高度以及旋转角度。
设置窗口并初始化 OpenGL
在这一步中,我们将配置窗口大小并初始化 OpenGL 和 GLUT。
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutCreateWindow("Clock Animation");
glutDisplayFunc(drawClockHands);
glutReshapeFunc(reshape);
glutIdleFunc(idle);
glClearColor(0.0, 0.0, 0.0, 1.0);
glutMainLoop();
return 0;
}
glutInit():用于初始化 GLUT 库。glutInitDisplayMode():设置窗口的显示模式。GLUT_DOUBLE表示使用双缓冲,GLUT_RGB表示使用 RGB 颜色模式。glutInitWindowSize():设置窗口的大小。glutCreateWindow():创建一个窗口,并将窗口的标题设置为“Clock Animation”。glutDisplayFunc():设置绘图函数,以便在需要绘制图形时调用drawClockHands函数。glutReshapeFunc():设置窗口大小调整的回调函数,即当窗口大小改变时,将调用reshape函数。glutIdleFunc():设置空闲回调函数,即在没有其他事件处理时,将调用idle函数。glClearColor(0.0, 0.0, 0.0, 1.0):将清空颜色设置为黑色(RGBA 颜色值)。glutMainLoop():进入 GLUT 的主循环,它持续运行,处理窗口事件、绘制图形和用户输入。
绘制时钟背景
创建一个名为drawClockHands的函数,我们将添加代码来绘制时钟的背景,包括一个深色的圆形。
void drawClockHands() {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glColor3f(0.1, 0.1, 0.1);
glVertex2f(-1, -1);
glVertex2f(1, -1);
glVertex2f(1, 1);
glVertex2f(-1, 1);
glEnd();
}
glClear():此函数调用会清除颜色缓冲区,为绘制新图形做准备。GL_COLOR_BUFFER_BIT表示清除颜色缓冲区,以便在绘制新图形之前清除上一次绘制的内容。glBegin():此函数表示开始定义一个图形元素(在这种情况下是一个四边形)。GL_QUADS表示我们要使用四边形来定义图形。glColor3f():将当前绘图颜色的浮点值设置为 RGB 颜色(红色、绿色、蓝色)。这里,颜色被设置为深灰色,RGB 值(0.1, 0.1, 0.1)代表深灰色。glVertex2f():定义四边形的角点。
这里的目的是在 OpenGL 窗口中绘制一个深灰色的矩形作为时钟的背景。四个顶点定义了矩形的边界,glBegin和glEnd之间的代码用于定义图形的外观。这是绘制时钟背景的一部分,通常在绘制时针、分针、秒针等其他元素之前进行。
旋转时钟
我们将添加代码,把时钟旋转 180 度,使 12 点位置在顶部。
// 在 drawClockHands() 函数内部
glPushMatrix();
glRotatef(ROTATE_DEGREES, 0, 0, 1);
glPushMatrix():这是 OpenGL 中的一个函数,它将当前矩阵状态(通常是模型视图矩阵)压入栈中,以便稍后可以恢复。这是一种常见的做法,因为在绘图中可能需要进行几种不同的矩阵变换,并且需要确保后续变换不会影响先前的状态。glRotatef():这是另一个 OpenGL 函数,用于执行旋转变换。它将当前模型视图矩阵绕 Z 轴旋转ROTATE_DEGREES度。(0, 0, 1)是旋转轴的定义,这里表示绕 Z 轴旋转。第一个值是绕 X 轴的旋转分量,第二个值是绕 Y 轴的旋转分量,第三个值是绕 Z 轴的旋转分量。
绘制时钟轮廓
我们将绘制时钟的轮廓,并为每个小时标记刻度。
// 在 drawClockHands() 函数内部
glLineWidth(3.0);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i += 6) {
double angle = i * M_PI / 180;
double x = 0.9 * cos(angle);
double y = 0.9 * sin(angle);
glColor3f(1.0, 1.0, 1.0);
glVertex2d(x, y);
}
glEnd();
这里的主要目的是在时钟的背景上绘制一个白色的圆形轮廓,代表时钟的外框。glLineWidth设置线条宽度,glBegin和glEnd之间的循环在以原点为中心的圆上绘制一系列点,然后将这些点连接起来形成一个封闭的环,即时钟的外轮廓。
获取当前时间并计算指针位置
我们将获取当前时间,并计算时钟指针(时针、分针和秒针)的位置。
// 在 drawClockHands() 函数内部
time_t rawtime;
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
int hours = timeinfo->tm_hour % 12;
int minutes = timeinfo->tm_min;
int seconds = timeinfo->tm_sec;
定义一个名为rawtime的变量,用于存储从系统获取的原始时间数据。time_t是一种数据类型,通常用于表示从特定时间点(通常是 1970 年 1 月 1 日)到当前时间的秒数。
定义一个指向tm结构的指针,名为timeinfo。tm结构用于存储时间和日期信息的各个组成部分,如年、月、日、时、分、秒等。
使用time函数获取当前系统时间,将时间的秒数存储在rawtime变量中。time函数返回的秒数表示从特定时间点(通常是 1970 年 1 月 1 日,也称为 UNIX 时间戳)到当前时间的秒数。
使用localtime函数将从time函数获取的秒数(存储在rawtime变量中)转换为具体的时间和日期信息,并将结果存储在timeinfo结构中。localtime函数将秒数转换为年、月、日、时、分、秒等信息,并将这些信息存储在timeinfo结构中。
从timeinfo结构中获取当前小时数,然后对 12 取模,将小时数限制在 12 小时制范围内,tm_hour表示小时。从timeinfo结构中获取当前分钟数,tm_min表示分钟。从timeinfo结构中获取当前秒数,tm_sec表示秒。
绘制时针
我们将在时钟上绘制时针。
// 在 drawClockHands() 函数内部
double hourAngle = -(90 + hours * 360 / 12) + (360 / 12) * minutes / 60;
glLineWidth(5.0); // 设置线条宽度
glBegin(GL_LINES);
glVertex2d(0, 0);
glColor3f(1.0, 0.0, 0.0); // 时针颜色
glVertex2d(0.5 * cos(hourAngle * M_PI / 180), 0.5 * sin(hourAngle * M_PI / 180));
glEnd();
首先,时钟通常采用 12 小时制,因此需要将hours的数值转换为表盘上的角度。此计算首先将 12 小时制的小时数映射到 360 度的角度范围。然后加上minutes(由分针的位置)对时针角度的贡献。得到的hourAngle表示时针相对于 12 点方向的角度。
然后,定义一个元素,在这种情况下是一条线段,代表时针。线段的起点位于时钟中心的坐标 (0, 0) 处。线段的终点,使用三角函数cos和sin来计算时针的终点位置。后续的分针和秒针也以这种方式绘制。
绘制分针
我们将添加代码在时钟上绘制分针。
// 在 drawClockHands() 函数内部
double minuteAngle = -(90 + minutes * 360 / 60);
glLineWidth(3.0); // 设置线条宽度
glBegin(GL_LINES);
glVertex2d(0, 0);
glColor3f(0.0, 1.0, 0.0); // 分针颜色
glVertex2d(0.7 * cos(minuteAngle * M_PI / 180), 0.7 * sin(minuteAngle * M_PI / 180));
glEnd();
时钟表盘通常为 60 分钟,因此需要将minutes的数值转换为表盘上的角度。此计算首先将分钟数映射到 360 度的角度范围,并减去 90 度,以使 0 分钟对应于 12 点方向。得到的minuteAngle表示分针相对于 12 点方向的角度。
绘制秒针
我们将添加代码在时钟上绘制秒针。
// 在 drawClockHands() 函数内部
double secondAngle = -(90 + seconds * 360 / 60);
glLineWidth(1.0); // 设置线条宽度
glBegin(GL_LINES);
glVertex2d(0, 0);
glColor3f(0.0, 0.0, 1.0); // 秒针颜色
glVertex2d(0.9 * cos(secondAngle * M_PI / 180), 0.9 * sin(secondAngle * M_PI / 180));
glEnd();
时钟通常为 60 秒,因此需要将seconds的数值转换为钟面上的角度。此计算首先将秒数映射到 360 度的角度范围,并减去 90 度,以使 0 秒对应于 12 点方向。得到的secondAngle表示秒针相对于 12 点方向的角度。
最后,我们将恢复原来的旋转并显示时钟动画。
// 在 drawClockHands() 函数内部
glPopMatrix();
glutSwapBuffers();
glPopMatrix()用于恢复先前保存的矩阵状态,glutSwapBuffers()用于完成双缓冲绘制,将绘制的图像渲染到屏幕上以实现平滑的动画效果。
窗口重塑与实时显示
我们将创建一个在窗口大小改变时被调用的reshape函数。
void reshape(int w, int h) {
glViewport(0, 0, w, h); // 设置 OpenGL 视口以调整窗口大小
glMatrixMode(GL_PROJECTION); // 切换到投影矩阵模式
glLoadIdentity(); // 重置当前矩阵
gluOrtho2D(-1, 1, -1, 1); // 设置正交投影矩阵
glMatrixMode(GL_MODELVIEW); // 切换回模型视图矩阵模式
}
这个reshape函数的目的是在窗口大小改变时,根据新的窗口大小调整 OpenGL 的视口,并设置合适的投影矩阵,以确保绘制的图形在新的窗口大小下仍能正确显示。
接下来,我们定义一个名为idle的函数,在程序空闲时执行一些操作。
void idle() {
glutPostRedisplay(); // 在空闲时请求重新显示
}
这个idle函数的目的是在程序空闲时调用glutPostRedisplay();请求重新绘制窗口内容,以便实时地为窗口内容制作动画或进行更新。
编译并运行项目
- 打开你的终端并导航到项目目录。
cd ~/project
- 使用以下命令编译代码:
gcc -o clock_opengl clock_opengl.c -lGL -lglut -lGLU -lm
- 运行应用程序:
./clock_opengl

总结
在这个项目中,你已经学习了如何使用 OpenGL 和 GLUT 创建一个简单的时钟动画。我们涵盖了设置项目文件、初始化窗口和 OpenGL、绘制时钟背景、旋转时钟、绘制时钟轮廓、获取当前时间以及绘制时钟指针。通过遵循这些步骤,你可以创建一个基本的时钟动画,以视觉上引人入胜的方式显示当前时间。



