3

I am trying to make a game that looks like a retro space shooter, where the lines are 3d wireframe. To achieve 3D in Python, I am using pygame for the window and PyOpenGL to render the objects.

I want to get OpenGL to render to a surface (not the display surface) and then via pygame scale the surface up and render it onto the display surface. This will hopefully give the effect of a low resolution window while being fit to work on modern screens.

The thing that is stopping me from doing this is that OpenGL renders to the display surface, and I cannot find any option that allows me to change what surface it draws to.

So the process should be: OpenGL renders to small surface, pygame scales surface and draws that to the display screen, repeat.

Here is my current code:

def main():
    pygame.init()
    display = (500,500)
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL) # Create display window

    gluPerspective(70, (display[0]/display[1]), 0.1, 50.0) # Setup view

    glTranslatef(0.0,0.0, -5) # Set view position

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT: # If X is clicked
                pygame.quit() # Close
                quit()
        glRotatef(1, 3, 1, 1) # Rotates view
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) # Clears the screen
        Cube() # Renders the cube onto the screen
        pygame.display.flip() # Updates the display
        pygame.time.wait(10)

main()

I've tried creating a surface with exactly the same settings as the display but OpenGL STILL won't render it to that surface.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241

1 Answers1

4

You have to create a Framebuffer Object (before the main loop) with a resoultion smaller than the window resolution. See also Framebuffer Object Extension Examples:

fb_size = [50, 50]

depth_buffer_obj = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer_obj)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fb_size[0], fb_size[1])

color_buffer_obj = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, color_buffer_obj)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, fb_size[0], fb_size[1])

fb_obj = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buffer_obj)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_buffer_obj)

status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if status != GL_FRAMEBUFFER_COMPLETE:
    print("incomplete framebuffer object")
glBindFramebuffer(GL_FRAMEBUFFER, 0)

Se the size of the viewport to the size of the framebuffer, clear the frame buffer and render the cube to the Framebuffer:

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit() 
            quit()

    glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
    glViewport (0, 0, fb_size[0], fb_size[1])
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

    glRotatef(1, 3, 1, 1)
    Cube()

Set the viewport size to the size of the window and use glBlitFramebuffer with the filter parameter GL_NEAREST to copy the pixels from the named framebuffer object to the default framebuffer. Note it is not necessary to clear the default framebuffer, because it is completely overwritten:

while True:

    # .....

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_obj)
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
    glViewport(0, 0, 500, 500)

    glBlitFramebuffer(
        0, 0, fb_size[0], fb_size[1],
        0, 0, 500, 500,
        GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT,
        GL_NEAREST)

    pygame.display.flip()
    pygame.time.wait(10)

Note, the line glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_obj) is not necessary, because fb_obj is bound for read and draw at this point.


If your system down't support glBlitFramebuffer, the you can create a framebuffer with a texture attached to its color plane:

fb_size = [50, 50]

depth_buffer_obj = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer_obj)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fb_size[0], fb_size[1])

#color_buffer_obj = glGenRenderbuffers(1)
#glBindRenderbuffer(GL_RENDERBUFFER, color_buffer_obj)
#glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, fb_size[0], fb_size[1])

color_tex_obj = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, color_tex_obj)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb_size[0], fb_size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None)

fb_obj = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buffer_obj)
#glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_buffer_obj)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_tex_obj, 0)

status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if status != GL_FRAMEBUFFER_COMPLETE:
    print("incomplete framebuffer object")
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glBindTexture(GL_TEXTURE_2D, 0) 

Render to the framebuffer and draw a quad with the the texture over the entire window to the default framebuffer:

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit() 
            quit()

    glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
    glViewport (0, 0, fb_size[0], fb_size[1])
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

    glRotatef(1, 3, 1, 1)
    gluSphere(gluNewQuadric( ), 2.0, 32, 32)

    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    glViewport(0, 0, 500, 500)
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    glMatrixMode(GL_MODELVIEW)
    glPushMatrix()
    glLoadIdentity()

    #glBlitFramebuffer(
    #    0, 0, fb_size[0], fb_size[1],
    #    0, 0, 500, 500,
    #    GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT,
    #    GL_NEAREST)

    glEnable(GL_TEXTURE_2D)
    glBindTexture(GL_TEXTURE_2D, color_tex_obj)
    glBegin(GL_TRIANGLE_FAN)
    glTexCoord2f(0,0)
    glVertex2f(-1,-1)
    glTexCoord2f(1,0)
    glVertex2f(1,-1)
    glTexCoord2f(1,1)
    glVertex2f(1,1)
    glTexCoord2f(0,1)
    glVertex2f(-1,1)
    glEnd()
    glDisable(GL_TEXTURE_2D)

    glMatrixMode(GL_PROJECTION)
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()

    pygame.display.flip()
    pygame.time.wait(10)
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • It's throwing a GLError error saying that "glBlitFramebuffer" is an "invalid operation" at the third to last function/line. – Harry Anthony Dec 12 '18 at 18:54
  • I've looked at some different websites and they say different things about the version of OpenGL PyOpenGL uses. Sourceforge says it uses 1.1 - 4.4 but the Python wiki says it uses up to version 1.2. I printed `GL_VERSION` and it said "`GL_VERSION (7938)`". I couldn't find a way to translate the number that I got into the versions that the websites talk about. Do you know what version I am using? – Harry Anthony Dec 12 '18 at 19:09
  • 3
    @HarryAnthony: `GL_VERSION` is an enum you pass to [`glGetString()`](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetString.xhtml). – genpfault Dec 12 '18 at 20:25