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

相关文章

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

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

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

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

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD