OpenGL/GLUT实践:实现反弹运动的三角形动画与键盘控制(电子科技大学信软图形与动画Ⅱ实验)

本文主要是介绍OpenGL/GLUT实践:实现反弹运动的三角形动画与键盘控制(电子科技大学信软图形与动画Ⅱ实验),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

源码见GitHub:A-UESTCer-s-Code

文章目录

    • 1 运行效果
    • 2 实验过程
      • 2.1 环境配置
      • 2.2 绘制三角形
        • 2.2.1 渲染函数
        • 2.2.2 主函数
        • 2.2.3 运行结果
      • 2.3 调整窗口大小
      • 2.4 简单动画与按键控制
        • 2.4.1 简单旋转
        • 2.4.2 键盘控制
      • 2.5 窗口反弹动画
        • 2.5.1 处理窗口大小变化
        • 2.5.2 渲染函数
        • 2.5.3 定时器
        • 2.5.4 控制速度

1 运行效果

我们运行程序,得到一个运动的等腰三角形,当其触碰到边框时会反弹,并且可以通过键盘上的F1、F2、F3来控制颜色,wasd来控制速度。

recording

2 实验过程

2.1 环境配置

OpenGL作为图形显示库,在图形建模、游戏开发和科学可视化等领域有着广泛的应用,而GLUT作为其补充库则提供了方便的窗口管理和用户交互功能,简化了OpenGL程序的开发过程。

步骤:

  1. 下载Glut的依赖库:

    • 访问OpenGL官网或直接从https://www.opengl.org/resources/libraries/glut/glut_downloads.php#windows下载Glut
    • 解压下载文件,得到glut.dllglut32.dllglut.libglut32.libglut.h5个文件
  2. 配置OpenGL环境:

    • glut.h文件复制到 Visual Studio 安装目录下的 include 文件夹内的一个新建名为 GL 的子文件夹中

      image-20240330085410414
    • glut32.lib文件复制到 Visual Studio 安装目录下的lib文件夹中的x86文件夹内

      image-20240330085440530
    • glut.lib文件复制到 Visual Studio 安装目录下的lib文件夹中的x64文件夹内

      image-20240330085509954
    • glut.dllglut32.dll文件复制到系统文件夹 C:\Windows\SysWOW64

      image-20240330085332324
  3. 创建OpenGL项目:

    • 在Microsoft Visual Studio中创建一个控制台应用程序项目

    • 添加源文件(如main.cpp)到项目中

    • 在源文件中引入OpenGL和GLUT库:#include <GL/glut.h>

  4. 编写测试程序:

    • 编写一个简单的OpenGL程序,例如,绘制一个正方形
    • 使用OpenGL提供的函数进行图形绘制
    • 设置窗口的显示模式、大小、位置等参数
    • 编译并运行程序,检查环境配置是否成功

    运行结果:

    image-20240330091416015

2.2 绘制三角形

具体步骤:

2.2.1 渲染函数
void renderScene() {glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区// 绘制三角形glBegin(GL_TRIANGLES);glColor3f(181./225, 206./225, 163./255); // 设置颜色为指定的RGB值glVertex2f(-0.88, -0.5); // 左下角glVertex2f(0.64, -0.5); // 右下角glVertex2f(0.43, 0.7); // 顶点glEnd();glFlush(); // 刷新绘图命令
}

作用:renderScene() 函数是一个回调函数,在窗口需要被绘制时被调用,用于绘制图形。

细节讲解:

  • glClear(GL_COLOR_BUFFER_BIT);:清除颜色缓冲区,确保每次绘制前都有一个干净的画布

  • glBegin(GL_TRIANGLES);glEnd();:开始和结束绘制三角形的过程

  • glColor3f(181./225, 206./225, 163./255);:设置绘制图形的颜色为指定的RGB值。需要注意的是,这里的颜色值是使用范围在0到1之间的小数来表示的,所以将RGB值除以255来进行归一化处理

  • glVertex2f():指定三角形的顶点坐标,这里使用的是二维坐标

    坐标是以窗口为中心, x x x y y y 轴范围从-11,三角形只需要设置三个顶点即可:

    glVertex2f(-0.88, -0.5); // 左下角
    glVertex2f(0.64, -0.5); // 右下角
    glVertex2f(0.43, 0.7); // 顶点
    
    image-20240330092749677
2.2.2 主函数
int main(int argc, char** argv) {glutInit(&argc, argv); // 初始化GLUT库glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // 设置显示模式为单缓冲和RGB颜色模式glutInitWindowSize(400, 400); // 设置窗口大小glutInitWindowPosition(100, 100); // 设置窗口位置glutCreateWindow("Simple Triangle Test"); // 创建窗口,并设置标题glutDisplayFunc(renderScene); // 设置绘图函数glutMainLoop(); // 进入主循环,等待事件return 0;
}

作用: main() 函数是程序的入口,主要负责初始化OpenGL环境和GLUT库,并进入主循环等待事件。

细节讲解:

  • glutInit(&argc, argv);:初始化GLUT库,接受程序启动时的命令行参数
  • glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);:设置显示模式为单缓冲和RGB颜色模式,这意味着窗口使用单缓冲区,并且颜色采用RGB模式
  • glutInitWindowSize(400, 400);:设置窗口的大小为400x400像素
  • glutInitWindowPosition(100, 100);:设置窗口的位置为屏幕左上角起始点向右下移动100像素的位置
  • glutCreateWindow("Simple Triangle Test");:创建窗口,并设置窗口标题为 “Simple Triangle Test”
  • glutDisplayFunc(renderScene);:注册回调函数 renderScene(),当窗口需要被重绘时会调用该函数进行绘制
  • glutMainLoop();:进入主循环,等待事件的发生,例如窗口的关闭、键盘输入等
2.2.3 运行结果

通过运行得到一个颜色为黄绿色(RGB为181, 206, 163),顶点为(0.43, 0.7)(0.64, -0.5)(-0.88, -0.5)的普通三角形

image-20240330093833522

2.3 调整窗口大小

当我们调整窗口时,图像出现了明显的变形,窗口大小改变会影响OpenGL的视口和投影矩阵,而默认情况下OpenGL使用的是透视投影矩阵,因此窗口大小改变会导致图像的变形。为了解决这个问题,我们需要重新计算投影矩阵,并将其与窗口的新大小相匹配。

image-20240330094129019

在OpenGL中,我们可以使用 glutReshapeFunc() 函数来注册一个回调函数,以便在窗口大小改变时进行处理。这个函数的原型如下:

void glutReshapeFunc(void (*func)(int width, int height));

这个函数将一个自定义的函数与窗口大小改变事件绑定,当窗口大小改变时,该函数就会被调用。

接下来,我们需要编写一个自定义的函数来处理窗口大小改变事件。这个函数应该重新计算投影矩阵,并将其与新的窗口大小相匹配。下面是一个示例函数:

void changeSize(int width, int height) {// 避免除以0if (height == 0) height = 1;// 设置视口大小glViewport(0, 0, width, height);// 设置投影矩阵glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);// 切换回模型观察矩阵glMatrixMode(GL_MODELVIEW);glLoadIdentity();
}

方法:

为了处理窗口大小的改变,我们使用了OpenGL提供的glutReshapeFunc()函数来注册一个回调函数,该函数会在窗口大小改变时被调用。在这个回调函数中,我们重新计算投影矩阵,并根据新的窗口大小设置视口和透视投影。

具体而言,我们的处理过程包括以下步骤:

  1. 检查窗口的高度是否为0,如果是则将其设置为1,以避免除以0的错误
  2. 计算窗口的宽高比,以便在设置透视投影时使用
  3. 将当前矩阵模式设置为投影矩阵模式,并重置矩阵
  4. 设置OpenGL的视口大小为新的改变后的窗口大小
  5. 使用gluPerspective()函数设置透视投影,其中包括视角大小、宽高比、近裁剪面和远裁剪面的距离
  6. 将当前矩阵模式设置回模型视图矩阵模式,并重置矩阵
  7. 使用gluLookAt()函数设置观察者的视点和方向,以模拟相机在场景中的位置和方向

最后,在 main() 函数中,我们需要将这个自定义的函数与窗口大小改变事件绑定起来,如下所示:

glutReshapeFunc(changeSize);

注意:这个函数要在主循环 glutMainLoop();,否则无法生效(因为程序一直在主循环运行,运行不到这个函数)

通过这样的设置,当我们调整窗口大小时,OpenGL会自动调用 changeSize() 函数来重新计算投影矩阵,从而保证图像的正确显示

