Skip to content

Instantly share code, notes, and snippets.

@ma77os
Last active May 25, 2018 21:43
Show Gist options
  • Save ma77os/8fdf96b69b140b896e58daa2831a0388 to your computer and use it in GitHub Desktop.
Save ma77os/8fdf96b69b140b896e58daa2831a0388 to your computer and use it in GitHub Desktop.
Particles_compute
/*
Particle system using a compute shader
Author: Tim Gerritsen <[email protected]> - Go Robot
Date: December 2017
Updated: Februari 2018, added comments
I tried to add as much comments as possible. Hope everything is clear.
If not, try to contact me through touchdesigner slack #glsl, or email.
*/
/* Setting the local size to 16x16, so in total this shader is ran 256 times the dispatch size */
layout (local_size_x = 16, local_size_y = 16) in;
uniform float uTime; // Current time in seconds (python: absTime.seconds) to generate random numbers
uniform float uFpsTick; // Length of a frame in seconds (1/fps)
uniform int uReset; // Compute shader will reset all output filters when set to 1
uniform int uParticles; // Amount of total particles (dispatch size * local size)
uniform vec4 uPositionSizeStart; // Random range start position xyz and start size
uniform vec4 uPositionSizeEnd; // Random range end position xyz and end size
uniform vec4 uDirectionSpeedStart; // Random range start direction xyz and start speed
uniform vec4 uDirectionSpeedEnd; // Random range end direction xyz and end speed
uniform vec2 uParticleLife; // Random range start and end lifetime
uniform float uSizeOverLife; // Alter size over lifetime. When negative decrease the size
uniform float uSpeed; // Overall speed
uniform vec2 uGravity; // Amount of gravity direction force and acceleration
uniform vec3 uCollider; // Collider
uniform vec2 uNoise; // Amount of noise force amplitude and frequency
uniform float uFloor; // Floor y position
// Data pixel offsets
#define DATA_LIFE 0
#define DATA_POSITION 1
#define DATA_VELOCITY 2
#define DATA_COLOR 3
// Particle states
#define PARTICLE_STATE_DEAD 0
#define PARTICLE_STATE_ALIVE 1
// Texture inputs
#define TEX_COLOR_LIFERANDOM 0
// Math stuff
#define PI 3.1415926536
// Defining a particle
struct Particle {
int index; // Index of the particle (0 to uParticles)
float lifetime; // Current particle lifetime in seconds
float maxlife; // Initial particle lifetime in seconds
int state; // Current particle state (defined above)
vec3 position; // Particle position
float size; // Particle size
vec3 direction; // The direction the particle is going
float speed; // And it's speed
vec4 color; // Color of the particle defined by the input texture
};
// Global variables
int gDataSize = 4; // This size represents the amount of vec4 data types 'fit' in the above struct.
// So 2 ints and 2 float can be merged together as 1 vec4. (so we can save it as color)
Particle gEmptyParticle = Particle(0,0,0,0, vec3(0),0, vec3(0),0, vec4(0)); // Defining an empty particle
Particle gParticle; // This will be set to the current particle
// This generates a 'random' number between 0 and 1
float Hash(vec2 p) { return fract(mod(sin(dot(p,vec2(12.9898,78.233))) * 43758.5453, PI)); }
// Helper function to transform a particle index and offset to pixel coordinate
// Offset is used to determine which 'vec4' we are fetching from the struct.
ivec2 IndexToXY(int index, int offset)
{
index = index * gDataSize + offset;
return ivec2(index % int(uTDOutputInfo.res.z), floor(index / uTDOutputInfo.res.z));
}
// Helper function to retrieve a color of the texture input
vec4 Tex(int type, vec2 uv)
{
return texture(sTD2DInputs[type], uv);
}
// Reads from output buffer. This will fetch the last frame that was rendered.
vec4 TexRead(int index, int offset)
{
return imageLoad(sTDComputeOutputs[0], IndexToXY(index, offset));
}
// Writes a certain pixel of information to the output buffer
void TexWrite(int index, int offset, vec4 data)
{
imageStore(sTDComputeOutputs[0], IndexToXY(index, offset), data);
}
// Reads the full particle struct and sets the global variable
void Read(int index)
{
vec4 life = TexRead(index, DATA_LIFE); // Reads the life data (index, lifetime, maxlife and state)
gParticle.index = index;
gParticle.lifetime = life.x;
gParticle.maxlife = life.y;
gParticle.state = int(life.z);
vec4 position = TexRead(index, DATA_POSITION); // Position data xyz, size
gParticle.position = position.xyz;
gParticle.size = position.w;
vec4 velocity = TexRead(index, DATA_VELOCITY); // Velocity xyz, speed
gParticle.direction = velocity.xyz;
gParticle.speed = velocity.w;
gParticle.color = TexRead(index, DATA_COLOR); // Color rgba
}
// This functions creates a new particle. The function will be called when a particle is dead.
void Birth()
{
vec2 xy = vec2(gParticle.index, uTime); // Creates a random seed per particle using the index and time
gParticle.maxlife = mix(uParticleLife.x, uParticleLife.y, Hash(xy)); // Find a random life span
gParticle.lifetime = gParticle.maxlife; // Sets the current lifetime to the maximum life of this particle
gParticle.state = PARTICLE_STATE_ALIVE; // Reincarnate the particle
{
vec4 rnd = vec4(Hash(xy+1), Hash(xy+2), Hash(xy+3), Hash(xy+4)); // Adding an offset to make sure we can random numbers
vec4 data = mix(uPositionSizeStart , uPositionSizeEnd, rnd); // Find a random position and size
gParticle.position = data.xyz;
gParticle.size = data.w;
}
{
vec4 rnd = vec4(Hash(xy+5), Hash(xy+6), Hash(xy+7), Hash(xy+8));
vec4 data = mix(uDirectionSpeedStart, uDirectionSpeedEnd, rnd); // Find a random velocity and speed
gParticle.direction = normalize(data.xyz);
gParticle.speed = data.w;
}
// Fetching the color of the particle. The uv position represents lifetime (x) and randomness (y)
gParticle.color = Tex(TEX_COLOR_LIFERANDOM, vec2(0, Hash(vec2(gParticle.index))));
}
// Kill the particle
void Death()
{
gParticle.state = PARTICLE_STATE_DEAD;
}
// Add some gravity to the scene
void AddGravity()
{
gParticle.direction = mix(gParticle.direction, vec3(0,-1,0), uGravity.x); // Updates the direction slightly downwards every frame
gParticle.speed += max(0, dot(gParticle.direction, vec3(0,-1,0))) * uGravity.y * 0.01; // Add a bit of speed when pointing downwards
}
// Adding some noise to the direction
void AddNoise()
{
gParticle.direction += vec3(TDSimplexNoise(gParticle.position*uNoise.y), TDSimplexNoise(gParticle.position*uNoise.y + 1), TDSimplexNoise(gParticle.position*uNoise.y + 2))*uNoise.x;
}
// The main update function. This function updates the position, direction and lifetime.
void Update()
{
if (gParticle.state == PARTICLE_STATE_ALIVE) { // When it's alive
AddGravity(); // Add some gravity
AddNoise(); // Add some noise
float life = 1-(gParticle.lifetime/gParticle.maxlife); // Calculate the life fraction (0 to 1)
// collision with floor
if(gParticle.position.y <= uFloor){
gParticle.position.y = uFloor + 0.1;
float xPos = Hash(gParticle.position.xy) * 0.5 - 0.25;
float yPos = Hash(gParticle.position.yz) * 0.7;
float zPos = Hash(gParticle.position.zx) * 1 - 0.5;
gParticle.direction += vec3(xPos,yPos,zPos);
}
// Update the position using the current direction and it's speed
gParticle.position += normalize(gParticle.direction) * gParticle.speed * uSpeed;
// circle collider
float dist = distance(gParticle.position, uCollider);
if(dist < 0.25){
float xPos2 = Hash(gParticle.position.xy) * .4 - .2;
float yPos2 = Hash(gParticle.position.yz) * 0.2;
// float zPos2 = Hash(gParticle.position.zx) * 2 - 1;
float zPos2 = 0;
gParticle.direction += vec3(xPos2,yPos2,zPos2);
gParticle.speed *= 0.93;
}
gParticle.lifetime -= uFpsTick * uSpeed; // Decrease it's current lifetime per frame
gParticle.size = gParticle.size + uSizeOverLife * life * uSpeed; // Increase or decrease size over time
// Fetch the new color (x according to the current life time fraction)
gParticle.color = Tex(TEX_COLOR_LIFERANDOM, vec2(life, Hash(vec2(gParticle.index))));
// Check we it's time to die
if (gParticle.lifetime < 0) {
gParticle.state = PARTICLE_STATE_DEAD; // Sets the state of the particle to dead. So the next time it will reincarnate
}
} else if (gParticle.state == PARTICLE_STATE_DEAD) { // When it's dead
Birth(); // Give birth to a new particle
}
}
// Writes the current global particle struct to the output buffer
void Write()
{
TexWrite(gParticle.index, DATA_LIFE, vec4(gParticle.lifetime, gParticle.maxlife, gParticle.state, 0));
TexWrite(gParticle.index, DATA_POSITION, vec4(gParticle.position, gParticle.size));
TexWrite(gParticle.index, DATA_VELOCITY, vec4(gParticle.direction, gParticle.speed));
TexWrite(gParticle.index, DATA_COLOR, gParticle.color);
}
void main()
{
// Calculate the current index. (x + y * maximum width)
int index = int(gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x);
if (uReset > 0) { // Reset the output buffer
imageStore(sTDComputeOutputs[0], ivec2(0), vec4(0));
for (int i = 0; i < gDataSize; i++) { // Make sure all vec4's are set to 0
imageStore(sTDComputeOutputs[0], IndexToXY(index, i), vec4(0));
}
return;
} else if (index < uParticles) { // Make sure we don't exceed our maximum amount of particles
Read(index); // First read the particle from the last output buffer
Update(); // Then update it's position and direction
Write(); // And write it back to the output buffer
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment