使用着色器#

着色器是在GPU上运行的图形程序,可用于许多不同的目的。

在这里,我们将查看一些非常简单的着色器程序,并学习如何向着色器传递数据以及如何从着色器传递数据

BASICArcade程序#

起始模板#
 1import arcade
 2
 3SCREEN_WIDTH = 800
 4SCREEN_HEIGHT = 600
 5SCREEN_TITLE = "Basic Arcade Template"
 6
 7
 8class MyWindow(arcade.Window):
 9    def __init__(self):
10        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
11        self.center_window()
12        self.background_color = arcade.color.ALMOND
13
14    def on_draw(self):
15        # Draw a simple circle to the screen
16        self.clear()
17        arcade.draw_circle_filled(
18            SCREEN_WIDTH / 2, 
19            SCREEN_HEIGHT / 2,
20            100,
21            arcade.color.AFRICAN_VIOLET
22        )
23
24
25app = MyWindow()
26arcade.run()

基本着色器程序#

从这里,我们添加一个非常基本的着色器,并将其绘制到屏幕上。该着色器仅根据像素的水平坐标设置颜色和Alpha。

我们必须定义顶点着色器和片段着色器程序。

  • 顶点着色器在每个传递的坐标上运行并可以对其进行修改。在这里,我们仅使用它来将坐标传递给片段着色器

  • 片段着色器为每个传递的像素设置颜色。在这里,我们为每个像素设置固定的颜色,并根据水平位置改变Alpha

我们需要将像素坐标传递给着色器,以便创建一个对象 quad_fs 以促进这一进程。

简单着色器#
 1import arcade
 2
 3SCREEN_WIDTH = 800
 4SCREEN_HEIGHT = 600
 5SCREEN_TITLE = "Basic Vertex and Fragment Shader"
 6
 7
 8class MyWindow(arcade.Window):
 9    def __init__(self):
10        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
11        self.center_window()
12        self.background_color = arcade.color.ALMOND
13        
14        # GL geometry that will be used to pass pixel coordinates to the shader
15        # It has the same dimensions as the screen
16        self.quad_fs = arcade.gl.geometry.quad_2d_fs()
17
18        # Create a simple shader program
19        self.prog = self.ctx.program(
20            vertex_shader="""
21            #version 330
22            in vec2 in_vert;
23            void main()
24            {
25                gl_Position = vec4(in_vert, 0., 1.);
26            }
27            """,
28            fragment_shader="""
29            #version 330
30            out vec4 fragColor;
31            void main()
32            {
33                // Set the pixel colour and alpha based on x position
34                fragColor = vec4(0.9, 0.5, 0.5, sin(gl_FragCoord.x / 50));
35            }
36            """
37        )
38
39    def on_draw(self):
40        # Draw a simple circle
41        self.clear()
42        arcade.draw_circle_filled(
43            SCREEN_WIDTH / 2, 
44            SCREEN_HEIGHT / 2,
45            100,
46            arcade.color.AFRICAN_VIOLET
47        )
48
49        # Run the shader and render to screen
50        # The shader code is run once for each pixel coordinate in quad_fs
51        # and the fragColor output added to the screen
52        self.quad_fs.render(self.prog)
53
54
55app = MyWindow()
56arcade.run()

将数据传递到着色器#

为了将数据传递给着色器程序,我们可以定义 uniforms 。Uniform是全局着色器变量,用作从着色器程序外部传递的参数。

我们必须在着色器中定义Uniform,然后在渲染之前向着色器程序注册python变量。

确保统一类型适用于要传递的数据,这一点很重要。

制服#
 1import arcade
 2
 3SCREEN_WIDTH = 800
 4SCREEN_HEIGHT = 600
 5SCREEN_TITLE = "Shader With Uniform"
 6
 7
 8class MyWindow(arcade.Window):
 9    def __init__(self):
