Bonus - Procedural / particle animation

Objectives:

  • Understand how to reload dynamically any attribute tables on the fly

Pre-requisites:

Principle

Any attribute can be updated from the python code to the GPU during the rendering loop, in the draw call of any object.

We provide an example in the code below, with the PythonAnimation class, which shows how this may be done with the simple case of four particles given a purely sinusoidal trajectory.

class PointAnimation(Mesh):
    """ Simple animated particle set """
    def __init__(self, shader):
        # render points with wide size to be seen
        GL.glPointSize(10)

        # instantiate and store 4 points to animate
        self.coords = ((-1, -1, -5), (1, -1, -5), (1, 1, -5), (-1, 1, -5))

        # send as position attribute to GPU, set uniform variable global_color.
        # GL_STREAM_DRAW tells OpenGL that attributes of this object
        # will change on a per-frame basis (as opposed to GL_STATIC_DRAW)
        super().__init__(shader, attributes=dict(position=self.coords),
                         usage=GL.GL_STREAM_DRAW, global_color=(0.5, 0.5, 0.8))

    def draw(self, primitives=GL.GL_POINTS, attributes=None, **uniforms):
        # compute a sinusoidal x-coord displacement, different for each point.
        # this could be any per-point function: build your own particle system!
        dp = [[sin(i + glfw.get_time()), 0, 0] for i in range(len(self.coords))]

        # update position buffer on CPU, send to GPU attribute to draw with it
        coords = np.array(self.coords, 'f') + np.array(dp, 'f')
        super().draw(primitives, attributes=dict(position=coords), **uniforms)

You can update any attribute buffer you had initialized in the constructor, but you don’t have to update all of them, you may choose to update only a subset.

You may extend this code in any direction of your choice for the project.

Code base

New file:

viewer.py

Previously used files from practical 2:

color.vert color.frag core.py transform.py