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<GLubyte>(1.0f);
        g = static_cast<GLubyte>(1.0f);
        b = static_cast<GLubyte>(1.0f);
        a = static_cast<GLubyte>(1.0f);

        life = 10.0f;  //粒子的生命值
    }
    /*操作符重载 < */
    bool operator<(const Particle& that) const {
        return this->cameraDistance > that.cameraDistance;
    }
};
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;
}

属性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) ;
}

如果粒子希望显示材质本身,可以直接将采样器得到的颜色赋值给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);

创建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
);
  • 创建并绑定VAO

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

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

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<float>(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;
  • 清理了深度和颜色缓冲
  • 创建模型矩阵
  • 创建透视矩阵
  • 创建观察者矩阵
  • 生成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);
  • 更新粒子信息
  • 把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<unsigned char>(rand() % 256);
    particle.g = static_cast<unsigned char>(rand() % 256);
    particle.b = static_cast<unsigned char>(rand() % 256);
    particle.a = static_cast<unsigned char>((rand() % 256) / 3);

}

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

/**
 * 查找生命值结束的粒子
 * @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;
}

查找生命值结束的粒子

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;

}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值