10        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
11        self.center_window()
12        self.background_color = arcade.color.ALMOND
13        
14        # GL geometry that will be used to pass pixel coordinates to the shader
15        # It has the same dimensions as the screen
16        self.quad_fs = arcade.gl.geometry.quad_2d_fs()
17
18        # Create a simple shader program
19        self.prog = self.ctx.program(
20            vertex_shader="""
21            #version 330
22            in vec2 in_vert;
23            void main()
24            {
25                gl_Position = vec4(in_vert, 0., 1.);
26            }
27            """,
28            fragment_shader="""
29            #version 330
30            // Define an input to receive total_time from python
31            uniform float time;
32            out vec4 fragColor;
33            void main()
34            {
35                // Set the pixel colour and alpha based on x position and time
36                fragColor = vec4(0.9, 0.5, 0.5, sin(gl_FragCoord.x / 50 + time));
37            }
38            """
39        )
40
41        # Create a variable to track program run time
42        self.total_time = 0
43
44    def on_update(self, delta_time):
45        # Keep tract o total time
46        self.total_time += delta_time
47
48    def on_draw(self):
49        # Draw a simple circle
50        self.clear()
51        arcade.draw_circle_filled(
52            SCREEN_WIDTH / 2, 
53            SCREEN_HEIGHT / 2,
54            100,
55            arcade.color.AFRICAN_VIOLET
56        )
57
58        # Register the uniform in the shader program
59        self.prog['time'] = self.total_time
60
61        # Run the shader and render to screen
62        # The shader code is run once for each pixel coordinate in quad_fs
63        # and the fragColor output added to the screen
64        self.quad_fs.render(self.prog)
65
66
67app = MyWindow()
68arcade.run()

从着色器访问纹理#

为了使着色器更有用,我们可能希望将纹理传递给它。

在这里,我们创建到纹理(和关联的帧缓冲区),并将它们作为统一采样器对象传递给着色器。与其他制服不同的是,我们需要将引用分配给一个整数纹理通道(而不是直接分配给Python对象) .use() 将其绑定到该通道的纹理。

纹理#
  1import arcade
  2
  3SCREEN_WIDTH = 800
  4SCREEN_HEIGHT = 600
  5SCREEN_TITLE = "Shader with Textures"
  6
  7
  8class MyWindow(arcade.Window):
  9    def __init__(self):
 10        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 11        self.center_window()
 12        self.background_color = arcade.color.ALMOND
 13        
 14        # GL geometry that will be used to pass pixel coordinates to the shader
 15        # It has the same dimensions as the screen
 16        self.quad_fs = arcade.gl.geometry.quad_2d_fs()
 17
 18        # Create textures and FBOs
 19        self.tex_0 = self.ctx.texture((self.width, self.height))
 20        self.fbo_0 = self.ctx.framebuffer(color_attachments=[self.tex_0])
 21
 22        self.tex_1 = self.ctx.texture((self.width, self.height))
 23        self.fbo_1 = self.ctx.framebuffer(color_attachments=[self.tex_1])
 24
 25        # Fill the textures with solid colours
 26        self.fbo_0.clear(color_normalized=(0.0, 0.0, 1.0, 1.0))
 27        self.fbo_1.clear(color_normalized=(1.0, 0.0, 0.0, 1.0))
 28
 29        # Create a simple shader program
 30        self.prog = self.ctx.program(
 31            vertex_shader="""
 32            #version 330
 33            in vec2 in_vert;
 34            // Get normalized coordinates
 35            in vec2 in_uv;
 36            out vec2 uv;
 37            void main()
 38            {
 39                gl_Position = vec4(in_vert, 0., 1.);
 40                uv = in_uv;
 41            }
 42            """,
 43            fragment_shader="""
 44            #version 330
 45            // Define an input to receive total_time from python
 46            uniform float time;
 47            // Define inputs to access textures
 48            uniform sampler2D t0;
 49            uniform sampler2D t1;
 50            in vec2 uv;
 51            out vec4 fragColor;
 52            void main()
 53            {
 54                // Set pixel color as a combination of the two textures
 55                fragColor = mix(
 56                    texture(t0, uv), 
 57                    texture(t1, uv), 
 58                    smoothstep(0.0, 1.0, uv.x));
 59                // Set the alpha based on time
 60                fragColor.w = sin(time);
 61            }
 62            """
 63        )
 64
 65        # Register the texture uniforms in the shader program
 66        self.prog['t0'] = 0
 67        self.prog['t1'] = 1
 68
 69        # Create a variable to track program run time
 70        self.total_time = 0
 71
 72    def on_update(self, delta_time):
 73        # Keep tract o total time
 74        self.total_time += delta_time
 75
 76    def on_draw(self):
 77        # Draw a simple circle
 78        self.clear()
 79        arcade.draw_circle_filled(
 80            SCREEN_WIDTH / 2, 
 81            SCREEN_HEIGHT / 2,
 82            100,
 83            arcade.color.AFRICAN_VIOLET
 84        )
 85
 86        # Register the uniform in the shader program
 87        self.prog['time'] = self.total_time
 88
 89        # Bind our textures to channels
 90        self.tex_0.use(0)
 91        self.tex_1.use(1)
 92
 93        # Run the shader and render to screen
 94        # The shader code is run once for each pixel coordinate in quad_fs
 95        # and the fragColor output added to the screen
 96        self.quad_fs.render(self.prog)
 97
 98
 99app = MyWindow()
