Render To Texture with Python 3 and Pyglet
This post will show you how to set up a framebuffer in OpenGL using Python and Pyglet.
See my previous post for some tips how to use OpenGL functions from Python.
Structure of the Program
As this is a demo program I did not structure it with python classes. However the complexity is already big enough to see that this flat structure is not suitable for any larger program.
The following illustrates the drawing steps. The OpenGL objects in the grey boxes are globals
in my python program.
There are two steps: first a triangle is rendered to a texture using a framebuffer, and second the texture is copied to the screen. Each step uses
- A Shader Program containing the GLSL code used for rendering
- A Vertex Buffer containing vertex data for the objects being drawn
- A Vertex Array Object containing the information how the data
in the buffer is linked to the vertex attributes in the shader program
The full program is available on github.
Scene description
In this example the framebuffer is used to render the triangle from the previous post in a very low resolution (30×20 px). This Image is then used for texturing two rectangles in the main window.
Note that both in the framebuffer and on the main screen I do not use any vertex transformation. Therefore only the x and y coordinates are used. The lower left corner of the screen has coordinates (-1, -1) and the upper right corner (1, 1). Texture coordinates however run from 0 to 1.
Vertex Array Objects
In the previous program there was only one GLSL program and only one vertex buffer. Therefore it was convenient to store the vertex attribute bindings in the default OpenGL state. In this program however we switch back and forth between the triangle rendering program which uses color attributes for the vertices and the copy to screen program which uses texture coordinates.
A vertex array object stores the information how data in a buffer is linked to vertex attributes of the shader program. So I can define this connection once (using glVertexAttribPointer) and then I only need to select the correct vertex array object when drawing.
def setup_render_vertexbuffer(): ... gl.glBindVertexArray(render_vao) gl.glEnableVertexAttribArray(loc_position) gl.glEnableVertexAttribArray(loc_color) # the following bindings will be remembered by the vao gl.glBindBuffer(gl.GL_ARRAY_BUFFER, render_vertexbuffer) gl.glVertexAttribPointer(loc_position, 2, gl.GL_FLOAT, False, ctypes.sizeof(COLOR_VERTEX), ctypes.c_void_p(COLOR_VERTEX.position.offset)) gl.glVertexAttribPointer(loc_color, 4, gl.GL_FLOAT, False, ctypes.sizeof(COLOR_VERTEX), ctypes.c_void_p(COLOR_VERTEX.color.offset)) gl.glBindVertexArray(0) def render_to_texture(): ... # draw using the vertex array for vertex information gl.glBindVertexArray(render_vao) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) gl.glBindVertexArray(0)
Gotcha: The Viewport
OpenGL needs to know the size of the rendering target. In this program there are two targets, so you need to tell OpenGL the new size every time you switch the drawing target.
def render_to_texture(): gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, framebuffer) gl.glViewport(0, 0, FB_WIDTH, FB_HEIGHT) ... def copy_texture_to_screen(): gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) gl.glViewport(0, 0, window.width, window.height) ...
Putting it together
The program has 300 lines, and as I mentioned before, it could benefit well from more structure and also from deduplication of code. Setting up two GLSL shader programs, two vertex array objects, two vertex buffers and two drawing steps has lead to some duplication, which could be avoided if these were all each two instances of a shader class, a vertex array class and a vertex buffer class.
def draw(): render_to_texture() copy_texture_to_screen() def main(): global window window = pyglet.window.Window() setup_framebuffer() setup_render_program() setup_render_vertexbuffer() setup_copy_program() setup_copy_vertexbuffer() window.on_draw = draw pyglet.clock.schedule_interval(lambda dt:None, 0.01) pyglet.app.run()
Try out the complete program. You can get it on github.
Please comment below if something does not work or what I could explain better.