directory

  1. Particles and particle systems
  2. Practice: Fountain effect
  3. Problems encountered
  4. data
  5. harvest

Through the practice of this article to achieve the following results

What are particles and particle systems

How do you define a particle? A particle has properties such as position information (x,y,z), direction of motion, color, health (start and end times), and so on

What particle system? A large number of objects (particles) with different positions, shapes, directions and colors are rendered to create the effect of a large number of particles moving.

Clear the concept we come to realize the fireworks effect step by step

Two, practice: fountain effect

Facing a larger or content of the project, I never tried before we often fear, timidity, to clarify the target at this moment, grasp the main line, using the structured thinking, dismantling process, and gradually realize each link, to solve problems in each link to, it is also a strange upgrade process, the following together to enjoy the process.

Objective: To understand and use particle systems to achieve cool effects, starting from the particle fountain.

Process and dismantling

  1. Tease out particle characteristics (coordinates, colors, motion vectors, start times) as well as gravity and drag effects and duration
  2. How a particle generator emits particles (reflection point, direction, quantity)
  3. Write GLSL code for shaders
  4. Write a Program
  5. Write GLSurfaceView’s Render for shader loading, compiling, linking, using, rendering

Let’s start with our specific practice

2.1. Define particle system objects

Defines particle properties and provides methods for adding particles

