This tutorial assumes you are already familiar with the material in Shader Toy Tutorial - Glow.
In this tutorial,
we take a look at adding animated particles. These particles can be used for an explosion
effect.
The “trick” to
this example, is the use of pseudo-random numbers to generate each particle’s angle and speed from
the initial explosion point. Why “pseudo-random”? This allows each processor on the GPU
to independently calculate each particle’s position at any point and time. We can then
allow the GPU to calculate in parallel.
First, we need a program that will load a shader. This program is also keeping track
of how much time has elapsed. This is necessary for us to calculate how far along the animation
sequence we are.
importarcadefromarcade.experimentalimportShadertoy# Derive an application window from Arcade's parent Window classclassMyGame(arcade.Window):def__init__(self):# Call the parent constructorsuper().__init__(width=1920,height=1080)# Used to track run-timeself.time=0.0# Load a file and create a shader from itfile_name="explosion.glsl"self.shadertoy=Shadertoy(size=self.get_size(),main_source=open(file_name).read())defon_draw(self):self.clear()# Set uniform data to send to the GLSL shaderself.shadertoy.program['pos']=self.mouse["x"],self.mouse["y"]# Run the GLSL codeself.shadertoy.render(time=self.time)defon_update(self,delta_time:float):# Track run timeself.time+=delta_timeif__name__=="__main__":window=MyGame()window.center_window()arcade.run()
// Origin of the particlesuniformvec2pos;// Constants// Number of particlesconstfloatPARTICLE_COUNT=100.0;// Max distance the particle can be from the position.// Normalized. (So, 0.3 is 30% of the screen.)constfloatMAX_PARTICLE_DISTANCE=0.3;// Size of each particle. Normalized.constfloatPARTICLE_SIZE=0.004;constfloatTWOPI=6.2832;// This function will return two pseudo-random numbers given an input seed.// The result is in polar coordinates, to make the points random in a circle// rather than a rectangle.vec2Hash12_Polar(floatt){floatangle=fract(sin(t*674.3)*453.2)*TWOPI;floatdistance=fract(sin((t+angle)*724.3)*341.2);returnvec2(sin(angle),cos(angle))*distance;}voidmainImage(outvec4fragColor,invec2fragCoord){// Normalized pixel coordinates (from 0 to 1)// Origin of the particlesvec2npos=(pos-.5*iResolution.xy)/iResolution.y;// Position of current pixel we are drawingvec2uv=(fragCoord-.5*iResolution.xy)/iResolution.y;// Re-center based on input coordinates, rather than origin.uv-=npos;// Default alpha is transparent.floatalpha=0.0;// Loop for each particlefor(floati=0.;i<PARTICLE_COUNT;i++){// Direction of particle + speedfloatseed=i+1.0;vec2dir=Hash12_Polar(seed);// Get position based on direction, magnitude, and explosion sizevec2particlePosition=dir*MAX_PARTICLE_DISTANCE;// Distance of this pixel from that particlefloatd=length(uv-particlePosition);// If we are within the particle size, set alpha to 1.0if(d<PARTICLE_SIZE)alpha=1.0;}// Output to screenfragColor=vec4(1.0,1.0,1.0,alpha);}
// Origin of the particlesuniformvec2pos;// Constants// Number of particlesconstfloatPARTICLE_COUNT=100.0;// Max distance the particle can be from the position.// Normalized. (So, 0.3 is 30% of the screen.)constfloatMAX_PARTICLE_DISTANCE=0.3;// Size of each particle. Normalized.constfloatPARTICLE_SIZE=0.004;// Time for each burst cycle, in seconds.constfloatBURST_TIME=2.0;constfloatTWOPI=6.2832;// This function will return two pseudo-random numbers given an input seed.// The result is in polar coordinates, to make the points random in a circle// rather than a rectangle.vec2Hash12_Polar(floatt){floatangle=fract(sin(t*674.3)*453.2)*TWOPI;floatdistance=fract(sin((t+angle)*724.3)*341.2);returnvec2(sin(angle),cos(angle))*distance;}voidmainImage(outvec4fragColor,invec2fragCoord){// Normalized pixel coordinates (from 0 to 1)// Origin of the particlesvec2npos=(pos-.5*iResolution.xy)/iResolution.y;// Position of current pixel we are drawingvec2uv=(fragCoord-.5*iResolution.xy)/iResolution.y;// Re-center based on input coordinates, rather than origin.uv-=npos;// Default alpha is transparent.floatalpha=0.0;// 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.// Auto resets if time goes beyond burst time. This causes the explosion to cycle.floattimeFract=fract(iTime*1/BURST_TIME);// Loop for each particlefor(floati=0.;i<PARTICLE_COUNT;i++){// Direction of particle + speedfloatseed=i+1.0;vec2dir=Hash12_Polar(seed);// Get position based on direction, magnitude, and explosion size// Adjust based on time scale. (0.0-1.0)vec2particlePosition=dir*MAX_PARTICLE_DISTANCE*timeFract;// Distance of this pixel from that particlefloatd=length(uv-particlePosition);// If we are within the particle size, set alpha to 1.0if(d<PARTICLE_SIZE)alpha=1.0;}// Output to screenfragColor=vec4(1.0,1.0,1.0,alpha);}
// Origin of the particlesuniformvec2pos;// Constants// Number of particlesconstfloatPARTICLE_COUNT=100.0;// Max distance the particle can be from the position.// Normalized. (So, 0.3 is 30% of the screen.)constfloatMAX_PARTICLE_DISTANCE=0.3;// Size of each particle. Normalized.constfloatPARTICLE_SIZE=0.004;// Time for each burst cycle, in seconds.constfloatBURST_TIME=2.0;constfloatTWOPI=6.2832;// This function will return two pseudo-random numbers given an input seed.// The result is in polar coordinates, to make the points random in a circle// rather than a rectangle.vec2Hash12_Polar(floatt){floatangle=fract(sin(t*674.3)*453.2)*TWOPI;floatdistance=fract(sin((t+angle)*724.3)*341.2);returnvec2(sin(angle),cos(angle))*distance;}voidmainImage(outvec4fragColor,invec2fragCoord){// Normalized pixel coordinates (from 0 to 1)// Origin of the particlesvec2npos=(pos-.5*iResolution.xy)/iResolution.y;// Position of current pixel we are drawingvec2uv=(fragCoord-.5*iResolution.xy)/iResolution.y;// Re-center based on input coordinates, rather than origin.uv-=npos;// Default alpha is transparent.floatalpha=0.0;// 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.// Auto resets if time goes beyond burst time. This causes the explosion to cycle.floattimeFract=fract(iTime*1/BURST_TIME);// Loop for each particlefor(floati=0.;i<PARTICLE_COUNT;i++){// Direction of particle + speedfloatseed=i+1.0;vec2dir=Hash12_Polar(seed);// Get position based on direction, magnitude, and explosion size// Adjust based on time scale. (0.0-1.0)vec2particlePosition=dir*MAX_PARTICLE_DISTANCE*timeFract;// Distance of this pixel from that particlefloatd=length(uv-particlePosition);// If we are within the particle size, set alpha to 1.0if(d<PARTICLE_SIZE)alpha=1.0;}// Output to screenfragColor=vec4(1.0,1.0,1.0,alpha*(1.0-timeFract));}
// Origin of the particlesuniformvec2pos;// Constants// Number of particlesconstfloatPARTICLE_COUNT=100.0;// Max distance the particle can be from the position.// Normalized. (So, 0.3 is 30% of the screen.)constfloatMAX_PARTICLE_DISTANCE=0.3;// Size of each particle. Normalized.constfloatPARTICLE_SIZE=0.004;// Time for each burst cycle, in seconds.constfloatBURST_TIME=2.0;// Particle brightnessconstfloatDEFAULT_BRIGHTNESS=0.0005;constfloatTWOPI=6.2832;// This function will return two pseudo-random numbers given an input seed.// The result is in polar coordinates, to make the points random in a circle// rather than a rectangle.vec2Hash12_Polar(floatt){floatangle=fract(sin(t*674.3)*453.2)*TWOPI;floatdistance=fract(sin((t+angle)*724.3)*341.2);returnvec2(sin(angle),cos(angle))*distance;}voidmainImage(outvec4fragColor,invec2fragCoord){// Normalized pixel coordinates (from 0 to 1)// Origin of the particlesvec2npos=(pos-.5*iResolution.xy)/iResolution.y;// Position of current pixel we are drawingvec2uv=(fragCoord-.5*iResolution.xy)/iResolution.y;// Re-center based on input coordinates, rather than origin.uv-=npos;// Default alpha is transparent.floatalpha=0.0;// 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.// Auto resets if time goes beyond burst time. This causes the explosion to cycle.floattimeFract=fract(iTime*1/BURST_TIME);// Loop for each particlefor(floati=0.;i<PARTICLE_COUNT;i++){// Direction of particle + speedfloatseed=i+1.0;vec2dir=Hash12_Polar(seed);// Get position based on direction, magnitude, and explosion size// Adjust based on time scale. (0.0-1.0)vec2particlePosition=dir*MAX_PARTICLE_DISTANCE*timeFract;// Distance of this pixel from that particlefloatd=length(uv-particlePosition);// Add glow based on distancealpha+=DEFAULT_BRIGHTNESS/d;}// Output to screenfragColor=vec4(1.0,1.0,1.0,alpha*(1.0-timeFract));}
// Origin of the particlesuniformvec2pos;// Constants// Number of particlesconstfloatPARTICLE_COUNT=100.0;// Max distance the particle can be from the position.// Normalized. (So, 0.3 is 30% of the screen.)constfloatMAX_PARTICLE_DISTANCE=0.3;// Size of each particle. Normalized.constfloatPARTICLE_SIZE=0.004;// Time for each burst cycle, in seconds.constfloatBURST_TIME=2.0;// Particle brightnessconstfloatDEFAULT_BRIGHTNESS=0.0005;// How many times to the particles twinkleconstfloatTWINKLE_SPEED=10.0;constfloatTWOPI=6.2832;// This function will return two pseudo-random numbers given an input seed.// The result is in polar coordinates, to make the points random in a circle// rather than a rectangle.vec2Hash12_Polar(floatt){floatangle=fract(sin(t*674.3)*453.2)*TWOPI;floatdistance=fract(sin((t+angle)*724.3)*341.2);returnvec2(sin(angle),cos(angle))*distance;}voidmainImage(outvec4fragColor,invec2fragCoord){// Normalized pixel coordinates (from 0 to 1)// Origin of the particlesvec2npos=(pos-.5*iResolution.xy)/iResolution.y;// Position of current pixel we are drawingvec2uv=(fragCoord-.5*iResolution.xy)/iResolution.y;// Re-center based on input coordinates, rather than origin.uv-=npos;// Default alpha is transparent.floatalpha=0.0;// 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.// Auto resets if time goes beyond burst time. This causes the explosion to cycle.floattimeFract=fract(iTime*1/BURST_TIME);// Loop for each particlefor(floati=0.;i<PARTICLE_COUNT;i++){// Direction of particle + speedfloatseed=i+1.0;vec2dir=Hash12_Polar(seed);// Get position based on direction, magnitude, and explosion size// Adjust based on time scale. (0.0-1.0)vec2particlePosition=dir*MAX_PARTICLE_DISTANCE*timeFract;// Distance of this pixel from that particlefloatd=length(uv-particlePosition);// Add glow based on distancefloatbrightness=DEFAULT_BRIGHTNESS*(sin(timeFract*TWINKLE_SPEED+i)*.5+.5);alpha+=brightness/d;}// Output to screenfragColor=vec4(1.0,1.0,1.0,alpha*(1.0-timeFract));}