Пример #1
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)
Пример #2
0
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))
Пример #3
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())