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
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 __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 load_vertex_buffer(self, fd, material, length): buffer_format, attributes, mesh_attributes = translate_buffer_format( material.vertex_format, self.attr_names) vao = VAO(material.name, mode=moderngl.TRIANGLES) vao.buffer(fd.read(length), buffer_format, attributes) setattr(material, 'vao', vao) setattr(material, 'buffer_format', buffer_format) setattr(material, 'attributes', attributes) setattr(material, 'mesh_attributes', mesh_attributes)
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 load_crate(): """ Load the crate model we want to render in a VAO object. """ scene = pywavefront.Wavefront('resources/scenes/crate.obj') vaos = [] for _, mat in scene.materials.items(): vbo = np.array(mat.vertices, dtype='f4') vao = VAO(mat.name, mode=moderngl.TRIANGLES) vao.buffer(vbo, '2f 3f 3f', ['aTex', 'aNormal', 'aPos']) vaos.append(vao) return vaos[0]
def load(self) -> Scene: """Loads and stl scene/file Returns: Scene: The Scene instance """ path = self.find_scene(self.meta.path) if not path: raise ImproperlyConfigured("Scene '{}' not found".format( self.meta.path)) file_obj = str(path) if file_obj.endswith('.gz'): file_obj = gzip.GzipFile(file_obj) stl_mesh = trimesh.load(file_obj, file_type='stl') scene = Scene(self.meta.resolved_path) scene_mesh = Mesh("mesh") scene_mesh.material = Material("default") vao = VAO("mesh", mode=moderngl.TRIANGLES) vao.buffer(numpy.array(stl_mesh.vertices, dtype='f4'), '3f', ['in_position']) vao.buffer(numpy.array(stl_mesh.vertex_normals, dtype='f4'), '3f', ['in_normal']) vao.index_buffer(numpy.array(stl_mesh.faces, dtype='u4')) scene_mesh.vao = vao scene_mesh.add_attribute('POSITION', 'in_position', 3) scene_mesh.add_attribute('NORMAL', 'in_normal', 3) scene.meshes.append(scene_mesh) scene.root_nodes.append(Node(mesh=scene_mesh)) scene.prepare() return scene
def test_normalized_types(self): """Ensure VAO wrapper can handle normalized types""" prog = self.createProgram() mesh = VAO("test", mode=moderngl.LINES) mesh.buffer(numpy.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0], dtype='i4'), '3ni', 'position') mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0, 0.0, 1.0], dtype='f4'), '3nf', 'normal') mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0], dtype='f4'), '2f', 'uv')
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 __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.program = self.load_program("primes_shader.glsl") self.program["color"].value = (191 / 255, 67 / 255, 69 / 255) self.program["size"].value = 0.005 self.zoom = 100 self.n = 2**25 self.program["n"].value = self.n primes = primes_from2to(self.n) self.buffer = self.ctx.buffer(primes) self.vao1 = VAO(name="boids_1") self.vao1.buffer(self.buffer, "1i", ["in_prime"])
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()
def quad_2d(size=(1.0, 1.0), pos=(0.0, 0.0), normals=True, uvs=True, attr_names=AttributeNames, name=None) -> VAO: """ Creates a 2D quad VAO using 2 triangles with normals and texture coordinates. Keyword Args: size (tuple): width and height pos (float): Center position x and y normals (bool): Include normals in VAO uvs (bool): Include normals in VAO attr_names (AttributeNames): Attrib name config name (str): Optional name for the VAO Returns: A :py:class:`VAO` instance. """ width, height = size xpos, ypos = pos pos_data = numpy.array([ xpos - width / 2.0, ypos + height / 2.0, 0.0, xpos - width / 2.0, ypos - height / 2.0, 0.0, xpos + width / 2.0, ypos - height / 2.0, 0.0, xpos - width / 2.0, ypos + height / 2.0, 0.0, xpos + width / 2.0, ypos - height / 2.0, 0.0, xpos + width / 2.0, ypos + height / 2.0, 0.0, ], dtype=numpy.float32) normal_data = numpy.array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, ], dtype=numpy.float32) uv_data = numpy.array([ 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ], dtype=numpy.float32) vao = VAO(name or "geometry:quad", mode=moderngl.TRIANGLES) vao.buffer(pos_data, '3f', [attr_names.POSITION]) if normals: vao.buffer(normal_data, '3f', [attr_names.NORMAL]) if uvs: vao.buffer(uv_data, '2f', [attr_names.TEXCOORD_0]) return vao
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 __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
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')
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)
def load(self, materials): name_map = { 'POSITION': self.meta.attr_names.POSITION, 'NORMAL': self.meta.attr_names.NORMAL, 'TEXCOORD_0': self.meta.attr_names.TEXCOORD_0, 'TANGENT': self.meta.attr_names.TANGENT, 'JOINTS_0': self.meta.attr_names.JOINTS_0, 'WEIGHTS_0': self.meta.attr_names.WEIGHTS_0, 'COLOR_0': self.meta.attr_names.COLOR_0, } meshes = [] # Read all primitives as separate meshes for now # According to the spec they can have different materials and vertex format for primitive in self.primitives: vao = VAO(self.name, mode=primitive.mode or moderngl.TRIANGLES) # Index buffer component_type, index_vbo = self.load_indices(primitive) if index_vbo is not None: vao.index_buffer( moderngl_window.ctx().buffer(index_vbo.tobytes()), index_element_size=component_type.size, ) attributes = {} vbos = self.prepare_attrib_mapping(primitive) for vbo_info in vbos: dtype, buffer = vbo_info.create() vao.buffer( buffer, " ".join(["{}{}".format(attr[1], DTYPE_BUFFER_TYPE[dtype]) for attr in vbo_info.attributes]), [name_map[attr[0]] for attr in vbo_info.attributes], ) for attr in vbo_info.attributes: attributes[attr[0]] = { 'name': name_map[attr[0]], 'components': attr[1], 'type': vbo_info.component_type.value, } bbox_min, bbox_max = self.get_bbox(primitive) meshes.append(Mesh( self.name, vao=vao, attributes=attributes, material=materials[primitive.material] if primitive.material is not None else None, bbox_min=bbox_min, bbox_max=bbox_max, )) return meshes
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 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 ))
def load(self): """Loads a wavefront/obj file including materials and textures Returns: Scene: The Scene instance """ path = self.find_scene(self.meta.path) logger.info("loading %s", path) if not path: raise ImproperlyConfigured("Scene '{}' not found".format( self.meta.path)) if path.suffix == '.bin': path = path.parent / path.stem VAOCacheLoader.attr_names = self.meta.attr_names data = pywavefront.Wavefront(str(path), create_materials=True, cache=self.meta.cache) scene = Scene(self.meta.resolved_path) texture_cache = {} for _, mat in data.materials.items(): mesh = Mesh(mat.name) # Traditional loader if mat.vertices: buffer_format, attributes, mesh_attributes = translate_buffer_format( mat.vertex_format, self.meta.attr_names) vbo = numpy.array(mat.vertices, dtype='f4') vao = VAO(mat.name, mode=moderngl.TRIANGLES) vao.buffer(vbo, buffer_format, attributes) mesh.vao = vao for attrs in mesh_attributes: mesh.add_attribute(*attrs) # Binary cache loader elif hasattr(mat, 'vao'): mesh = Mesh(mat.name) mesh.vao = mat.vao for attrs in mat.mesh_attributes: mesh.add_attribute(*attrs) else: # Empty continue scene.meshes.append(mesh) mesh.material = Material(mat.name) scene.materials.append(mesh.material) mesh.material.color = mat.diffuse if mat.texture: # A texture can be referenced multiple times, so we need to cache loaded ones texture = texture_cache.get(mat.texture.path) if not texture: # HACK: pywavefront only give us an absolute path rel_path = os.path.relpath(mat.texture.find(), str(path.parent)) logger.info("Loading: %s", rel_path) with texture_dirs([path.parent]): texture = resources.textures.load( TextureDescription( label=rel_path, path=rel_path, mipmap=True, anisotropy=16.0, )) texture_cache[rel_path] = texture mesh.material.mat_texture = MaterialTexture( texture=texture, sampler=None, ) node = Node(mesh=mesh) scene.root_nodes.append(node) # Not supported yet for obj # self.calc_scene_bbox() scene.prepare() return scene
def test_add_illegal_buffer(self): """Attempt to add illegal buffer""" vao = VAO() with self.assertRaises(VAOError): vao.buffer("stuff", '1f', 'in_position')
def cube( size=(1.0, 1.0, 1.0), center=(0.0, 0.0, 0.0), normals=True, uvs=True, name=None, attr_names=AttributeNames, ) -> VAO: """Creates a cube VAO with normals and texture coordinates Keyword Args: width (float): Width of the cube height (float): Height of the cube depth (float): Depth of the cube center: center of the cube as a 3-component tuple normals: (bool) Include normals uvs: (bool) include uv coordinates name (str): Optional name for the VAO attr_names (AttributeNames): Attribute names Returns: A :py:class:`moderngl_window.opengl.vao.VAO` instance """ width, height, depth = size width, height, depth = width / 2.0, height / 2.0, depth / 2.0 # fmt: off pos = numpy.array([ center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] + depth, center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] + width, center[1] - height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] - width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] - width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] + height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, ], dtype=numpy.float32) if normals: normal_data = numpy.array([ -0, 0, 1, -0, 0, 1, -0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, ], dtype=numpy.float32) if uvs: uvs_data = numpy.array([ 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0 ], dtype=numpy.float32) # fmt: on vao = VAO(name or "geometry:cube") # Add buffers vao.buffer(pos, "3f", [attr_names.POSITION]) if normals: vao.buffer(normal_data, "3f", [attr_names.NORMAL]) if uvs: vao.buffer(uvs_data, "2f", [attr_names.TEXCOORD_0]) return vao
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 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()
def sphere( radius=0.5, sectors=32, rings=16, normals=True, uvs=True, name: str = None, attr_names=AttributeNames, ) -> VAO: """Creates a sphere. Keyword Args: radius (float): Radius or the sphere rings (int): number or horizontal rings sectors (int): number of vertical segments normals (bool): Include normals in the VAO uvs (bool): Include texture coordinates in the VAO name (str): An optional name for the VAO attr_names (AttributeNames): Attribute names Returns: A :py:class:`VAO` instance """ R = 1.0 / (rings - 1) S = 1.0 / (sectors - 1) vertices = [0] * (rings * sectors * 3) normals = [0] * (rings * sectors * 3) uvs = [0] * (rings * sectors * 2) v, n, t = 0, 0, 0 for r in range(rings): for s in range(sectors): y = math.sin(-math.pi / 2 + math.pi * r * R) x = math.cos(2 * math.pi * s * S) * math.sin(math.pi * r * R) z = math.sin(2 * math.pi * s * S) * math.sin(math.pi * r * R) uvs[t] = s * S uvs[t + 1] = r * R vertices[v] = x * radius vertices[v + 1] = y * radius vertices[v + 2] = z * radius normals[n] = x normals[n + 1] = y normals[n + 2] = z t += 2 v += 3 n += 3 indices = [0] * rings * sectors * 6 i = 0 for r in range(rings - 1): for s in range(sectors - 1): indices[i] = r * sectors + s indices[i + 1] = (r + 1) * sectors + (s + 1) indices[i + 2] = r * sectors + (s + 1) indices[i + 3] = r * sectors + s indices[i + 4] = (r + 1) * sectors + s indices[i + 5] = (r + 1) * sectors + (s + 1) i += 6 vao = VAO(name or "sphere", mode=mlg.TRIANGLES) vbo_vertices = numpy.array(vertices, dtype=numpy.float32) vao.buffer(vbo_vertices, "3f", [attr_names.POSITION]) if normals: vbo_normals = numpy.array(normals, dtype=numpy.float32) vao.buffer(vbo_normals, "3f", [attr_names.NORMAL]) if uvs: vbo_uvs = numpy.array(uvs, dtype=numpy.float32) vao.buffer(vbo_uvs, "2f", [attr_names.TEXCOORD_0]) vbo_elements = numpy.array(indices, dtype=numpy.uint32) vao.index_buffer(vbo_elements, index_element_size=4) return vao
def test_divisors(self): """Test defining buffers with different divisor types""" 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/v', 'position') mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0, 0.0, 1.0], dtype='f4'), '3f/r', 'normal') mesh.buffer(numpy.array([0.0, 0.0, 1.0, 1.0], dtype='f4'), '2f/i', 'uv') buffer1 = mesh.get_buffer_by_name('position') buffer2 = mesh.get_buffer_by_name('normal') buffer3 = mesh.get_buffer_by_name('uv') attributes = ['position', 'normal', 'uv'] self.assertEqual(buffer1.content(attributes), (buffer1.buffer, '3f/v', 'position')) self.assertEqual(buffer2.content(attributes), (buffer2.buffer, '3f/r', 'normal')) self.assertEqual(buffer3.content(attributes), (buffer3.buffer, '2f/i', 'uv'))
def test_illegal_draw_mode(self): """Create vao with illegal draw mode""" with self.assertRaises(VAOError): VAO(mode=1337)
def bbox(size=(1.0, 1.0, 1.0), name=None, attr_names=AttributeNames): """ Generates a bounding box with (0.0, 0.0, 0.0) as the center. This is simply a box with ``LINE_STRIP`` as draw mode. Keyword Args: size (tuple): x, y, z size of the box name (str): Optional name for the VAO attr_names (AttributeNames): Attribute names Returns: A :py:class:`moderngl_window.opengl.vao.VAO` instance """ width, height, depth = size[0] / 2.0, size[1] / 2.0, size[2] / 2.0 # fmt: off pos = numpy.array([ width, -height, depth, width, height, depth, -width, -height, depth, width, height, depth, -width, height, depth, -width, -height, depth, width, -height, -depth, width, height, -depth, width, -height, depth, width, height, -depth, width, height, depth, width, -height, depth, width, -height, -depth, width, -height, depth, -width, -height, depth, width, -height, -depth, -width, -height, depth, -width, -height, -depth, -width, -height, depth, -width, height, depth, -width, height, -depth, -width, -height, depth, -width, height, -depth, -width, -height, -depth, width, height, -depth, width, -height, -depth, -width, -height, -depth, width, height, -depth, -width, -height, -depth, -width, height, -depth, width, height, -depth, -width, height, -depth, width, height, depth, -width, height, -depth, -width, height, depth, width, height, depth, ], dtype=numpy.float32) # fmt: on vao = VAO(name or "geometry:cube", mode=moderngl.LINE_STRIP) vao.buffer(pos, "3f", [attr_names.POSITION]) return vao
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))