100arcade.run()

从着色器绘制到纹理#

最后,我们有一个使用着色器读取和写入相同纹理的示例。

我们使用 with fbo: 语法来告诉arcade我们希望呈现到新的帧缓冲区,而不是默认的帧缓冲区。

一旦着色器更新了帧缓冲区,我们需要将其内容复制到要显示的屏幕上。

纹理#
 1import arcade
 2
 3SCREEN_WIDTH = 800
 4SCREEN_HEIGHT = 600
 5SCREEN_TITLE = "An Empty Program"
 6
 7
 8class MyWindow(arcade.Window):
 9    def __init__(self):
10        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
11        self.center_window()
12        self.background_color = arcade.color.ALMOND
13        
14        # GL geometry that will be used to pass pixel coordinates to the shader
15        # It has the same dimensions as the screen
16        self.quad_fs = arcade.gl.geometry.quad_2d_fs()
17
18        # Create texture and FBO
19        self.tex = self.ctx.texture((self.width, self.height))
20        self.fbo = self.ctx.framebuffer(color_attachments=[self.tex])
21
22        # Put something in the framebuffer to start
23        self.fbo.clear(color=arcade.color.ALMOND)
24        with self.fbo:
25            arcade.draw_circle_filled(
26                SCREEN_WIDTH / 2, 
27                SCREEN_HEIGHT / 2,
28                100,
29                arcade.color.AFRICAN_VIOLET
30            )
31
32        # Create a simple shader program
33        self.prog = self.ctx.program(
34            vertex_shader="""
35            #version 330
36            in vec2 in_vert;
37            void main()
38            {
39                gl_Position = vec4(in_vert, 0., 1.);
40            }
41            """,
42            fragment_shader="""
43            #version 330
44            // Define input to access texture
45            uniform sampler2D t0;
46            out vec4 fragColor;
47            void main()
48            {
49                // Overwrite this pixel with the colour from its neighbour
50                ivec2 pos = ivec2(gl_FragCoord.xy) + ivec2(-1, -1);
51                fragColor = texelFetch(t0, pos, 0);
52            }
53            """
54        )
55
56        # Register the texture uniform in the shader program
57        self.prog['t0'] = 0
58
59    def on_draw(self):
60        # Activate our new framebuffer to render to
61        with self.fbo:
62            # Bind our texture to the first channel
63            self.tex.use(0)
64
65            # Run the shader and render to the framebuffer
66            self.quad_fs.render(self.prog)
67        
68        # Copy the framebuffer to the screen to display
69        self.ctx.copy_framebuffer(self.fbo, self.ctx.screen)
70
71
72app = MyWindow()
73arcade.run()