OpenGL/GLUT实践:粒子系统,并添加纹理、动态模糊、边界碰撞(电子科技大学信软图形与动画Ⅱ实验)

本文主要是介绍OpenGL/GLUT实践:粒子系统,并添加纹理、动态模糊、边界碰撞(电子科技大学信软图形与动画Ⅱ实验),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

文章目录

    • 1 运行效果
    • 2 实验过程
      • 2.1 基本粒子系统
        • 2.1.1 定义粒子结构
        • 2.1.2 创建粒子并初始化
          • 2.1.2.1 创建粒子
          • 2.1.2.2 初始化
        • 2.1.3 粒子状态更新与绘制
          • 2.1.3.1 绘制
          • 2.1.3.2 更新
        • 2.1.4 实现效果
      • 2.2 添加纹理
        • 2.2.1 纹理添加
        • 2.2.2 渲染粒子
        • 2.2.3 实现效果
      • 2.3 运动模糊效果
      • 2.4 边界碰撞效果
      • 2.5 火焰效果
          • 1)粒子的生成位置
          • 2)粒子的颜色设置
          • 3)粒子的速度设置
          • 4)粒子的加速度设置

1 运行效果

最终的火焰 Demo 效果:

recording

基础粒子系统:

recording

加入纹理的粒子系统:

recording

实现动态模糊:

recording

实现边界碰撞:

recording

2 实验过程

2.1 基本粒子系统

2.1.1 定义粒子结构

粒子结构定义了粒子在粒子系统中的属性,每个属性都对粒子的行为和外观有着重要的影响。以下是粒子结构的详细描述:

  • active:一个布尔值,指示该粒子是否处于活跃状态。当粒子被激活时,其值为 true,表示粒子仍在系统中活动;当粒子被标记为非活跃状态时,其值为 false,系统可能会将其从渲染或模拟中移除。

  • life:浮点数,表示粒子的生命周期。生命周期定义了粒子在系统中存在的时间长度。当生命周期耗尽时,粒子可能会被标记为非活跃状态,并被系统移除。

  • fade:浮点数,表示粒子的衰老速度。衰老速度决定了粒子的生命周期如何逐渐减少。通常,衰老速度越高,粒子的生命周期减少得越快。

  • r, g, b:浮点数,分别表示粒子的颜色。使用红、绿、蓝(RGB)三个分量来定义粒子的颜色,这决定了粒子在渲染时的外观。

  • x, y, z:浮点数,表示粒子的三维空间位置。粒子在三维空间中的位置决定了其在场景中的位置,从而影响了其在屏幕上的呈现位置。

  • v_x, v_y, v_z:浮点数,表示粒子在三维空间中的速度。速度定义了粒子在每个时间步长内在空间中移动的距离和方向。

  • a_x, a_y, a_z:浮点数,表示粒子在三维空间中的加速度。加速度影响了粒子速度的变化,从而影响了粒子在空间中的运动。

通过这些属性,粒子系统可以精确控制和模拟粒子的行为,从而实现各种动态效果和视觉效果。

2.1.2 创建粒子并初始化
2.1.2.1 创建粒子

我们使用先前定义的结构类型 particle 来定义一个数组,用于存储粒子的信息。

2.1.2.2 初始化

在函数 InitPaticleSystem 中初始化粒子信息:循环初始化粒子:通过一个 for 循环遍历所有的粒子,对每个粒子进行初始化。

  • 粒子生命周期和衰老速度:使用随机数为每个粒子设置一个初始生命周期 init_life,并将其存储在 life 中。同时,将衰老速度 speed_aging 设置为 TIME

  • 粒子颜色和位置:设置每个粒子的颜色为红色 (r = 1.0f, g = 0.0f, b = 0.0f),并将其初始位置设为 (0, 50, 0)。

  • 粒子速度和方向:通过球坐标系的转换公式,为每个粒子设置初始速度和方向。随机生成角度 thetarho,然后计算球坐标系中的速度分量,并存储在 v_x, v_y, v_z 中。

  • 粒子加速度:设置粒子在 Y 轴方向上的加速度为 -30.0f,这代表了重力的作用。

通过这个函数,我们可以初始化粒子系统,并为每个粒子设置初始的生命周期、颜色、位置、速度、加速度等属性。

2.1.3 粒子状态更新与绘制
2.1.3.1 绘制

RenderScene 函数用于绘制粒子场景。首先,通过 glClear 函数清除屏幕和深度缓冲区,确保每次绘制前都有一个干净的画布。

  • 绘制粒子: 在一个循环中遍历所有粒子,获取每个粒子的位置信息(x、y、z 坐标)。然后,利用 glColor4f 函数设置绘制点的颜色,其中的透明度值由粒子的生命值决定,即 particles[i].life。通过设置颜色的 alpha 值,我们可以实现粒子随着生命周期的减少逐渐消失的效果
  • 设置点的大小: 使用 glPointSize 函数设置绘制点的大小为 4.0f
  • 绘制点: 使用 glBegin(GL_POINTS) 开始绘制点,然后通过 glVertex3f 函数将每个粒子的位置信息传递给 OpenGL,绘制出粒子。
  • 切换缓冲区并显示: 最后,使用 glutSwapBuffers 函数切换前后缓冲区,将绘制好的图像显示在屏幕上。

通过以上步骤,实现了将粒子系统中的粒子渲染到 OpenGL 窗口中。

2.1.3.2 更新
  • Update 函数: 用于更新粒子的状态。通过一个循环遍历所有的粒子,更新它们的位置、速度和生命周期。具体:
    • 更新粒子的位置,根据当前速度和时间步长(TIME)计算粒子在每个时间步长内移动的距离,并更新粒子的位置。
    • 更新粒子的速度,根据粒子的加速度和时间步长计算粒子在每个时间步长内速度的变化,并更新粒子的速度。
    • 减少粒子的生命周期,根据粒子的衰老速度,减少粒子的生命周期。
    • 如果粒子的生命周期小于 0,说明粒子已经到达了生命周期的末尾,需要重新生成粒子:给予粒子新的生命周期、位置和速度。
  • TimerFunction 函数: 一个计时器函数,用于定时更新粒子系统并触发重新绘制。在每次调用时,先调用 Update() 函数更新粒子状态,然后通过 glutPostRedisplay() 请求重新绘制。
2.1.4 实现效果

通过补充一些常规的函数如窗口响应函数、初始化函数、主函数,我们实现了如下的基本粒子系统:

recording

2.2 添加纹理

2.2.1 纹理添加
  1. 载入纹理:

    首先,我们需要定义一个全局变量 texture,用来存储纹理的 ID。然后,添加一个函数 LoadGLTextures 来加载纹理。在这个函数中,我们使用 SOIL 库的函数 SOIL_load_OGL_texture 来加载位图,并将其转换为纹理。加载成功后,将纹理绑定到 OpenGL 中,并设置纹理过滤参数。最后,函数返回成功或失败的标志。

  2. 修改初始化函数:

    在初始化函数 InitGL 中,首先调用 LoadGLTextures 函数来载入纹理。如果纹理加载失败,则返回 false。接着,进行 OpenGL 的初始化设置,包括启用光滑着色模式、设置背景颜色、深度缓冲、混合等。然后,启用纹理映射并将载入的纹理绑定到当前的纹理单元。

2.2.2 渲染粒子

渲染粒子时,给粒子贴上纹理可以提升渲染效果。为了实现这个目标,我们需要绘制一个正方形,并将纹理贴在正方形上。但是由于绘制大量正方形的速度相对较慢,为了提高效率,我们可以利用 OpenGL 绘制三角形的高效性,采用绘制两个三角形的方式来绘制正方形。

