Previously, we setup a simple Python script that displayed a static red triangle using OpenGL. This is great and all, but boring. Let's add the model-view-projection matrix so we can move this triangle around the screen. To make it clear what is going on, I will do all matrix operations directly in the script using Numpy.
The Model-View-Projection Matrix
def perspective(fov, aspect, near, far): n, f = near, far t = np.tan((fov * np.pi / 180) / 2) * near b = - t r = t * aspect l = b * aspect assert abs(n - f) > 0 return np.array(( ((2*n)/(r-l), 0, 0, 0), ( 0, (2*n)/(t-b), 0, 0), ((r+l)/(r-l), (t+b)/(t-b), (f+n)/(n-f), -1), ( 0, 0, 2*f*n/(n-f), 0)))
along with the "look-at" view matrix:
def normalized(v): norm = linalg.norm(v) return v / norm if norm > 0 else v def look_at(eye, target, up): zax = normalized(eye - target) xax = normalized(np.cross(up, zax)) yax = np.cross(zax, xax) x = - xax.dot(eye) y = - yax.dot(eye) z = - zax.dot(eye) return np.array(((xax, yax, zax, 0), (xax, yax, zax, 0), (xax, yax, zax, 0), ( x, y, z, 1)))
The position of the model will not change for now, but in order to be explicit about that we'll use the identity matrix in our calculations. The full model-view-projection matrix is then created:
def create_mvp(program_id, width, height): fov, near, far = 45, 0.1, 100 eye = np.array((4,3,3)) target, up = np.array((0,0,0)), np.array((0,1,0)) projection = perspective(fov, width / height, near, far) view = look_at(eye, target, up) model = np.identity(4) mvp = model @ view @ projection matrix_id = gl.glGetUniformLocation(program_id, 'MVP') return matrix_id, mvp.astype(np.float32)
Uniform variables are static values pushed on the graphics card. They do not change over the execution of a shader program - that is, they are independent of the triangle or fragment the shader is working on. In this simple example, there is only one MVP matrix and we could set it outside the main loop, but we'll quickly want to change it within the loop and so we will set the uniform matrix on each pass.
def main_loop(window, mvp_matrix_id, mvp): while ( glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window) ): gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) # Set the view matrix gl.glUniformMatrix4fv(mvp_matrix_id, 1, False, mvp) # Draw the triangle gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) glfw.swap_buffers(window) glfw.poll_events()
Finally, in the main program, we have to pass in the MVP matrix to the main loop:
if __name__ == '__main__': width, height = 500, 400 with create_main_window(width, height) as window: with create_vertex_buffer(): with load_shaders() as prog_id: mvp_matrix_id, mvp = create_mvp(prog_id, width, height) main_loop(window, mvp_matrix_id, mvp)
And now our triangle is turned and moved around - or is it that we've turned and moved around? We'll worry about that later perhaps.
The full script for this example can be found on gitlab.com/metamost/learning-opengl-with-python.