基于OpenGL的雨雪粒子系统实现

基于OpenGL的雨雪粒子系统实现

一、系统架构设计

graph TD A[主循环] --> B[输入处理] A --> C[粒子更新] A --> D[物理模拟] A --> E[渲染] C --> F[键盘事件] D --> G[重力计算] D --> H[风力计算] E --> I[视口管理] E --> J[粒子渲染]

二、核心代码实现

1. 粒子结构体定义

struct Particle {
    glm::vec3 position;   // 位置
    glm::vec3 velocity;   // 速度
    float size;           // 大小(0.1-3.0)
    float life;           // 生命周期(0.0-1.0)
    glm::vec4 color;      // 颜色(RGBA)
    bool isSnow;          // 类型标识
    float rotation;       // 旋转角度
    float spinSpeed;      // 旋转速度
};

2. 粒子系统类

class ParticleSystem {
public:
    ParticleSystem(int maxParticles);
    void Update(float deltaTime);
    void Render();
    
    // 控制接口
    void SetWindSpeed(float speed);
    void SetRainSize(float minSize, float maxSize);
    void ToggleWeatherType();  // 切换雨雪模式

private:
    Particle particles[10000];
    int activeCount;
    float windSpeed;
    float rainMinSize, rainMaxSize;
    bool isSnowing;

    void EmitParticle();
    void ApplyPhysics(Particle& p, float deltaTime);
};

3. 物理模拟实现

void ParticleSystem::ApplyPhysics(Particle& p, float deltaTime) {
    // 重力影响
    p.velocity.y -= 9.8f * deltaTime * (isSnowing ? 0.5f : 1.5f);
    
    // 风力影响
    p.velocity.x += windSpeed * deltaTime * (isSnowing ? 0.8f : 0.3f);
    
    // 空气阻力
    p.velocity *= 0.995f;
    
    // 位置更新
    p.position += p.velocity * deltaTime;
    
    // 生命周期衰减
    p.life -= deltaTime;
    p.size = glm::mix(0.1f, 3.0f, 1.0f - p.life);  // 大小随生命周期变化
}

4. 渲染实现(使用VBO)

void ParticleSystem::Render() {
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
    // 动态更新VBO数据
    glBufferData(GL_ARRAY_BUFFER, activeCount * sizeof(Particle), 
                particles, GL_STREAM_DRAW);
    
    // 着色器参数设置
    shader.use();
    shader.setFloat("time", glfwGetTime());
    shader.setVec3("cameraPos", cameraPos);
    
    // 分类型渲染
    if(isSnowing) {
        shader.setInt("weatherType", 1);
        glDrawArrays(GL_POINTS, 0, activeCount);
    } else {
        shader.setInt("weatherType", 0);
        glDrawArrays(GL_POINTS, 0, activeCount);
    }
    
    glBindVertexArray(0);
    glDisable(GL_BLEND);
}

三、关键功能实现

1. 雨雪大小控制

void ParticleSystem::SetRainSize(float minSize, float maxSize) {
    rainMinSize = minSize;
    rainMaxSize = maxSize;
    
    for(auto& p : particles) {
        if(p.isSnow) continue;
        p.size = glm::mix(rainMinSize, rainMaxSize, 
                        rand()/float(RAND_MAX));
    }
}

2. 风速变化系统

void ParticleSystem::SetWindSpeed(float speed) {
    windSpeed = speed;
    
    // 添加风场扰动
    static float noiseScale = 0.1f;
    for(auto& p : particles) {
        glm::vec3 windOffset = glm::vec3(
            perlinNoise(p.position.x * noiseScale, 
                        p.position.z * noiseScale),
            0.0f,
            perlinNoise(p.position.y * noiseScale,
                        p.position.z * noiseScale)
        );
        p.velocity += windOffset * 0.5f;
    }
}

3. 粒子生成逻辑

void ParticleSystem::EmitParticle() {
    if(activeCount >= 10000) return;
    
    Particle p;
    p.position = cameraPos + glm::vec3(
        (rand()%100 - 50)/50.0f,  // 水平随机偏移
        5.0f,                     // 初始高度
        (rand()%100 - 50)/50.0f
    );
    
    p.velocity = glm::vec3(
        (isSnowing ? 0.1f : 0.3f) * (rand()%100 - 50)/50.0f,
        isSnowing ? -1.0f : -3.0f,
        (isSnowing ? 0.1f : 0.3f) * (rand()%100 - 50)/50.0f
    );
    
    p.size = isSnowing ? 0.5f + (rand()%100)/50.0f : 0.2f;
    p.life = 1.0f;
    p.color = isSnowing ? glm::vec4(1.0f, 1.0f, 1.0f, 0.8f) 
                        : glm::vec4(0.0f, 0.0f, 1.0f, 0.5f);
    
    particles[activeCount++] = p;
}

