DEV C++ 计算机图形学期末作业中等难度示例(2下)基于 GLUT 的粒子系统从架构到实现的全过程

目录

摘要概览

一、粒子系统概念与应用背景

1.1 粒子系统的起源与发展

1.2 粒子系统的典型应用

1.3 本文目标

二、核心数据结构与全局状态

2.1 Particle 结构定义

2.2 全局状态变量

三、随机化与动态扩容策略

3.1 随机数生成

3.2 动态扩容函数 ensureCapacity

四、粒子初始化逻辑

4.1 火焰模式初始化

4.2 雨滴模式初始化

4.3 烟雾模式初始化

五、OpenGL 环境与粒子系统初始化

5.1 initGL 函数

六、模式切换与交互控制

七、渲染与更新循环

八、窗口调整与定时器

8.1 窗口大小变化处理

8.2 固定帧率控制

九、程序入口与资源管理

9.1 main 函数流程

十、性能优化与扩展思路

结语


摘要概览

本文将从粒子系统的概念与应用背景切入,依次介绍核心数据结构、随机化与动态扩容策略、粒子初始化逻辑、OpenGL 环境配置、模式切换与用户交互、渲染与更新流程、窗口与定时器回调,以及程序入口与资源清理。最后给出常见优化与扩展思路,帮助读者深入理解并基于此框架快速实现火焰、雨滴、烟雾等逼真粒子效果。本文引用了多个权威教程与白皮书,包括 LearnOpenGL、GPU Gems、Swiftless Tutorials 等经典资料,以确保内容的专业性与可扩展性。


一、粒子系统概念与应用背景

1.1 粒子系统的起源与发展

  • 起源:最早由 Pixar 的 Bill Reeves 在《星舰奇遇记 II:可汗怒火》一片中提出,用于模拟爆炸、烟雾等效果Oregon State University Engineering

  • 发展:随着 GPU 并行计算能力提升,传统 CPU 模拟逐步向 GPU 加速迁移,从早期的点绘制(GL_POINTS)到现代的 Compute Shader 和 Transform Feedback 技术ogldev.orgubm-twvideo01.s3.amazonaws.com

1.2 粒子系统的典型应用

  • 视觉特效:火焰、烟雾、尘土、爆炸、魔法光效等,提升游戏与影视的沉浸感学习OpenGLswiftless.com

  • 环境模拟:雨雪、雾霭、水花等自然现象opengl-notes.readthedocs.io

  • 科学可视化:流体、粒子碰撞、天体物理等研究领域。

1.3 本文目标

  • 效果:实现火焰、雨滴、烟雾三种模式,参数可调,效果逼真。

  • 架构:基于 C/GLUT 实现,核心在于一个可扩容的 Particle 数组,迭代更新并绘制。

  • 可扩展性:留有接口方便迁移到 VBO、Instancing 或 Compute Shader 等高级方案opengl-tutorial.org


二、核心数据结构与全局状态

2.1 Particle 结构定义

typedef struct {
    float life, initialLife, fade;      // 生命周期管理
    float size, sizePeak;               // 粒子尺寸与峰值
    GLfloat r, g, b, a;                 // 颜色与透明度
    float x, y, z;                      // 三维位置
    float vx, vy, vz;                   // 三维速度
} Particle;
  • lifeinitialLife:当前剩余寿命与初始寿命,fade = 1/initialLife

  • size 随时间在 [PARTICLE_SIZE_MIN, sizePeak] 匀速变化。

  • r,g,b,a:通过透明度叠加(加色法)实现渐隐与色彩过渡NVIDIA Developer

2.2 全局状态变量

 
int g_mode;                // MODE_FIRE, MODE_RAIN, MODE_SMOKE
int level = 5;             // 级别,用于计算 totalCount = baseCount * level
int baseCount = 200;
int currentCount;          
int capacity = 0;          // 已分配数组容量
float expandFactor = 1.2f; // 扩容倍数
int winWidth, winHeight;   // 窗口(或屏幕)尺寸
Particle *particles = NULL;

  • 模式切换:通过 g_mode 决定初始化与更新逻辑。

  • 可调粒子数level 可通过键盘 +/- 修改,自动重算并扩容。

  • 窗口尺寸:用于正交投影下渲染 HUD 文本。


