In this Python Modern Opengl we are going to talk about Perspective Projection. so we are using some codes from the previous articles. especially we are using the codes from the below link.
You need an image texture that should be 512 x 512 in your project directory. Also you need to install a library that is called pip install pillow , Iam using this image a Texture.
Python Modern Opengl Perspective Projection
Let’s create our example, so now this is the complete code for Python Modern Opengl Perspective Projection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
import glfw from OpenGL.GL import * import OpenGL.GL.shaders import numpy as np import pyrr from PIL import Image def main(): if not glfw.init(): return window = glfw.create_window(720, 600, "Codeloop.org - Pyopengl Perspective Projection", None, None) if not window: glfw.terminate() return glfw.make_context_current(window) # positions colors texture coords cube = [ -0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, -0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, -0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, -0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5, 0.5, -0.5, 0.0, 0.0, 1.0, 1.0, 1.0, -0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, -0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, -0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, -0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, -0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0 ] cube = np.array(cube, dtype=np.float32) indices = [ 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16, 17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20 ] indices = np.array(indices, dtype=np.uint32) VERTEX_SHADER = """ #version 330 core layout(location = 0) in vec3 position; layout(location = 1) in vec3 color; layout(location = 2) in vec2 InTexCoords; out vec3 newColor; out vec2 OutTexCoords; uniform mat4 transform; uniform mat4 view; uniform mat4 model; uniform mat4 projection; void main() { gl_Position = projection * view * model * transform * vec4(position, 1.0f); newColor = color; OutTexCoords = InTexCoords; } """ FRAGMENT_SHADER = """ #version 330 core in vec3 newColor; in vec2 OutTexCoords; out vec4 outColor; uniform sampler2D samplerTex; void main() { outColor = texture(samplerTex, OutTexCoords); } """ shader = OpenGL.GL.shaders.compileProgram( OpenGL.GL.shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER), OpenGL.GL.shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER) ) VBO = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, VBO) glBufferData(GL_ARRAY_BUFFER, cube.nbytes, cube, GL_STATIC_DRAW) EBO = glGenBuffers(1) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO) glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW) position = glGetAttribLocation(shader, 'position') if position != -1: glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 8, ctypes.c_void_p(0)) glEnableVertexAttribArray(position) color = glGetAttribLocation(shader, 'color') if color != -1: glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 8, ctypes.c_void_p(12)) glEnableVertexAttribArray(color) texCoords = glGetAttribLocation(shader, 'InTexCoords') if texCoords != -1: glVertexAttribPointer(texCoords, 2, GL_FLOAT, GL_FALSE, cube.itemsize * 8, ctypes.c_void_p(24)) glEnableVertexAttribArray(texCoords) texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, texture) 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_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) image = Image.open("wood.jpg") img_data = np.array(list(image.getdata()), np.uint8) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data) glGenerateMipmap(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, 0) glUseProgram(shader) glBindTexture(GL_TEXTURE_2D, texture) glUniform1i(glGetUniformLocation(shader, "samplerTex"), 0) glClearColor(0.0, 0.0, 0.0, 1.0) glEnable(GL_DEPTH_TEST) view = pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0, 0.0, -3.0])) projection = pyrr.matrix44.create_perspective_projection_matrix(45.0, 720 / 600, 0.1, 100.0) model = pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0, 0.0, 0.0])) view_loc = glGetUniformLocation(shader, "view") proj_loc = glGetUniformLocation(shader, "projection") model_loc = glGetUniformLocation(shader, "model") glUniformMatrix4fv(view_loc, 1, GL_FALSE, view) glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection) glUniformMatrix4fv(model_loc, 1, GL_FALSE, model) while not glfw.window_should_close(window): glfw.poll_events() glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) rot_x = pyrr.Matrix44.from_x_rotation(0.5 * glfw.get_time()) rot_y = pyrr.Matrix44.from_y_rotation(0.8 * glfw.get_time()) transformLoc = glGetUniformLocation(shader, "transform") glUniformMatrix4fv(transformLoc, 1, GL_FALSE, rot_x @ rot_y) glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None) glfw.swap_buffers(window) glfw.terminate() if __name__ == "__main__": main() |
So in the above code iam not going to describe all codes because i have already describe these codes in the previous articles, also if you want to learn more you can check the previous articles from the above links. but i will describe some important of them.
These are the vertex and fragment shaders
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
VERTEX_SHADER = """ #version 330 in vec3 position; in vec3 color; in vec2 InTexCoords; out vec3 newColor; out vec2 OutTexCoords; uniform mat4 transform; uniform mat4 view; uniform mat4 model; uniform mat4 projection; void main() { gl_Position = projection * view * model * transform * vec4(position, 1.0f); newColor = color; OutTexCoords = InTexCoords; } """ FRAGMENT_SHADER = """ #version 330 in vec3 newColor; in vec2 OutTexCoords; out vec4 outColor; uniform sampler2D samplerTex; void main() { outColor = texture(samplerTex, OutTexCoords); } """ |
So at the top of vertex shader we have the version for the shader and we have three input values for our position, color and texture coordinates. and we have two output value for our color and texture coordinates. in the fragment shader we have two input value for our new color and the texture coordinates, and also we have and output value for color with uniform variable.
What is Uniform Variable?
So a uniform is a global Shader variable declared with the “uniform” storage qualifier. These act as parameters that the user of a shader program can pass to that program. Their values are stored in a program object. Uniforms are so named because they do not change from one shader invocation to the next within a particular rendering call.this makes them unlike shader stage inputs and outputs, which are often different for each invocation of a shader stage.
What Are Shaders?
Shaders are little programs that rest on the GPU. These programs are run for each specific section of the graphics pipeline. so In a basic sense, shaders are nothing more than programs transforming inputs to outputs. Shaders are also very isolated programs
Vertex Shader
The vertex shader is a program on the graphics card that processes each vertex and its attributes as they appear in the vertex array. Its duty is to output the final vertex position in device coordinates and to output any data the fragment shader requires. That’s why the 3D transformation should take place here. The fragment shader depends on attributes like the color and texture coordinates, which will usually be passed from input to output without any calculations. Remember that our vertex position is already specified as device coordinates and no other attributes exist, so the vertex shader will be fairly bare bones.
Fragment Shader
the output from the vertex shader is interpolated over all the pixels on the screen covered by a primitive. so These pixels are called fragments and this is what the fragment shader operates on. just like the vertex shader it has one mandatory output, the final color of a fragment. It’s up to you to write the code for computing this color from vertex colors, texture coordinates and any other data coming from the vertex shader.
OK now this is the important point of this article that we are going to talk about Perspective Projection. before this we are going to talk about Orthographic Projection.
Orthographic Projection
An orthographic projection matrix defines a cube-like frustum box that defines the clipping space where each vertex outside this box is clipped. When creating an orthographic projection matrix we specify the width, height and length of the visible frustum. All the coordinates that end up inside this frustum after transforming them to clip space with the orthographic projection matrix won’t be clipped. The frustum looks a bit like a container:
also frustum defines the visible coordinates and is specified by a width, a height and a near and far plane. Any coordinate in front of the near plane is clipped and the same applies to coordinates behind the far plane. The orthographic frustum directly maps all coordinates inside the frustum to normalized device coordinates since the w
component of each vector is untouched; if the w
component is equal to 1.0
perspective division doesn’t change the coordinates.
Perspective Projection
If you ever were to enjoy the graphics the real life has to offer you’ll notice that objects that are farther away appear much smaller. This weird effect is something we call perspective. Perspective is especially noticeable when looking down the end of an infinite motorway or railway as seen in the following image:
So now in here we have the code for the Perspective Projection
1 2 3 4 5 6 7 8 9 10 11 |
view =pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0,0.0,-3.0] )) projection = pyrr.matrix44.create_perspective_projection(20.0, 720/600, 0.1, 100.0) model = pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0,0.0,0.0])) view_loc = glGetUniformLocation(shader, "view") proj_loc = glGetUniformLocation(shader, "projection") model_loc = glGetUniformLocation(shader, "model") glUniformMatrix4fv(view_loc, 1, GL_FALSE, view) glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection) glUniformMatrix4fv(model_loc, 1, GL_FALSE, model) |
So run the complete code and this will be the result
Subscribe and Get Free Video Courses & Articles in your Email
After adding the missing glfw import the code breaks
OpenGL.error.GLError: GLError(
err = 1281,
description = b’invalid value’,
baseOperation = glVertexAttribPointer,
pyArgs = (
-1,
3,
GL_FLOAT,
GL_FALSE,
32,
c_void_p(12),
),
cArgs = (
-1,
3,
GL_FLOAT,
GL_FALSE,
32,
c_void_p(12),
),
cArguments = (
-1,
3,
GL_FLOAT,
GL_FALSE,
32,
c_void_p(12),
)
)
did you copied the code from above
I copied the code directly from these tutorials but I had this error in the last 3 tutorials: texturing rectangle, texturing cube and projection cube.
I searched on internet and the problem is that glGetAttribLocation (the last valid funtion is returning -1 instead of the expected 1 when getting the color from the shader because it consider it not active. I found two solutions:
The simplest: delete/comment lines 133 to 135
The better: replace lines 127 to 140 by:
# set the position of shader
position = 0
glBindAttribLocation(shader, position, ‘position’)
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 8, ctypes.c_void_p(0))
glEnableVertexAttribArray(position)
# set the color of shader
color = 1
glBindAttribLocation(shader, color, ‘color’)
glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 8, ctypes.c_void_p(12))
glEnableVertexAttribArray(color)
# set the texCoords of shader
texCoords = 2
glBindAttribLocation(shader, texCoords, “InTexCoords”)
glVertexAttribPointer(texCoords, 2, GL_FLOAT, GL_FALSE, cube.itemsize * 8, ctypes.c_void_p(24))
glEnableVertexAttribArray(texCoords)
If you look carefully you will notice that there is only 1 change (same change for the three attributes), instead of getting the attribute location you are binding it because you already know the order of the vectors: first position (location 0), second color (location 1), third texture (location 2)
the glVertexAttribPointer and glEnableVertexAttribArray is the same.
PS.- Same solution for the three tutorials glBindAttribLocation instead of glGetAttribLocation
This article is updated and now it is working