Public class ParticleSystem {// position XYZ private final int POSITION_COMPONENT_COUNT = 3; // color private final int COLOR_COMPONENT_COUNT = 3; Xyz private final int VECTOR_COMPONENT_COUNT = 3; Private final int PARTICLE_START_TIME_COMPONENT_COUNT = 1; private final int TOTAL_COMPONENT_COUNT = POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT + VECTOR_COMPONENT_COUNT + PARTICLE_START_TIME_COMPONENT_COUNT; Private final int STRIDE = TOTAL_COMPONENT_COUNT * vertexarray.bytes_per_float; private final int STRIDE = TOTAL_COMPONENT_COUNT * vertexarray.bytes_per_float; // private int nextParticle; Private int curParticleCount; // Private final float[] particles; Private final int maxParticleCount; //VBO private final VertexArray vertexArray; public ParticleSystem(int maxParticleCount) { this.particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT]; this.maxParticleCount = maxParticleCount; this.vertexArray = new VertexArray(particles); } /** * Adds particles to FloatBuffer ** @param Position * @param color color * @Param Direction motion vector * @param particStartTime Start time */ public void addParticle(Geometry.Point position, int color, Geometry.Vector direction, float particStartTime) { final int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT; int currentOffset = particleOffset; nextParticle++; if (curParticleCount < maxParticleCount) { curParticleCount++; NextParticle == maxParticleCount {nextParticle = 0; } // fill the position coordinate xyz particles[currentOffset++] = position.x; particles[currentOffset++] = position.y; particles[currentOffset++] = position.z; // fill the Color RGB particles[currentOffset++] = color.red (Color) / 255; particles[currentOffset++] = Color.green(color) / 255; particles[currentOffset++] = Color.blue(color) / 255; // populate the motion vector particles[currentOffset++] = direction.x; particles[currentOffset++] = direction.y; particles[currentOffset++] = direction.z; Particles [currentOffset++] = particStartTime; Vertexarray. updateBuffer(particles, particleOffset, TOTAL_COMPONENT_COUNT); // add the new particle to the vertexArray FloatBuffer. }}Copy the code

2.2. Define the particle emitter object

Public class long {private final Geometry.Point position; Private final int color; Private final Geometry.Vector direction; public ParticleShooter(Geometry.Point position, int color, Geometry.Vector direction) { this.position = position; this.color = color; this.direction = direction; } /** * Add particle ** @param particleSystem @param currentTime */ public void addParticles(particleSystem particleSystem, float currentTime) { particleSystem.addParticle(position, color, direction, currentTime); }}Copy the code

2.3. Write vertex and slice shaders

uniform float u_Time; attribute vec3 a_Position; attribute vec3 a_Color; attribute vec3 a_Direction; attribute float a_PatricleStartTime; varying vec3 v_Color; varying float v_ElapsedTime; void main(){ v_Color = a_Color; // Particle duration Current time - Start time v_ElapsedTime = u_Time - a_PatricleStartTime; Float gravityFactor = v_ElapsedTime * v_ElapsedTime / 9.8; float gravityFactor = v_ElapsedTime * v_ElapsedTime / 9.8; Vec3 curPossition = a_Position + (a_Direction * v_ElapsedTime); vec3 curPossition = a_Position + (a_Direction * v_ElapsedTime); // Subtract the effect of gravity or drag. Y -= gravityFactor; // Pass the current position to the pixel shader gl_Position = VEC4 (curPossition,1.0) via the built-in variable; Gl_PointSize = 10.0; } precision mediump float; varying vec3 v_Color; varying float v_ElapsedTime; Void main(){gl_FragColor = vec4(v_Color/v_ElapsedTime, 1.0); }Copy the code

2.4. Write Program to encapsulate shaders

public class ParticleShaderProgram { private final String U_TIME ="u_Time"; private final String A_POSITION="a_Position"; private final String A_COLOR="a_Color"; private final String A_DIRECTION="a_Direction"; private final String A_PATRICLE_START_TIME="a_PatricleStartTime"; private final int program; private final int uTimeLocation; private final int aPositionLocation; private final int aColorLocation; private final int aDirectionLocation; private final int aPatricleStartTimeLocation; Public ParticleShaderProgram(Context Context) {// Generate Program String vertexShaderCoder = ShaderHelper.loadAsset(context.getResources(), "particle_vertex_shader.glsl"); String fragmentShaderCoder = ShaderHelper.loadAsset(context.getResources(), "particle_fragment_shader.glsl"); this.program = ShaderHelper.loadProgram(vertexShaderCoder,fragmentShaderCoder); / / to get uniform and the location of the attribute uTimeLocation = GLES20. GlGetUniformLocation (program, U_TIME); aPositionLocation = GLES20.glGetAttribLocation(program,A_POSITION); aColorLocation = GLES20.glGetAttribLocation(program,A_COLOR); aDirectionLocation = GLES20.glGetAttribLocation(program,A_DIRECTION); aPatricleStartTimeLocation = GLES20.glGetAttribLocation(program,A_PATRICLE_START_TIME); Public void setimaginative (float curTime){public void setimaginative (float curTime){ GLES20.glUniform1f(uTimeLocation,curTime); } public int getProgram() { return program; } public int getaPositionLocation() { return aPositionLocation; } public int getaColorLocation() { return aColorLocation; } public int getaDirectionLocation() { return aDirectionLocation; } public int getaPatricleStartTimeLocation() { return aPatricleStartTimeLocation; } public void useProgram(){ GLES20.glUseProgram(program); }}Copy the code

Write Render to load and Render

public class ParticlesRender implements GLSurfaceView.Renderer { private final Context mContext; private ParticleShaderProgram mProgram; private ParticleSystem mParticleSystem; private long mSystemStartTimeNS; private ParticleShooter mParticleShooter; public ParticlesRender(Context context) { this.mContext = context; } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glClearColor(0f,0f,0f,0f); mProgram = new ParticleShaderProgram(mContext); MParticleSystem = new ParticleSystem(10000); mParticleSystem = new ParticleSystem(10000); // mSystemStartTimeNS = system.nanotime (); // Define mParticleShooter = new ParticleShooter(new Geometry.Point(0f, -0.9f, 0f), color.rgb (255, 50, 5), The new Geometry. The Vector (0 f, f 0.3, 0 f)); } @override public void onSurfaceChanged(GL10 gl, int width, int height) {gles20. glViewport(0,0,width,height); } @Override public void onDrawFrame(GL10 gl) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); Float curTime = (system.nanotime () -mSystemStartTimens)/1000000000f; float curTime = (system.nanotime () -mSystemStartTimens)/1000000000f; / / particle generator added particle mParticleShooter. AddParticles (mParticleSystem, curTime); // useProgram mprogram.useprogram (); // Open Uniform variable mprogram.setimaginative (curTime); // Set the attribute variable mParticleSystem. BindData (mProgram); // Start drawing the particle mparticlesystem.draw (); }} public class ParticleSystem {... public void bindData(ParticleShaderProgram program) { int dataOffset = 0; vertexArray.setVertexAttributePointer(dataOffset, program.getaPositionLocation(), POSITION_COMPONENT_COUNT, STRIDE); dataOffset +=POSITION_COMPONENT_COUNT; vertexArray.setVertexAttributePointer(dataOffset, program.getaColorLocation(), COLOR_COMPONENT_COUNT, STRIDE); dataOffset +=COLOR_COMPONENT_COUNT; vertexArray.setVertexAttributePointer(dataOffset, program.getaDirectionLocation(), VECTOR_COMPONENT_COUNT, STRIDE); dataOffset +=VECTOR_COMPONENT_COUNT; vertexArray.setVertexAttributePointer(dataOffset, program.getaPatricleStartTimeLocation(), PARTICLE_START_TIME_COMPONENT_COUNT, STRIDE); } public void draw() { GLES20.glDrawArrays(GLES20.GL_POINTS,0,curParticleCount); }} VertexArray setVertexAttributePointer add method used to vertex shader attributes of the Attribute variable assignment public class VertexArray {... public void setVertexAttributePointer(int dataOffset, int location, int count, int stride) { floatBuffer.position(dataOffset); GLES20.glVertexAttribPointer(location,count,GLES20.GL_FLOAT,false,stride,floatBuffer); GLES20.glEnableVertexAttribArray(location); floatBuffer.position(0); }}Copy the code

Use Render in GLSurfaceView

public class ParticleActivity extends Activity{ private GLSurfaceView glSurfaceView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_particle_layout); glSurfaceView = findViewById(R.id.glSurfaceView); glSurfaceView.setEGLContextClientVersion(2); glSurfaceView.setRenderer(new ParticlesRender(this)); } @Override protected void onResume() { super.onResume(); glSurfaceView.onResume(); } @Override protected void onPause() { super.onPause(); glSurfaceView.onPause(); }}Copy the code

Results the following

Don’t worry. As long as you are on the right path, analyze and solve the problems you encounter. The most important thing is to keep moving forward.

Three, problem,

You can see the following problems

  1. There is no change in direction of particle emission
  2. The overlap between the new launch and the falling

Let’s solve them one by one.

Problem 1. Randomly changing the direction of the emitted particles

The direction of the particle emission is determined by the emitter, and we now have a fixed Vector sending up (0f, 0.3F, 0F).

MParticleShooter = new ParticleShooter(new Geometry.Point(0f, -0.9f, 0f), color.rgb (255, 50, 5), The new Geometry. The Vector (0 f, f 0.3, 0 f)); public class ParticleShooter { ... public void addParticles(ParticleSystem particleSystem, float currentTime) { particleSystem.addParticle(position, color, direction, currentTime); }... }Copy the code

So if we want to change this to a random direction, we need to change this to a random direction and we do random directions with random numbers and matrix transformations

public class ParticleShooter { ... private float[] rotationMatrix = new float[16]; private final Random random = new Random(); final float angleVarianceInDegrees = 20f; public void addParticles(ParticleSystem particleSystem, float currentTime) { Matrix.setRotateEulerM(rotationMatrix, 0, (random.nextfloat ()-0.5f) * angleVarianceInDegrees, (random.nextfloat ()-0.5f) * angleVarianceInDegrees, (random. NextFloat () to 0.5 f) * angleVarianceInDegrees); float[] tmpDirectionFloat = new float[4]; Matrix.multiplyMV(tmpDirectionFloat,0, rotationMatrix,0, new float[]{direction.x,direction.y,direction.z,1f},0); Geometry.Vector newDirection = new Geometry.Vector(tmpDirectionFloat[0], tmpDirectionFloat[1], tmpDirectionFloat[2]); particleSystem.addParticle(position, color, newDirection, currentTime); }... }Copy the code

The effect is as follows:

Problem 2. Overlapping coverage

By changing the direction of the particles, it looks like the overlap is gone, is that true, or is it hard to see?

To test the problem we add particles at the same time, a particle system that sends multiple batches of particles at the same time. It’s easy to change, just add a for loop.

public void addParticles(ParticleSystem particleSystem, float currentTime, int count) { for (int i = 0; i < count; I++) {matrix.setrotateeulerm (rotationMatrix, 0, (random.nextfloat () -0.5f) * angleVarianceInDegrees, (random.nextFloat() -0.5f) * angleVarianceInDegrees, (random.nextfloat () -0.5f) * angleVarianceInDegrees); float[] tmpDirectionFloat = new float[4]; Matrix.multiplyMV(tmpDirectionFloat, 0, rotationMatrix, 0, new float[]{direction.x, direction.y, direction.z, 1f}, 0); Geometry.Vector newDirection = new Geometry.Vector(tmpDirectionFloat[0], tmpDirectionFloat[1], tmpDirectionFloat[2]); particleSystem.addParticle(position, color, newDirection, currentTime); }}Copy the code

If we pass 20 to count, it looks like this

You can see the overlap again. Why is that?

At the same time, at the same position, there are both falling particles and rising particles. If the falling particles are rendered, the rising particles will be overwritten.

So how to solve it?

OpenGL provides the additive blending technique GL_BLEND_, with the following formula

Output = (source factor * source fragment) + (target factor * target fragment)Copy the code

The onSurfaceCrate is set in Render’s onSurfaceCrate

@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glClearColor(0f,0f,0f,0f); GLES20.glEnable(GLES20.GL_BLEND); Gles20.gl_one * source fragment) + (gles20.gl_one * target fragment) gles20.glblendFunc (gles20.gl_one, gles20.gl_one); . }Copy the code

The effect is as follows:

You can see that there is no overlap anymore.

Another problem is that each particle is a point, size set to 10 by gl_PointSize, and you see square particles. Can you change it to a circle or specify a style? _

Problem 3. Change point to texture image

Next we will use the texture image to draw each point as a point Sprite. If you are not familiar with the use of texture, please read the OpenGL ES texture. First modify the pixel shader, add 2D texture

precision mediump float; uniform sampler2D u_TextureUnit; varying vec3 v_Color; varying float v_ElapsedTime; Void main(){// Particle color changes with color //gl_FragColor = vec4(v_Color/v_ElapsedTime, 1.0); / / changed to: Gl_FragColor = VEC4 (v_Color/v_ElapsedTime, 1.0) * texture2D(u_TextureUnit, gl_PointCoord); }Copy the code

Then modify Program, parse sample2D, and assign

public class ParticleShaderProgram { ... private final String U_TEXTURE_UNIT ="u_TextureUnit"; private final int uTextureUnit; public ParticleShaderProgram(Context context) { ... uTextureUnit = GLES20.glGetUniformLocation(program,U_TEXTURE_UNIT); . } public void setUniforms(float curTime, int textureId){ GLES20.glUniform1f(uTimeLocation,curTime); GlActiveTexture (gles20.gl_texture0); Gles20.glbindtexture (gles20.gl_texture_2d,textureId); / / assignment GLES20. GlUniform1i (uTextureUnit, 0); }... }Copy the code

Finally, define textureId in Render and pass the value in onDrawFrame

public class ParticlesRender { ... private int mTextureId; public void onSurfaceCreated(GL10 gl, EGLConfig config) { ... mTextureId = TextureHelper.loadTexture(mContext, R.drawable.particle_texture); . @Override public void onDrawFrame(GL10 gl) { ... Open Uniform mProgram.setuniforms (curTime,mTextureId); . }}... }Copy the code

The effect is as follows:

Finally we modify the point size and motion vector defined in the vertex shader

Gl_PointSize = 25.0; // Define mParticleShooter = new ParticleShooter(new Geometry.Point(0f, -0.9f, 0f), color.rgb (255, 50, 5), The new Geometry. The Vector (0 f, f 0.8, 0 f));Copy the code

That’s what we showed at the beginning

This article is here, through practice, step by step to achieve the final fountain effect. In the next article, we will continue to learn about practical particle systems to achieve the effect of fireworks in the air.

Four, data

OpenGL ES 3.0 Programming Guide OpenGL ES Application Development Practice Guide

[OpenGL] Shader example analysis (7)- Snow falling effect] [OpenGL] Shader example analysis (7)- snow falling effect

Five, the harvest

  1. Understand particle properties and particle systems
  2. Achieve fountain effect step by step through mission disassembly
  3. Solve problems encountered (gravity, launch direction, overlay, point sprites)

Thank you for reading.

In the next article, we will continue to learn about practical particle systems to achieve the effect of fireworks in the air. Welcome to pay attention to the public account “audio and video development journey”, learn and grow together.

Welcome to communicate

The code has been synchronized to Github

The original link