Perspective#

perspective.py#
1# flake8: noqa
2"""
3Perspective example using the lower level rendering API.
4
5This is definitely in the advanced section, but it can be
6a useful tool to learn. Sometimes we want perspective
7projection for things like backgrounds. This can be
8done very efficiently with shaders.
9
10In this example we render content into a framebuffer /
11virtual screen and map that on a texture we can rotate
12in 3D.
13
14If Python and Arcade are installed, this example can be run from the command line with:
15python -m arcade.examples.perspective
16"""
17
18from array import array
19
20import arcade
21from pyglet.math import Mat4
22from arcade.gl import BufferDescription
23
24
25class Perspective(arcade.Window):
26
27 def __init__(self):
28 super().__init__(800, 600, "Perspective", resizable=True)
29 # Simple texture shader for the plane.
30 # It support projection and model matrix
31 # and a scroll value for texture coordinates
32 self.program = self.ctx.program(
33 vertex_shader="""
34 #version 330
35
36 uniform mat4 projection;
37 uniform mat4 model;
38
39 in vec3 in_pos;
40 in vec2 in_uv;
41
42 out vec2 uv;
43
44 void main() {
45 gl_Position = projection * model * vec4(in_pos, 1.0);
46 uv = in_uv;
47 }
48 """,
49 fragment_shader="""
50 #version 330
51
52 uniform sampler2D layer;
53 uniform vec2 scroll;
54
55 in vec2 uv;
56 out vec4 fragColor;
57
58 void main() {
59 fragColor = texture(layer, uv + scroll);
60 }
61 """,
62 )
63
64 # # Matrix for perspective projection
65 self.proj = Mat4.perspective_projection(self.aspect_ratio, 0.1, 100, fov=75)
66 # # Configure the projection in the shader
67 self.program["projection"] = self.proj
68
69 # Framebuffer / virtual screen to render the contents into
70 self.fbo = self.ctx.framebuffer(
71 color_attachments=self.ctx.texture(size=(1024, 1024))
72 )
73
74 # Set up the geometry buffer for the plane.
75 # This is four points with texture coordinates
76 # creating a rectangle
77 buffer = self.ctx.buffer(
78 data=array(
79 'f',
80 [
81 # x y z u v
82 -1, 1, 0, 0, 1, # Top Left
83 -1, -1, 0, 0, 0, # Bottom Left
84 1, 1, 0, 1, 1, # Top Right
85 1, -1, 0, 1, 0, # Bottom right
86 ]
87 )
88 )
89 # Make this into a geometry object we can draw-
90 # Here we describe the contents of the buffer so the shader can understand it
91 self.geometry = self.ctx.geometry(
92 content=[BufferDescription(buffer, "3f 2f", ("in_pos", "in_uv"))],
93 mode=self.ctx.TRIANGLE_STRIP,
94 )
95
96 # Create some sprites
97 self.spritelist = arcade.SpriteList()
98 for y in range(8):
99 for x in range(8):
100 self.spritelist.append(
101 arcade.Sprite(
102 ":resources:images/tiles/boxCrate_double.png",
103 center_x=64 + x * 128,
104 center_y=64 + y * 128,
105 )
106 )
107 self.time = 0
108
109 def on_draw(self):
110 # Every frame we can update the offscreen texture if needed
111 self.draw_offscreen()
112 # Clear the window
113 self.clear()
114
115 # Bind the texture containing the offscreen data to channel 0
116 self.fbo.color_attachments[0].use(unit=0)
117
118 # Move the plane into camera view and rotate it
119 translate = Mat4.from_translation((0, 0, -2))
120 rotate = Mat4.from_rotation(self.time / 2, (1, 0, 0))
121 self.program["model"] = translate @ rotate
122
123 # Scroll the texture coordinates
124 self.program["scroll"] = 0, -self.time / 5
125
126 # Draw the plane
127 self.geometry.render(self.program)
128
129 def on_update(self, delta_time: float):
130 self.time += delta_time
131
132 def draw_offscreen(self):
133 """Render into the texture mapped """
134 # Activate the offscreen framebuffer and draw the sprites into it
135 with self.fbo.activate() as fbo:
136 fbo.clear()
137 arcade.set_viewport(0, self.fbo.width, 0, self.fbo.height)
138 self.spritelist.draw()
139
140 def on_resize(self, width: int, height: int):
141 super().on_resize(width, height)
142 self.program["projection"] = Mat4.perspective_projection(self.aspect_ratio, 0.1, 100, fov=75)
143
144
145Perspective().run()