四、着色器实现

1. 顶点着色器

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec4 aColor;
layout(location = 2) in float aSize;
layout(location = 3) in float aRotation;

uniform mat4 projection;
uniform mat4 view;
uniform float time;

out vec4 particleColor;
out float particleSize;

void main() {
    float scale = aSize * (1.0 - (time - aLife) / 2.0);
    vec3 rotatedPos = vec3(
        cos(aRotation) * aPos.x - sin(aRotation) * aPos.z,
        aPos.y,
        sin(aRotation) * aPos.x + cos(aRotation) * aPos.z
    );
    
    gl_Position = projection * view * vec4(rotatedPos, 1.0);
    gl_PointSize = scale * 1000.0 / gl_Position.w;
    
    particleColor = aColor;
    particleSize = scale;
}

2. 片元着色器

#version 330 core
in vec4 particleColor;
in float particleSize;
out vec4 FragColor;

uniform sampler2D snowTexture;
uniform int weatherType;

void main() {
    vec2 texCoords = gl_PointCoord * 2.0 - 1.0;
    texCoords.y = 1.0 - texCoords.y;
    
    float alpha = smoothstep(0.95, 1.0, length(texCoords));
    
    if(weatherType == 1) {
        FragColor = texture(snowTexture, texCoords) * particleColor * alpha;
    } else {
        FragColor = vec4(particleColor.rgb, particleColor.a * alpha);
    }
}

五、交互功能实现

1. 控制台指令

void ProcessInput(GLFWwindow* window) {
    if(glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        particleSystem.SetWindSpeed(5.0f);
    if(glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        particleSystem.SetWindSpeed(0.0f);
    
    if(glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS)
        particleSystem.ToggleWeatherType();
    
    if(glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
        particleSystem.SetRainSize(0.2f, 1.5f);
    
    if(glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
        particleSystem.SetRainSize(0.5f, 0.8f);
}

2. 可视化调试信息

void RenderDebugInfo() {
    ImGui::Begin("Weather Controls");
    ImGui::SliderFloat("Wind Speed", &windSpeed, -5.0f, 5.0f);
    ImGui::SliderFloat("Rain Size", &rainMinSize, 0.1f, 3.0f);
    ImGui::Checkbox("Snow Mode", &isSnowing);
    ImGui::End();
}

参考代码 使用opengl模拟雨雪粒子天气系统,有雨雪大小风速变化等功能 youwenfan.com/contentcna/73047.html

六、性能优化方案

  1. LOD技术

    // 根据距离调整粒子细节
    float distance = glm::length(cameraPos - p.position);
    if(distance > 20.0f) {
        p.size *= 0.5f;
        p.color.a *= 0.7f;
    }
    
  2. GPU Instancing

    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(InstanceData) * instanceCount, 
                &instances[0], GL_DYNAMIC_DRAW);
    
  3. 剔除算法

    if(particle.position.y < cameraPos.y - 10.0f) {
        MarkForDeletion(particle);
    }
    

七、扩展功能建议

  1. 粒子碰撞

    // 简单地面碰撞检测
    if(p.position.y < 0.0f) {
        p.position.y = 0.0f;
        p.velocity.y *= -0.7f;  // 反弹
    }
    
  2. 动态纹理

    // 实现雨滴涟漪效果
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, rippleTexture);
    glUniform1i(glGetUniformLocation(shader.program, "rippleTex"), 1);
    
  3. 粒子系统分层

    // 天空层与地面层分离
    if(p.isSkyParticle) {
        shader.setBool("isSkyLayer", true);
    } else {
        shader.setBool("isSkyLayer", false);
    }
    

八、调试与测试

  1. 帧率监控

    double fps = 1.0 / deltaTime;
    std::cout << "FPS: " << fps << std::endl;
    
  2. 粒子可视化调试

    // 绘制粒子运动轨迹
    glBegin(GL_LINES);
    for(auto& p : particles) {
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(p.position, 0.0f);
        glVertex3f(p.position + p.velocity, 0.0f);
    }
    glEnd();
    

九、完整项目结构

WeatherSystem/
├── src/
│   ├── main.cpp
│   ├── shader/
│   │   ├── particle.vert
│   │   └── particle.frag
│   ├── textures/
│   │   ├── snow.png
│   │   └── rain.png
│   └── utils/
│       └── perlin_noise.h
├── CMakeLists.txt
└── data/
    └── config.ini

该方案结合了粒子系统基础原理与现代图形技术,通过可调节参数实现了逼真的天气模拟效果。开发者可根据实际需求扩展地形交互、光照反射等高级特性。

posted @ 2025-07-26 10:14  kiyte  阅读(66)  评论(0)    收藏  举报