三、随机化与动态扩容策略

3.1 随机数生成

static float frand(float min, float max) {
    return min + (float)rand() / RAND_MAX * (max - min);
}
  • 简单线性映射,频繁调用保证粒子属性多样化。

3.2 动态扩容函数 ensureCapacity

 
void ensureCapacity(int newCount) {
    if (newCount <= capacity) return;
    int newCap = max((int)(capacity*expandFactor), newCount);
    particles = realloc(particles, newCap * sizeof(Particle));
    capacity = newCap;
}

  • 原则:先尝试按 expandFactor 扩容,若仍不足则直接扩至所需大小,避免频繁 realloc

  • 失败处理realloc 失败时退出并弹窗提醒,确保稳定性。


四、粒子初始化逻辑

4.1 火焰模式初始化

if (g_mode==MODE_FIRE) {
    p->initialLife = frand(LIFE_MIN_FIRE, LIFE_MAX_FIRE);
    p->life = p->initialLife; p->fade = 1/p->initialLife;
    p->x=p->y=p->z=0;
    float theta=frand(0,2*PI), phi=frand(0,EMIT_ANGLE*PI/180);
    float spd=frand(SPEED_MIN_FIRE, SPEED_MAX_FIRE);
    p->vx = spd*sin(phi)*cos(theta);
    p->vy = spd*cos(phi);
    p->vz = spd*sin(phi)*sin(theta);
    p->size = frand(PARTICLE_SIZE_MIN, PARTICLE_SIZE_MAX);
    p->sizePeak = p->size * 1.5;
    p->r=1; p->g=1; p->b=0; p->a=1;
}

  • 发射角度EMIT_ANGLE 控制圆锥范围内随机分布opengl-notes.readthedocs.io

  • 速度扰动:结合 TURBULENCE 可在后续更新中增加随机抖动。

4.2 雨滴模式初始化

 
else if (g_mode==MODE_RAIN) {
    p->x = frand(-4,4); p->y = frand(RAIN_Y_BOTTOM, RAIN_Y_TOP);
    p->z = frand(-2,2); p->vx = p->vz =0;
    p->vy = -frand(SPEED_MIN_RAIN, SPEED_MAX_RAIN);
    p->size = frand(1,2); p->sizePeak = p->size;
    p->r=0.8; p->g=0.8; p->b=1; p->a=0.7;
}

izePeak = p->size; p->r=0.8; p->g=0.8; p->b=1; p->a=0.7; }

  • 垂直下落:初始 vy 为负,GRAVITY_RAIN 在更新中持续加速。

  • 重置范围:超出底部后重置至顶部随机位置。

4.3 烟雾模式初始化

 
else { // MODE_SMOKE
    p->x = SMOKE_X_RIGHT;
    p->y = frand(0,3); p->z = frand(-2,2);
    p->vx = -frand(SPEED_MIN_SMOKE, SPEED_MAX_SMOKE);
    p->vy = frand(-0.005,0.005); p->vz = frand(-0.005,0.005);
    p->size = frand(2,5); p->sizePeak = p->size;
    p->r=p->g=p->b=0.5; p->a=0.6;
}

  • 从右向左漂移SMOKE_X_RIGHT/LEFT 控制漂出界限后重置。

  • 轻微上下扰动:营造自然飘散感。


五、OpenGL 环境与粒子系统初始化

5.1 initGL 函数

 
void initGL(void) {
    srand(time(NULL));                      // 随机种子
    winWidth = GetSystemMetrics(...);       // 获取全屏分辨率
    winHeight=...; glutFullScreen();        // 切换全屏
    g_mode = MODE_FIRE; level=5;
    currentCount = baseCount*level;
    ensureCapacity(currentCount);
    for (i=0; i<currentCount; ++i) 
        initParticle(i);
    glShadeModel(GL_SMOOTH);
    glClearColor(0,0,0,1);
    glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
}

  • 混合模式GL_SRC_ALPHA, GL_ONE(加色法),适合火焰等发光特效NVIDIA Developer

  • 抗锯齿:启用点平滑提高小尺寸粒子渲染质量。

六、模式切换与交互控制

