Beispiel #1
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
Beispiel #2
0
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)
Beispiel #3
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)
Beispiel #4
0
    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
Beispiel #5
0
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]
Beispiel #6
0
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)
Beispiel #7
0
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()
Beispiel #8
0
    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()
Beispiel #9
0
 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')
Beispiel #10
0
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
Beispiel #11
0
    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'))
Beispiel #12
0
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())
Beispiel #13
0
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
Beispiel #14
0
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))
Beispiel #15
0
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
Beispiel #16
0
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 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
        ))
Beispiel #18
0
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)
Beispiel #19
0
    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
Beispiel #20
0
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)
Beispiel #21
0
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
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))
Beispiel #23
0
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)
Beispiel #24
0
 def test_add_illegal_buffer(self):
     """Attempt to add illegal buffer"""
     vao = VAO()
     with self.assertRaises(VAOError):
         vao.buffer("stuff", '1f', 'in_position')