So for the past day i’ve been developing an efficient particle generator that looks very nice. Current performance tests are for 600k particles spawned at 500fps. The main advantage here is that the processing of the particles is moved entirely onto the gpu the only part that is’nt is the creation and destruction of the entire handler object.
Here is an example using a single spawn point for the data:
And here is one with multiple spawn locations and different decay values for each
Here is the code for the system:
Particle.java
package com.lcass.graphics;
import java.util.Random;
import com.lcass.core.Core;
import com.lcass.graphics.texture.spritecomponent;
public class Particles {
public Core core;
public Random random;
private particle_spawn[] particle_spawns;
public Particles(Core core, int max_effect) {
this.core = core;
random = new Random();
random.setSeed(System.nanoTime());
particle_spawns = new particle_spawn[max_effect];
}
public void tick() {
for (particle_spawn a : particle_spawns) {
if (a != null) {
a.tick();
}
}
}
public void render() {
for (particle_spawn a : particle_spawns) {
if (a != null) {
a.render();
}
}
}
public void create_spawn(Vertex2d data, spritecomponent s, int amount,
float decay, int size) {
for (int i = 0; i < particle_spawns.length; i++) {
if (particle_spawns[i] == null) {
particle_spawns[i] = new particle_spawn(this, data, s, decay,
size, amount, i,1,1);
return;
}
}
}
public void create_spawn(Vertex2d data, spritecomponent s, int amount,
float decay, int size,int wait) {
for (int i = 0; i < particle_spawns.length; i++) {
if (particle_spawns[i] == null) {
particle_spawns[i] = new particle_spawn(this, data, s, decay,
size, amount, i,wait,1);
return;
}
}
}
public void create_spawn(Vertex2d data, spritecomponent s, int amount,
float decay, int size,int wait,float velocity) {
for (int i = 0; i < particle_spawns.length; i++) {
if (particle_spawns[i] == null) {
particle_spawns[i] = new particle_spawn(this, data, s, decay,
size, amount, i,wait,velocity);
return;
}
}
}
public void remove(int location) {// called by spawns ONLY otherwise you
// have a memory leak
particle_spawns[location] = null;
}
public void clear() {
for (particle_spawn a : particle_spawns) {
if (a != null) {
a.clean_kill();
}
}
}
}
particle_spawn.java
this is the carrier object
package com.lcass.graphics;
import com.lcass.graphics.texture.spritecomponent;
import com.lcass.util.Progressive_buffer;
public class particle_spawn {
public VBO particles;
public float decay, wait;
public int location = 0;
private Particles particle;
// As noted in the shader this is set for the preference of an 8 by 8 effect
// grid so each effect with 1/8th the width and 1/8th the height of the
// entire texture
public particle_spawn(Particles particle, Vertex2d data, spritecomponent s,
float decay, int size, int amount, int location, int wait, float velocity) {// location
// is
// done
// with
// a
// recursive
// nature,
// the
// particle
// kills
// itself
this.decay = decay;
this.particle = particle;
particles = new VBO(particle.core.G.mainvbo);
particles.set_decay(decay);
particles.set_spawn(particle.core.G.convert_coordinates(data));
particles.set_point();
particles.set_size(size);
particles.bind_texture(s.t);
particles.set_velocity(velocity);
velocity = velocity * 4;
this.wait = wait;
Vertex2d[] temp = new Vertex2d[amount];
for (int i = 0; i < amount; i++) {
temp[i] = particle.core.G
.convert_coordinates(new Vertex2d(
(float) ((particle.random
.nextInt((int)velocity) - (velocity/2))
+ data.x + ((particle.random.nextInt((int)velocity) - (velocity/2)) * Math
.sin(i))),
(float) ((particle.random
.nextInt((int)velocity) - (velocity/2))
+ data.y + ((particle.random.nextInt((int)velocity) - (velocity/2)) * Math
.cos(i)))));
temp[i].u = s.x;
temp[i].v = s.y;
}
particles.create_point(temp);
}
public void tick() {
if (!is_alive()) {
kill();
particle.remove(location);// it kills itself.
}
}
public boolean is_alive() {
if(particles == null){
return false;
}
if (decay + (wait * decay) > particles.get_render_ticks()) {
return true;
} else {
return false;
}
}
public void kill() {
if(particles != null){
particles.dispose();
particles = null;
}
}
public void clean_kill() {
if (particles != null) {
particles.dispose();
particles = null;
}
particle.remove(location);
}
public void render() {
if (particles != null) {
particles.render();
}
}
}
VBO.create_point , it requires specific values sent to the shader it does this by binding them to x y z and w respectively in a vec4 point passed to the vertex shader
public void create_point(Vertex2d[] coordinates) {
vertex = BufferUtils.createFloatBuffer(coordinates.length * 4);
texture = BufferUtils.createFloatBuffer(coordinates.length * 2);
vertcount = coordinates.length;
for (int i = 0; i < coordinates.length; i++) {
vertex.put(coordinates[i].x);
vertex.put(coordinates[i].y);
vertex.put(r.nextFloat());
vertex.put(i);
texture.put(coordinates[i].u);
texture.put(coordinates[i].v);
}
vertid = GL15.glGenBuffers();
texcoordid = GL15.glGenBuffers();
vertex.rewind();
texture.rewind();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertid);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertex, GL15.GL_DYNAMIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, texcoordid);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, texture, GL15.GL_DYNAMIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
Vertex shader
uniform float alpha;
uniform float time;
uniform vec2 spawn;
uniform float decay;
uniform float velocity = 1;
uniform vec2 dimension;
uniform float size;
attribute vec4 vecin;
attribute vec2 texin;
void main(){
float x_move,y_move,negate_x,negate_y;
x_move = ((time * (vecin.x - spawn.x))) * velocity * vecin.z;
y_move = ((time * (vecin.y - spawn.y))) * velocity * vecin.z;
x_move/= 10000;
y_move/= 10000;
gl_Position = vec4(vecin.x + x_move,vecin.y + y_move,texin.x,texin.y);
gl_PointSize = vecin.w;
}
Geometry shader
#version 330
//uses an 8 by 8 spritesheet make sure you use it!
layout (points) in;
layout (triangle_strip) out;
layout (max_vertices = 4) out;
uniform vec2 dimension;
uniform float size;
uniform float time;
uniform float decay;
uniform vec2 spawn;
out vec2 texcoord;
void main(){
if(gl_in[0].gl_PointSize * (time/decay) < decay * 30){
vec2 size_adjust = vec2(size/dimension.x,size/dimension.y);
float eigth = (1/8);
gl_Position = vec4(gl_in[0].gl_Position.x,gl_in[0].gl_Position.y,1,1);
texcoord = vec2(gl_in[0].gl_Position.z,gl_in[0].gl_Position.w + eigth);
EmitVertex();
gl_Position = vec4(gl_in[0].gl_Position.x,gl_in[0].gl_Position.y + size_adjust.y,1,1);
texcoord = vec2(gl_in[0].gl_Position.z,gl_in[0].gl_Position.w);
EmitVertex();
gl_Position = vec4(gl_in[0].gl_Position.x + size_adjust.x,gl_in[0].gl_Position.y,1,1);
texcoord = vec2(gl_in[0].gl_Position.z + (1/8),gl_in[0].gl_Position.w + (1/8));
EmitVertex();
gl_Position = vec4(gl_in[0].gl_Position.x + size_adjust.x,gl_in[0].gl_Position.y + size_adjust.y,1,1);
texcoord = vec2(gl_in[0].gl_Position.z + eigth,gl_in[0].gl_Position.w);
EmitVertex();
EndPrimitive();
}
}
Fragment shader
this is fairly basic this one
uniform sampler2D texture;
uniform float alpha;
in vec2 texcoord;
void main(){
gl_FragColor = vec4(texture2D(texture,texcoord).rgb,alpha);
}
Feel free to use it and if you need any help with how to use it I will gladly help you out other than that happy particling.