在该程序中,用户可通过键盘快速在三种粒子模式(火焰、雨滴、烟雾)与不同等级(粒子数量)间切换。

  • glutKeyboardFunc 用于注册键盘回调,使得按键事件可触发自定义函数处理用户输入Stack Overflow

  • 按下 F/R/S 分别调用 switchMode(MODE_FIRE/RAIN/SMOKE),重新初始化所有粒子,保证视觉效果瞬时切换且无残留学习OpenGL

  • 按下 +/- 调整 level(最低为 1),重算 currentCount = baseCount * level 后调用 ensureCapacity 动态扩容,并批量 initParticle 重新生成粒子阵列,实时反馈粒子密度变化。


七、渲染与更新循环

核心的渲染逻辑在 display() 回调中完成,流程如下:

  1. 清除缓冲:调用 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 清除上一帧的颜色与深度信息,确保渲染一致性学习OpenGL

  2. 设置相机gluLookAt(0,2,8, 0,1,0, 0,1,0) 将视点置于场景前方,稍微俯视,便于观察粒子云形态。

  3. 迭代更新每个粒子

    • 火焰:叠加重力衰减 GRAVITY_FIRE,并加入随机扰动 TURBULENCE,模拟燃烧中的湍流NVIDIA Developer;通过生命周期比值 t 控制颜色从黄色向红色渐变和透明度衰减。

    • 雨滴:施加重力加速度 GRAVITY_RAIN 使其加速下落,超出底部边界后重置到顶部随机横向位置,实现连续效果。

    • 烟雾:匀速向左漂移并伴随微小上下扰动,超出左边界时重置至右侧,模拟无缝循环漂散informit.com

  4. 绘制:使用 glPointSize(p->size)GL_POINTS,在固定渲染管线下绘制每个粒子,结合点平滑提高小粒子渲染质量(依赖硬件支持)Stack Overflow

  5. HUD 文本:切换到正交投影后在右上角通过 glutBitmapCharacter 绘制当前等级文本,为用户提供调参反馈。

  6. 双缓冲交换glutSwapBuffers() 保证无闪烁的平滑显示。


八、窗口调整与定时器

8.1 窗口大小变化处理

  • reshape(int w, int h) 回调在窗口尺寸改变时触发,更新 winWidth/winHeight 并重新设置视口 glViewport(0,0,w,h) 和透视投影 gluPerspective(45, (float)w/h, 1, 100),保持场景比例不失真OpenGL

8.2 固定帧率控制

  • 使用 glutTimerFunc(16, timer, 0) 以约 16 ms 间隔(≈60FPS)重复调用自身并触发 glutPostRedisplay(),既简洁又跨平台,常用于 GLUT 应用的定时渲染Stack Overflow


九、程序入口与资源管理

9.1 main 函数流程

 
int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
    glutInitWindowSize(800,600);
    glutCreateWindow("Particle System");
    initGL();                                 // 随机种子、全屏、初始粒子、OpenGL状态
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutTimerFunc(0, timer, 0);
    glutMainLoop();                           // 进入事件与渲染主循环
    free(particles);                          // (实际因 glutMainLoop 永不返回,此行仅形式上存在)
    return 0;
}

  • GLUT 初始化:包括显示模式与窗口创建。

  • 回调注册:将渲染、窗口调整、键盘、定时器等函数绑定。

  • 内存释放:程序正常退出时清理动态分配的 particles 数组。


十、性能优化与扩展思路

  1. 批量绘制(Instancing)

    • 使用顶点缓冲对象(VBO)+ glDrawArraysInstanced,将每帧所有粒子数据一次性上传 GPU 并绘制,减少 CPU–GPU 通信开销学习OpenGL

  2. 纹理点精灵(Point Sprites)

    • 用单张带 alpha 通道的纹理替代纯色点,可实现丰富火焰、烟雾质感,参见 OpenGL 点精灵教程informit.com

  3. GPU 加速:Compute Shader

    • 将粒子更新逻辑迁移到 Compute Shader,在 GPU 上并行计算数千乃至百万粒子,大幅提升性能;可参考相关教程与 GPU Gems 专题RedditNVIDIA Developer

  4. 交互界面(ImGui)

    • 引入 Dear ImGui,实现参数(速度、大小、生命周期、重力等)实时调节,增强可调试性与演示效果GitHub

  5. 物理扩展

    • 添加风场、粒子间碰撞、流体模拟等,使特效更具现实感;可借鉴物理引擎或专门的流体模拟论文。

  6. 多线程

    • 在后台线程并行更新粒子属性,主线程专注渲染,利用多核 CPU 提升整体吞吐。


