着色器玩具粒子#
本教程假定您已经熟悉中的材料 着色器玩具-发光 。在本教程中,我们将了解如何添加动画粒子。这些粒子可以用于爆炸效果。
这个例子的“诀窍”是使用伪随机数从初始爆炸点生成每个粒子的角度和速度。为什么是“伪随机”?这使得GPU上的每个处理器可以独立地计算每个粒子在任何时间点的位置。然后,我们可以允许GPU并行计算。
加载着色器#
首先,我们需要一个加载着色器的程序。这个程序还记录了已经过去了多少时间。这对于我们计算我们在动画序列中走了多远是必要的。
1import arcade
2from arcade.experimental import Shadertoy
3
4
5# Derive an application window from Arcade's parent Window class
6class MyGame(arcade.Window):
7
8 def __init__(self):
9 # Call the parent constructor
10 super().__init__(width=1920, height=1080)
11
12 # Used to track run-time
13 self.time = 0.0
14
15 # Load a file and create a shader from it
16 file_name = "explosion.glsl"
17 self.shadertoy = Shadertoy(size=self.get_size(),
18 main_source=open(file_name).read())
19
20 def on_draw(self):
21 self.clear()
22 # Set uniform data to send to the GLSL shader
23 self.shadertoy.program['pos'] = self.mouse["x"], self.mouse["y"]
24
25 # Run the GLSL code
26 self.shadertoy.render(time=self.time)
27
28 def on_update(self, delta_time: float):
29 # Track run time
30 self.time += delta_time
31
32
33if __name__ == "__main__":
34 window = MyGame()
35 window.center_window()
36 arcade.run()
带有粒子的初始着色器#
1// Origin of the particles
2uniform vec2 pos;
3
4// Constants
5
6// Number of particles
7const float PARTICLE_COUNT = 100.0;
8// Max distance the particle can be from the position.
9// Normalized. (So, 0.3 is 30% of the screen.)
10const float MAX_PARTICLE_DISTANCE = 0.3;
11// Size of each particle. Normalized.
12const float PARTICLE_SIZE = 0.004;
13const float TWOPI = 6.2832;
14
15// This function will return two pseudo-random numbers given an input seed.
16// The result is in polar coordinates, to make the points random in a circle
17// rather than a rectangle.
18vec2 Hash12_Polar(float t) {
19 float angle = fract(sin(t * 674.3) * 453.2) * TWOPI;
20 float distance = fract(sin((t + angle) * 724.3) * 341.2);
21 return vec2(sin(angle), cos(angle)) * distance;
22}
23
24void mainImage( out vec4 fragColor, in vec2 fragCoord )
25{
26 // Normalized pixel coordinates (from 0 to 1)
27 // Origin of the particles
28 vec2 npos = (pos - .5 * iResolution.xy) / iResolution.y;
29 // Position of current pixel we are drawing
30 vec2 uv = (fragCoord- .5 * iResolution.xy) / iResolution.y;
31
32 // Re-center based on input coordinates, rather than origin.
33 uv -= npos;
34
35 // Default alpha is transparent.
36 float alpha = 0.0;
37
38 // Loop for each particle
39 for (float i= 0.; i < PARTICLE_COUNT; i++) {
40 // Direction of particle + speed
41 float seed = i + 1.0;
42 vec2 dir = Hash12_Polar(seed);
43 // Get position based on direction, magnitude, and explosion size
44 vec2 particlePosition = dir * MAX_PARTICLE_DISTANCE;
45 // Distance of this pixel from that particle
46 float d = length(uv - particlePosition);
47 // If we are within the particle size, set alpha to 1.0
48 if (d < PARTICLE_SIZE)
49 alpha = 1.0;
50 }
51 // Output to screen
52 fragColor = vec4(1.0, 1.0, 1.0, alpha);
53}
添加粒子移动#
1// Origin of the particles
2uniform vec2 pos;
3
4// Constants
5
6// Number of particles
7const float PARTICLE_COUNT = 100.0;
8// Max distance the particle can be from the position.
9// Normalized. (So, 0.3 is 30% of the screen.)
10const float MAX_PARTICLE_DISTANCE = 0.3;
11// Size of each particle. Normalized.
12const float PARTICLE_SIZE = 0.004;
13// Time for each burst cycle, in seconds.
14const float BURST_TIME = 2.0;
15const float TWOPI = 6.2832;
16
17// This function will return two pseudo-random numbers given an input seed.
18// The result is in polar coordinates, to make the points random in a circle
19// rather than a rectangle.
20vec2 Hash12_Polar(float t) {
21 float angle = fract(sin(t * 674.3) * 453.2) * TWOPI;
22 float distance = fract(sin((t + angle) * 724.3) * 341.2);
23 return vec2(sin(angle), cos(angle)) * distance;
24}
25
26void mainImage( out vec4 fragColor, in vec2 fragCoord )
27{
28 // Normalized pixel coordinates (from 0 to 1)
29 // Origin of the particles
30 vec2 npos = (pos - .5 * iResolution.xy) / iResolution.y;
31 // Position of current pixel we are drawing
32 vec2 uv = (fragCoord- .5 * iResolution.xy) / iResolution.y;
33
34 // Re-center based on input coordinates, rather than origin.
35 uv -= npos;
36
37 // Default alpha is transparent.
38 float alpha = 0.0;
39
40 // 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.
41 // Auto resets if time goes beyond burst time. This causes the explosion to cycle.
42 float timeFract = fract(iTime * 1 / BURST_TIME);
43
44 // Loop for each particle
45 for (float i= 0.; i < PARTICLE_COUNT; i++) {
46 // Direction of particle + speed
47 float seed = i + 1.0;
48 vec2 dir = Hash12_Polar(seed);
49 // Get position based on direction, magnitude, and explosion size
50 // Adjust based on time scale. (0.0-1.0)
51 vec2 particlePosition = dir * MAX_PARTICLE_DISTANCE * timeFract;
52 // Distance of this pixel from that particle
53 float d = length(uv - particlePosition);
54 // If we are within the particle size, set alpha to 1.0
55 if (d < PARTICLE_SIZE)
56 alpha = 1.0;
57 }
58 // Output to screen
59 fragColor = vec4(1.0, 1.0, 1.0, alpha);
60}
淡出#
1// Origin of the particles
2uniform vec2 pos;
3
4// Constants
5
6// Number of particles
7const float PARTICLE_COUNT = 100.0;
8// Max distance the particle can be from the position.
9// Normalized. (So, 0.3 is 30% of the screen.)
10const float MAX_PARTICLE_DISTANCE = 0.3;
11// Size of each particle. Normalized.
12const float PARTICLE_SIZE = 0.004;
13// Time for each burst cycle, in seconds.
14const float BURST_TIME = 2.0;
15const float TWOPI = 6.2832;
16
17// This function will return two pseudo-random numbers given an input seed.
18// The result is in polar coordinates, to make the points random in a circle
19// rather than a rectangle.
20vec2 Hash12_Polar(float t) {
21 float angle = fract(sin(t * 674.3) * 453.2) * TWOPI;
22 float distance = fract(sin((t + angle) * 724.3) * 341.2);
23 return vec2(sin(angle), cos(angle)) * distance;
24}
25
26void mainImage( out vec4 fragColor, in vec2 fragCoord )
27{
28 // Normalized pixel coordinates (from 0 to 1)
29 // Origin of the particles
30 vec2 npos = (pos - .5 * iResolution.xy) / iResolution.y;
31 // Position of current pixel we are drawing
32 vec2 uv = (fragCoord- .5 * iResolution.xy) / iResolution.y;
33
34 // Re-center based on input coordinates, rather than origin.
35 uv -= npos;
36
37 // Default alpha is transparent.
38 float alpha = 0.0;
39
40 // 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.
41 // Auto resets if time goes beyond burst time. This causes the explosion to cycle.
42 float timeFract = fract(iTime * 1 / BURST_TIME);
43
44 // Loop for each particle
45 for (float i= 0.; i < PARTICLE_COUNT; i++) {
46 // Direction of particle + speed
47 float seed = i + 1.0;
48 vec2 dir = Hash12_Polar(seed);
49 // Get position based on direction, magnitude, and explosion size
50 // Adjust based on time scale. (0.0-1.0)
51 vec2 particlePosition = dir * MAX_PARTICLE_DISTANCE * timeFract;
52 // Distance of this pixel from that particle
53 float d = length(uv - particlePosition);
54 // If we are within the particle size, set alpha to 1.0
55 if (d < PARTICLE_SIZE)
56 alpha = 1.0;
57 }
58 // Output to screen
59 fragColor = vec4(1.0, 1.0, 1.0, alpha * (1.0 - timeFract));
60}
发光粒子#
1// Origin of the particles
2uniform vec2 pos;
3
4// Constants
5
6// Number of particles
7const float PARTICLE_COUNT = 100.0;
8// Max distance the particle can be from the position.
9// Normalized. (So, 0.3 is 30% of the screen.)
10const float MAX_PARTICLE_DISTANCE = 0.3;
11// Size of each particle. Normalized.
12const float PARTICLE_SIZE = 0.004;
13// Time for each burst cycle, in seconds.
14const float BURST_TIME = 2.0;
15// Particle brightness
16const float DEFAULT_BRIGHTNESS = 0.0005;
17
18const float TWOPI = 6.2832;
19
20// This function will return two pseudo-random numbers given an input seed.
21// The result is in polar coordinates, to make the points random in a circle
22// rather than a rectangle.
23vec2 Hash12_Polar(float t) {
24 float angle = fract(sin(t * 674.3) * 453.2) * TWOPI;
25 float distance = fract(sin((t + angle) * 724.3) * 341.2);
26 return vec2(sin(angle), cos(angle)) * distance;
27}
28
29void mainImage( out vec4 fragColor, in vec2 fragCoord )
30{
31 // Normalized pixel coordinates (from 0 to 1)
32 // Origin of the particles
33 vec2 npos = (pos - .5 * iResolution.xy) / iResolution.y;
34 // Position of current pixel we are drawing
35 vec2 uv = (fragCoord- .5 * iResolution.xy) / iResolution.y;
36
37 // Re-center based on input coordinates, rather than origin.
38 uv -= npos;
39
40 // Default alpha is transparent.
41 float alpha = 0.0;
42
43 // 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.
44 // Auto resets if time goes beyond burst time. This causes the explosion to cycle.
45 float timeFract = fract(iTime * 1 / BURST_TIME);
46
47 // Loop for each particle
48 for (float i= 0.; i < PARTICLE_COUNT; i++) {
49 // Direction of particle + speed
50 float seed = i + 1.0;
51 vec2 dir = Hash12_Polar(seed);
52 // Get position based on direction, magnitude, and explosion size
53 // Adjust based on time scale. (0.0-1.0)
54 vec2 particlePosition = dir * MAX_PARTICLE_DISTANCE * timeFract;
55 // Distance of this pixel from that particle
56 float d = length(uv - particlePosition);
57 // Add glow based on distance
58 alpha += DEFAULT_BRIGHTNESS / d;
59 }
60 // Output to screen
61 fragColor = vec4(1.0, 1.0, 1.0, alpha * (1.0 - timeFract));
62}
闪烁的粒子#
1// Origin of the particles
2uniform vec2 pos;
3
4// Constants
5
6// Number of particles
7const float PARTICLE_COUNT = 100.0;
8// Max distance the particle can be from the position.
9// Normalized. (So, 0.3 is 30% of the screen.)
10const float MAX_PARTICLE_DISTANCE = 0.3;
11// Size of each particle. Normalized.
12const float PARTICLE_SIZE = 0.004;
13// Time for each burst cycle, in seconds.
14const float BURST_TIME = 2.0;
15// Particle brightness
16const float DEFAULT_BRIGHTNESS = 0.0005;
17// How many times to the particles twinkle
18const float TWINKLE_SPEED = 10.0;
19
20const float TWOPI = 6.2832;
21
22// This function will return two pseudo-random numbers given an input seed.
23// The result is in polar coordinates, to make the points random in a circle
24// rather than a rectangle.
25vec2 Hash12_Polar(float t) {
26 float angle = fract(sin(t * 674.3) * 453.2) * TWOPI;
27 float distance = fract(sin((t + angle) * 724.3) * 341.2);
28 return vec2(sin(angle), cos(angle)) * distance;
29}
30
31void mainImage( out vec4 fragColor, in vec2 fragCoord )
32{
33 // Normalized pixel coordinates (from 0 to 1)
34 // Origin of the particles
35 vec2 npos = (pos - .5 * iResolution.xy) / iResolution.y;
36 // Position of current pixel we are drawing
37 vec2 uv = (fragCoord- .5 * iResolution.xy) / iResolution.y;
38
39 // Re-center based on input coordinates, rather than origin.
40 uv -= npos;
41
42 // Default alpha is transparent.
43 float alpha = 0.0;
44
45 // 0.0 - 1.0 normalized fraction representing how far along in the explosion we are.
46 // Auto resets if time goes beyond burst time. This causes the explosion to cycle.
47 float timeFract = fract(iTime * 1 / BURST_TIME);
48
49 // Loop for each particle
50 for (float i= 0.; i < PARTICLE_COUNT; i++) {
51 // Direction of particle + speed
52 float seed = i + 1.0;
53 vec2 dir = Hash12_Polar(seed);
54 // Get position based on direction, magnitude, and explosion size
55 // Adjust based on time scale. (0.0-1.0)
56 vec2 particlePosition = dir * MAX_PARTICLE_DISTANCE * timeFract;
57 // Distance of this pixel from that particle
58 float d = length(uv - particlePosition);
59 // Add glow based on distance
60 float brightness = DEFAULT_BRIGHTNESS * (sin(timeFract * TWINKLE_SPEED + i) * .5 + .5);
61 alpha += brightness / d;
62 }
63 // Output to screen
64 fragColor = vec4(1.0, 1.0, 1.0, alpha * (1.0 - timeFract));
65}