Search code examples
pythonopengltransparencypygletpyopengl

How to clear screen with a transparency layer instead of a solid color in pyglet/pyopengl


I am new to pyglet, and I am trying to draw an object that moves around. I want to show the trajectory of this object, but make the older positions fade gradually. Therefore I am trying to clear the screen in every draw with a semitransparent color.

But, for some reason, the window clear method is opaque, even when I set the color to be almost transparent. The code works if I omit the clear and draw a semitransparent square as big as the window, but I don't understand why the clear method produces a solid color.

This is my code:

import pyglet


class GameWindow(pyglet.window.Window):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.px, self.py = 10, 10
        self.vx, self.vy = 15, 60
        self.ax, self.ay = 0, -5

    def update(self, dt):
        self.px, self.py = self.px + self.vx * dt, self.py + self.vy * dt
        self.vx, self.vy = self.vx + self.ax * dt, self.vy + self.ay * dt

    def on_draw(self):
        # clear screen with semitransparent layer
        pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
        pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
        pyglet.gl.glClearColor(0, 0, 0, 0.01)  # black and almost transparent
        self.clear()

        # calculate and draw object
        relative_vertices = [[20, 20], [20, -20], [-20, -20], [-20, 20]]
        vertices = []
        for v in relative_vertices:
            vertices.append(self.px + v[0])
            vertices.append(self.py + v[1])
        colors = [255, 0, 0] * len(relative_vertices)
        pyglet.graphics.vertex_list(len(relative_vertices), ("v2f", vertices), ("c3B", colors)).draw(pyglet.gl.GL_POLYGON)


if __name__ == "__main__":
    config = pyglet.gl.Config(double_buffer=False)
    window = GameWindow(400, 400, config=config)
    pyglet.clock.schedule_interval(window.update, 1/120)
    pyglet.app.run()

For some reason, I can draw transparent objects, but the background when clear is used is opaque. Why is this not working?

Also, I would like to know if there is a way to make all background pixels more transparent maintaining their color, instead of having to draw a semitransparent black layer on top of them.


Solution

  • PyGlet is a OpenGL wrapper. The clear method use glClear. glClear does nothing else than copy the current clear color (which is set by glClearColor ) to all pixels of the color plane (of the current framebuffer). Nothing is blended with this operations.

    You've to blend a rectangle on the entire window, instead of using clear:

    # pyglet.gl.glClearColor(0, 0, 0, 0.01)  # <--- delete
    # self.clear()                           # <--- delete
    cover_vertices = [0, 0, 400, 0, 400, 400, 0, 400]
    cover_colors = [0, 0, 0, 2] * (len(cover_vertices)//2)
    pyglet.graphics.vertex_list(len(cover_vertices)//2, ("v2f", cover_vertices), ("c4B", cover_colors)).draw(pyglet.gl.GL_POLYGON)
    


    Is there a way to make this look better? I had already tried this but I was not happy with the result. I would expect the dark red trajectory to slowly fade into black again

    In this case the blend mode has to be changed. Draw a rectangle which covers the screen and is subtracted from the destination color. This can be achieved by setting the glBlendEquation parameter to GL_FUNC_REVERSE_SUBTRACT.
    Finally draw the red rectangle on top, with blending mode disabled:

    # subtract [1, 1, 1, 0] from all pixels in the frambuffer
            pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
            pyglet.gl.glBlendEquation(pyglet.gl.GL_FUNC_REVERSE_SUBTRACT)
            pyglet.gl.glBlendFunc(pyglet.gl.GL_ONE, pyglet.gl.GL_ONE)
            cover_vertices = [0, 0, 400, 0, 400, 400, 0, 400]
            cover_colors = [1, 1, 1, 0] * (len(cover_vertices)//2)
            pyglet.graphics.vertex_list(len(cover_vertices)//2, ("v2f", cover_vertices), ("c4B", cover_colors)).draw(pyglet.gl.GL_POLYGON)
    
            # draw the object on top of the frambuffer
            pyglet.gl.glDisable(pyglet.gl.GL_BLEND)
            relative_vertices = [[20, 20], [20, -20], [-20, -20], [-20, 20]]
            vertices = []
            for v in relative_vertices:
                vertices.append(self.px + v[0])
                vertices.append(self.py + v[1])
            colors = [255, 0, 0] * len(relative_vertices)
            pyglet.graphics.vertex_list(len(relative_vertices), ("v2f", vertices), ("c3B", colors)).draw(pyglet.gl.GL_POLYGON)