结语

通过以上详尽分解,本文完整呈现了一个基于 GLUT 的粒子系统从架构到实现的全过程,并提供多种优化与扩展路径。无论是初学者快速上手,还是进阶者迁移到现代 OpenGL 管线、GPU 加速,均可在此基础上轻松构建各类炫酷视觉特效。祝你在粒子特效的世界中玩得愉快!

完整代码:

#define GLUT_DISABLE_ATEXIT_HACK
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#define MODE_FIRE   0   // 火焰模式
#define MODE_RAIN   1   // 雨滴模式
#define MODE_SMOKE  2   // 烟雾模式

// ---------------- 全局状态 ----------------
int g_mode;                // 当前显示模式(火/雨/烟)
const int baseCount = 200; // 每一级别对应的粒子基数
int level = 5;             // 粒子等级,初始值5
int currentCount;          // 当前总粒子数 = baseCount * level
int capacity = 0;          // 分配的数组容量

// 窗口宽高(用于正交投影显示文字)
int winWidth;
int winHeight;

// 扩容系数
float expandFactor = 1.2f;

// ---------------- 场景参数 ----------------
float EMIT_ANGLE        = 20.0f;   // 发射角度
float GRAVITY_FIRE      = 0.0005f; // 火焰重力衰减
float GRAVITY_RAIN      = 0.0020f; // 雨滴重力加速度
float SPEED_MIN_FIRE    = 0.05f;   // 火焰粒子最小速度
float SPEED_MAX_FIRE    = 0.20f;   // 火焰粒子最大速度
float SPEED_MIN_RAIN    = 0.30f;   // 雨滴最小速度
float SPEED_MAX_RAIN    = 0.40f;   // 雨滴最大速度
float SPEED_MIN_SMOKE   = 0.01f;   // 烟雾粒子最小速度
float SPEED_MAX_SMOKE   = 0.03f;   // 烟雾粒子最大速度
float PARTICLE_SIZE_MIN = 1.0f;    // 粒子最小尺寸
float PARTICLE_SIZE_MAX = 4.0f;    // 粒子最大尺寸
float LIFE_MIN_FIRE     = 0.8f;    // 火焰粒子最小生命周期
float LIFE_MAX_FIRE     = 1.5f;    // 火焰粒子最大生命周期
float TURBULENCE        = 0.002f;  // 火焰扰动强度
float RAIN_Y_TOP        = 6.0f;    // 雨滴重置顶部Y坐标
float RAIN_Y_BOTTOM     = -2.0f;   // 雨滴底部Y坐标
float SMOKE_X_LEFT      = -6.0f;   // 烟雾重置左侧X坐标
float SMOKE_X_RIGHT     = 6.0f;    // 烟雾初始右侧X坐标

// 定义单个粒子属性结构体
typedef struct {
    float life;        // 当前剩余生命
    float initialLife; // 初始生命
    float fade;        // 衰减速度 = 1/initialLife
    float size;        // 当前尺寸
    float sizePeak;    // 尺寸峰值
    GLfloat r, g, b, a; // 颜色与透明度
    float x, y, z;     // 位置
    float vx, vy, vz;  // 速度
} Particle;

static Particle *particles = NULL; // 动态粒子数组指针

// 返回[min, max]范围内随机浮点数
static float frand(float min, float max) {
    return min + (float)rand() / (float)RAND_MAX * (max - min);
}

/**
 * 确保数组容量至少为newCount
 * 如果不足,按expandFactor扩容
 */
void ensureCapacity(int newCount) {
    if (newCount <= capacity) return;
    int newCap = (int)(capacity * expandFactor);
    if (newCap < newCount) newCap = newCount;
    if (newCap < 1) newCap = 1;
    Particle *tmp = (Particle*)realloc(particles, newCap * sizeof(Particle));
    if (!tmp) {
        MessageBox(NULL, "Memory allocation failed", "Error", MB_OK|MB_ICONERROR);
        exit(EXIT_FAILURE);
    }
    particles = tmp;
    capacity  = newCap;
}

