使用着色器#
着色器是在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()