使用OpenGL创建简单时钟动画

CCBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

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

👀 预览

Clock Opengl

🎯 任务

在这个项目中,你将学习:

  • 如何设置项目文件和库
  • 如何创建窗口并初始化OpenGL
  • 如何绘制时钟背景和轮廓
  • 如何旋转时钟,使12点位置位于顶部
  • 如何获取当前时间并计算时钟指针的位置
  • 如何在时钟上绘制时针、分针和秒针
  • 如何调整窗口大小并实时显示时钟

🏆 成果

完成这个项目后,你将能够:

  • 设置并初始化OpenGL和GLUT
  • 使用OpenGL绘制基本形状和线条
  • 在OpenGL中旋转对象
  • 获取当前时间并用于动画对象
  • 处理窗口大小调整和图形的实时显示

创建项目文件

首先,在终端中运行以下命令来安装OpenGLGLUT库:

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_WIDTHWINDOW_HEIGHTROTATE_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窗口中绘制一个深灰色的矩形作为时钟的背景。四个顶点定义了矩形的边界,glBeginglEnd之间的代码用于定义图形的外观。这是绘制时钟背景的一部分,通常在绘制时针、分针、秒针等其他元素之前进行。

✨ 查看解决方案并练习

旋转时钟

我们将添加代码,把时钟旋转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设置线条宽度,glBeginglEnd之间的循环在以原点为中心的圆上绘制一系列点,然后将这些点连接起来形成一个封闭的环,即时钟的外轮廓。

✨ 查看解决方案并练习

获取当前时间并计算时钟指针的位置

我们将获取当前时间,并计算时钟指针(时针、分针和秒针)的位置。

// 在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结构的指针,名为timeinfotm结构用于存储时间和日期信息的各个组成部分,如年、月、日、时、分、秒等。

使用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)处。线段的终点,使用三角函数cossin来计算时针的终点位置。后续的分针和秒针也以这种方式绘制。

✨ 查看解决方案并练习

绘制分针

我们将添加代码在时钟上绘制分针。

// 在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();请求重新绘制窗口内容,以便实时地为窗口内容制作动画或进行更新。

✨ 查看解决方案并练习

编译并运行项目

  1. 打开你的终端并导航到项目目录。
cd ~/project
  1. 使用以下命令编译代码:
gcc -o clock_opengl clock_opengl.c -lGL -lglut -lGLU -lm
  1. 运行应用程序:
./clock_opengl
Clock Opengl
✨ 查看解决方案并练习

总结

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

您可能感兴趣的其他 C 教程