• Android OpenGL ES 3.0 粒子特效


    1.粒子特效

    粒子特效:Particles Effect,粒子特效本质上是通过一次或者多次渲染绘制出大量位置、形状或者颜色不同的物体(粒子),形成大量粒子运动的视觉效果。所以,粒子特效天然适合用OpenGL ES 实例化(Instancing)实现。

    2.定义粒子

    定义粒子,通常一个粒子有一个生命值,生命值结束该粒子消失,还有描述粒子在(x, y, z)三个方向的位置(偏移)和运动速度,以及粒子的颜色等属性。可以把粒子定义成一个结构体:

    struct Particle {
        GLfloat dxSpeed,dySpeed,dzSpeed;// 粒子速度
        GLubyte r,g,b,a; //r,g,b,a      //粒子颜色
        GLfloat dx,dy,dz; // 粒子的位置
        GLfloat life;               //粒子出现的时长
        GLfloat cameraDistance;
    
        Particle(){
    
            dxSpeed = 1.0f;
            dySpeed = 1.0f;
            dzSpeed = 1.0f;
    
            dx = 0.0f;
            dy = 0.0f;
            dz = 0.0f;
            
            r = static_cast(1.0f);
            g = static_cast(1.0f);
            b = static_cast(1.0f);
            a = static_cast(1.0f);
    
            life = 10.0f;  //粒子的生命值
        }
        /*操作符重载 < */
        bool operator<(const Particle& that) const {
            return this->cameraDistance > that.cameraDistance;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    3.顶点着色器
    #version 300 es
    precision mediump float;
    layout(location = 0) in vec3 a_vertex;          //粒子顶点
    layout(location = 1) in vec2 a_texCoord;        //粒子材质
    layout(location = 2) in vec3 a_offset;          //粒子位置
    layout(location = 3) in vec4 a_particlesColor;    //粒子颜色
    
    uniform mat4 u_mat;    //MVP矩阵   观察矩阵*模型矩阵*透视矩阵
    
    /*输出的材质和颜色*/
    out vec2 v_texCoord;
    out vec4 v_color;
    
    void main() {
        gl_Position = u_mat * vec4(a_vertex - vec3(0.0, 0.95, 0.0) + a_offset, 1.0);
        v_texCoord = a_texCoord;
        v_color = a_particlesColor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    属性a_offset是粒子的位置偏移,最终确定粒子的位置,属性a_particlesColor表示照在粒子表面光的颜色,这两个属性均为实例化数组,因为每个粒子有不同的位置和颜色。

    将粒子相关的属性传进来,mvp矩阵传进来,对位置做简单的计算,并将颜色和材质传递给片元着色器

    4.片元着色器
    #version 300 es
    precision mediump float;
    in vec2 v_texCoord;
    in vec4 v_color;
    layout(location = 0) out vec4 outColor;
    uniform sampler2D s_TextureMap;
    
    
    void main() {
        outColor = texture(s_TextureMap, v_texCoord) * v_color;
    //    outColor = texture(s_TextureMap, v_texCoord) ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果粒子希望显示材质本身,可以直接将采样器得到的颜色赋值给outColor,不用再跟v_color相乘

    5.程序实现过程代码解析
    5.1绘制前的准备
    glGenBuffers(1, &m_ParticlesVertexVboId);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesVertexVboId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data),
                 g_vertex_buffer_data,
                 GL_STATIC_DRAW);
    
    glGenBuffers(1, &m_ParticlesPosVboId);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesPosVboId);
    // Initialize with empty (NULL) buffer : it will be updated later, each frame.
    glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 3 * sizeof(GLfloat),
                 NULL, GL_DYNAMIC_DRAW);
    
    glGenBuffers(1, &m_ParticlesColorVboId);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
    // Initialize with empty (NULL) buffer : it will be updated later, each frame.
    glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 4 * sizeof(GLubyte),
                 NULL, GL_DYNAMIC_DRAW);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    创建VBO,注意:粒子的顶点是GL_STATIC_DRAW,位置和颜色会动态变化,所以是GL_DYNAMIC_DRAW

    // Generate VAO Id
    glGenVertexArrays(1, &m_VaoId);
    glBindVertexArray(m_VaoId);
    
    /*给shader传递数据*/
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesVertexVboId);
    glVertexAttribPointer(
            0,
            3,
            GL_FLOAT,
            GL_FALSE,
            5 * sizeof(GLfloat),
            (void *) 0
    );
    
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesVertexVboId);
    glVertexAttribPointer(
            1,
            2,
            GL_FLOAT,
            GL_FALSE,
            5 * sizeof(GLfloat),
            (const void *) (3 * sizeof(GLfloat))
    );
    
    
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesPosVboId);
    glVertexAttribPointer(
            2,
            3,
            GL_FLOAT,
            GL_FALSE,
            0,
            (void *) 0
    );
    
    glEnableVertexAttribArray(3);
    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
    glVertexAttribPointer(
            3,
            4,
            GL_UNSIGNED_BYTE,
            GL_TRUE,
            0,
            (void *) 0
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 创建并绑定VAO

    • 给shader中的变量a_vertex、a_texCoord、a_offset、a_particlesColor赋值

    glVertexAttribDivisor(0, 0);
    glVertexAttribDivisor(1, 0);
    glVertexAttribDivisor(2, 1);
    glVertexAttribDivisor(3, 1);
    
    • 1
    • 2
    • 3
    • 4

    glVertexAttribDivisor(0, 0);表示实例化绘制时对 index =0 的属性不更新;glVertexAttribDivisor(2, 1); 用于指定 index = 2 的属性为实例化数组,1 表示每绘制一个实例,更新一次数组中的元素。

    5.2 进行绘制操作
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDisable(GL_BLEND); //关闭混合
    
    //    value += 1;
    //    value = value % 360;
    //    value = value % 360;
    
        //转化为弧度角
        float radiansX = static_cast(MATH_PI / 180.0f * value);
    
        glm::mat4 modelMat = glm::mat4(1.0f);  //模型矩阵
        modelMat = glm::scale(modelMat, glm::vec3(0.8f, 0.8f, 0.8f));
        modelMat = glm::rotate(modelMat, radiansX, glm::vec3(1.0f, 0.0f, 0.0f));
        modelMat = glm::rotate(modelMat, radiansX, glm::vec3(0.0f, 1.0f, 0.0f));
        modelMat = glm::translate(modelMat, glm::vec3(0.0f, 0.0f, 0.0f));
    
        glm::mat4 projection =glm::perspective(45.0f, 0.5f, 0.1f, 100.f);
    
        // viewMat matrix
        glm::mat4 viewMat = glm::lookAt(
                glm::vec3(0, 6, 0), // Camera is at (0,0,1), in World Space
                glm::vec3(0, 0, 0), // and looks at the origin
                glm::vec3(0, 0, 1)  // Head is up (set to 0,-1,0 to look upside-down)
        );
    
    
        glm::mat4 mvpMatrix = projection * viewMat * modelMat;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 清理了深度和颜色缓冲
    • 创建模型矩阵
    • 创建透视矩阵
    • 创建观察者矩阵
    • 生成MVP矩阵
    int particleCount = UpdateParticles();
    
    m_pOpenGLShader->Bind();
    glBindVertexArray(m_VaoId);
    
    m_pOpenGLShader->SetUniformValue("u_mat",mvpMatrix);
    
    // Bind the RGBA map
    m_pOpenGLShader->SetUniformValue("s_TextureMap",0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_texID[3]);
    
    glDrawArraysInstanced(GL_TRIANGLES, 0, 36, particleCount);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 更新粒子信息
    • 把MVP矩阵传给Shader
    • 初始化采样器
    • 实例化进行绘制
    void MSParticlesSample::GenerateNewParticle(Particle &particle) {
        particle.life = 10.0f;
        particle.cameraDistance = -1.0f;
        particle.dx = (rand() % 2000 - 1000.0f) / 3000.0f;
        particle.dy = (rand() % 2000 - 1000.0f) / 3000.0f;
        particle.dz = (rand() % 2000 - 1000.0f) / 3000.0f;
    
        float spread = 1.5f;
    
        glm::vec3 maindir = glm::vec3(0.0f, 2.0f, 0.0f);
        glm::vec3 randomdir = glm::vec3(
                (rand() % 2000 - 1000.0f) / 1000.0f,
                (rand() % 2000 - 1000.0f) / 1000.0f,
                (rand() % 2000 - 1000.0f) / 1000.0f
        );
    
        glm::vec3 speed = maindir + randomdir * spread;
        particle.dxSpeed = speed.x;
        particle.dySpeed = speed.y;
        particle.dzSpeed = speed.z;
    
        particle.r = static_cast(rand() % 256);
        particle.g = static_cast(rand() % 256);
        particle.b = static_cast(rand() % 256);
        particle.a = static_cast((rand() % 256) / 3);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    新粒子的速度、偏移以及颜色都是随机生成的

    /**
     * 查找生命值结束的粒子
     * @return 
     */
    int MSParticlesSample::FindUnusedParticle() {
        for (int i = m_LastUsedParticle; i < MAX_PARTICLES; i++)
        {
            if (m_ParticlesContainer[i].life <= 0)
            {
                m_LastUsedParticle = i;
                return i;
            }
        }
    
        for (int i = 0; i < m_LastUsedParticle; i++)
        {
            if (m_ParticlesContainer[i].life <= 0)
            {
                m_LastUsedParticle = i;
                return i;
            }
        }
    
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    查找生命值结束的粒子

    int MSParticlesSample::UpdateParticles() {
        int newParticles = 300;
        for (int i = 0; i < newParticles; i++)
        {
            int particleIndex = FindUnusedParticle();
            if (particleIndex >= 0)
            {
                GenerateNewParticle(m_ParticlesContainer[particleIndex]);
            }
        }
    
    
        int particlesCount = 0;
        for (int i = 0; i < MAX_PARTICLES; i++)
        {
    
            Particle &p = m_ParticlesContainer[i]; // shortcut
    
            if (p.life > 0.0f)
            {
                float delta = 0.1f;
            
                glm::vec3 speed = glm::vec3(p.dxSpeed, p.dySpeed, p.dzSpeed), pos = glm::vec3(p.dx,
                                                                                              p.dy,
                                                                                              p.dz);
                p.life -= delta;
                if (p.life > 0.0f)
                {
    
                  
                    //模拟简单的物理:只有重力,没有碰撞
                    speed += glm::vec3(0.0f, 0.81f, 0.0f) * delta * 0.5f;
                    pos += speed * delta;
    
                    p.dxSpeed = speed.x;
                    p.dySpeed = speed.y;
                    p.dzSpeed = speed.z;
    
                    p.dx = pos.x;
                    p.dy = pos.y;
                    p.dz = pos.z;
    
                    m_pParticlesPosData[3 * particlesCount + 0] = p.dx;
                    m_pParticlesPosData[3 * particlesCount + 1] = p.dy;
                    m_pParticlesPosData[3 * particlesCount + 2] = p.dz;
    
                    m_pParticlesColorData[4 * particlesCount + 0] = p.r;
                    m_pParticlesColorData[4 * particlesCount + 1] = p.g;
                    m_pParticlesColorData[4 * particlesCount + 2] = p.b;
                    m_pParticlesColorData[4 * particlesCount + 3] = p.a;
    
                }
    
                particlesCount++;
    
            }
        }
    
        glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesPosVboId);
        glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 3 * sizeof(GLfloat), NULL,
                     GL_DYNAMIC_DRAW); 
    
        glBufferSubData(GL_ARRAY_BUFFER, 0, particlesCount * sizeof(GLfloat) * 3, m_pParticlesPosData);
    
        glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
        glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 4 * sizeof(GLubyte), NULL,
                     GL_DYNAMIC_DRAW); 
        
        glBufferSubData(GL_ARRAY_BUFFER, 0, particlesCount * sizeof(GLubyte) * 4,
                        m_pParticlesColorData);
    
        return particlesCount;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    更新粒子(更新粒子的位置、运动速度和生命值),然后更新实例化数组

  • 相关阅读:
    2022年11月华南师范大学自考本科-《计算机信息管理课程实验—C++程序设计》实践题目
    如何上传项目到github?
    软考 系统架构设计师系列知识点之数字孪生体(2)
    Python 遍历字典的若干方法
    10、Redis分布式系统之数据分区算法
    01-Node中的系统模块:fs文件模块、path路径模块、正则、http模块
    ±15kV ESD 保护、3V-5.5V 供电、真 RS-232 收发器MS2232/MS2232T
    iperf-2.0.9 在 Linux下的编译 与 海思平台的交叉编译
    Verilig语法之——Generate 结构
    CMT2380F32模块开发2-IDE软件配置
  • 原文地址:https://blog.csdn.net/u014078003/article/details/127982439