Frame of 42yeah

Site Defunct

注意!截止到 16/9/2019 ,这个博客已经被搬迁到了 这里 。以后我的东西都会发在那里。拜拜啦!
目录表
main.cpp

粒子系统!

我已经大概有很久很久没有看过 OpenGL 了吧……所以我决定今天看一哈,于是我恢复了我龟速的进度,也就是 这里 。于是,咱们来总结一下我看了的内容。

OpenGL 的 VBO 是很灵活的。从我的个人理解来说,它就是一条储存在 GPU 里边的数组。所以,我们不仅可以在一开始的时候直接用 glBufferData 来初始化他,还可以用很多很多的方法!这里是官方文档给出的:

……对,其实就两种,所以没有很多很多种方法。但是没关系!这不还是初始化好了吗?但是,这时别以为里边的内容就再也不能更改了!即使是通过 glBufferStorage 来初始化的,里面的内容也是可以更改的。这个感觉就像在 C++ 里边来了一句 char * const buf 一样吧……指针不能再改了,但不代表里边的内容不能改:

于是现在我们知道了有什么初始化 VBO 的数组,还有怎么修改一个已经初始化过的 VBO 了。我们发现了 VBO 很灵活,那实际上,可不可以根据 glMapBuffer 或者 glBufferSubData 来进行每一桢对 VBO 的修改呢?答案是可以的!而且实际上,这个很可能就是 Instancing 出来之前大家对粒子系统的实现(毕竟一帧一次 glBufferData 太浪费内存了)。所以说干就干,马上开写!

粒子系统!

经过了一会儿的脑热和半个小时左右的 debug ,终于写好了……话说 OpenGL debug 是真的费时间……有的时候什么都看不到,就看到一个黑屏还是很绝望的……有的时候看到的不是黑屏,显示效果却很迷,又一时不知道为什么,就更绝望了……

我的粒子系统其实就是一开始先 book 好 1000 个点:

// generate points
std::vector<Point> points;
for (int i = 0; i < MAX_PARTICLES; i++) {
    points.push_back(Point());
    points[i].alive = false;
}

然后进入主循环,对于每一个点都看一次,如果对应的点是 alive 的,我们则根据他的运动方向更新他。如果不 alive ,我们就生成他。但是一帧只生成一个点,避免爆破式增长(虽然程序运行一开始还是爆破式增长了……):

// 生成一个点, 如果点满了就杀一个,然后更新其他的
int furthest = 0; // 离得最远的索引
bool spacious = false; // 是不是还有空位
int i;
for (i = 0; i < points.size(); i++) {
    // 如果这个点活着,并且这个点比最远的还要离得更远,并且我们已经没有更多点可以用了,
    // 那他就是我们的新的最远的点了(准备把这个点删掉)
    if (points[i].alive && !spacious &&
        fabs(points[i].position.x) > fabs(points[furthest].position.x) &&
        fabs(points[i].position.y) > fabs(points[furthest].position.y)) {
        furthest = i;
    }

    // 如果活着,我们就根据这个点的运动方向,移动一下这个点
    if (living) {
        points[i].position += points[i].direction * delta;
        glBufferSubData(GL_ARRAY_BUFFER, (i * 3) * sizeof(glm::vec3), sizeof(glm::vec3), &points[i].position);
    }

    // 如果这个点没活着,挺好,那之后大概也没有活着的点了(点满1000个的时候,因为每一帧都是生成一个,销毁一个,所有的点都应该永远活着
    if (!points[i].alive) {
        spacious = true;
        furthest = i; // 我们假设他是离得最远的
        break;
    }
}

最后,还记得上面那串代码的 furthest 吗?这个东西记录的索引要不就是最远,也就是 准备被删然后在原位重新生成,要不就是 准备生成 的。所以实际上,我们完全可以把这个当成 “我们准备在这里创建一个新的粒子” 的点。怪我没命名好,可以叫 gen 什么的:

points[furthest].alive = true;
points[furthest].position = glm::vec3(0.0f, 0.0f, 0.0f);
points[furthest].direction = glm::vec3((distrib(device) - 0.5f) * 2,
                               (distrib(device) - 0.5f) * 2,
                               0.0f); // 随机生成个新的方向,给点走

这样一来,一个粒子系统就完工啦!

会动!

这里是完整的源码,注释会少一点(因为有些注释是我写这个的时候加的):

main.cpp

#include <iostream>
#include "../include/glad/glad.h"
#include <GLFW/glfw3.h>
#include <fstream>
#include <sstream>
#include <vector>
#include <random>
#include <glm/glm.hpp>

#include "utility.hpp"


#define MAX_PARTICLES 1000


struct Point {
    glm::vec3 position;
    glm::vec3 direction;
    bool alive;
};


int alive(std::vector<Point> array) {
    int count = 0;

    for (int i = 0; i < array.size(); i++) {
        if (array[i].alive) { count++; }
    }
    return count;
}


int main(int argc, const char * argv[]) {
    // init glfw
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow *window = glfwCreateWindow(800, 600, "buffer", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    gladLoadGL();


    // compile programs!
    GLuint prog = program("shaders/vertex.glsl", "shaders/fragment.glsl");

    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glPointSize(20.0f);


    // generate points
    std::vector<Point> points;
    for (int i = 0; i < MAX_PARTICLES; i++) {
        points.push_back(Point());
        points[i].alive = false;
    }

    // get random machine up & running!
    std::random_device device;
    std::uniform_real_distribution<float> distrib;


    GLuint VAO, VBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * sizeof(glm::vec3), nullptr, GL_DYNAMIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, nullptr);


    float past = glfwGetTime();
    while (!glfwWindowShouldClose(window)) {
        float now = glfwGetTime();
        float delta = now - past;
        past = now;

        glfwPollEvents();

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(prog);
        glBindVertexArray(VAO);

        int living = alive(points);
        glDrawArrays(GL_POINTS, 0, living);

        // gen one particles, kill one if too much, and move others
        int furthest = 0;
        bool spacious = false;
        int i;
        for (i = 0; i < points.size(); i++) {
            if (points[i].alive && !spacious &&
                fabs(points[i].position.x) > fabs(points[furthest].position.x) &&
                fabs(points[i].position.y) > fabs(points[furthest].position.y)) {
                furthest = i;
            }

            if (living) {
                points[i].position += points[i].direction * delta;
                glBufferSubData(GL_ARRAY_BUFFER, (i * 3) * sizeof(glm::vec3), sizeof(glm::vec3), &points[i].position);
            }

            if (!points[i].alive) {
                spacious = true;
                furthest = i;
                break;
            }
        }
        points[furthest].alive = true;
        points[furthest].position = glm::vec3(0.0f, 0.0f, 0.0f);
        points[furthest].direction = glm::vec3((distrib(device) - 0.5f) * 2,
                                       (distrib(device) - 0.5f) * 2,
                                       0.0f);

        glfwSwapBuffers(window);
    }

    return 0;
}

那就这样啦!

评论区