Last active
May 25, 2018 21:43
-
-
Save ma77os/8fdf96b69b140b896e58daa2831a0388 to your computer and use it in GitHub Desktop.
Particles_compute
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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