执行结果:

recording

2.4 简单动画与按键控制

2.4.1 简单旋转

目的:

本部分旨在实现一个简单的OpenGL动画,让一个三角形在窗口中绕固定轴匀速旋转。

方法:

为了实现动画效果,我们需要做以下几个步骤:

  1. main()函数中,设置双缓冲区模式(GLUT_DOUBLE)以及RGB颜色模式(GLUT_RGB),这样可以避免画面闪烁并且产生平滑的动画效果

    双缓冲区通过在后一个缓冲区里绘画,并不停交换前后缓冲区(可见缓冲区),来产生平滑的动画。使用双缓冲区可以预防闪烁。

  2. 使用glutSwapBuffers()函数在每一帧绘制完毕后切换前后缓冲区,将后台缓冲区的内容绘制到屏幕上

  3. 使用glutTimerFunc()函数设置一个定时器,以一定的时间间隔调用指定的函数,在这个函数中更新动画的状态

  4. 在定时器函数中更新三角形的旋转角度,并重新注册定时器,实现连续的动画效果

    • GLfloat angle = 0;:定义了一个全局变量angle,用于表示旋转角度,初始值为0
    • timer()函数:是一个定时器函数,在每次被调用时更新旋转角度,并请求重绘窗口。这里使用了glutPostRedisplay()函数请求重绘,以触发renderScene()函数的执行。同时,通过glutTimerFunc()函数设置了一个16毫秒的定时器,用于每隔一定时间调用一次timer()函数,从而实现连续的动画效果

    定时器函数

    GLfloat angle = 0; // 设置初始旋转角度
    // 定时器函数,每16ms调用一次,用于旋转三角形
    void timer(int value)
    {angle += 1; // 旋转角度glutPostRedisplay(); // 重绘glutTimerFunc(16, timer, 0); // 设置定时器
    } 
    
  5. 在渲染函数renderScene()中,使用更新后的旋转角度绘制旋转的三角形

    renderScene()函数:在这里,首先清除颜色缓冲区,然后将当前矩阵推入堆栈,接着根据当前的旋转角度对场景进行旋转,绘制一个黄绿色的三角形。最后,通过glutSwapBuffers()函数进行双缓冲区的切换,将后台缓冲区的内容显示到屏幕上。

    // 渲染函数,绘制一个单色的三角形
    void renderScene()
    {glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区glPushMatrix();glRotatef(angle, 1.0, 0.0, 0.0); // 绕x旋转// 绘制三角形// ...glPopMatrix(); glutSwapBuffers(); // 设置双缓冲
    }
    

结果:
recording

通过以上步骤,我们可以实现一个简单的OpenGL动画,让一个三角形在窗口中不断旋转。动画效果平滑流畅。

2.4.2 键盘控制

普通按键处理:

