1"""
2Shows simple use of tranform feedback.
3
4Transforming is similar to rendering except that the output
5or the shader is a buffer instead of a framebuffer/screen.
6
7This examples shows a common ping-pong technique were we
8transform a buffer with positions and velocities between
9two buffers so we always work on the previous state.
10
11* A list of N points are initialized with random positions and velocities
12* A point of gravity is moving around on the screen affecting the points
13
14Using transforms in this way makes us able to process
15a system that is reacting to external forces in this way.
16There are no predetermined paths and they system just lives on its own.
17"""
18from array import array
19import math
20import time
21import random
22import arcade
23from arcade.gl import BufferDescription, geometry
24
25# Do the math to figure out our screen dimensions
26SCREEN_WIDTH = 800
27SCREEN_HEIGHT = 600
28SCREEN_TITLE = "Transform Feedback"
29
30
31class MyGame(arcade.Window):
32
33 def __init__(self, width, height, title):
34 super().__init__(width, height, title, resizable=True)
35 self.time = 0
36
37 # Program to visualize the points
38 self.points_progran = self.ctx.program(
39 vertex_shader="""
40 #version 330
41 in vec2 in_pos;
42 out vec3 color;
43 void main() {
44 // Let's just give them a "random" color based on the vertex id
45 color = vec3(
46 mod((gl_VertexID * 100 % 11) / 10.0, 1.0),
47 mod((gl_VertexID * 100 % 27) / 10.0, 1.0),
48 mod((gl_VertexID * 100 % 71) / 10.0, 1.0));
49 // Pass the point position to primitive assembly
50 gl_Position = vec4(in_pos, 0.0, 1.0);
51 }
52 """,
53 fragment_shader="""
54 #version 330
55
56 // Color passed in from the vertex shader
57 in vec3 color;
58 // The pixel we are writing to in the framebuffer
59 out vec4 fragColor;
60
61 void main() {
62 // Fill the point
63 fragColor = vec4(color, 1.0);
64 }
65 """,
66 )
67
68 # A program tranforming points being affected by a gravity point
69 self.gravity_program = self.ctx.program(
70 vertex_shader="""
71 #version 330
72
73 // Delta time (since last frame)
74 uniform float dt;
75 // Strength of gravity
76 uniform float force;
77 // Position of gravity
78 uniform vec2 gravity_pos;
79
80 // The format of the data in our tranform buffer(s)
81 in vec2 in_pos;
82 in vec2 in_vel;
83
84 // We are writing to a buffer of the same format
85 out vec2 out_pos;
86 out vec2 out_vel;
87
88 void main() {
89 // Simplified gravity calculations
90 vec2 dir = normalize(gravity_pos - in_pos) * force;
91 vec2 vel = in_vel + dir / length(dir) * 0.01;
92
93 // Write to the output buffer
94 out_vel = vel;
95 out_pos = in_pos + vel * dt;
96 }
97 """,
98 )
99 N = 50_000
100 # Make two buffers we tranform between so we can work on the previous result
101 self.buffer_1 = self.ctx.buffer(data=array('f', self.gen_initial_data(N)))
102 self.buffer_2 = self.ctx.buffer(reserve=self.buffer_1.size)
103
104 # We also need to be able to visualize both versions (draw to the screen)
105 self.vao_1 = self.ctx.geometry([BufferDescription(self.buffer_1, '2f 2x4', ['in_pos'])])
106 self.vao_2 = self.ctx.geometry([BufferDescription(self.buffer_2, '2f 2x4', ['in_pos'])])
107
108 # We need to be able to tranform both buffers (ping-pong)
109 self.gravity_1 = self.ctx.geometry([BufferDescription(self.buffer_1, '2f 2f', ['in_pos', 'in_vel'])])
110 self.gravity_2 = self.ctx.geometry([BufferDescription(self.buffer_2, '2f 2f', ['in_pos', 'in_vel'])])
111
112 self.ctx.enable_only() # Ensure no context flags are set
113 self.time = time.time()
114
115 def gen_initial_data(self, count):
116 for _ in range(count):
117 yield random.uniform(-1.2, 1.2) # pos x
118 yield random.uniform(-1.2, 1.2) # pos y
119 yield random.uniform(-.3, .3) # velocity x
120 yield random.uniform(-.3, .3) # velocity y
121
122 def on_draw(self):
123 self.clear()
124 self.ctx.point_size = 2 * self.get_pixel_ratio()
125
126 # Calculate the actual delta time and current time
127 t = time.time()
128 frame_time = t - self.time
129 self.time = t
130
131 # Set uniforms in the program
132 self.gravity_program['dt'] = frame_time
133 self.gravity_program['force'] = 0.25
134 self.gravity_program['gravity_pos'] = math.sin(self.time * 0.77) * 0.25, math.cos(self.time) * 0.25
135
136 # Transform data in buffer_1 into buffer_2
137 self.gravity_1.transform(self.gravity_program, self.buffer_2)
138 # Render the result (Draw buffer_2)
139 self.vao_2.render(self.points_progran, mode=self.ctx.POINTS)
140
141 # Swap around stuff around so we transform back and fourth between the two buffers
142 self.gravity_1, self.gravity_2 = self.gravity_2, self.gravity_1
143 self.vao_1, self.vao_2 = self.vao_2, self.vao_1
144 self.buffer_1, self.buffer_2 = self.buffer_2, self.buffer_1
145
146
147if __name__ == "__main__":
148 window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
149 window.center_window()
150 arcade.run()