当前位置: 首页 > news >正文

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;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 数据分析——基础
  • cowrie部署中遇到的坑
  • sqlite3 相关知识
  • 【佳学基因检测】在bagisto中,grouped products(同组产品)和bundled products(打包产品)有什么不同?
  • Nvidia GPU benchmark压力测试工具
  • 003: Visual Studio 配置 VTK 开发环境的方法与比较
  • Qt工程实践_06_Qt MSVC2O17编译器下的程序添加VS2017生成的动态链接库方法
  • Windows用户取消共享文件夹密码方法(Method for Windows Users to Cancel Shared Folder Password)
  • 科研绘图系列:R语言柱状图分布(histogram plot)
  • Mybatis【分页插件,缓存,一级缓存,二级缓存,常见缓存面试题】
  • 重头开始嵌入式第三十四天(数据库二)
  • html备忘录
  • IDEA 2024最新软件下载
  • 【全网最全】2024年数学建模国赛C题超详细保奖思路+可视化图表+成品论文+matlab/python代码等(后续会更新
  • Elastic Stack--ELFK实例与Dashboard界面
  • iOS 系统授权开发
  • JavaScript的使用你知道几种?(上)
  • mysql 5.6 原生Online DDL解析
  • Netty 4.1 源代码学习:线程模型
  • ubuntu 下nginx安装 并支持https协议
  • 电商搜索引擎的架构设计和性能优化
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 观察者模式实现非直接耦合
  • 盘点那些不知名却常用的 Git 操作
  • 配置 PM2 实现代码自动发布
  • 浅谈web中前端模板引擎的使用
  • 实现简单的正则表达式引擎
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 原生 js 实现移动端 Touch 滑动反弹
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • # Apache SeaTunnel 究竟是什么?
  • ### RabbitMQ五种工作模式:
  • #QT(QCharts绘制曲线)
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (1)(1.9) MSP (version 4.2)
  • (14)Hive调优——合并小文件
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (9)目标检测_SSD的原理
  • (LLM) 很笨
  • (编译到47%失败)to be deleted
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (学习日记)2024.02.29:UCOSIII第二节
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET CLR基本术语
  • .Net OpenCVSharp生成灰度图和二值图
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • ??在JSP中,java和JavaScript如何交互?
  • [ 转载 ] SharePoint 资料
  • [145] 二叉树的后序遍历 js
  • [2009][note]构成理想导体超材料的有源THz欺骗表面等离子激元开关——