使用 glutKeyboardFunc 函数来注册处理普通按键事件的回调函数。

  • 普通按键是指字母数字和其他可以用 ASCII 代码表示的键

  • 我们可以在普通按键回调函数中执行相应的操作,例如根据按下的键执行不同的操作,或者响应程序的退出等

    void processNormalKeys(unsigned char key, int x, int y)
    {switch (key){case 27: // ESC键exit(0);break;
    }
    

特殊按键处理:

使用 glutSpecialFunc 函数来注册处理特殊按键事件的回调函数。

  • 特殊按键的标识符通常由预定义的常量表示,如 GLUT_KEY_F1GLUT_KEY_LEFT

  • 我们可以在特殊按键回调函数中根据按下的特殊键执行相应的操作,例如改变颜色、移动物体等

    记得将颜色值设为全局变量,渲染时,使用全局变量对颜色进行设置

    void processSpecialKeys(int key, int x, int y)
    {switch (key){case GLUT_KEY_F1:red = 181. / 225;green = 206. / 225;blue = 163. / 255;break;case GLUT_KEY_F2:red = 163. / 255;green = 163. / 255;blue = 163. / 255;break;case GLUT_KEY_F3:red = 30. / 255;green = 60. / 255;blue = 153. / 255;break;}
    

最后在main函数里面设置这两个回调函数即可

int main(int argc, char** argv)
{// ...glutSpecialFunc(processSpecialKeys); // 设置特殊键回调函数glutKeyboardFunc(processNormalKeys); // 设置普通键回调函数// ...
}

运行结果:

recording
  • processSpecialKeys 函数,根据按下的特殊键设置不同的颜色值。具体地,当按下F1键时,设置颜色为黄绿色;按下F2键时,设置颜色为灰色;按下F3键时,设置颜色为蓝色
  • processNormalKeys 函数,增加对ESC键的处理。当按下ESC键时,退出程序

2.5 窗口反弹动画

这一部分,我们要实现可以反弹运动的三角形动画,键盘控制三角形移动速度、颜色。

先引入几个全局变量:

GLfloat x = 0, y = 0, rsize = 50; // 设置初始位置和大小
GLfloat dx = 1.5, dy = 1.5; // 设置初始速度
GLfloat windowWidth, windowHeight; // 窗口大小
GLfloat red = 181. / 225, green = 206. / 225, blue = 163. / 255; // 设置初始颜色为黄绿色
2.5.1 处理窗口大小变化
  • 函数调用 glViewport 函数设置新的视口大小

    glViewport(0, 0, w, h); // 设置视口大小
    
  • 函数计算新的宽高比,并根据宽高比的大小调整窗口的宽度和高度。如果宽度小于或等于高度,那么窗口的宽度被设置为100,高度则根据宽高比进行调整。否则,窗口的高度被设置为100,宽度则根据宽高比进行调整

    aspectRatio = (GLfloat)w / (GLfloat)h; // 计算窗口的宽高比
    if (w <= h)
    {windowWidth = 100;windowHeight = 100 / aspectRatio;
    }
    else
    {windowWidth = 100 * aspectRatio;windowHeight = 100;
    }
    

    引入了 windowWidthwindowHeight 全局变量,用于保存窗口的宽度和高度

  • 使用 glOrtho 函数设置正交投影矩阵,将场景限定在一个固定大小的区域内,以保持图形的比例不变

     glOrtho(-windowWidth, windowWidth, -windowHeight, windowHeight, 1.0, -1.0);
    
2.5.2 渲染函数

渲染函数修改:

  • 在绘制三角形时,顶点坐标根据 xy 的值确定,以及矩形大小 rsize 的一半,顶点定义如下:

    glVertex2f(x, y);     
    glVertex2f(x + rsize, y);
    glVertex2f(x + rsize / 2, y + rsize);
    
2.5.3 定时器

修改了定时器函数 timer

  • 先更新位置 xy

    x += dx;
    y += dy;
    
  • 然后检测是否与窗口边界发生碰撞,如果碰撞,则将对应的速度分量 dxdy 取反,实现反弹效果,并稍微调整位置以避免卡在边界上

     // 检测碰撞,如果碰到边界就反弹if (x + rsize > windowWidth || x < -windowWidth){dx = -dx;x += dx; // 稍微调整位置,防止卡在边界上}if (y + rsize > windowHeight || y < -windowHeight){dy = -dy;y += dy; // 稍微调整位置,防止卡在边界上}
    
  • 使用 glutPostRedisplay() 触发重绘,以更新窗口中的图形

  • 设置定时器每16ms触发一次 glutTimerFunc(16, timer, 1);,以实现动画效果

2.5.4 控制速度

控制速度的实现:

我们使用了 processNormalKeys 函数来处理普通按键事件

  1. 当按下 ESC 键时,程序退出
  2. 当按下 ‘w’ 键时,增加图形在y方向上的速度
  3. 当按下 ‘s’ 键时,减少图形在y方向上的速度,但保证速度不小于0
  4. 当按下 ‘a’ 键时,减少图形在x方向上的速度,但保证速度不小于0
  5. 当按下 ‘d’ 键时,增加图形在x方向上的速度
switch (key)
{
case 27: // ESC键exit(0);break;
case 'w': // 增加y方向的速度dy += 0.1;break;
case 's': // 减少y方向的速度,但不小于0if (dy > 0){dy -= 0.1;            }break;
case 'a': // 减少x方向的速度,但不小于0if (dx > 0){dx -= 0.1;            }break;
case 'd': // 增加x方向的速度dx += 0.1;break;
}

这篇关于OpenGL/GLUT实践:实现反弹运动的三角形动画与键盘控制(电子科技大学信软图形与动画Ⅱ实验)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1131323

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P