着色器玩具粒子#

本教程假定您已经熟悉中的材料 着色器玩具-发光 。在本教程中,我们将了解如何添加动画粒子。这些粒子可以用于爆炸效果。

这个例子的“诀窍”是使用伪随机数从初始爆炸点生成每个粒子的角度和速度。为什么是“伪随机”?这使得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()

带有粒子的初始着色器#

../../_images/step_2.png
 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}

添加粒子移动#

../../_images/step_3.gif
 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}

发光粒子#

../../_images/glow.png
 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}