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

news2024/11/24 14:23:38

源码见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 life
		glColor4f(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 Right
		glTexCoord2d(0, 1); glVertex3f(x - 2.0f, y + 2.0f, z); // Top Left
		glTexCoord2d(1, 0); glVertex3f(x + 2.0f, y - 2.0f, z); // Bottom Right
		glTexCoord2d(0, 0); glVertex3f(x - 2.0f, y - 2.0f, z); // Bottom Left
		glEnd();

		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;	

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PostgreSQL + PostGIS:空间数据存储及管理解决方案

在数据库领域&#xff0c;PostgreSQL 已成为最强大、最通用的选项之一。它管理大量数据的能力、对 SQL 标准的遵守以及可扩展的架构使其受到学术界和工业界的喜爱。然而&#xff0c;真正让 PostgreSQL 脱颖而出的原因之一是它与PostGIS的集成&#xff0c;这是一个允许您有效处理…

第七课,条件表达式与初识分支判断

一&#xff0c;什么是判断 判断&#xff0c;就是在做某件事前&#xff0c;先问问满不满足条件。 进行逻辑判断&#xff0c;是生活中常见的行为。 “今天出门你要带伞吗&#xff1f;” “那得看天气怎么样&#xff0c;如果下雨或者太阳太大就带伞&#xff0c;否则就不带。”…

内存卡乱码问题解析恢复方案

一、内存卡乱码现象探析 在数字化时代&#xff0c;内存卡作为便携式数据存储设备&#xff0c;广泛应用于手机、相机、行车记录仪等多种电子设备中。然而&#xff0c;不少用户在使用过程中会遇到内存卡乱码的问题&#xff0c;即原本有序存储的文件突然变得无法识别&#xff0c;…

【前端面试】设计循环双端队列javascript

题目 https://leetcode.cn/problems/design-circular-deque/description/ 存储循环队列的向量空间是循环的,用通俗的话来讲,就是我们在做next或者prev操作时,不会发生溢出 取模、或者直接判断是否为0/size返回一个值。 数组实现 用函数来实现一个类,定义容量、头尾指针…

青远生态为云南林业规划院定制开发的自然保护地规划智能编制系统顺利通过验收

8月30日&#xff0c;青远生态为云南省林业调查规划院开发的自然保护地规划智能编制系统顺利通过验收。该系统具有智能推荐规划内容、自动生成投资估算表、智能编制规划报告等功能&#xff0c;集合了拉丁名填充、表格制作等丰富实用的工具&#xff0c;显著提升了规划工作的效率和…

电力系统有滤波器还需要装电抗器吗

在电力系统中&#xff0c;滤波器和电抗器各有不同的功能&#xff0c;尽管它们都能改善电力质量。是否需要同时安装滤波器和电抗器&#xff0c;取决于系统的具体需求和现状。以下是一些考虑因素&#xff1a; 1、滤波器的功能&#xff1a; 谐波滤波&#xff1a;滤波器主要用于抑…

基于vue框架的超市会员管理系统设计与实现xeb8c(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;会员,商品分类,商品信息,订单信息,积分等级,礼品信息,礼品兑换 开题报告内容 基于Vue框架的超市会员管理系统设计与实现开题报告 一、研究背景与意义 随着消费者对个性化服务和优惠活动需求的增加&#xff0c;超市会员管理成为提升顾…

Docker安装及验证,小白必备

Docker安装 本教程以centos系统为例 1、Docker安装前准备工作 切换国内源 cp -a /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak #备份设置为华为云的yum wget -O /etc/yum.repos.d/CentOS-Base.repo https://repo.huaweicloud.com/repository…

专用于理解游戏场景的开源大模型-VideoGameBunny

大模型在游戏开发领域扮演了重要角色&#xff0c;从AI机器人生成到场景搭建覆盖各个领域。但在游戏场景理解、图像识别、内容描述方面很差。 为了解决这些难题&#xff0c;加拿大阿尔伯塔的研究人员专门开源了一款针对游戏领域的大模型VideoGameBunny&#xff08;以下简称“VG…

7-8月月报 | Apache SeaTunnel社区进展一览

各位热爱 Apache SeaTunnel 的小伙伴们&#xff0c;社区 7-8 月份月报来啦&#xff01;这两个月项目有了哪些进展&#xff1f;又有谁登上了我们社区的贡献者榜单呢&#xff1f;快来一睹为快吧。 Merge Stars 感谢以下小伙伴上两个月为 Apache SeaTunnel 项目和社区发展所做的…

非时序检查(Non-Sequential Check)

单元或宏&#xff08;macro&#xff09;的库文件可以将时序弧指定为非时序&#xff08;non-sequential&#xff09;检查&#xff0c;例如两个数据引脚之间的时序弧。非时序检查是指两个引脚之间的检查&#xff0c;两者都不是时钟。一个引脚是约束引脚&#xff0c;其作用类似于数…

WPF在MVVM架构下使用DataGrid并实现行删除

一、效果演示 二、Model创建 //User&#xff1a;用于绑定DataGrid控件的数据 private ObservableCollection<User> _users new ObservableCollection<User>();public ObservableCollection<User> Users{get { return _users; }set { _users value; }}//Sel…

day43|打家劫舍系列 198.打家劫舍 213. 打家劫舍 II 337.打家劫舍 III

文章目录 前言198.打家劫舍思路方法一213. 打家劫舍 II思路方法一337.打家劫舍 III思路方法一方法二 暴力搜索和记忆化递推总结前言 198.打家劫舍 思路 非常直接的思路 dp五部曲 dp极其下标含义:**考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。**考虑的意思是…

IP地址安全与隐私保护

在当今数字化时代&#xff0c;IP地址作为网络设备的唯一身份标识&#xff0c;在网络安全与隐私保护中扮演着至关重要的角色。然而&#xff0c;随着网络技术的飞速发展&#xff0c;IP地址也面临着诸多挑战&#xff0c;对用户的隐私和网络安全构成了潜在威胁。本文将对IP地址在网…

JAVA基础:封装、继承和多态(详讲)

1 封装 面向对象的三大特征 &#xff1a; 封装&#xff0c; 继承&#xff0c; 多态 。 封装可以从三个层面理解 将属性和方法组合在一起&#xff08;封闭在一起&#xff09; 将属性隐藏起来&#xff0c; 对外提供可以间接操作属性的方法。&#xff08;提高程序设计安全性&…

CephFS使用

CephFS使用 一、CephFS架构二、部署CepfFS服务1、部署MDS服务2、创建CephFS metadata和data存储池3、创建cephFS并验证4、创建客户端账户5、安装ceph客户端并同步认证文件6、内核空间挂载ceph-fs6.1 客户端通过key文件挂载6.2 开机自动挂载 7、客户端模块挂载7.1 用户空间挂载c…

​​​​​​​《黑神话:悟空》—— 高科技点亮西游神话璀璨之路

《黑神话&#xff1a;悟空》作为一款以中国神话为背景的西游题材单机游戏&#xff0c;自诞生起便备受瞩目。它以中国古典名著《西游记》为蓝本&#xff0c;文化内涵深厚&#xff0c;承载着无数国人的童年回忆和文化情感。凭借高科技打造出美轮美奂的画面——细腻逼真的环境场景…

代理服务器详解(proxy server)

什么是代理服务器 (proxy server) 代理服务器&#xff08;Proxy Server&#xff09;是一个中间服务器&#xff0c;位于客户端和目标服务器之间。它代表客户端向目标服务器发送请求&#xff0c;并将目标服务器的响应返回给客户端&#xff0c;其模型如下图所示&#xff1a; 客户…

2024年“羊城杯”粤港澳大湾区网络安全大赛Misc 部分解析

2024年“羊城杯”粤港澳大湾区网络安全大赛Misc 部分解析 前言&#xff1a;数据安全&#xff1a;不一样的数据库_2&#xff1a;Misc - hiden&#xff1a;Misc - miaoro&#xff1a; 前言&#xff1a; 本次 解析是后期复现 当时没时间打 用于交流学习&#xff0c;感谢支持&…

代码随想录算法训练营第三十二天(动态规划 一)

前几天有点忙加上贪心后面好难QWQ 暂时跳过两天的贪心&#xff0c;开始学动归 动态规划理论基础: 文章链接:代码随想录 文章思维导图: 文章摘要: 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&…