具体实现步骤如下:

  1. 绘制正方形: 我们使用 glBegin(GL_TRIANGLE_STRIP) 开始绘制三角形带,然后按顺时针或逆时针顺序指定正方形的四个顶点,以构成两个三角形,从而绘制出正方形。
  2. 设置顶点坐标和纹理坐标: 在指定顶点的同时,我们需要建立起顶点坐标和纹理坐标的对应关系。通过使用 glTexCoord2d 函数,为每个顶点指定对应的纹理坐标,以确定纹理贴图在正方形上的位置和方向。
  3. 绘制两个三角形: 通过指定顶点的顺序,构成两个三角形,分别由顶点组合 (v0, v1, v2)(v1, v2, v3) 组成。

通过以上步骤,我们可以利用绘制两个三角形的方式来绘制正方形,并在其上贴上纹理,实现粒子的渲染效果。这样的做法在提高渲染效率的同时,也可以保持良好的渲染质量。

2.2.3 实现效果

我们选择如下图片作为纹理:

image-20240420102623878

经过上面的纹理设置,我们最终得到效果如下:

recording

2.3 运动模糊效果

为模拟动态模糊效果,我们可以按照以下步骤进行操作:

  1. 保留每帧绘图结果: 在每一帧绘制结束后,保存当前的绘图结果,即将帧缓冲区的内容复制到另一个缓冲区中,以便后续使用。
  2. 绘制半透明的黑色长方形: 在每一帧绘制过程中,绘制一个半透明的黑色长方形,覆盖在当前帧绘制结果上。这个长方形的透明度可以根据需要进行调整,通常选择一个适中的透明度。
  3. 混合绘图结果: 将保存的前几帧绘图结果与当前帧绘图结果进行混合,从而模拟运动模糊的效果。可以通过修改混合函数的参数来调整混合的效果,例如使用加权平均值混合。

实现步骤:

  1. initGL 函数中启用混合功能,以便实现半透明效果。

    首先要在 initGL 函数中添加如下语句,这样透明度才会有效。

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
  2. 在render函数中,将清除缓冲区的操作修改为仅清除深度缓冲区,以保留之前帧的绘图结果。

    render 函数中,将一开始的

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 
    

    改为

    glClear(GL_DEPTH_BUFFER_BIT );  
    
  3. render 函数的末尾添加绘制半透明的黑色长方形的操作,以模拟运动模糊效果。

    glColor4f(0.0f, 0.0f, 0.0f, 0.1f);
    glRectf(-windowWidth, -windowHeight, windowWidth, windowHeight);
    

最终 render 函数如下:

void RenderScene(void)
{glClear(GL_DEPTH_BUFFER_BIT);for (int i = 0; i < MAX_PARTICLES; i++)					       {float x = particles[i].x;						       float y = particles[i].y;float z = 0;// Draw particle using RGB values, alpha value based on it's lifeglColor4f(particles[i].r, particles[i].g, particles[i].b, particles[i].life);glBegin(GL_TRIANGLE_STRIP); glTexCoord2d(1, 1); glVertex3f(x + 2.0f, y + 2.0f, z); // Top RightglTexCoord2d(0, 1); glVertex3f(x - 2.0f, y + 2.0f, z); // Top LeftglTexCoord2d(1, 0); glVertex3f(x + 2.0f, y - 2.0f, z); // Bottom RightglTexCoord2d(0, 0); glVertex3f(x - 2.0f, y - 2.0f, z); // Bottom LeftglEnd();glPopMatrix();}glColor4f(0.0f, 0.0f, 0.0f, 0.1f);glRectf(-windowWidth, -windowHeight, windowWidth, windowHeight);glutSwapBuffers();
}

得到效果:

recording

2.4 边界碰撞效果

为了实现边界碰撞效果,我们需要添加一个名为 checkBump 的函数,并将其在 TimerFunction 中的 Update(); 函数后调用。这个函数的作用是检查所有粒子的位置是否越过了边界,如果是,则立即将其速度反向,以模拟碰撞效果。

void checkBump()
{for (int i = 0; i < MAX_PARTICLES; i++){float x = particles[i].x;float y = particles[i].y;// 检测是否碰撞到边界if (x > windowWidth || x < -windowWidth){particles[i].v_x = -particles[i].v_x;x += particles[i].v_x; // 稍微调整位置,防止卡在边界上}if (y > windowHeight || y < -windowHeight){particles[i].v_y = -particles[i].v_y;y += particles[i].v_y; // 稍微调整位置,防止卡在边界上}}
}

该函数会遍历所有粒子,检查其位置是否超出了窗口边界。如果发现粒子碰到了边界,就会立即反转相应的速度分量,从而实现了边界碰撞效果。同时,为了防止粒子卡在边界上,稍微调整了粒子的位置。

通过这种方式,我们可以在粒子系统中实现边界碰撞效果,增加了系统的真实感和趣味性。

实现效果如下:

recording

2.5 火焰效果

要实现火焰效果,我们需要调整粒子的初始化 InitPaticleSystem 和更新函数 Update ,以便更好地模拟火焰的外观和行为。下面是需要进行的改动:

1)粒子的生成位置

我们可以使用正态分布随机数生成器来随机生成粒子的初始位置,使生成的火焰粒子中间多,两边少,看起来更加自然。这里使用了均值为0,标准差为10的正态分布。

std::default_random_engine generator;
std::normal_distribution<float> distribution(0.0, 10.0);
//...
particles[i].x = distribution(generator);
particles[i].y = -55.0f;
2)粒子的颜色设置