/**
 * 初始化或重置第i个粒子,根据模式分配位置、速度、颜色等
 */
void initParticle(int i) {
    ensureCapacity(i + 1);
    Particle *p = &particles[i];
    if (g_mode == MODE_FIRE) {
        // 火焰粒子属性
        p->initialLife = frand(LIFE_MIN_FIRE, LIFE_MAX_FIRE);
        p->life = p->initialLife;
        p->fade = 1.0f / p->initialLife;
        p->x = p->y = p->z = 0.0f;
        float theta = frand(0, 2.0f * M_PI);
        float phi = frand(0, EMIT_ANGLE * M_PI / 180.0f);
        float spd = frand(SPEED_MIN_FIRE, SPEED_MAX_FIRE);
        p->vx = spd * sinf(phi) * cosf(theta);
        p->vz = spd * sinf(phi) * sinf(theta);
        p->vy = spd * cosf(phi);
        p->size = frand(PARTICLE_SIZE_MIN, PARTICLE_SIZE_MAX);
        p->sizePeak = p->size * 1.5f;
        p->r = 1.0f; p->g = 1.0f; p->b = 0.0f; p->a = 1.0f;
    } else if (g_mode == MODE_RAIN) {
        // 雨滴粒子属性
        p->x = frand(-4.0f, 4.0f);
        p->y = frand(RAIN_Y_BOTTOM, RAIN_Y_TOP);
        p->z = frand(-2.0f, 2.0f);
        p->vx = p->vz = 0.0f;
        p->vy = -frand(SPEED_MIN_RAIN, SPEED_MAX_RAIN);
        p->size = frand(1.0f, 2.0f);
        p->sizePeak = p->size;
        p->r = 0.8f; p->g = 0.8f; p->b = 1.0f; p->a = 0.7f;
    } else {
        // 烟雾粒子属性
        p->x = SMOKE_X_RIGHT;
        p->y = frand(0.0f, 3.0f);
        p->z = frand(-2.0f, 2.0f);
        p->vx = -frand(SPEED_MIN_SMOKE, SPEED_MAX_SMOKE);
        p->vy = frand(-0.005f, 0.005f);
        p->vz = frand(-0.005f, 0.005f);
        p->size = frand(2.0f, 5.0f);
        p->sizePeak = p->size;
        p->r = p->g = p->b = 0.5f; p->a = 0.6f;
    }
}

/**
 * OpenGL初始化
 * - 随机种子
 * - 获取屏幕尺寸
 * - 创建窗口模式与粒子数组
 */
