0

There is a particle system for an explosion, similar to fireworks:

enter image description here

Code of vertex shader:

#version 300 es
uniform float u_lastTimeExplosion; // time elapsed since the explosion
// explosion center (particle coordinates are set relative to this center
uniform vec3 u_centerPosition; 
uniform float u_sizeSprite;
// particle lifetime in seconds
layout(location = 0) in float a_lifeTime;
// initial position of the particle at the time of the explosion
layout(location = 1) in vec3 a_startPosition;
layout(location = 2) in vec3 a_endPosition; // final position of the particle
out float v_lifeTime; // remaining particle lifetime
void main()
{
    // calculate particle position (algorithm from the book of D.Ginsburg, B.Purnomo)
    gl_Position.xyz = a_startPosition + (u_lastTimeExplosion * a_endPosition);
    gl_Position.xyz += u_centerPosition;
    gl_Position.w = 1.0;
    // calculate the remaining particle lifetime
    v_lifeTime = 1.0 - (u_lastTimeExplosion / a_lifeTime);
    v_lifeTime = clamp(v_lifeTime, 0.0, 1.0);
    // calculate sprite size based on remaining life time
    gl_PointSize = pow(v_lifeTime, 5.0) * u_sizeSprite;
}

Code of fragment shader:

#version 300 es
precision lowp float;
in float v_lifeTime;
uniform vec4 u_color;
out vec4 fragColor;
uniform sampler2D s_texture;
void main()
{
    vec4 texColor = texture(s_texture, gl_PointCoord);
    fragColor = u_color * texColor;
    // increase sprite transparency
    fragColor.a *= v_lifeTime;
}

Three vertex buffers are used: for an array with particle lifetime; array of initial coordinates of particles; array of final particle coordinates:

lifeTimeAsFloatBuffer.position(0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBO[0]);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,
        FLOAT_SIZE * numberParticles, lifeTimeAsFloatBuffer,
        GLES30.GL_STATIC_DRAW);

startPositionAsFloatBuffer.position(0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBO[1]);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,
        FLOAT_SIZE * NUM_COORDINATES * numberParticles,
        startPositionAsFloatBuffer, GLES30.GL_STATIC_DRAW);

endPositionAsFloatBuffer.position(0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBO[2]);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,
        FLOAT_SIZE * NUM_COORDINATES * numberParticles,
        endPositionAsFloatBuffer, GLES30.GL_STATIC_DRAW);

Generate Vertex data:

private final float[] lifeTimeData; // life time of particles
private final float[] startPositionData; // start coordinates of particles
private final float[] endPositionData; // end coordinates of particles
...
public void createDataVertex() {
    float maxLifeTime = 3.0f; 
    for (int i = 0; i < numberParticles; i ++) {
        // life time of particle, random value 0-3 second
        lifeTimeData[i] = random.nextFloat() * maxLifeTime;
    }
    float[] xyz;
    for (int i = 0; i < numberParticles * NUM_COORDINATES; i += NUM_COORDINATES) {
        xyz = getPointForSphere(startRadius); // start position particle
        startPositionData[i] = xyz[0] * aspect;
        startPositionData[i + 1] = xyz[1];
        startPositionData[i + 2] = xyz[2];
        xyz = getPointForSphere(endRadius); // end position particle
        endPositionData[i] = xyz[0] * aspect;
        endPositionData[i + 1] = xyz[1];
        endPositionData[i + 2] = xyz[2];
        }
    lifeTimeAsFloatBuffer = floatBuffer(lifeTimeData);
    startPositionAsFloatBuffer = floatBuffer(startPositionData);
    endPositionAsFloatBuffer = floatBuffer(endPositionData);
}

Pass data to shader:

public void onDrawFrame(GL10 glUnused) {
    ...
    GLES30.glEnableVertexAttribArray(startPositionLink);
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBO[1]);
    GLES30.glVertexAttribPointer(startPositionLink, NUM_COORDINATES, 
    GLES30.GL_FLOAT, false, FLOAT_SIZE * NUM_COORDINATES, 0);
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
    ...
}

Problem: on some physical devices there is a slowdown in rendering (FPS reduction). Even if there are several hundred particles. Watched a post: Improve Particle system OpenGL. But here the problem seems to be different.

Question: Is there a way to further optimize this particle system? Any answer/comment would be very valuable.

alexrnov
  • 2,346
  • 3
  • 18
  • 34
  • 1
    So, just a few hundred particles is causing a slowdown? Seems surprising because it's relatively trivial. Can you confirm that you're issuing them all in a single draw call? Also, is your texture sensible (i.e. it's either small, or it's large but has mipmaps)? – Columbo Mar 13 '20 at 14:13
  • @Columbo Hello! Thanks! Most likely, all in a single draw call - added example to the question. Texture - green circle on black background, file *.png, 4 KB. Mipmaps not used. If me need to use mipmap, please write this as your answer, I will mark it as right answer. – alexrnov Mar 14 '20 at 01:02
  • @Columbo Can also use a VAO? – alexrnov Mar 14 '20 at 01:24
  • @Columbo Slowdown usually occurs when there are several explosions at a time. – alexrnov Mar 14 '20 at 01:30
  • 1
    Resolution of the texture is more important than the file size of the png. Mipmapping will likely help a lot if the texture is large (e.g. 1024x1024) and likely make no difference if it's small (e.g. 32x32). I would turn it on anyway. – Columbo Mar 14 '20 at 21:37
  • @Columbo Thanks you for the comment! Size 128x128. I will reduce to 32x32 or use mipmap. Additional question, if possible. Can using transform feedback have a positive effect on performance? – alexrnov Mar 15 '20 at 01:33

1 Answers1

0

As it turned out, the performance is greatly affected by the size of the sprite. If the initial size of the sprite is from 1 to 10, then everything is fine:

GLES20.glUniform1f(sizeSpriteLink, 10f);

If the size of the sprite increases, then is a slowdown in rendering (FPS reduction). Not sure, but it seems that increasing the size of the sprite greatly affects the consumption of graphic memory resources. Maybe someone knows about this?

On the advice of Columbo added a mipmap for particle texture. It is desirable that the size of the sprite should not exceed the size of the texture.

GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D)

It is also better to use this type of filtering to increase performance:

// one value is taken from the nearest pyramid level
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
        GLES20.GL_NEAREST_MIPMAP_NEAREST);
alexrnov
  • 2,346
  • 3
  • 18
  • 34