在火焰效果中,粒子的颜色通常会随着其与火源之间的距离变化而变化。这是因为在火焰的中心部位,燃烧更为激烈,火焰的温度和亮度更高,因此颜色更接近黄色或橙色。而在火焰的边缘部位,燃烧相对较弱,火焰的温度和亮度较低,颜色则更接近红色。

为了模拟这种效果,我们可以通过计算粒子与火源之间的距离,并根据距离来设置粒子的颜色。在上述代码中,我们首先计算了粒子与火源之间的距离,然后将这个距离与最大距离进行归一化,得到一个取值范围在0到1之间的插值因子 lerp_factor

接着,我们使用线性插值的方式来设置粒子的颜色。线性插值是一种简单的插值方法,它通过两个已知点之间的直线来估计介于这两个点之间的其他点的值。在这里,我们将 lerp_factor 应用于颜色的 g 绿色分量,使得随着粒子与火源的距离增加,绿色分量的值逐渐减小,从而实现了颜色的渐变效果。

float distance = abs(particles[i].x);
float maxDistance = 15.0f;std::uniform_real_distribution<float> random_factor(0.0f, 0.1f);// Set color for particle
float lerp_factor = distance / maxDistance;
particles[i].r = 1.0f;
particles[i].g = (1.0f - lerp_factor); 
particles[i].b = 0.0f; 
3)粒子的速度设置

火焰通常具有上升的趋势,因此我们可以将粒子的初始速度设置为向上的方向,并添加一定的水平方向的随机速度成分。

float speedRange = 10.0f;
particles[i].v_x = ((rand() % 100) / 100.0f) * speedRange - speedRange / 2.0f;
particles[i].v_y = 2.0f;
4)粒子的加速度设置

火焰的上升通常受到重力的影响,但同时也受到火焰本身的推动。因此,我们可以将粒子的竖直方向的加速度设置为一个正值,以模拟火焰的上升效果。

particles[i].a_x = 0.0f;						
particles[i].a_y = 50.0f;	

这篇关于OpenGL/GLUT实践:粒子系统,并添加纹理、动态模糊、边界碰撞(电子科技大学信软图形与动画Ⅱ实验)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

Docker集成CI/CD的项目实践

《Docker集成CI/CD的项目实践》本文主要介绍了Docker集成CI/CD的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、引言1.1 什么是 CI/CD?1.2 docker 在 CI/CD 中的作用二、Docke

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

.NET利用C#字节流动态操作Excel文件

《.NET利用C#字节流动态操作Excel文件》在.NET开发中,通过字节流动态操作Excel文件提供了一种高效且灵活的方式处理数据,本文将演示如何在.NET平台使用C#通过字节流创建,读取,编辑及保... 目录用C#创建并保存Excel工作簿为字节流用C#通过字节流直接读取Excel文件数据用C#通过字节