def test_create(self): mesh = VAO("test", mode=moderngl.LINES) mesh.buffer(numpy.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0], dtype='f4'), '3f', 'position') mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0, 0.0, 1.0], dtype='f4'), '3f', 'normal') mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0], dtype='f4'), '2f', 'uv') # Ensure basic properties are correct self.assertEqual(mesh.name, "test") self.assertEqual(mesh.vertex_count, 2) self.assertEqual(mesh.mode, moderngl.LINES) # Ensure buffers are present self.assertIsInstance(mesh.get_buffer_by_name('position'), BufferInfo) self.assertIsInstance(mesh.get_buffer_by_name('normal'), BufferInfo) self.assertIsInstance(mesh.get_buffer_by_name('uv'), BufferInfo) self.assertEqual(mesh.get_buffer_by_name('something'), None) # Create a progam using a subset of the buffers prog = self.createProgram() vao = mesh.instance(prog) self.assertIsInstance(vao, moderngl.VertexArray) # Render directly with VAO and VertexArray instance mesh.render(prog) vao.render()
class Boids2(moderngl_window.WindowConfig): """Minimal WindowConfig example""" gl_version = (3, 3) window_size = 256, 256 aspect_ratio = 1.0 title = "Basic Window Config" resource_dir = (Path(__file__) / '../../resources').absolute() def __init__(self, **kwargs): super().__init__(**kwargs) # Offscreen buffers self.texture_1 = self.ctx.texture(self.wnd.buffer_size, 4, dtype='f4') self.texture_2 = self.ctx.texture(self.wnd.buffer_size, 4, dtype='f4') self.fbo_1 = self.ctx.framebuffer(color_attachments=[self.texture_1]) self.fbo_2 = self.ctx.framebuffer(color_attachments=[self.texture_1]) # VAOs self.quad_fs = geometry.quad_fs() N = 1000 def gen_boids(n): for i in range(n): yield random.uniform(-1, 1) yield random.uniform(-1, 1) yield random.uniform(-1, 1) yield random.uniform(-1, 1) data = np.fromiter(gen_boids(N), dtype='f4', count=N * 4) self.boids_buffer_1 = self.ctx.buffer(data=data) self.boids_buffer_2 = self.ctx.buffer(reserve=data.nbytes) self.boids_vao_1 = VAO(name='boids_1') self.boids_vao_1.buffer(self.boids_buffer_1, '2f 2f', ['in_position', 'in_velocity']) self.boids_vao_2 = VAO(name='boids_2') self.boids_vao_2.buffer(self.boids_buffer_2, '2f 2f', ['in_position', 'in_velocity']) # Programs self.tex_prog = self.load_program('programs/texture.glsl') self.tex_prog['texture0'].value = 0 self.boid_points = self.load_program( 'programs/boids2/boid_points.glsl') # self.boid_locality = self.load_program('programs/boids2/boids_locality_info.glsl') def render(self, time, frametime): self.fbo_1.use() # Render initial data to framebuffer self.boids_vao_1.render(self.boid_points, mode=moderngl.POINTS) # debug render fbo self.wnd.fbo.use() self.texture_1.use(location=0) self.quad_fs.render(self.tex_prog)
class Terrain(mglw.WindowConfig): """""" title = "Terrain" resource_dir = (Path(__file__) / '../resources').absolute() aspect_ratio = None window_size = 1280, 720 resizable = True samples = 16 clear_color = (51 / 255, 51 / 255, 51 / 255) def __init__(self, **kwargs): super().__init__(**kwargs) self.camera = KeyboardCamera(self.wnd.keys, aspect_ratio=self.wnd.aspect_ratio) self.terrain_program = self.load_program('my_shader.glsl') self.ctx.front_face = 'cw' # self.ctx.wireframe = True self.translate = Matrix44.from_translation((0, 1., .25)) self.rotate = Matrix44.look_at((0., 1., .25), (0., 0., -1.), (0., 1., 0.)) self.projection = (self.camera.projection.matrix * (self.translate * self.rotate)).astype('f4') self.terrain_program['projection'].write(self.projection.tobytes()) terrain_resolution = 5 points = generate(terrain_resolution).astype('f4') self.buffer = self.ctx.buffer(points) indices = generate_index_buffer(terrain_resolution).astype('i4') print(indices) self.index_buffer = self.ctx.buffer(indices) self.vao_1 = VAO(name='vao_1') self.vao_1.buffer(self.buffer, '3f', ['in_position']) self.vao_1.index_buffer(self.index_buffer) def render(self, time, frame_time): self.ctx.clear(*self.clear_color) # self.ctx.clear(0.) self.ctx.enable_only(moderngl.DEPTH_TEST | moderngl.CULL_FACE) self.vao_1.render(self.terrain_program, mode=moderngl.POINTS) def resize(self, width: int, height: int): self.camera.projection.update(aspect_ratio=self.wnd.aspect_ratio)
class PolarPrimes(mglw.WindowConfig): """ Render prime numbers in polar coordinates """ gl_version = (3, 3) title = "PolarPrimes" samples = 16 window_size = 720, 720 aspect_ratio = 1 resizable = False resource_dir = Path(__file__).parent.joinpath('resources').resolve() def __init__(self, **kwargs): super().__init__(**kwargs) self.program = self.load_program('my_shader.glsl') self.program['color'].value = (191 / 255, 67 / 255, 69 / 255) self.program['size'].value = .005 self.zoom = 100 self.n = 2**25 self.program['n'].value = self.n primes = primesfrom2to(self.n) self.buffer = self.ctx.buffer(primes) self.vao1 = VAO(name='boids_1') self.vao1.buffer(self.buffer, '1i', ['in_prime']) def render(self, time, frame_time): r = g = b = 51 / 255 self.ctx.clear(r, g, b) self.ctx.enable(moderngl.BLEND) self.ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA self.program['n'].value = max((-f(time / 2**3) + 1) / 2 * self.n, 2000) self.vao1.render(self.program, mode=moderngl.POINTS) def key_event(self, key, action, modifiers): keys = self.wnd.keys if action == keys.ACTION_PRESS: if key == keys.SPACE: self.timer.toggle_pause()
class TextWriter2D(BaseText): """Simple monspaced bitmapped text renderer""" def __init__(self): super().__init__() meta = FontMeta( resources.data.load( DataDescription(path="bitmapped/text/meta.json"))) self._texture = resources.textures.load( TextureDescription( path="bitmapped/textures/VeraMono.png", kind="array", mipmap=True, layers=meta.characters, )) self._program = resources.programs.load( ProgramDescription(path="bitmapped/programs/text_2d.glsl")) self._init(meta) self._string_buffer = self.ctx.buffer(reserve=1024 * 4) self._string_buffer.clear(chunk=b'\32') pos = self.ctx.buffer(data=bytes([0] * 4 * 3)) self._vao = VAO("textwriter", mode=moderngl.POINTS) self._vao.buffer(pos, '3f', 'in_position') self._vao.buffer(self._string_buffer, '1u/i', 'in_char_id') self._text: str = None @property def text(self) -> str: return self._text @text.setter def text(self, value: str): print(len(value)) self._text = value self._string_buffer.orphan(size=len(value) * 4) self._string_buffer.clear(chunk=b'\32') self._write(value) def _write(self, text: str): self._string_buffer.clear(chunk=b'\32') print(self._string_buffer.size) self._string_buffer.write( numpy.fromiter( self._translate_string(text), dtype=numpy.uint32, )) def draw(self, pos, length=-1, size=24.0): # Calculate ortho projection based on viewport vp = self.ctx.fbo.viewport w, h = vp[2] - vp[0], vp[3] - vp[1] projection = matrix44.create_orthogonal_projection_matrix( 0, # left w, # right 0, # bottom h, # top 1.0, # near -1.0, # far dtype=numpy.float32, ) self._texture.use(location=0) self._program["m_proj"].write(projection) self._program["text_pos"].value = pos self._program["font_texture"].value = 0 self._program[ "char_size"].value = self._meta.char_aspect_wh * size, size self._vao.render(self._program, instances=len(self._text))
class VolumetricTetrahedralMesh(CameraWindow): """Volumetric Tetrahedral Mesh. The dataset was provided by: Mara Catalina Aguilera Canon at the Bournemouth University (UK). Area of research: Graph Neuro Networks, Finite Element Method An example rendering a volumetric mesh of the format: ``[[p1, p2, p3, p4], [p1, p2, p3, p4], ..]`` were ```px``` represent a 3d point in a tetraherdon. A geometry shader calculates and emits the tetraherdons as triangles and calculate normals on the fly while rendering data. This helps us avoid doing this expensive operation in python and greatly reduces the memory requirement. Controls: - Camera: Mouse for rotation. AWSD + QE for translation - Press b to toggle blend mode on/off - Mouse wheel to increase or decrease the threshold for a tetra to be alive """ gl_version = (4, 1) title = "Volumetric Tetrahedra lMesh" aspect_ratio = None resource_dir = (Path(__file__) / '../../resources').resolve() samples = 4 def __init__(self, **kwargs): super().__init__(**kwargs) # Finetune camera self.wnd.mouse_exclusivity = True self.camera.projection.update(near=.01, far=100) self.camera.mouse_sensitivity = .5 self.camera.velocity = 2.5 self.camera.projection.update(fov=60) # Scene states self.with_blending = False self.line_color = (0.0, 0.0, 0.0) self.mesh_color = (0.0, 0.8, 0.0) self.threshold = 0.5 # For rendering background self.quad_fs = geometry.quad_fs() # (172575,) | 57,525 vertices vertices = np.load(self.resource_dir / 'data/tetrahedral_mesh/mesh_nodes.npy') vertices = np.concatenate(vertices) # (259490, 4) (1037960,) indices indices = np.load(self.resource_dir / 'data/tetrahedral_mesh/element_nodes.npy') indices = np.concatenate(indices) - 1 # Probability of a tetrahedron is still alive w, h = 8192, int(np.ceil(indices.shape[0] / 8192)) self.alive_data = np.random.random_sample(w * h) self.alive_texture = self.ctx.texture((w, h), 1, dtype='f2') self.alive_texture.write(self.alive_data.astype('f2')) # Original geometry with indices self.geometry = VAO(name='geometry_indices') self.geometry.buffer(vertices, '3f', 'in_position') self.geometry.index_buffer(indices, index_element_size=4) self.prog_background = self.load_program( 'programs/tetrahedral_mesh/bg.glsl') self.prog_gen_tetra = self.load_program( vertex_shader='programs/tetrahedral_mesh/gen_tetra_vert.glsl', geometry_shader='programs/tetrahedral_mesh/gen_tetra_geo.glsl', fragment_shader='programs/tetrahedral_mesh/gen_tetra_frag.glsl', ) self.prog_gen_tetra_lines = self.load_program( vertex_shader='programs/tetrahedral_mesh/gen_tetra_vert.glsl', geometry_shader='programs/tetrahedral_mesh/gen_tetra_geo.glsl', fragment_shader='programs/tetrahedral_mesh/lines_frag.glsl', ) # Query object for measuring the rendering call in OpenGL # It delivers the GPU time it took to process commands self.query = self.ctx.query(samples=True, any_samples=True, time=True, primitives=True) self.total_elapsed = 0 def render(self, time, frametime): # Render background self.ctx.wireframe = False if not self.with_blending: self.ctx.enable_only(moderngl.NOTHING) self.quad_fs.render(self.prog_background) # Handle blend mode toggle if self.with_blending: self.ctx.enable_only(moderngl.BLEND) self.ctx.blend_func = moderngl.ONE, moderngl.ONE else: self.ctx.enable_only(moderngl.DEPTH_TEST | moderngl.CULL_FACE) # Render tetrahedral mesh translate = Matrix44.from_translation((0.0, 2.5, -15.0), dtype='f4') rotate = Matrix44.from_eulers((np.radians(180), 0, 0), dtype='f4') scale = Matrix44.from_scale((400, 400, 400), dtype='f4') mat = self.camera.matrix * translate * rotate * scale # All render calls inside this context are timed with self.query: self.alive_texture.use(location=0) self.prog_gen_tetra['alive_texture'].value = 0 self.prog_gen_tetra['threshold'].value = self.threshold self.prog_gen_tetra['color'].value = self.mesh_color self.prog_gen_tetra['m_cam'].write(mat) self.prog_gen_tetra['m_proj'].write(self.camera.projection.matrix) self.geometry.render(self.prog_gen_tetra, mode=moderngl.LINES_ADJACENCY) # Render lines self.ctx.wireframe = True self.alive_texture.use(location=0) self.prog_gen_tetra_lines['alive_texture'].value = 0 self.prog_gen_tetra_lines['threshold'].value = self.threshold self.prog_gen_tetra_lines['color'].value = self.line_color self.prog_gen_tetra_lines['m_cam'].write(mat) self.prog_gen_tetra_lines['m_proj'].write( self.camera.projection.matrix) self.geometry.render(self.prog_gen_tetra_lines, mode=moderngl.LINES_ADJACENCY) self.total_elapsed = self.query.elapsed def key_event(self, key, action, modifiers): super().key_event(key, action, modifiers) keys = self.wnd.keys if action == keys.ACTION_PRESS: if key == keys.B: self.with_blending = not self.with_blending print("With blending:", self.with_blending) if self.with_blending: self.mesh_color = 0.01, 0.01, 0.01 self.line_color = 0.01, 0.01, 0.01 else: self.mesh_color = 0.0, 0.8, 0.0 self.line_color = 0.0, 0.0, 0.0 def mouse_scroll_event(self, x_offset, y_offset): if y_offset > 0: self.threshold += 0.01 else: self.threshold -= 0.01 self.threshold = max(min(self.threshold, 1.0), 0.0) def close(self): # 1 s = 1000000000 ns # 1 s = 1000000 μs avg = self.total_elapsed / self.wnd.frames print("Average rendering time per frame: {} ns | {} μs".format( round(avg, 4), # ns round(avg / 1000, 4), # μs ))
class FragmentPicking(moderngl_window.WindowConfig): """ Demonstrates how you can pick exact positions on a model efficiently using the gpu. We render the scene info to various layers in an offscreen framebuffer and use this to fetch the view position of the picked fragment. We can then apply the inverse modelview to create points positions that matches the original mesh. In this example we pick points on a mesh mapped with a texture containing heat information. We pick the position, heat value and the normal of the fragment. The normal is then used to hide points that point away from the camera. """ title = "Fragment Picking" gl_version = 3, 3 window_size = 1280, 720 aspect_ratio = None resizable = True resource_dir = (Path(__file__) / '../../resources').resolve() def __init__(self, **kwargs): super().__init__(**kwargs) print("window buffer size:", self.wnd.buffer_size) self.marker_file = Path('markers.bin') # Object rotation self.x_rot = 0 self.y_rot = 0 # Object position self.zoom = 0 # Load scene cached to speed up loading! self.scene = self.load_scene('scenes/fragment_picking/centered.obj', cache=True) # Grab the raw mesh/vertexarray self.mesh = self.scene.root_nodes[0].mesh.vao self.mesh_texture = self.scene.root_nodes[ 0].mesh.material.mat_texture.texture self.projection = Projection3D( fov=60, aspect_ratio=self.wnd.aspect_ratio, near=1.0, far=100.0, ) # --- Offscreen render target # RGBA color/diffuse layer self.offscreen_diffuse = self.ctx.texture(self.wnd.buffer_size, 4) # Textures for storing normals (16 bit floats) self.offscreen_normals = self.ctx.texture(self.wnd.buffer_size, 4, dtype='f2') # Texture for storing the view positions rendered to framebuffer self.offscreen_viewpos = self.ctx.texture(self.wnd.buffer_size, 4, dtype='f4') # Texture for storing depth values self.offscreen_depth = self.ctx.depth_texture(self.wnd.buffer_size) # Create a framebuffer we can render to self.offscreen = self.ctx.framebuffer( color_attachments=[ self.offscreen_diffuse, self.offscreen_normals, self.offscreen_viewpos, ], depth_attachment=self.offscreen_depth, ) # This is just for temp changing depth texture parameters # temporary so we can use it as a normal texture self.depth_sampler = self.ctx.sampler( filter=(moderngl.LINEAR, moderngl.LINEAR), compare_func='', ) # A fullscreen quad just for rendering offscreen textures to the window self.quad_fs = geometry.quad_fs() # --- Shaders # Simple program just rendering texture self.texture_program = self.load_program( 'programs/fragment_picking/texture.glsl') self.texture_program['texture0'].value = 0 # Geomtry shader writing to two offscreen layers (color, normal) + depth self.geometry_program = self.load_program( 'programs/fragment_picking/geometry.glsl') self.geometry_program['texture0'].value = 0 # use texture channel 0 # Shader for linearizing depth (debug visualization) self.linearize_depth_program = self.load_program( 'programs/linearize_depth.glsl') self.linearize_depth_program['texture0'].value = 0 self.linearize_depth_program['near'].value = self.projection.near self.linearize_depth_program['far'].value = self.projection.far # Shader for picking the world position of a fragment self.fragment_picker_program = self.load_program( 'programs/fragment_picking/picker.glsl') self.fragment_picker_program[ 'position_texture'].value = 0 # Read from texture channel 0 self.fragment_picker_program[ 'normal_texture'].value = 1 # Read from texture channel 1 self.fragment_picker_program[ 'diffuse_texture'].value = 2 # Read from texture channel 2 # Picker geometry self.marker_byte_size = 7 * 4 # position + normal + temperature (7 x 32bit floats) self.picker_output = self.ctx.buffer(reserve=self.marker_byte_size) self.picker_vao = VAO(mode=moderngl.POINTS) # Shader for rendering markers self.marker_program = self.load_program( 'programs/fragment_picking/markers.glsl') self.marker_program['color'].value = 1.0, 0.0, 0.0, 1.0 # Marker geometry self.marker_buffer = self.ctx.buffer( reserve=self.marker_byte_size * 1000) # Resever room for 1000 points self.marker_vao = VAO(name="markers", mode=moderngl.POINTS) self.marker_vao.buffer(self.marker_buffer, '3f 3f 1f', ['in_position', 'in_normal', 'temperature']) self.num_markers = 0 # Debug geometry self.quad_normals = geometry.quad_2d(size=(0.25, 0.25), pos=(0.75, 0.875)) self.quad_depth = geometry.quad_2d(size=(0.25, 0.25), pos=(0.5, 0.875)) self.quad_positions = geometry.quad_2d(size=(0.25, 0.25), pos=(0.25, 0.875)) def render(self, time, frametime): self.ctx.enable(moderngl.DEPTH_TEST | moderngl.CULL_FACE) translation = Matrix44.from_translation((0, 0, -45 + self.zoom), dtype='f4') rotation = Matrix44.from_eulers((self.y_rot, self.x_rot, 0), dtype='f4') self.modelview = translation * rotation # Render the scene to offscreen buffer self.offscreen.clear() self.offscreen.use() # Render the scene self.geometry_program['modelview'].write(self.modelview) self.geometry_program['projection'].write(self.projection.matrix) self.mesh_texture.use( location=0) # bind texture from obj file to channel 0 self.depth_sampler.use(location=0) self.mesh.render(self.geometry_program) # render mesh self.depth_sampler.clear(location=0) # Activate the window as the render target self.ctx.screen.use() self.ctx.disable(moderngl.DEPTH_TEST) # Render offscreen diffuse layer to screen self.offscreen_diffuse.use(location=0) self.quad_fs.render(self.texture_program) # Render markers if self.num_markers > 0: self.ctx.point_size = 6.0 # Specify fragment size of the markers self.marker_program['modelview'].write(self.modelview) self.marker_program['projection'].write(self.projection.matrix) self.marker_vao.render(self.marker_program, vertices=self.num_markers) self.render_debug() def render_debug(self): """Debug rendering. Offscreen buffers""" # Debug rendering of normal and depth buffer self.offscreen_normals.use() self.quad_normals.render(self.texture_program) self.offscreen_depth.use(location=0) # bind depth sampler to channel 0 self.depth_sampler.use(location=0) # temp override the parameters self.quad_depth.render(self.linearize_depth_program) self.depth_sampler.clear(location=0) # Remove the override self.offscreen_viewpos.use() self.quad_positions.render(self.texture_program) def mouse_drag_event(self, x, y, dx, dy): """Pick up mouse drag movements""" self.x_rot -= dx / 100 self.y_rot -= dy / 100 def mouse_press_event(self, x, y, button): """Attempts to get the view position from a fragment""" # only care about right mouse button clicks if button != self.wnd.mouse.right: return # mouse coordinates starts in upper left corner # pixel positions starts and lower left corner pos = int(x * self.wnd.pixel_ratio), int(self.wnd.buffer_height - (y * self.wnd.pixel_ratio)) print("Picking mouse position", x, y) print("Viewport position", pos) self.fragment_picker_program['texel_pos'].value = pos self.fragment_picker_program['modelview'].write(self.modelview) self.offscreen_viewpos.use(location=0) self.offscreen_normals.use(location=1) self.offscreen_diffuse.use(location=2) self.picker_vao.transform(self.fragment_picker_program, self.picker_output, vertices=1) # Print position x, y, z, nx, ny, nz, temperature = struct.unpack( '7f', self.picker_output.read()) if z == 0.0: print('Point is not on the mesh') return print(f"Position: {x} {y} {z}") print(f"Normal: {nx} {ny} {nz}") print( f"Temperature: {round(temperature * 255)} (byte) {temperature} (float)" ) self.marker_buffer.write(self.picker_output.read(), offset=self.marker_byte_size * self.num_markers) self.num_markers += 1 def mouse_scroll_event(self, x_offset, y_offset): self.zoom += y_offset def key_event(self, key, action, modifiers): keys = self.wnd.keys # Key presses if action == keys.ACTION_PRESS: if key == keys.F1: print('Loading marker file') self.load_markers(self.marker_file) if key == keys.F2: print('Saving marker file') self.save_markers(self.marker_file) def load_markers(self, path: Path): """Loads markers from file""" if not self.marker_file.exists(): return with open(self.marker_file, mode='rb') as fd: size = self.marker_file.stat().st_size self.num_markers = size // self.marker_byte_size self.marker_buffer.write(fd.read(), offset=0) def save_markers(self, path: Path): """Dump markers from file""" if self.num_markers == 0: return with open('markers.bin', mode='wb') as fd: fd.write( self.marker_buffer.read(size=self.num_markers * self.marker_byte_size))
class NBodySim(mglw.WindowConfig): gl_version = (4, 3) title = "N-Body Simulation" resource_dir = (Path(__file__) / '../resources').absolute() screenshots_dir = (Path(__file__) / '../screen_recordings/uniform100').absolute() aspect_ratio = None window_size = 1280, 720 resizable = False samples = 16 N = len(planets) # if `N` is below 64 use it as the number of workers. If `N` is larger use larger worker groups consts = { "COMPUTE_SIZE": min(64, N), "N": N, "DT": 20 # DT of 20 seconds } NUM_GROUP = int(ceil(N / 64)) def __init__(self, **kwargs): super().__init__(**kwargs) self.ctx.point_size = 5 # load programs self.calculate_force = self.load_compute('calculate_force.glsl', self.consts) self.move_planets = self.load_compute('move_planets.glsl', self.consts) self.render_program = self.load_program('points.glsl') # set projection matrices projection_matrix = perspective(60, self.wnd.aspect_ratio, .001, 7e15).astype('f4') # you might want to change the z component in order to zoom out further. camera_matrix = translation((0, 0, -.1)).astype('f4') self.render_program['m_projection'].write(projection_matrix.tobytes()) self.render_program['m_camera'].write(camera_matrix.tobytes()) # set properties for making the gif mglw.settings.SCREENSHOT_PATH = self.screenshots_dir self.simulated_time = 0 # take a picture every every 15 days of simulated time self.gif_interval = 60 * 60 * 24 * 15 self._last_capture = float('-inf') # use predefined values positions = np.empty((self.N, 3)).astype('f4') velocities = np.empty((self.N, 3)).astype('f4') force = np.empty((self.N, 3)).astype('f4') mass = np.empty((self.N, 1)).astype('f4') for index, planet in enumerate(planets): velocities[index] = planet['velocity'] positions[index] = planet['location'] force[index] = planet['force'] mass[index] = planet['mass'] colors = np.array( [[255, 255, 0, 255], [171, 159, 111, 255], [128, 111, 43, 255], [8, 138, 41, 255], [255, 111, 0, 255], [171, 153, 138, 255], [145, 118, 94, 255], [214, 199, 186, 255], [67, 111, 181, 255]]) / 255 # interleave data interleaved = list(zip(positions.tolist(), velocities.tolist(), force.tolist(), mass.tolist())) interleaved = [item for l in interleaved for sublist in l for item in sublist] interleaved = struct.pack('<' + '10d' * self.N, *interleaved) # create two buffers to switch between self.buffer1 = self.ctx.buffer(interleaved) self.buffer2 = self.ctx.buffer(reserve=len(interleaved)) self.color_buffer = self.ctx.buffer(colors.astype('f4')) # create a VAO with buffer 1 bound to it to render the balls self.render_vao = VAO(name='render_vao') self.render_vao.buffer(self.buffer1, '3f8 3f8 3f8 1f8', ['in_position', 'in_velocity', 'in_force', 'in_mass']) self.render_vao.buffer(self.color_buffer, '4f', ['in_color']) # bind the buffers to 1 and 0 respectively self._toggle = False self.buffer1.bind_to_storage_buffer(self._toggle) self.buffer2.bind_to_storage_buffer(not self._toggle) def render(self, time, frame_time): if self.simulated_time - self._last_capture > self.gif_interval: # render the result to the screen self.ctx.clear(51 / 255, 51 / 255, 51 / 255) self.ctx.enable(moderngl.BLEND) self.render_vao.render(self.render_program, mode=moderngl.POINTS) # take a screenshot screenshot.create(self.wnd.fbo) self._last_capture = self.simulated_time # run the compute shader self.calculate_force.run(group_x=self.NUM_GROUP) # swap buffers self._swap_buffers() # run the compute shader self.move_planets.run(group_x=self.NUM_GROUP) # swap buffers self._swap_buffers() self.simulated_time += self.consts['DT'] def _swap_buffers(self) -> None: """ Swap the two buffers for the compute-shaders by re-binding them. :return: None """ self._toggle = not self._toggle self.buffer1.bind_to_storage_buffer(self._toggle) self.buffer2.bind_to_storage_buffer(not self._toggle) def load_compute(self, uri, consts): """ read compute shader code and set consts """ with open(self.resource_dir / uri, 'r') as fp: content = fp.read() # feed constant values for key, value in consts.items(): content = content.replace(f"0//%{key}%", str(value)) return self.ctx.compute_shader(content)
class NBodySim(mglw.WindowConfig): gl_version = (4, 3) title = "N-Body Simulation" resource_dir = (Path(__file__) / "../resources").absolute() take_screenshots = False screenshots_dir = (Path(__file__) / "../screen_recordings/uniform100").absolute() aspect_ratio = None window_size = 1280, 720 resizable = False samples = 16 N = len(planets) # if `N` is below 64 use it as the number of workers. If `N` is larger use larger worker groups consts = {"GROUP_SIZE": min(64, N), "N": N, "DT": 60 * 60} # DT of 1 h NUM_GROUP = int(ceil(N / 64)) def __init__(self, **kwargs): super().__init__(**kwargs) self.ctx.point_size = 5 # load programs self.calculate_force = self.load_compute_shader("calculate_force.glsl", defines=self.consts) self.move_planets = self.load_compute_shader("move_planets.glsl", defines=self.consts) self.render_program = self.load_program("points.glsl") # set projection matrices projection_matrix = perspective(60, self.wnd.aspect_ratio, 0.001, 7e15).astype("f4") # you might want to change the z component in order to zoom out further. camera_matrix = translation((0, 0, -4)).astype("f4") self.render_program["m_projection"].write(projection_matrix.tobytes()) self.render_program["m_camera"].write(camera_matrix.tobytes()) # set properties for making the gif mglw.settings.SCREENSHOT_PATH = self.screenshots_dir self.simulated_time = 0 # take a picture every every 15 days of simulated time self.gif_interval = 60 * 60 * 24 * 15 self._last_capture = float("-inf") # use predefined values positions = np.empty((self.N, 3)).astype("f4") velocities = np.empty((self.N, 3)).astype("f4") force = np.empty((self.N, 3)).astype("f4") mass = np.empty((self.N, 1)).astype("f4") for index, planet in enumerate(planets): velocities[index] = planet["velocity"] positions[index] = planet["location"] force[index] = planet["force"] mass[index] = planet["mass"] colors = (np.array([ [255, 255, 0, 255], [171, 159, 111, 255], [128, 111, 43, 255], [8, 138, 41, 255], [255, 111, 0, 255], [171, 153, 138, 255], [145, 118, 94, 255], [214, 199, 186, 255], [67, 111, 181, 255], ]) / 255) # interleave data interleaved = list( zip(positions.tolist(), velocities.tolist(), force.tolist(), mass.tolist())) interleaved = [ item for l in interleaved for sublist in l for item in sublist ] interleaved = struct.pack("<" + "10d" * self.N, *interleaved) # create two buffers to switch between self.buffer1 = self.ctx.buffer(interleaved) self.buffer2 = self.ctx.buffer(reserve=len(interleaved)) self.color_buffer = self.ctx.buffer(colors.astype("f4")) # create a VAO with buffer 1 bound to it to render the balls self.render_vao = VAO(name="render_vao") self.render_vao.buffer( self.buffer1, "3f8 3f8 3f8 1f8", ["in_position", "in_velocity", "in_force", "in_mass"], ) self.render_vao.buffer(self.color_buffer, "4f", ["in_color"]) # bind the buffers to 1 and 0 respectively self._toggle = False self.buffer1.bind_to_storage_buffer(self._toggle) self.buffer2.bind_to_storage_buffer(not self._toggle) def render(self, time, frame_time): # render the result to the screen self.ctx.clear(51 / 255, 51 / 255, 51 / 255) self.ctx.enable(moderngl.BLEND) self.render_vao.render(self.render_program, mode=moderngl.POINTS) if (self.take_screenshots and self.simulated_time - self._last_capture > self.gif_interval): # take a screenshot screenshot.create(self.wnd.fbo) self._last_capture = self.simulated_time # run the compute shader self.calculate_force.run(group_x=self.NUM_GROUP) # swap buffers self._swap_buffers() # run the compute shader self.move_planets.run(group_x=self.NUM_GROUP) # swap buffers self._swap_buffers() self.simulated_time += self.consts["DT"] def _swap_buffers(self) -> None: """ Swap the two buffers for the compute-shaders by re-binding them. :return: None """ self._toggle = not self._toggle self.buffer1.bind_to_storage_buffer(self._toggle) self.buffer2.bind_to_storage_buffer(not self._toggle)
class Boids(moderngl_window.WindowConfig): title = "Boids" resource_dir = (Path(__file__) / '../resources').absolute() aspect_ratio = None window_size = 1024, 1024 resizable = False samples = 4 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.move_program = self.load_program('move_boids.glsl') self.render_boids = self.load_program('render_boids.glsl') self.boid_logic = self.load_program('locality_info.glsl') self.boid_points = self.load_program('boid_points.glsl') self.view_area = self.load_program('render_view_area.glsl') self.texture_1 = self.ctx.texture(self.wnd.buffer_size, 4, dtype='f4') self.fbo_1 = self.ctx.framebuffer(color_attachments=[self.texture_1]) n = 50 # 3**n self.render_boids['size'].value = 0.01 self.render_boids['num_boids'].value = n positions = ((np.random.random_sample((n, 2)) - .5) * 2.) # positions = np.zeros((n, 2)) velocities = f(n, .75) * 0.005 acceleration = np.zeros((n, 2)) pos_vel = np.array([ *zip(positions.tolist(), velocities.tolist(), acceleration.tolist()) ]).flatten().astype('f4') self.boids_buffer_1 = self.ctx.buffer(pos_vel) self.boids_buffer_2 = self.ctx.buffer(reserve=pos_vel.nbytes) self.boids_vao_1 = VAO(name='boids_1') self.boids_vao_1.buffer( self.boids_buffer_1, '2f 2f 2f', ['in_position', 'in_velocity', 'in_acceleration']) self.boids_vao_2 = VAO(name='boids_2') self.boids_vao_2.buffer( self.boids_buffer_2, '2f 2f 2f', ['in_position', 'in_velocity', 'in_acceleration']) self.boid_logic['texture0'].value = 0 # self.view_area['texture0'].value = 0 def render(self, time, frame_time): # self.program['time'].value = time self.ctx.clear(51 / 255, 51 / 255, 51 / 255) # render info to texture self.fbo_1.use() self.boids_vao_1.render(self.boid_points, mode=moderngl.POINTS) # output updated velocity self.boids_vao_1.transform(self.boid_logic, self.boids_buffer_2, mode=moderngl.POINTS) # update their positions self.boids_vao_2.transform(self.move_program, self.boids_buffer_1, mode=moderngl.POINTS) # render boids to screen self.wnd.fbo.use() self.boids_vao_1.render(self.render_boids, mode=moderngl.POINTS)
class Boids(moderngl_window.WindowConfig): """ An attempt to make something boid-list with GL3.3. Not currently working as intended, but still creates and interesting result. For this to properly work we need to split the calculations into several passes. We are doing this the O(n^2) way with the gpu using transform feedback. To make the data avaialble to the vertex shader (looping through it) we copy the vertex buffer every frame to a texture. A better way in the future is to use compute shader. """ title = "Boids" resource_dir = (Path(__file__) / '../../resources').absolute() aspect_ratio = 3440 / 1440 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) MAX_TEX_WIDTH = 8192 N = MAX_TEX_WIDTH * 1 def gen_initial_data(n, x_area=2.0, y_area=2.0): for n in range(n): # position yield (random.random() - 0.5) * x_area yield (random.random() - 0.5) * y_area # Velocity yield (random.random() - 0.5) yield (random.random() - 0.5) # Create geometry data gen = gen_initial_data(N, x_area=self.aspect_ratio * 2 * 0.9, y_area=2.0 * 0.95) data = numpy.fromiter(gen, count=N * 4, dtype='f4') self.boids_buffer_1 = self.ctx.buffer(data.tobytes()) self.boids_buffer_2 = self.ctx.buffer(data=self.boids_buffer_1.read()) self.boids_vao_1 = VAO(name='boids_1', mode=moderngl.POINTS) self.boids_vao_1.buffer(self.boids_buffer_1, '2f 2f', ['in_position', 'in_velocity']) self.boids_vao_2 = VAO(name='boids_2', mode=moderngl.POINTS) self.boids_vao_2.buffer(self.boids_buffer_2, '2f 2f', ['in_position', 'in_velocity']) self.boids_texture = self.ctx.texture( (MAX_TEX_WIDTH, N * 2 // MAX_TEX_WIDTH), components=2, dtype='f4') # Programs self.boids_render_program = self.load_program( 'programs/boids/boids_render.glsl') self.boids_transform_program = self.load_program( 'programs/boids/boids_transform.glsl') # Prepare for rendering self.m_proj = matrix44.create_orthogonal_projection( -self.aspect_ratio, self.aspect_ratio, -1.0, 1.0, -1.0, 1.0, dtype='f4', ) self.boids_render_program['m_proj'].write(self.m_proj.tobytes()) self.boids_transform_program['data'].value = 0 self.boids_transform_program['num_boids'].value = N self.boids_transform_program['tex_width'].value = MAX_TEX_WIDTH def render(self, time, frame_time): self.boids_texture.use(location=0) self.boids_transform_program[ 'timedelta'].value = frame_time # max(frame_time, 1.0 / 60.0) self.boids_vao_1.transform(self.boids_transform_program, self.boids_buffer_2) self.boids_vao_2.render(self.boids_render_program) # Swap around .. self.boids_vao_1, self.boids_vao_2 = self.boids_vao_2, self.boids_vao_1 self.boids_buffer_1, self.boids_buffer_2 = self.boids_buffer_2, self.boids_buffer_1 # Write vertex data into texture so we can interate it in shader self.boids_texture.write(self.boids_buffer_1.read())
class MyWindow(CameraWindow): resource_dir = "." N = 50_000_000 vsync = False def __init__(self, **kwargs): super().__init__(**kwargs) self.camera.projection.update(near=0.1, far=10) # we have to import them here, they require a running context from pycuda import autoinit from pycuda.gl import autoinit import pycuda.gl as cuda_gl # data positions = np.random.random((self.N, 3)).astype("f4") self.pbuffer = self.ctx.buffer(positions) self.cbuffer = self.ctx.buffer( reserve=4 * self.N * 4 ) # self.N*4 floats (4 bytes per float) # render program self.render_prog = self.load_program("file.glsl") # vao self.vao = VAO(mode=mgl.POINTS) self.vao.buffer(self.pbuffer, "3f", ["in_position"]) self.vao.buffer(self.cbuffer, "4f", ["in_color"]) # pycuda stuff self.mod = SourceModule( """ __global__ void color_them(float4 *dest, int n, float time) { const int tid = blockIdx.x * blockDim.x + threadIdx.x; if (tid >= n) {return;} float v = (float)tid / (float)n; dest[tid] = make_float4(__cosf(time*3.), __sinf(time*2.), __sinf(time), 1.); } """, keep=True, cache_dir="./cache", ) self.buffer_cu = cuda_gl.RegisteredBuffer(self.cbuffer._glo) self.color_them = self.mod.get_function("color_them") def process(self, time): # map the buffer dst_mapping = self.buffer_cu.map() ptr = np.uintp(dst_mapping.device_ptr_and_size()[0]) self.color_them( ptr, np.int32(self.N), np.float32(time), grid=(self.N // 1024 + 1, 1, 1), block=(1024, 1, 1), ) cuda_driver.Context.synchronize() dst_mapping.unmap() def render(self, time: float, frame_time: float): self.render_prog["m_proj"].write(self.camera.projection.matrix) self.render_prog["m_camera"].write(self.camera.matrix) self.process(time) self.vao.render(self.render_prog)