void initGL(void) {
    // 初始化随机种子
    srand((unsigned)time(NULL));
    // 获取系统屏幕分辨率
    winWidth = GetSystemMetrics(SM_CXSCREEN);
    winHeight = GetSystemMetrics(SM_CYSCREEN);
    // 切换到全屏模式
    glutFullScreen();

    // 初始化粒子模式与数量
    g_mode = MODE_FIRE;
    currentCount = baseCount * level;
    ensureCapacity(currentCount);
    for (int i = 0; i < currentCount; ++i) initParticle(i);

    // 基本OpenGL状态
    glShadeModel(GL_SMOOTH);
    glClearColor(0, 0, 0, 1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
}

/**
 * 切换粒子模式(火/雨/烟)并重置所有粒子
 */
void switchMode(int mode) {
    g_mode = mode;
    for (int i = 0; i < currentCount; ++i) initParticle(i);
}

/**
 * 键盘回调函数:
 * - F/R/S 切换模式
 * - +/- 调整等级并重置粒子
 */
void keyboard(unsigned char key, int x, int y) {
    if (key == 'f' || key == 'F') switchMode(MODE_FIRE);
    else if (key == 'r' || key == 'R') switchMode(MODE_RAIN);
    else if (key == 's' || key == 'S') switchMode(MODE_SMOKE);
    else if (key == '+' || key == '=') level++;
    else if (key == '-' || key == '_') level = (level > 1 ? level - 1 : 1);
    else return;

    // 根据等级更新粒子数并重建数组
    currentCount = baseCount * level;
    ensureCapacity(currentCount);
    for (int i = 0; i < currentCount; ++i) initParticle(i);
    printf("[DEBUG] Level=%d, Particles=%d\n", level, currentCount);
}

/**
 * 渲染回调:
 * - 清除缓冲
 * - 3D视图设置
 * - 更新并绘制每个粒子
 * - 顶层正交投影用于显示等级文本
 */
void display(void) {
    // 清除颜色和深度缓冲
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 设置3D相机
    glLoadIdentity();
    gluLookAt(0.0, 2.0, 8.0,  0.0, 1.0, 0.0,  0.0, 1.0, 0.0);

    // 遍历粒子数组,更新物理和渲染
    for (int i = 0; i < currentCount; ++i) {
        Particle *p = &particles[i];
        if (g_mode == MODE_FIRE) {
            // 火焰动态更新
            p->vy -= GRAVITY_FIRE;
            p->vx += frand(-1.0f, 1.0f) * TURBULENCE;
            p->vz += frand(-1.0f, 1.0f) * TURBULENCE;
            p->x += p->vx; p->y += p->vy; p->z += p->vz;
            p->life -= p->fade * 0.02f;
            float t = 1.0f - p->life / p->initialLife;
            p->g = (t < 0.5f) ? 1.0f - 0.6f*(t/0.5f)
                             : 0.4f - 0.4f*((t-0.5f)/0.5f);
            p->a = 1.0f - t;
            p->size = (t < 0.5f) ? PARTICLE_SIZE_MIN + (p->sizePeak-PARTICLE_SIZE_MIN)*(t/0.5f)
                                  : p->sizePeak*(1.0f-(t-0.5f)/0.5f);
            if (p->life <= 0.0f) initParticle(i);
        } else if (g_mode == MODE_RAIN) {
            // 雨滴动态更新
            p->vy -= GRAVITY_RAIN;
            p->x += p->vx; p->y += p->vy; p->z += p->vz;
            if (p->y < RAIN_Y_BOTTOM) {
                p->y = RAIN_Y_TOP;
                p->x = frand(-4.0f, 4.0f);
                p->vy = -frand(SPEED_MIN_RAIN, SPEED_MAX_RAIN);
            }
        } else {
            // 烟雾动态更新
            p->x += p->vx; p->y += p->vy; p->z += p->vz;
            if (p->x < SMOKE_X_LEFT) initParticle(i);
        }
        // 绘制点
        glPointSize(p->size);
        glBegin(GL_POINTS);
          glColor4f(p->r, p->g, p->b, p->a);
          glVertex3f(p->x, p->y, p->z);
        glEnd();
    }

    // 切换到正交投影,在右上角绘制文本
    glMatrixMode(GL_PROJECTION);
    glPushMatrix(); glLoadIdentity();
    gluOrtho2D(0, winWidth, 0, winHeight);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix(); glLoadIdentity();
    glColor3f(1,1,1);
    char buf[32]; sprintf(buf, "Level: %d", level);
    glRasterPos2i(winWidth - 100, winHeight - 30);
    for (char* c = buf; *c; ++c) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *c);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);

    // 交换前后缓冲
    glutSwapBuffers();
}

/**
 * 窗口大小改变时回调:
 * 更新视口和投影矩阵,同时更新winWidth/winHeight
 */
void reshape(int w, int h) {
    if (h == 0) h = 1;
    winWidth = w;
    winHeight = h;
    float ratio = (float)w / (float)h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, ratio, 1.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
}

/**
 * 定时器回调:
 * 保持大约60FPS刷新
 */
void timer(int v) {
    glutPostRedisplay();
    glutTimerFunc(16, timer, 0);
}

int main(int argc, char** argv) {
    // GLUT初始化
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 初始窗口尺寸将在initGL中被重写为全屏分辨率
    glutInitWindowSize(800, 600);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Particle System (等级式 +/- 全屏)");

    // 初始化OpenGL与粒子系统
    initGL();

    // 注册回调
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutTimerFunc(0, timer, 0);

    // 进入主循环
    glutMainLoop();

    // 程序结束前释放内存
    free(particles);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值