天天看點

NDK OpenGL ES 3.0 開發(十四):粒子(Particles)OpenGL ES 粒子(Particles)聯系與交流

作者:位元組流動

來源:

https://blog.csdn.net/Kennethdroid/article/details/102881654

OpenGL ES 粒子(Particles)

NDK OpenGL ES 3.0 開發(十四):粒子(Particles)OpenGL ES 粒子(Particles)聯系與交流
NDK OpenGL ES 3.0 開發(十三):執行個體化(Instancing)

一文中我們了解到 OpenGL ES 執行個體化(Instancing)是一種隻調用一次渲染函數就能繪制出很多物體的技術,可以實作将資料一次性發送給 GPU ,避免了 CPU 多次向 GPU 下達渲染指令,提升了渲染性能。

而粒子系統本質上是通過一次或者多次渲染繪制出大量位置、形狀或者顔色不同的物體(粒子),形成大量粒子運動的視覺效果。是以,粒子系統天然适合用OpenGL ES 執行個體化(Instancing)實作。

定義粒子,通常一個粒子有一個生命值,生命值結束該粒子消失,還有描述粒子在(x, y, z)三個方向的位置(偏移)和運動速度,以及粒子的顔色等屬性。本文中粒子的定義:

struct Particle {
    GLfloat dx,dy,dz;//offset 控制粒子的位置
    GLfloat dxSpeed,dySpeed,dzSpeed;//speed 控制粒子的運動速度
    GLubyte r,g,b,a; //r,g,b,a 控制粒子的顔色
    GLfloat life; //控制粒子的生命值
    Particle()
    {
        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);

        dxSpeed = 1.0f;
        dySpeed = 1.0f;
        dzSpeed = 1.0f;

        life = 0.0f;
    }
};           

渲染粒子需要用到的頂點着色器:

#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_MVPMatrix;//變換矩陣
out vec2 v_texCoord;
out vec4 v_color;
void main()
{
    gl_Position = u_MVPMatrix * vec4(a_vertex - vec3(0.0, 0.95, 0.0) + a_offset, 1.0);
    // vec3(0.0, 0.95, 0.0) 是為了使粒子整體向 y 軸負方向有一個偏移
    v_texCoord = a_texCoord;
    v_color = a_particlesColor;
}           

渲染粒子需要用到的片段着色器:

#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;
}           

屬性

a_offset

是粒子的位置偏移,最終确定粒子的位置,屬性

a_particlesColor

表示照在粒子表面光的顔色,這兩個屬性均為執行個體化數組,因為每個粒子有不同的位置和顔色。

設定屬性

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 表示每繪制一個執行個體,更新一次數組中的元素。

因為每次執行個體化渲染粒子時,都要更新

a_offset

a_particlesColor

執行個體化數組,是以設定其對應的 VBO 為動态類型 GL_DYNAMIC_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);           

新粒子的速度、偏移以及顔色都是随機生成的,生成新粒子的實作為:

void ParticlesSample::Init()
{
    for (int i = 0; i < MAX_PARTICLES; i++)
    {
        GenerateNewParticle(m_ParticlesContainer[i]);
    }
}

void ParticlesSample::GenerateNewParticle(Particle &particle)
{
    particle.life = 5.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);

}           

查找生命值結束的粒子:

int ParticlesSample::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 ParticlesSample::UpdateParticles()
{
    LOGCATE("ParticlesSample::UpdateParticles");

    //每次生成 300 個新粒子,産生爆炸的效果
    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
        //生命值大于 0 的粒子進行更新
        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.081f, 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); // Buffer orphaning, a common way to improve streaming perf. See above link for details.
    GO_CHECK_GL_ERROR();
    glBufferSubData(GL_ARRAY_BUFFER, 0, particlesCount * sizeof(GLfloat) * 3, m_pParticlesPosData);
    GO_CHECK_GL_ERROR();

    glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
    glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 4 * sizeof(GLubyte), NULL,
                 GL_DYNAMIC_DRAW); // Buffer orphaning, a common way to improve streaming perf. See above link for details.
    glBufferSubData(GL_ARRAY_BUFFER, 0, particlesCount * sizeof(GLubyte) * 4,
                    m_pParticlesColorData);
    return particlesCount;
}           

每次繪制時,先擷取生命值大于 0 粒子的數量再進行繪制:

void ParticlesSample::Draw(int screenW, int screenH)
{
    LOGCATE("ParticlesSample::Draw()");
    if (m_ProgramObj == GL_NONE || m_TextureId == GL_NONE) return;
    glEnable(GL_DEPTH_TEST);
    glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glDisable(GL_BLEND);

    UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, (float) screenW / screenH);

    //每次擷取生命值大于 0 粒子的數量
    int particleCount = UpdateParticles();

    // Use the program object
    glUseProgram(m_ProgramObj);

    glBindVertexArray(m_VaoId);
    glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);

    // Bind the RGBA map
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_TextureId);
    glUniform1i(m_SamplerLoc, 0);

    glDrawArraysInstanced(GL_TRIANGLES, 0, 36, particleCount);
}           

聯系與交流

技術交流/擷取源碼可以添加我的微信:Byte-Flow

「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
NDK OpenGL ES 3.0 開發(十四):粒子(Particles)OpenGL ES 粒子(Particles)聯系與交流

繼續閱讀