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

相关文章

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S

springboot依靠security实现digest认证的实践

《springboot依靠security实现digest认证的实践》HTTP摘要认证通过加密参数(如nonce、response)验证身份,避免明文传输,但存在密码存储风险,相比基本认证更安全,却因... 目录概述参数Demopom.XML依赖Digest1Application.JavaMyPasswo

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

Java 结构化并发Structured Concurrency实践举例

《Java结构化并发StructuredConcurrency实践举例》Java21结构化并发通过作用域和任务句柄统一管理并发生命周期,解决线程泄漏与任务追踪问题,提升代码安全性和可观测性,其核心... 目录一、结构化并发的核心概念与设计目标二、结构化并发的核心组件(一)作用域(Scopes)(二)任务句柄

Java中的Schema校验技术与实践示例详解

《Java中的Schema校验技术与实践示例详解》本主题详细介绍了在Java环境下进行XMLSchema和JSONSchema校验的方法,包括使用JAXP、JAXB以及专门的JSON校验库等技术,本文... 目录1. XML和jsON的Schema校验概念1.1 XML和JSON校验的必要性1.2 Sche

SpringBoot集成WebService(wsdl)实践

《SpringBoot集成WebService(wsdl)实践》文章介绍了SpringBoot项目中通过缓存IWebService接口实现类的泛型入参类型,减少反射调用提升性能的实现方案,包含依赖配置... 目录pom.XML创建入口ApplicationContextUtils.JavaJacksonUt

MyCat分库分表的项目实践

《MyCat分库分表的项目实践》分库分表解决大数据量和高并发性能瓶颈,MyCat作为中间件支持分片、读写分离与事务处理,本文就来介绍一下MyCat分库分表的实践,感兴趣的可以了解一下... 目录一、为什么要分库分表?二、分库分表的常见方案三、MyCat简介四、MyCat分库分表深度解析1. 架构原理2. 分

Java 中的 equals 和 hashCode 方法关系与正确重写实践案例

《Java中的equals和hashCode方法关系与正确重写实践案例》在Java中,equals和hashCode方法是Object类的核心方法,广泛用于对象比较和哈希集合(如HashMa... 目录一、背景与需求分析1.1 equals 和 hashCode 的背景1.2 需求分析1.3 技术挑战1.4