├── .gitignore ├── 3rdparty └── cimg │ ├── build.bat │ └── cimg.i ├── Content ├── brain.obj ├── cube.obj ├── eva.bmp ├── eva.obj ├── suzanne.obj ├── uvmap.bmp ├── uvmap_suzanne.bmp ├── uvtemplate.DDS ├── uvtemplate.bmp ├── uvtemplate.png └── uvtemplate.tga ├── Shaders ├── Common │ ├── StandardShading.fragmentshader │ └── StandardShading.vertexshader ├── Tutorial10 │ ├── StandardShading.vertexshader │ └── StandardTransparentShading.fragmentshader ├── Tutorial2 │ ├── SimpleFragmentShader.fragmentshader │ └── SimpleVertexShader.vertexshader ├── Tutorial3 │ ├── SimpleTransform.vertexshader │ └── SingleColor.fragmentshader ├── Tutorial4 │ ├── ColorFragmentShader.fragmentshader │ └── TransformVertexShader.vertexshader ├── Tutorial5 │ ├── TextureFragmentShader.fragmentshader │ └── TransformVertexShader.vertexshader ├── Tutorial6 │ ├── TextureFragmentShader.fragmentshader │ └── TransformVertexShader.vertexshader ├── Tutorial7 │ ├── TextureFragmentShader.fragmentshader │ └── TransformVertexShader.vertexshader ├── Tutorial8 │ ├── StandardShading.fragmentshader │ └── StandardShading.vertexshader └── Tutorial9 │ ├── StandardShading.fragmentshader │ └── StandardShading.vertexshader ├── common.py ├── controls.py ├── csgl ├── __init__.py ├── mat4.py ├── multimethods.py ├── test_mat4.py ├── test_vec3.py ├── test_vec4.py ├── vec3.py └── vec4.py ├── example_screenshot.py ├── glew.py ├── glew_wish.py ├── multimethods.py ├── notes.txt ├── objloader.py ├── readme.md ├── texture.py ├── tutorial1.py ├── tutorial10.py ├── tutorial2.py ├── tutorial3.py ├── tutorial4.py ├── tutorial5.py ├── tutorial6.py ├── tutorial7.py ├── tutorial8.py ├── tutorial9.py ├── utilities.py ├── vao_test.py └── vboindexer.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | Content/male_apose_closed2.obj 3 | Content/male_apose_closed.obj 4 | glfw3.dll 5 | test.bmp 6 | out.txt -------------------------------------------------------------------------------- /3rdparty/cimg/build.bat: -------------------------------------------------------------------------------- 1 | cl.exe /I C:\Python27\include /D_USRDLL /D_WINDLL cimg_wrap.c /link /DLL /OUT:cimg.dll -------------------------------------------------------------------------------- /3rdparty/cimg/cimg.i: -------------------------------------------------------------------------------- 1 | %module cimg 2 | %{ 3 | /* Includes the header in the wrapper code */ 4 | #include "CImg.h" 5 | %} 6 | 7 | /* Parse the header file to generate wrappers */ 8 | %include "CImg.h" -------------------------------------------------------------------------------- /Content/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender3D v249 OBJ File: untitled.blend 2 | # www.blender3d.org 3 | mtllib cube.mtl 4 | v 1.000000 -1.000000 -1.000000 5 | v 1.000000 -1.000000 1.000000 6 | v -1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 -1.000000 8 | v 1.000000 1.000000 -1.000000 9 | v 0.999999 1.000000 1.000001 10 | v -1.000000 1.000000 1.000000 11 | v -1.000000 1.000000 -1.000000 12 | vt 0.748573 0.750412 13 | vt 0.749279 0.501284 14 | vt 0.999110 0.501077 15 | vt 0.999455 0.750380 16 | vt 0.250471 0.500702 17 | vt 0.249682 0.749677 18 | vt 0.001085 0.750380 19 | vt 0.001517 0.499994 20 | vt 0.499422 0.500239 21 | vt 0.500149 0.750166 22 | vt 0.748355 0.998230 23 | vt 0.500193 0.998728 24 | vt 0.498993 0.250415 25 | vt 0.748953 0.250920 26 | vn 0.000000 0.000000 -1.000000 27 | vn -1.000000 -0.000000 -0.000000 28 | vn -0.000000 -0.000000 1.000000 29 | vn -0.000001 0.000000 1.000000 30 | vn 1.000000 -0.000000 0.000000 31 | vn 1.000000 0.000000 0.000001 32 | vn 0.000000 1.000000 -0.000000 33 | vn -0.000000 -1.000000 0.000000 34 | usemtl Material_ray.png 35 | s off 36 | f 5/1/1 1/2/1 4/3/1 37 | f 5/1/1 4/3/1 8/4/1 38 | f 3/5/2 7/6/2 8/7/2 39 | f 3/5/2 8/7/2 4/8/2 40 | f 2/9/3 6/10/3 3/5/3 41 | f 6/10/4 7/6/4 3/5/4 42 | f 1/2/5 5/1/5 2/9/5 43 | f 5/1/6 6/10/6 2/9/6 44 | f 5/1/7 8/11/7 6/10/7 45 | f 8/11/7 7/12/7 6/10/7 46 | f 1/2/8 2/9/8 3/13/8 47 | f 1/2/8 3/13/8 4/14/8 48 | -------------------------------------------------------------------------------- /Content/eva.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/eva.bmp -------------------------------------------------------------------------------- /Content/uvmap.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/uvmap.bmp -------------------------------------------------------------------------------- /Content/uvmap_suzanne.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/uvmap_suzanne.bmp -------------------------------------------------------------------------------- /Content/uvtemplate.DDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/uvtemplate.DDS -------------------------------------------------------------------------------- /Content/uvtemplate.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/uvtemplate.bmp -------------------------------------------------------------------------------- /Content/uvtemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/uvtemplate.png -------------------------------------------------------------------------------- /Content/uvtemplate.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/Content/uvtemplate.tga -------------------------------------------------------------------------------- /Shaders/Common/StandardShading.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | in vec3 Position_worldspace; 6 | in vec3 Normal_cameraspace; 7 | in vec3 EyeDirection_cameraspace; 8 | in vec3 LightDirection_cameraspace; 9 | 10 | // Ouput data 11 | out vec3 color; 12 | 13 | // Values that stay constant for the whole mesh. 14 | uniform mat4 MV; 15 | uniform vec3 LightPosition_worldspace; 16 | 17 | void main(){ 18 | 19 | // Light emission properties 20 | // You probably want to put them as uniforms 21 | vec3 LightColor = vec3(1,1,1); 22 | float LightPower = 50.0f; 23 | 24 | // Material properties 25 | vec3 MaterialDiffuseColor = vec3(0.5,0.5,0.5); 26 | vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor; 27 | vec3 MaterialSpecularColor = vec3(0.3,0.3,0.3); 28 | 29 | // Distance to the light 30 | float distance = length( LightPosition_worldspace - Position_worldspace ); 31 | 32 | // Normal of the computed fragment, in camera space 33 | vec3 n = normalize( Normal_cameraspace ); 34 | // Direction of the light (from the fragment to the light) 35 | vec3 l = normalize( LightDirection_cameraspace ); 36 | // Cosine of the angle between the normal and the light direction, 37 | // clamped above 0 38 | // - light is at the vertical of the triangle -> 1 39 | // - light is perpendicular to the triangle -> 0 40 | // - light is behind the triangle -> 0 41 | float cosTheta = clamp( dot( n,l ), 0,1 ); 42 | 43 | // Eye vector (towards the camera) 44 | vec3 E = normalize(EyeDirection_cameraspace); 45 | // Direction in which the triangle reflects the light 46 | vec3 R = reflect(-l,n); 47 | // Cosine of the angle between the Eye vector and the Reflect vector, 48 | // clamped to 0 49 | // - Looking into the reflection -> 1 50 | // - Looking elsewhere -> < 1 51 | float cosAlpha = clamp( dot( E,R ), 0,1 ); 52 | 53 | color = 54 | // Ambient : simulates indirect lighting 55 | MaterialAmbientColor + 56 | // Diffuse : "color" of the object 57 | MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) + 58 | // Specular : reflective highlight, like a mirror 59 | MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Shaders/Common/StandardShading.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec3 vertexNormal_modelspace; 6 | 7 | // Output data ; will be interpolated for each fragment. 8 | out vec2 UV; 9 | out vec3 Position_worldspace; 10 | out vec3 Normal_cameraspace; 11 | out vec3 EyeDirection_cameraspace; 12 | out vec3 LightDirection_cameraspace; 13 | 14 | // Values that stay constant for the whole mesh. 15 | uniform mat4 MVP; 16 | uniform mat4 V; 17 | uniform mat4 M; 18 | uniform vec3 LightPosition_worldspace; 19 | 20 | void main(){ 21 | 22 | // Output position of the vertex, in clip space : MVP * position 23 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 24 | 25 | // Position of the vertex, in worldspace : M * position 26 | Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz; 27 | 28 | // Vector that goes from the vertex to the camera, in camera space. 29 | // In camera space, the camera is at the origin (0,0,0). 30 | vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz; 31 | EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace; 32 | 33 | // Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity. 34 | vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz; 35 | LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace; 36 | 37 | // Normal of the the vertex, in camera space 38 | Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not. 39 | } 40 | -------------------------------------------------------------------------------- /Shaders/Tutorial10/StandardShading.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec2 vertexUV; 6 | layout(location = 2) in vec3 vertexNormal_modelspace; 7 | 8 | // Output data ; will be interpolated for each fragment. 9 | out vec2 UV; 10 | out vec3 Position_worldspace; 11 | out vec3 Normal_cameraspace; 12 | out vec3 EyeDirection_cameraspace; 13 | out vec3 LightDirection_cameraspace; 14 | 15 | // Values that stay constant for the whole mesh. 16 | uniform mat4 MVP; 17 | uniform mat4 V; 18 | uniform mat4 M; 19 | uniform vec3 LightPosition_worldspace; 20 | 21 | void main(){ 22 | 23 | // Output position of the vertex, in clip space : MVP * position 24 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 25 | 26 | // Position of the vertex, in worldspace : M * position 27 | Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz; 28 | 29 | // Vector that goes from the vertex to the camera, in camera space. 30 | // In camera space, the camera is at the origin (0,0,0). 31 | vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz; 32 | EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace; 33 | 34 | // Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity. 35 | vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz; 36 | LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace; 37 | 38 | // Normal of the the vertex, in camera space 39 | Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not. 40 | 41 | // UV of the vertex. No special space for this one. 42 | UV = vertexUV; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Shaders/Tutorial10/StandardTransparentShading.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | in vec3 Position_worldspace; 6 | in vec3 Normal_cameraspace; 7 | in vec3 EyeDirection_cameraspace; 8 | in vec3 LightDirection_cameraspace; 9 | 10 | // Ouput data 11 | out vec4 color; 12 | 13 | // Values that stay constant for the whole mesh. 14 | uniform sampler2D myTextureSampler; 15 | uniform mat4 MV; 16 | uniform vec3 LightPosition_worldspace; 17 | 18 | void main(){ 19 | 20 | // Light emission properties 21 | // You probably want to put them as uniforms 22 | vec3 LightColor = vec3(1,1,1); 23 | float LightPower = 50.0f; 24 | 25 | // Material properties 26 | vec3 MaterialDiffuseColor = texture2D( myTextureSampler, UV ).rgb; 27 | vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor; 28 | vec3 MaterialSpecularColor = vec3(0.3,0.3,0.3); 29 | 30 | // Distance to the light 31 | float distance = length( LightPosition_worldspace - Position_worldspace ); 32 | 33 | // Normal of the computed fragment, in camera space 34 | vec3 n = normalize( Normal_cameraspace ); 35 | // Direction of the light (from the fragment to the light) 36 | vec3 l = normalize( LightDirection_cameraspace ); 37 | // Cosine of the angle between the normal and the light direction, 38 | // clamped above 0 39 | // - light is at the vertical of the triangle -> 1 40 | // - light is perpendicular to the triangle -> 0 41 | // - light is behind the triangle -> 0 42 | float cosTheta = clamp( dot( n,l ), 0,1 ); 43 | 44 | // Eye vector (towards the camera) 45 | vec3 E = normalize(EyeDirection_cameraspace); 46 | // Direction in which the triangle reflects the light 47 | vec3 R = reflect(-l,n); 48 | // Cosine of the angle between the Eye vector and the Reflect vector, 49 | // clamped to 0 50 | // - Looking into the reflection -> 1 51 | // - Looking elsewhere -> < 1 52 | float cosAlpha = clamp( dot( E,R ), 0,1 ); 53 | 54 | color.rgb = 55 | // Ambient : simulates indirect lighting 56 | MaterialAmbientColor + 57 | // Diffuse : "color" of the object 58 | MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) + 59 | // Specular : reflective highlight, like a mirror 60 | MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance); 61 | 62 | color.a = 0.3; 63 | } 64 | -------------------------------------------------------------------------------- /Shaders/Tutorial2/SimpleFragmentShader.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Ouput data 4 | out vec3 color; 5 | 6 | void main() 7 | { 8 | 9 | // Output color = red 10 | color = vec3(1,0,0); 11 | 12 | } -------------------------------------------------------------------------------- /Shaders/Tutorial2/SimpleVertexShader.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | 6 | void main(){ 7 | 8 | gl_Position.xyz = vertexPosition_modelspace; 9 | gl_Position.w = 1.0; 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Shaders/Tutorial3/SimpleTransform.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | 6 | // Values that stay constant for the whole mesh. 7 | uniform mat4 MVP; 8 | 9 | void main(){ 10 | 11 | // Output position of the vertex, in clip space : MVP * position 12 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Shaders/Tutorial3/SingleColor.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Ouput data 4 | out vec3 color; 5 | 6 | void main() 7 | { 8 | 9 | // Output color = red 10 | color = vec3(1,0,0); 11 | 12 | } -------------------------------------------------------------------------------- /Shaders/Tutorial4/ColorFragmentShader.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec3 fragmentColor; 5 | 6 | // Ouput data 7 | out vec3 color; 8 | 9 | void main(){ 10 | 11 | // Output color = color specified in the vertex shader, 12 | // interpolated between all 3 surrounding vertices 13 | color = fragmentColor; 14 | 15 | } -------------------------------------------------------------------------------- /Shaders/Tutorial4/TransformVertexShader.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec3 vertexColor; 6 | 7 | // Output data ; will be interpolated for each fragment. 8 | out vec3 fragmentColor; 9 | // Values that stay constant for the whole mesh. 10 | uniform mat4 MVP; 11 | 12 | void main(){ 13 | 14 | // Output position of the vertex, in clip space : MVP * position 15 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 16 | 17 | // The color of each vertex will be interpolated 18 | // to produce the color of each fragment 19 | fragmentColor = vertexColor; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Shaders/Tutorial5/TextureFragmentShader.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | 6 | // Ouput data 7 | out vec3 color; 8 | 9 | // Values that stay constant for the whole mesh. 10 | uniform sampler2D myTextureSampler; 11 | 12 | void main(){ 13 | 14 | // Output color = color of the texture at the specified UV 15 | color = texture2D( myTextureSampler, UV ).rgb; 16 | } -------------------------------------------------------------------------------- /Shaders/Tutorial5/TransformVertexShader.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec2 vertexUV; 6 | 7 | // Output data ; will be interpolated for each fragment. 8 | out vec2 UV; 9 | 10 | // Values that stay constant for the whole mesh. 11 | uniform mat4 MVP; 12 | 13 | void main(){ 14 | 15 | // Output position of the vertex, in clip space : MVP * position 16 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 17 | 18 | // UV of the vertex. No special space for this one. 19 | UV = vertexUV; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Shaders/Tutorial6/TextureFragmentShader.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | 6 | // Ouput data 7 | out vec3 color; 8 | 9 | // Values that stay constant for the whole mesh. 10 | uniform sampler2D myTextureSampler; 11 | 12 | void main(){ 13 | 14 | // Output color = color of the texture at the specified UV 15 | color = texture2D( myTextureSampler, UV ).rgb; 16 | } -------------------------------------------------------------------------------- /Shaders/Tutorial6/TransformVertexShader.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec2 vertexUV; 6 | 7 | // Output data ; will be interpolated for each fragment. 8 | out vec2 UV; 9 | 10 | // Values that stay constant for the whole mesh. 11 | uniform mat4 MVP; 12 | 13 | void main(){ 14 | 15 | // Output position of the vertex, in clip space : MVP * position 16 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 17 | 18 | // UV of the vertex. No special space for this one. 19 | UV = vertexUV; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Shaders/Tutorial7/TextureFragmentShader.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | 6 | // Ouput data 7 | out vec3 color; 8 | 9 | // Values that stay constant for the whole mesh. 10 | uniform sampler2D myTextureSampler; 11 | 12 | void main(){ 13 | 14 | // Output color = color of the texture at the specified UV 15 | color = texture2D( myTextureSampler, UV ).rgb; 16 | } -------------------------------------------------------------------------------- /Shaders/Tutorial7/TransformVertexShader.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec2 vertexUV; 6 | 7 | // Output data ; will be interpolated for each fragment. 8 | out vec2 UV; 9 | 10 | // Values that stay constant for the whole mesh. 11 | uniform mat4 MVP; 12 | 13 | void main(){ 14 | 15 | // Output position of the vertex, in clip space : MVP * position 16 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 17 | 18 | // UV of the vertex. No special space for this one. 19 | UV = vertexUV; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Shaders/Tutorial8/StandardShading.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | in vec3 Position_worldspace; 6 | in vec3 Normal_cameraspace; 7 | in vec3 EyeDirection_cameraspace; 8 | in vec3 LightDirection_cameraspace; 9 | 10 | // Ouput data 11 | out vec3 color; 12 | 13 | // Values that stay constant for the whole mesh. 14 | uniform sampler2D myTextureSampler; 15 | uniform mat4 MV; 16 | uniform vec3 LightPosition_worldspace; 17 | 18 | void main(){ 19 | 20 | // Light emission properties 21 | // You probably want to put them as uniforms 22 | vec3 LightColor = vec3(1,1,1); 23 | float LightPower = 50.0f; 24 | 25 | // Material properties 26 | vec3 MaterialDiffuseColor = texture2D( myTextureSampler, UV ).rgb; 27 | vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor; 28 | vec3 MaterialSpecularColor = vec3(0.3,0.3,0.3); 29 | 30 | // Distance to the light 31 | float distance = length( LightPosition_worldspace - Position_worldspace ); 32 | 33 | // Normal of the computed fragment, in camera space 34 | vec3 n = normalize( Normal_cameraspace ); 35 | // Direction of the light (from the fragment to the light) 36 | vec3 l = normalize( LightDirection_cameraspace ); 37 | // Cosine of the angle between the normal and the light direction, 38 | // clamped above 0 39 | // - light is at the vertical of the triangle -> 1 40 | // - light is perpendicular to the triangle -> 0 41 | // - light is behind the triangle -> 0 42 | float cosTheta = clamp( dot( n,l ), 0,1 ); 43 | 44 | // Eye vector (towards the camera) 45 | vec3 E = normalize(EyeDirection_cameraspace); 46 | // Direction in which the triangle reflects the light 47 | vec3 R = reflect(-l,n); 48 | // Cosine of the angle between the Eye vector and the Reflect vector, 49 | // clamped to 0 50 | // - Looking into the reflection -> 1 51 | // - Looking elsewhere -> < 1 52 | float cosAlpha = clamp( dot( E,R ), 0,1 ); 53 | 54 | color = 55 | // Ambient : simulates indirect lighting 56 | MaterialAmbientColor + 57 | // Diffuse : "color" of the object 58 | MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) + 59 | // Specular : reflective highlight, like a mirror 60 | MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance); 61 | 62 | } -------------------------------------------------------------------------------- /Shaders/Tutorial8/StandardShading.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec2 vertexUV; 6 | layout(location = 2) in vec3 vertexNormal_modelspace; 7 | 8 | // Output data ; will be interpolated for each fragment. 9 | out vec2 UV; 10 | out vec3 Position_worldspace; 11 | out vec3 Normal_cameraspace; 12 | out vec3 EyeDirection_cameraspace; 13 | out vec3 LightDirection_cameraspace; 14 | 15 | // Values that stay constant for the whole mesh. 16 | uniform mat4 MVP; 17 | uniform mat4 V; 18 | uniform mat4 M; 19 | uniform vec3 LightPosition_worldspace; 20 | 21 | void main(){ 22 | 23 | // Output position of the vertex, in clip space : MVP * position 24 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 25 | 26 | // Position of the vertex, in worldspace : M * position 27 | Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz; 28 | 29 | // Vector that goes from the vertex to the camera, in camera space. 30 | // In camera space, the camera is at the origin (0,0,0). 31 | vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz; 32 | EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace; 33 | 34 | // Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity. 35 | vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz; 36 | LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace; 37 | 38 | // Normal of the the vertex, in camera space 39 | Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not. 40 | 41 | // UV of the vertex. No special space for this one. 42 | UV = vertexUV; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Shaders/Tutorial9/StandardShading.fragmentshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Interpolated values from the vertex shaders 4 | in vec2 UV; 5 | in vec3 Position_worldspace; 6 | in vec3 Normal_cameraspace; 7 | in vec3 EyeDirection_cameraspace; 8 | in vec3 LightDirection_cameraspace; 9 | 10 | // Ouput data 11 | out vec3 color; 12 | 13 | // Values that stay constant for the whole mesh. 14 | uniform sampler2D myTextureSampler; 15 | uniform mat4 MV; 16 | uniform vec3 LightPosition_worldspace; 17 | 18 | void main(){ 19 | 20 | // Light emission properties 21 | // You probably want to put them as uniforms 22 | vec3 LightColor = vec3(1,1,1); 23 | float LightPower = 50.0f; 24 | 25 | // Material properties 26 | vec3 MaterialDiffuseColor = texture2D( myTextureSampler, UV ).rgb; 27 | vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor; 28 | vec3 MaterialSpecularColor = vec3(0.3,0.3,0.3); 29 | 30 | // Distance to the light 31 | float distance = length( LightPosition_worldspace - Position_worldspace ); 32 | 33 | // Normal of the computed fragment, in camera space 34 | vec3 n = normalize( Normal_cameraspace ); 35 | // Direction of the light (from the fragment to the light) 36 | vec3 l = normalize( LightDirection_cameraspace ); 37 | // Cosine of the angle between the normal and the light direction, 38 | // clamped above 0 39 | // - light is at the vertical of the triangle -> 1 40 | // - light is perpendicular to the triangle -> 0 41 | // - light is behind the triangle -> 0 42 | float cosTheta = clamp( dot( n,l ), 0,1 ); 43 | 44 | // Eye vector (towards the camera) 45 | vec3 E = normalize(EyeDirection_cameraspace); 46 | // Direction in which the triangle reflects the light 47 | vec3 R = reflect(-l,n); 48 | // Cosine of the angle between the Eye vector and the Reflect vector, 49 | // clamped to 0 50 | // - Looking into the reflection -> 1 51 | // - Looking elsewhere -> < 1 52 | float cosAlpha = clamp( dot( E,R ), 0,1 ); 53 | 54 | color = 55 | // Ambient : simulates indirect lighting 56 | MaterialAmbientColor + 57 | // Diffuse : "color" of the object 58 | MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) + 59 | // Specular : reflective highlight, like a mirror 60 | MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance); 61 | 62 | } -------------------------------------------------------------------------------- /Shaders/Tutorial9/StandardShading.vertexshader: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 vertexPosition_modelspace; 5 | layout(location = 1) in vec2 vertexUV; 6 | layout(location = 2) in vec3 vertexNormal_modelspace; 7 | 8 | // Output data ; will be interpolated for each fragment. 9 | out vec2 UV; 10 | out vec3 Position_worldspace; 11 | out vec3 Normal_cameraspace; 12 | out vec3 EyeDirection_cameraspace; 13 | out vec3 LightDirection_cameraspace; 14 | 15 | // Values that stay constant for the whole mesh. 16 | uniform mat4 MVP; 17 | uniform mat4 V; 18 | uniform mat4 M; 19 | uniform vec3 LightPosition_worldspace; 20 | 21 | void main(){ 22 | 23 | // Output position of the vertex, in clip space : MVP * position 24 | gl_Position = MVP * vec4(vertexPosition_modelspace,1); 25 | 26 | // Position of the vertex, in worldspace : M * position 27 | Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz; 28 | 29 | // Vector that goes from the vertex to the camera, in camera space. 30 | // In camera space, the camera is at the origin (0,0,0). 31 | vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz; 32 | EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace; 33 | 34 | // Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity. 35 | vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz; 36 | LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace; 37 | 38 | // Normal of the the vertex, in camera space 39 | Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not. 40 | 41 | // UV of the vertex. No special space for this one. 42 | UV = vertexUV; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | from OpenGL.GL import * 2 | from OpenGL.GL.ARB import * 3 | from OpenGL.GLU import * 4 | from OpenGL.GLUT import * 5 | from OpenGL.GLUT.special import * 6 | from OpenGL.GL.shaders import * 7 | 8 | 9 | frame_count = 0 10 | 11 | def pre_frame(): 12 | pass 13 | 14 | def post_fram(): 15 | frame_count += 1 16 | 17 | def disable_vsyc(): 18 | import glfw 19 | glfw.swap_interval(0) 20 | 21 | def enable_vsyc(): 22 | import glfw 23 | glfw.swap_interval(1) 24 | 25 | #return GLuint 26 | def LoadShaders(vertex_file_path,fragment_file_path): 27 | # Create the shaders 28 | VertexShaderID = glCreateShader(GL_VERTEX_SHADER) 29 | FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER) 30 | 31 | # Read the Vertex Shader code from the file 32 | VertexShaderCode = "" 33 | with open(vertex_file_path,'r') as fr: 34 | for line in fr: 35 | VertexShaderCode += line 36 | # alternatively you could use fr.readlines() and then join in to a single string 37 | 38 | FragmentShaderCode = "" 39 | with open(fragment_file_path,'r') as fr: 40 | for line in fr: 41 | FragmentShaderCode += line 42 | # alternatively you could use fr.readlines() and then join in to a single string 43 | 44 | # Compile Vertex Shader 45 | print("Compiling shader: %s"%(vertex_file_path)) 46 | glShaderSource(VertexShaderID, VertexShaderCode) 47 | glCompileShader(VertexShaderID) 48 | 49 | # Check Vertex Shader 50 | result = glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS) 51 | if not result: 52 | raise RuntimeError(glGetShaderInfoLog(VertexShaderID)) 53 | 54 | # Compile Fragment Shader 55 | print("Compiling shader: %s"%(fragment_file_path)) 56 | glShaderSource(FragmentShaderID,FragmentShaderCode) 57 | glCompileShader(FragmentShaderID) 58 | 59 | # Check Fragment Shader 60 | result = glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS) 61 | if not result: 62 | raise RuntimeError(glGetShaderInfoLog(FragmentShaderID)) 63 | 64 | 65 | 66 | # Link the program 67 | print("Linking program") 68 | ProgramID = glCreateProgram() 69 | glAttachShader(ProgramID, VertexShaderID) 70 | glAttachShader(ProgramID, FragmentShaderID) 71 | glLinkProgram(ProgramID) 72 | 73 | # Check the program 74 | result = glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS) 75 | if not result: 76 | raise RuntimeError(glGetShaderInfoLog(ProgramID)) 77 | 78 | glDeleteShader(VertexShaderID); 79 | glDeleteShader(FragmentShaderID); 80 | 81 | return ProgramID; -------------------------------------------------------------------------------- /controls.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ Tutorial 5: Textured Cube 3 | """ 4 | from __future__ import print_function 5 | 6 | from OpenGL.GL import * 7 | from csgl import * 8 | 9 | import glfw 10 | import math as mathf 11 | 12 | ViewMatrix = mat4.fill(0) 13 | ProjectionMatrix = mat4.fill(0) 14 | 15 | def getViewMatrix(): 16 | return ViewMatrix 17 | 18 | def getProjectionMatrix(): 19 | return ProjectionMatrix 20 | 21 | 22 | # Initial position : on +Z 23 | position = vec3( 0, 0, 5 ) 24 | # Initial horizontal angle : toward -Z 25 | horizontalAngle = 3.14 26 | # Initial vertical angle : none 27 | verticalAngle = 0.0 28 | # Initial Field of View 29 | initialFoV = 45.0 30 | 31 | speed = 3.0 # 3 units / second 32 | mouseSpeed = 0.005 33 | 34 | lastTime = None 35 | 36 | def computeMatricesFromInputs(window): 37 | global lastTime 38 | global position 39 | global horizontalAngle 40 | global verticalAngle 41 | global initialFoV 42 | global ViewMatrix 43 | global ProjectionMatrix 44 | 45 | # glfwGetTime is called only once, the first time this function is called 46 | if lastTime == None: 47 | lastTime = glfw.get_time() 48 | 49 | # Compute time difference between current and last frame 50 | currentTime = glfw.get_time() 51 | deltaTime = currentTime - lastTime 52 | 53 | # Get mouse position 54 | xpos,ypos = glfw.get_cursor_pos(window) 55 | 56 | # Reset mouse position for next frame 57 | glfw.set_cursor_pos(window, 1024/2, 768/2); 58 | 59 | # Compute new orientation 60 | horizontalAngle += mouseSpeed * float(1024.0/2.0 - xpos ); 61 | verticalAngle += mouseSpeed * float( 768.0/2.0 - ypos ); 62 | 63 | # Direction : Spherical coordinates to Cartesian coordinates conversion 64 | direction = vec3( 65 | mathf.cos(verticalAngle) * mathf.sin(horizontalAngle), 66 | mathf.sin(verticalAngle), 67 | mathf.cos(verticalAngle) * mathf.cos(horizontalAngle) 68 | ) 69 | 70 | # Right vector 71 | right = vec3( 72 | mathf.sin(horizontalAngle - 3.14/2.0), 73 | 0.0, 74 | mathf.cos(horizontalAngle - 3.14/2.0) 75 | ) 76 | 77 | # Up vector 78 | up = vec3.cross( right, direction ) 79 | 80 | # Move forward 81 | if glfw.get_key( window, glfw.KEY_UP ) == glfw.PRESS or glfw.get_key( window, glfw.KEY_W ) == glfw.PRESS: 82 | position += direction * deltaTime * speed; 83 | 84 | # Move backward 85 | if glfw.get_key( window, glfw.KEY_DOWN ) == glfw.PRESS or glfw.get_key( window, glfw.KEY_S ) == glfw.PRESS: 86 | position -= direction * deltaTime * speed 87 | 88 | # Strafe right 89 | if glfw.get_key( window, glfw.KEY_RIGHT ) == glfw.PRESS or glfw.get_key( window, glfw.KEY_D ) == glfw.PRESS: 90 | position += right * deltaTime * speed 91 | 92 | # Strafe left 93 | if glfw.get_key( window, glfw.KEY_LEFT ) == glfw.PRESS or glfw.get_key( window, glfw.KEY_A ) == glfw.PRESS: 94 | position -= right * deltaTime * speed 95 | 96 | 97 | FoV = initialFoV# - 5 * glfwGetMouseWheel(); # Now GLFW 3 requires setting up a callback for this. It's a bit too complicated for this beginner's tutorial, so it's disabled instead. 98 | 99 | # Projection matrix : 45 Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units 100 | ProjectionMatrix = mat4.perspective(FoV, 4.0 / 3.0, 0.1, 100.0) 101 | # Camera matrix 102 | ViewMatrix = mat4.lookat( 103 | position, # Camera is here 104 | position+direction, # and looks here : at the same position, plus "direction" 105 | up # Head is up (set to 0,-1,0 to look upside-down) 106 | ) 107 | 108 | # For the next frame, the "last time" will be "now" 109 | lastTime = currentTime 110 | -------------------------------------------------------------------------------- /csgl/__init__.py: -------------------------------------------------------------------------------- 1 | from csgl.vec4 import vec4 2 | from csgl.vec3 import vec3 3 | from csgl.mat4 import mat4 4 | -------------------------------------------------------------------------------- /csgl/mat4.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ The curiously simply graphics library 3 | 4 | A quick stand-in for the OpenGL mathematics (GLM) library. 5 | PyOpenGL supports numpy 6 | """ 7 | 8 | from __future__ import print_function 9 | 10 | from OpenGL.GL import * 11 | from ctypes import * 12 | from csgl.vec3 import * 13 | from csgl.vec4 import * 14 | 15 | import sys 16 | import math 17 | import copy 18 | 19 | # [Xx Yx Zx Tx] [0 4 8 12] 20 | # [Xy Yy Zy Ty] [1 5 9 13] 21 | # [Xz Yz Zz Tz] [2 6 10 14] 22 | # [0 0 0 1 ] [3 7 11 15] 23 | 24 | class mat4(object): 25 | def __init__(self,*data): 26 | # ctype array to make it directly callable by tutorials. 27 | self._data = (GLfloat * 16)() 28 | 29 | # special case, empty constructor fills matrix w/ zeroes 30 | if len(data) == 0: 31 | self.data = self.zeroes().data 32 | else: 33 | self.data = data 34 | 35 | @property 36 | def data(self): 37 | return self._data 38 | 39 | @data.setter 40 | def data(self,data): 41 | # mat4.data(0,1,2,.....,15) 42 | if len(data) == 16: 43 | for i,d in enumerate(data): self._data[i] = data[i] 44 | 45 | # mat4.data([0,1,2,.....,15]) 46 | if len(data) == 1: 47 | for i,d in enumerate(data[0]): self._data[i] = data[0][i] 48 | 49 | def copy(self): 50 | """ Create a new copy of matrix 51 | """ 52 | return copy.deepcopy(self) 53 | 54 | def __getitem__(self,row): 55 | """ Allow matrix indexing using [row][col] notation. 56 | 57 | Equivalent to C++ operator[](int row, int col) 58 | """ 59 | return pointer(GLfloat.from_address(addressof(self._data) + sizeof(GLfloat) * row * 4)) 60 | 61 | @staticmethod 62 | def zeroes(): 63 | """ Fill w/ zeroes 64 | """ 65 | return mat4.fill(0) 66 | 67 | @staticmethod 68 | def fill(v): 69 | return mat4([v for i in range(0,16)]) 70 | 71 | @staticmethod 72 | def identity(): 73 | return mat4( 74 | 1,0,0,0, 75 | 0,1,0,0, 76 | 0,0,1,0, 77 | 0,0,0,1) 78 | 79 | @staticmethod 80 | def perspective(fov_deg,aspect,z_near,z_far): 81 | assert(aspect != 0.0) 82 | assert(z_near != z_far) 83 | 84 | fov = math.radians(fov_deg) 85 | tan_half_fov = math.tan(fov / 2.0) 86 | 87 | m = mat4.zeroes() 88 | m[0][0] = 1.0 / (aspect * tan_half_fov) 89 | m[1][1] = 1.0 / (tan_half_fov) 90 | m[2][2] = -(z_far+z_near) / (z_far - z_near) 91 | m[2][3] = -1.0 92 | m[3][2] = -(2.0 * z_far * z_near) / (z_far - z_near) 93 | return m 94 | 95 | @staticmethod 96 | def lookat(eye,center,up): 97 | f = (center-eye).normalized() 98 | s = (vec3.cross(f,up).normalized()) 99 | u = (vec3.cross(s,f)) 100 | 101 | m = mat4.identity() 102 | m[0][0] = s.x 103 | m[1][0] = s.y 104 | m[2][0] = s.z 105 | 106 | m[0][1] = u.x; 107 | m[1][1] = u.y; 108 | m[2][1] = u.z; 109 | 110 | m[0][2] =-f.x; 111 | m[1][2] =-f.y; 112 | m[2][2] =-f.z; 113 | 114 | m[3][0] =-vec3.dot(s, eye); 115 | m[3][1] =-vec3.dot(u, eye); 116 | m[3][2] = vec3.dot(f, eye); 117 | return m 118 | 119 | def transpose(self): 120 | new_mat = mat4() 121 | for r in range(0,4): 122 | for c in range(0,4): 123 | new_mat[c][r] = self[r][c] 124 | 125 | def transposed(self): 126 | new_mat = self.transpose() 127 | self._data = new_mat._data 128 | 129 | def rotatex(self,angle): 130 | rad = math.radians(angle) 131 | m = self 132 | m[0][2] = math.cos(rad) 133 | m[0][3] = math.sin(rad) 134 | m[2][0] = -math.sin(rad) 135 | m[2][2] = math.cos(rad) 136 | 137 | def translate(self,vec3): 138 | self._data[12] = vec3.x 139 | self._data[13] = vec3.y 140 | self._data[14] = vec3.z 141 | 142 | def __mul__(self,other): 143 | m = mat4.zeroes() 144 | 145 | # swap matrix multiplication order to account for right sided (column oriented) multiplcation 146 | # glm was the basis for this code. (their code is much prettier) 147 | a = other#self 148 | b = self#other 149 | 150 | for r in range(0,4): 151 | for c in range(0,4): 152 | for i in range(0,4): 153 | m[r][c] += a[r][i] * b[i][c] 154 | 155 | #print("result--\n",m) 156 | return m 157 | 158 | @staticmethod 159 | def arith(op,a,b): 160 | """ Perform arithmetic `op` on `a` and `b' 161 | """ 162 | rtype = type(b) 163 | if rtype is mat4: 164 | ret = mat4() 165 | for r in range(0,4): 166 | for c in range(0,4): 167 | ret[r][c] = op(a[r][c],b[r][c]) 168 | return ret 169 | elif rtype is float or rtype is int: 170 | raise NotImplementedError("rtype vec4 not yet supported, but it should be. ") 171 | 172 | @staticmethod 173 | def arith_inline(op,a,b): 174 | """ Perform arithmetic `op` on `self` and `b' 175 | """ 176 | rtype = type(b) 177 | if rtype is mat4: 178 | for r in range(0,4): 179 | for c in range(0,4): 180 | a[r][c] = op(a[r][c],b[r][c]) 181 | return a 182 | 183 | def __add__(self, other):return mat4.arith(operator.add,self,other) 184 | def __iadd__(self,other):return mat4.arith_inline(operator.add,self,other) 185 | def __radd__(self,other):return mat4.arith(operator.add,self,other) 186 | 187 | def __sub__(self, other):return mat4.arith(operator.sub,self,other) 188 | def __isub__(self,other):return mat4.arith_inline(operator.sub,self,other) 189 | def __rsub__(self,other):return mat4.arith(operator.sub,self,other) 190 | 191 | def __eq__(self,other): 192 | for i in range(0,16): 193 | if math.fabs(self.data[i] - other.data[i]) >= sys.float_info.epsilon:return False 194 | return True 195 | 196 | def __ne__(self,other): 197 | return not (self==other) 198 | 199 | def __str__(self): 200 | return "%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n"%( 201 | self._data[0],self._data[1],self._data[2],self._data[3], 202 | self._data[4],self._data[5],self._data[6],self._data[7], 203 | self._data[8],self._data[9],self._data[10],self._data[11], 204 | self._data[12],self._data[13],self._data[14],self._data[15]) 205 | 206 | def __unicode__(self): 207 | print("unicode"); 208 | return "%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n"%( 209 | self._data[0],self._data[1],self._data[2],self._data[3], 210 | self._data[4],self._data[5],self._data[6],self._data[7], 211 | self._data[8],self._data[9],self._data[10],self._data[11], 212 | self._data[12],self._data[13],self._data[14],self._data[15] 213 | ) 214 | -------------------------------------------------------------------------------- /csgl/multimethods.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ Multiple method utility decorator 3 | 4 | C++ Users: This is the simplest way of mimicing 5 | operator overloading. 6 | 7 | Note: As written this decorator only supports 8 | positional arguments. 9 | """ 10 | 11 | # http://www.artima.com/weblogs/viewpost.jsp?thread=101605 12 | registry = {} 13 | 14 | class MultiMethod(object): 15 | def __init__(self, name): 16 | self.name = name 17 | self.typemap = {} 18 | 19 | # self = a MultiMethod instance, instance = the object we want to bind to 20 | def __call__(self, instance, *args): 21 | types = tuple(arg.__class__ for arg in args) # a generator expression! 22 | function = self.typemap.get(types) 23 | print("types:",types) 24 | if function is None: 25 | raise TypeError("no match") 26 | return function(instance, *args) 27 | 28 | def register(self, types, function): 29 | if types in self.typemap: 30 | raise TypeError("duplicate registration") 31 | self.typemap[types] = function 32 | 33 | 34 | def multimethod(*types): 35 | def register(function): 36 | name = function.__name__ 37 | mm = registry.get(name) 38 | if mm is None: 39 | mm = registry[name] = MultiMethod(name) 40 | mm.register(types, function) 41 | def getter(instance, *args, **kwargs): 42 | return mm(instance, *args, **kwargs) 43 | #return mm 44 | return getter 45 | return register 46 | 47 | def multimethod2(*types): 48 | class _multimethod2(object): 49 | 50 | def __init__(self,method): 51 | self.method = method 52 | print("Init method:",method) 53 | def __get__(self,instance,cls): 54 | print("Getting instance ",instance," of class ",cls) 55 | return lambda *args, **kw: self.method(cls, *args, **kw) 56 | 57 | # can we somehow register the class above here in the registry 58 | # and then return only 1 instance or create a global class variable 59 | # that is assigned information about the original caller? 60 | # 61 | print("new multimethod2") 62 | return _multimethod2 63 | 64 | def multiple_decorators(func): 65 | return classmethod(func) 66 | #@my_dec 67 | #def foo():pass => foo = my_ec(f) 68 | # Python 3.x (needs support for function annotations) 69 | # class MultiMethod(object): 70 | # def __init__(self, name): 71 | # self.name = name 72 | # self.typemap = {} 73 | 74 | # # self = a MultiMethod instance, instance = the object we want to bind to 75 | # def __call__(self, instance, *args): 76 | # types = tuple(arg.__class__ for arg in args) # a generator expression! 77 | # function = self.typemap.get(types) 78 | 79 | # if function is None: 80 | # raise TypeError("no match") 81 | # return function(instance, *args) 82 | 83 | # def register(self, types, function): 84 | # if types in self.typemap: 85 | # raise TypeError("duplicate registration") 86 | # self.typemap[types] = function 87 | 88 | # def multimethod(function): 89 | # name = function.__name__ 90 | # mm = registry.get(name) 91 | # if mm is None: 92 | # mm = registry[name] = MultiMethod(name) 93 | 94 | # types = tuple(function.__annotations__.values()) 95 | # mm.register(types, function) 96 | # # return a function instead of a object - Python binds this automatically 97 | # def getter(instance, *args, **kwargs): 98 | # return mm(instance, *args, **kwargs) 99 | # return getter 100 | 101 | 102 | def main(): 103 | class Foo(object): 104 | @multimethod() 105 | def add(self): 106 | print("Add foo",self) 107 | 108 | @multimethod(int) 109 | def add(self,value): 110 | print("Add foo int") 111 | 112 | class Bar(object): 113 | @multimethod() 114 | def add(self): 115 | print("Add bar") 116 | @multimethod(int) 117 | def add(self): 118 | print("Add bar int") 119 | 120 | f = Foo() 121 | b = Bar() 122 | 123 | f.add(1) 124 | b.add() 125 | 126 | if __name__=="__main__": 127 | main() 128 | -------------------------------------------------------------------------------- /csgl/test_mat4.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import unittest 5 | import random 6 | import os 7 | import sys 8 | 9 | from csgl.vec3 import * 10 | from csgl.vec4 import * 11 | from csgl.mat4 import * 12 | 13 | class TestMat4Operators(unittest.TestCase): 14 | def setUp(self): 15 | # hardcode seed to keep results identical 16 | random.seed(42) 17 | 18 | # Python "float" is implemented as C++ double 19 | self._data_x = [random.randint(0,100) for i in range(0,16)] 20 | self._data_y = [random.randint(0,100) for i in range(0,16)] 21 | 22 | 23 | 24 | def test_addition(self): 25 | x = mat4(*self._data_x) 26 | y = mat4(*self._data_y) 27 | xy = x+y 28 | 29 | idx = lambda r,c: r*4 + c 30 | 31 | for r in range(0,4): 32 | for c in range(0,4): 33 | self.assertEqual(xy[r][c],self._data_x[idx(r,c)] + self._data_y[idx(r,c)]) 34 | 35 | def test_subtraction(self): 36 | x = mat4(*self._data_x) 37 | y = mat4(*self._data_y) 38 | xy = x-y 39 | 40 | idx = lambda r,c: r*4 + c 41 | 42 | for r in range(0,4): 43 | for c in range(0,4): 44 | self.assertEqual(xy[r][c],self._data_x[idx(r,c)] - self._data_y[idx(r,c)]) 45 | 46 | def test_multiplication(self): 47 | x = mat4(*self._data_x) 48 | y = mat4(*self._data_y) 49 | xy = x*y 50 | 51 | idx = lambda r,c: r*4 + c 52 | 53 | data = [0] * 16 54 | for r in range(0,4): 55 | for c in range(0,4): 56 | for i in range(0,4): 57 | data[idx(r,c)] += y[r][i] * x[i][c] # remember that mat4 mults are flipped to support right side vec4 mults 58 | 59 | for r in range(0,4): 60 | for c in range(0,4): 61 | self.assertEqual(xy[r][c],data[idx(r,c)]) 62 | 63 | 64 | 65 | def main(): 66 | unittest.main() 67 | 68 | 69 | 70 | 71 | 72 | if __name__=='__main__': 73 | main() -------------------------------------------------------------------------------- /csgl/test_vec3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import unittest 5 | import random 6 | import os 7 | import sys 8 | 9 | from csgl.vec3 import * 10 | from csgl.vec4 import * 11 | from csgl.mat4 import * 12 | 13 | class TestVec3Operators(unittest.TestCase): 14 | def setUp(self): 15 | # hardcode seed to keep results identical 16 | random.seed(42) 17 | 18 | # Python "float" is implemented as C++ double 19 | self._data_x = [random.randint(0,100) for i in range(0,3)] 20 | self._data_y = [random.randint(0,100) for i in range(0,3)] 21 | self._data_scalar = random.randint(0,100) 22 | 23 | 24 | def test_addition(self): 25 | a = vec3(*self._data_x) 26 | b = vec3(*self._data_y) 27 | c = self._data_scalar 28 | 29 | ab = a+b 30 | self.assertEqual(ab.x,a.x+b.x) 31 | self.assertEqual(ab.y,a.y+b.y) 32 | self.assertEqual(ab.z,a.z+b.z) 33 | 34 | ac = a + c 35 | self.assertAlmostEqual(ac.x,a.x+c,places=6) 36 | self.assertAlmostEqual(ac.y,a.y+c,places=6) 37 | self.assertAlmostEqual(ac.z,a.z+c,places=6) 38 | 39 | def test_subtraction(self): 40 | a = vec3(*self._data_x) 41 | b = vec3(*self._data_y) 42 | c = self._data_scalar 43 | 44 | ab = a-b 45 | self.assertEqual(ab.x,a.x-b.x) 46 | self.assertEqual(ab.y,a.y-b.y) 47 | self.assertEqual(ab.z,a.z-b.z) 48 | 49 | ac = a - c 50 | self.assertAlmostEqual(ac.x,a.x-c,places=6) 51 | self.assertAlmostEqual(ac.y,a.y-c,places=6) 52 | self.assertAlmostEqual(ac.z,a.z-c,places=6) 53 | 54 | def test_division(self): 55 | a = vec3(*self._data_x) 56 | b = vec3(*self._data_y) 57 | c = self._data_scalar if self._data_scalar != 0 else random.randint(1,100) 58 | 59 | ab = a/b 60 | self.assertAlmostEqual(ab.x,a.x/b.x,places=6) 61 | self.assertAlmostEqual(ab.y,a.y/b.y,places=6) 62 | self.assertAlmostEqual(ab.z,a.z/b.z,places=6) 63 | 64 | ac = a / c 65 | self.assertAlmostEqual(ac.x,a.x/c,places=6) 66 | self.assertAlmostEqual(ac.y,a.y/c,places=6) 67 | self.assertAlmostEqual(ac.z,a.z/c,places=6) 68 | 69 | def test_multiplication(self): 70 | a = vec3(*self._data_x) 71 | b = vec3(*self._data_y) 72 | c = self._data_scalar 73 | 74 | ab = a*b 75 | self.assertAlmostEqual(ab.x,a.x*b.x,places=6) 76 | self.assertAlmostEqual(ab.y,a.y*b.y,places=6) 77 | self.assertAlmostEqual(ab.z,a.z*b.z,places=6) 78 | 79 | ac = a * c 80 | self.assertAlmostEqual(ac.x,a.x*c,places=6) 81 | self.assertAlmostEqual(ac.y,a.y*c,places=6) 82 | self.assertAlmostEqual(ac.z,a.z*c,places=6) 83 | 84 | def main(): 85 | unittest.main() 86 | 87 | 88 | if __name__=='__main__': 89 | main() -------------------------------------------------------------------------------- /csgl/test_vec4.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from __future__ import print_function 3 | 4 | from csgl.vec3 import * 5 | from csgl.vec4 import * 6 | from csgl.mat4 import * 7 | 8 | import unittest 9 | import random 10 | import os 11 | import sys 12 | 13 | 14 | 15 | class Testvec4Operators(unittest.TestCase): 16 | def setUp(self): 17 | # hardcode seed to keep results identical 18 | random.seed(42) 19 | 20 | # Python "float" is implemented as C++ double 21 | self._data_x = [random.randint(0,100) for i in range(0,4)] 22 | self._data_y = [random.randint(0,100) for i in range(0,4)] 23 | self._data_scalar = random.randint(0,100) 24 | 25 | 26 | def test_addition(self): 27 | a = vec4(*self._data_x) 28 | b = vec4(*self._data_y) 29 | c = self._data_scalar 30 | 31 | ab = a+b 32 | self.assertEqual(ab.x,a.x+b.x) 33 | self.assertEqual(ab.y,a.y+b.y) 34 | self.assertEqual(ab.z,a.z+b.z) 35 | self.assertEqual(ab.w,a.w+b.w) 36 | 37 | ac = a + c 38 | self.assertAlmostEqual(ac.x,a.x+c,places=6) 39 | self.assertAlmostEqual(ac.y,a.y+c,places=6) 40 | self.assertAlmostEqual(ac.z,a.z+c,places=6) 41 | self.assertAlmostEqual(ac.w,a.w+c,places=6) 42 | 43 | def test_subtraction(self): 44 | a = vec4(*self._data_x) 45 | b = vec4(*self._data_y) 46 | c = self._data_scalar 47 | 48 | ab = a-b 49 | self.assertEqual(ab.x,a.x-b.x) 50 | self.assertEqual(ab.y,a.y-b.y) 51 | self.assertEqual(ab.z,a.z-b.z) 52 | self.assertEqual(ab.w,a.w-b.w) 53 | 54 | ac = a - c 55 | self.assertAlmostEqual(ac.x,a.x-c,places=6) 56 | self.assertAlmostEqual(ac.y,a.y-c,places=6) 57 | self.assertAlmostEqual(ac.z,a.z-c,places=6) 58 | self.assertAlmostEqual(ac.w,a.w-c,places=6) 59 | 60 | def test_division(self): 61 | a = vec4(*self._data_x) 62 | b = vec4(*self._data_y) 63 | 64 | # avoid div by 0 65 | a.w = a.w if a.w != 0.0 else random.randint(1,100) 66 | b.w = b.w if a.w != 0.0 else random.randint(1,100) 67 | c = self._data_scalar if self._data_scalar != 0 else random.randint(1,100) 68 | 69 | ab = a/b 70 | self.assertAlmostEqual(ab.x,a.x/b.x,places=6) 71 | self.assertAlmostEqual(ab.y,a.y/b.y,places=6) 72 | self.assertAlmostEqual(ab.z,a.z/b.z,places=6) 73 | self.assertAlmostEqual(ab.w,a.w/b.w,places=6) 74 | 75 | ac = a / c 76 | self.assertAlmostEqual(ac.x,a.x/c,places=6) 77 | self.assertAlmostEqual(ac.y,a.y/c,places=6) 78 | self.assertAlmostEqual(ac.z,a.z/c,places=6) 79 | self.assertAlmostEqual(ac.w,a.w/c,places=6) 80 | 81 | def test_multiplication(self): 82 | a = vec4(*self._data_x) 83 | b = vec4(*self._data_y) 84 | c = self._data_scalar 85 | 86 | ab = a*b 87 | self.assertAlmostEqual(ab.x,a.x*b.x,places=6) 88 | self.assertAlmostEqual(ab.y,a.y*b.y,places=6) 89 | self.assertAlmostEqual(ab.z,a.z*b.z,places=6) 90 | self.assertAlmostEqual(ab.w,a.w*b.w,places=6) 91 | 92 | ac = a * c 93 | self.assertAlmostEqual(ac.x,a.x*c,places=6) 94 | self.assertAlmostEqual(ac.y,a.y*c,places=6) 95 | self.assertAlmostEqual(ac.z,a.z*c,places=6) 96 | self.assertAlmostEqual(ac.w,a.w*c,places=6) 97 | 98 | def main(): 99 | unittest.main() 100 | 101 | 102 | if __name__=='__main__': 103 | main() -------------------------------------------------------------------------------- /csgl/vec3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ The curiously simply graphics library 3 | 4 | A quick stand-in for the OpenGL mathematics (GLM) library. 5 | PyOpenGL supports numpy 6 | """ 7 | from __future__ import print_function 8 | from __future__ import division 9 | 10 | from OpenGL.GL import * 11 | from multimethods import multimethod 12 | 13 | import sys 14 | import math 15 | import copy 16 | import operator 17 | 18 | def arith(op,a,b): 19 | btype = type(b) 20 | if btype is vec3: 21 | return vec3(op(a.x,b.x),op(a.y,b.y),op(a.z,b.z)) 22 | elif btype is float or btype is int: 23 | return vec3(op(a.x,b),op(a.y,b),op(a.z,b)) 24 | 25 | class vec3(object): 26 | def __init__(self,x=0,y=0,z=0): 27 | self.data_ = (GLfloat * 3)() 28 | self.x = x 29 | self.y = y 30 | self.z = z 31 | 32 | def __hash__(self): 33 | return hash((self.x,self.y,self.z)) 34 | 35 | def copy(self): 36 | return copy.deepcopy(self) 37 | 38 | @property 39 | def x(self): 40 | return self.data_[0] 41 | @x.setter 42 | def x(self,value): 43 | self.data_[0] = value 44 | 45 | @property 46 | def y(self): 47 | return self.data_[1] 48 | @y.setter 49 | def y(self,value): 50 | self.data_[1] = value 51 | 52 | @property 53 | def z(self): 54 | return self.data_[2] 55 | @z.setter 56 | def z(self,value): 57 | self.data_[2] = value 58 | 59 | 60 | def length(self): 61 | return math.sqrt(self.sqr_length()) 62 | 63 | def sqr_length(self): 64 | return self.x*self.x + self.y*self.y + self.z*self.z 65 | 66 | @staticmethod 67 | def lerp(a,b,t): 68 | ba = b-a 69 | return a + t*(ba) 70 | 71 | @staticmethod 72 | def cross(a,b): 73 | return vec3(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x) 74 | 75 | @staticmethod 76 | def dot(a,b): 77 | return a.x*b.x + a.y*b.y + a.z*b.z 78 | 79 | def normalize(self): 80 | l = self.length() 81 | self.x = self.x / l 82 | self.y = self.y / l 83 | self.w = self.z / l 84 | 85 | def normalized(self): 86 | l = self.length() 87 | return vec3(self.x / l, self.y / l, self.z / l) 88 | 89 | @staticmethod 90 | def arith(op,a,b): 91 | """ Perform arithmetic `op` on `a` and `b' 92 | 93 | Input: 94 | op(operator) - Python operator type 95 | a(vec3) - left hand side vector3 ( 96 | *type always equals vec3, support for int+vec3() is handled by _i_ methods 97 | b(int,float,vec3) - right hand side int, float, or vector3. 98 | 99 | Notes: 100 | Python doesn't support method overloading in the C++ sense so this 101 | utility method performs an r-value type check. 102 | """ 103 | rtype = type(b) 104 | if rtype is vec3: 105 | return vec3(op(a.x,b.x),op(a.y,b.y),op(a.z,b.z)) 106 | elif rtype is float or rtype is int: 107 | return vec3(op(a.x,b),op(a.y,b),op(a.z,b)) 108 | 109 | @staticmethod 110 | def arith_inline(op,a,b): 111 | """ Perform arithmetic `op` on `self` and `b' 112 | 113 | *See arith documentation for explanation. 114 | **arith_inline handles: my_vec3 += other_vec3 -or- 115 | my_vec3 += 3 116 | """ 117 | rtype = type(b) 118 | if rtype is vec3: 119 | a.x = op(a.x,b.x) 120 | a.y = op(a.y,b.y) 121 | a.z = op(a.z,b.z) 122 | return a 123 | elif rtype is float or rtype is int: 124 | a.x = op(a.x,b) 125 | a.y = op(a.y,b) 126 | a.z = op(a.z,b) 127 | return a 128 | 129 | # todo: consider less visually awful approach to overloading. 130 | def __add__(self, other):return vec3.arith(operator.add,self,other) 131 | def __iadd__(self,other):return vec3.arith_inline(operator.add,self,other) 132 | def __radd__(self,other):return vec3.arith(operator.add,self,other) 133 | 134 | def __sub__(self, other):return vec3.arith(operator.sub,self,other) 135 | def __isub__(self,other):return vec3.arith_inline(operator.sub,self,other) 136 | def __rsub__(self,other):return vec3.arith(operator.sub,self,other) 137 | 138 | def __mul__(self, other):return vec3.arith(operator.mul,self,other) 139 | def __imul__(self,other):return vec3.arith_inline(operator.mul,self,other) 140 | def __rmul__(self,other):return vec3.arith(operator.mul,self,other) 141 | 142 | # for python 3 143 | def __truediv__(self, other):return vec3.arith(operator.truediv,self,other) 144 | def __itruediv__(self,other):return vec3.arith_inline(operator.truediv,self,other) 145 | def __rtruediv__(self,other):return vec3.arith(operator.truediv,self,other) 146 | 147 | def __div__(self, other):return vec3.arith(operator.div,self,other) 148 | def __idiv__(self,other):return vec3.arith_inline(operator.div,self,other) 149 | def __rdiv__(self,other):return vec3.arith(operator.div,self,other) 150 | 151 | def __eq__(self,other): 152 | """ Equality operator (==) 153 | 154 | *Note: Be careful w/ comparing floating point values, use 155 | some threshold for equality. 156 | """ 157 | if math.fabs(self.x - other.x) >= sys.float_info.epsilon:return False 158 | if math.fabs(self.y - other.y) >= sys.float_info.epsilon:return False 159 | if math.fabs(self.z - other.z) >= sys.float_info.epsilon:return False 160 | return True 161 | 162 | def __ne__(self,other): 163 | return not (self==other) 164 | 165 | def __str__(self): 166 | return unicode(self).encode('utf-8') 167 | 168 | def __unicode__(self): 169 | return "%f %f %f"%(self.x,self.y,self.z) -------------------------------------------------------------------------------- /csgl/vec4.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ The curiously simply graphics library 3 | 4 | A quick stand-in for the OpenGL mathematics (GLM) library. 5 | PyOpenGL supports numpy 6 | """ 7 | from __future__ import print_function 8 | 9 | from OpenGL.GL import * 10 | 11 | import sys 12 | import math 13 | import copy 14 | import operator 15 | 16 | class vec4(object): 17 | def __init__(self,x=0,y=0,z=0,w=0): 18 | self.data_ = (GLfloat * 4)() 19 | self.x = x 20 | self.y = y 21 | self.z = z 22 | self.w = w 23 | 24 | def copy(self): 25 | return copy.deepcopy(self) 26 | 27 | @property 28 | def x(self): 29 | return self.data_[0] 30 | @x.setter 31 | def x(self,value): 32 | self.data_[0] = value 33 | 34 | @property 35 | def y(self): 36 | return self.data_[1] 37 | @y.setter 38 | def y(self,value): 39 | self.data_[1] = value 40 | 41 | @property 42 | def z(self): 43 | return self.data_[2] 44 | @z.setter 45 | def z(self,value): 46 | self.data_[2] = value 47 | 48 | @property 49 | def w(self): 50 | return self.data_[3] 51 | @w.setter 52 | def w(self,value): 53 | self.data_[3] = value 54 | 55 | 56 | @staticmethod 57 | def arith(op,a,b): 58 | """ Perform arithmetic `op` on `a` and `b' 59 | 60 | Input: 61 | op(operator) - Python operator type 62 | a(vec4) - left hand side vector3 ( 63 | *type always equals vec4, support for int+vec4() is handled by _i_ methods 64 | b(int,float,vec4) - right hand side int, float, or vector3. 65 | 66 | Notes: 67 | Python doesn't support method overloading in the C++ sense so this 68 | utility method performs an r-value type check. 69 | 70 | TODO: 71 | This method is not correct for vec4 multiplication or 72 | division. It makes no sense to multiply vec4*vec4. 73 | Vec4 * scalar is fine. 74 | """ 75 | print(op) 76 | rtype = type(b) 77 | if rtype is vec4: 78 | return vec4(op(a.x,b.x),op(a.y,b.y),op(a.z,b.z),op(a.w,b.w)) 79 | elif rtype is float or rtype is int: 80 | return vec4(op(a.x,b),op(a.y,b),op(a.z,b),op(a.w,b)) 81 | 82 | @staticmethod 83 | def arith_inline(op,a,b): 84 | """ Perform arithmetic `op` on `self` and `b' 85 | 86 | *See arith documentation for explanation. 87 | **arith_inline handles: my_vec4 += other_vec4 -or- 88 | my_vec4 += 3 89 | """ 90 | rtype = type(b) 91 | if rtype is vec4: 92 | a.x = op(a.x,b.x) 93 | a.y = op(a.y,b.y) 94 | a.z = op(a.z,b.z) 95 | a.w = op(a.w,b.w) 96 | return a 97 | elif rtype is float or rtype is int: 98 | a.x = op(a.x,b) 99 | a.y = op(a.y,b) 100 | a.z = op(a.z,b) 101 | a.w = op(a.w,b) 102 | return a 103 | 104 | # todo: consider less visually awful approach to overloading. 105 | def __add__(self, other):return vec4.arith(operator.add,self,other) 106 | def __iadd__(self,other):return vec4.arith_inline(operator.add,self,other) 107 | def __radd__(self,other):return vec4.arith(operator.add,self,other) 108 | 109 | def __sub__(self, other):return vec4.arith(operator.sub,self,other) 110 | def __isub__(self,other):return vec4.arith_inline(operator.sub,self,other) 111 | def __rsub__(self,other):return vec4.arith(operator.sub,self,other) 112 | 113 | def __mul__(self, other):return vec4.arith(operator.mul,self,other) 114 | def __imul__(self,other):return vec4.arith_inline(operator.mul,self,other) 115 | def __rmul__(self,other):return vec4.arith(operator.mul,self,other) 116 | 117 | # for python 3 118 | def __truediv__(self, other):return vec4.arith(operator.truediv,self,other) 119 | def __itruediv__(self,other):return vec4.arith_inline(operator.truediv,self,other) 120 | def __rtruediv__(self,other):return vec4.arith(operator.truediv,self,other) 121 | 122 | def __div__(self, other):return vec4.arith(operator.div,self,other) 123 | def __idiv__(self,other):return vec4.arith_inline(operator.div,self,other) 124 | def __rdiv__(self,other):return vec4.arith(operator.div,self,other) 125 | 126 | 127 | def __eq__(self,other): 128 | if math.fabs(self.x - other.x) >= sys.float_info.epsilon:return False 129 | if math.fabs(self.y - other.y) >= sys.float_info.epsilon:return False 130 | if math.fabs(self.z - other.z) >= sys.float_info.epsilon:return False 131 | if math.fabs(self.w - other.w) >= sys.float_info.epsilon:return False 132 | return True 133 | 134 | def __ne__(self,other): 135 | return not (self==other) 136 | 137 | def __str__(self): 138 | return unicode(self).encode('utf-8') 139 | 140 | def __unicode__(self): 141 | return "%f %f %f %f"%(self.x,self.y,self.z,self.w) 142 | 143 | #vec4 constants 144 | vec4.zero = vec4(0,0,0,0) 145 | vec4.up = vec4(0,1,0,0) 146 | vec4.down = vec4(0,-1,0,0) 147 | vec4.right = vec4(1,0,0,0) 148 | vec4.left = vec4(-1,0,0,0) 149 | vec4.forward = vec4(0,0,-1,0) 150 | vec4.backward = vec4(0,0,1,0) 151 | 152 | def main(): 153 | x = vec4(1,2,3,4) 154 | y = vec4(1,2,3,4) 155 | xy = x+y 156 | xy_actual = vec4(2,4,6,8) 157 | 158 | print(xy == xy_actual) 159 | assert(xy == xy_actual) 160 | y = x.copy() 161 | y.x = 12 162 | y = vec4.zero 163 | print(x) 164 | print(y) 165 | print(xy) 166 | 167 | if __name__=='__main__': 168 | main() 169 | -------------------------------------------------------------------------------- /example_screenshot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 8: Basic Shading 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | from utilities import screenshot 18 | 19 | import common 20 | import glfw 21 | import sys 22 | import os 23 | import controls 24 | import objloader 25 | 26 | # Global window 27 | window = None 28 | null = c_void_p(0) 29 | 30 | def opengl_init(): 31 | global window 32 | # Initialize the library 33 | if not glfw.init(): 34 | print("Failed to initialize GLFW\n",file=sys.stderr) 35 | return False 36 | 37 | # Open Window and create its OpenGL context 38 | window = glfw.create_window(1024, 768, "Tutorial 08", None, None) #(in the accompanying source code this variable will be global) 39 | glfw.window_hint(glfw.SAMPLES, 4) 40 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 41 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 42 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 43 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 44 | 45 | if not window: 46 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 47 | glfw.terminate() 48 | return False 49 | 50 | # Initialize GLEW 51 | glfw.make_context_current(window) 52 | glewExperimental = True 53 | 54 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 55 | # more information including why can remove this code.a 56 | if glewInit() != GLEW_OK: 57 | print("Failed to initialize GLEW\n",file=stderropen.sys); 58 | return False 59 | return True 60 | 61 | def main(): 62 | 63 | # Initialize GLFW and open a window 64 | if not opengl_init(): 65 | return 66 | 67 | # Enable key events 68 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 69 | glfw.set_cursor_pos(window, 1024/2, 768/2) 70 | 71 | # Set opengl clear color to something other than red (color used by the fragment shader) 72 | glClearColor(0.0,0.0,0.0,0.0) 73 | 74 | # Enable depth test 75 | glEnable(GL_DEPTH_TEST) 76 | 77 | # Accept fragment if it closer to the camera than the former one 78 | glDepthFunc(GL_LESS) 79 | 80 | # Cull triangles which normal is not towards the camera 81 | glEnable(GL_CULL_FACE) 82 | 83 | vertex_array_id = glGenVertexArrays(1) 84 | glBindVertexArray( vertex_array_id ) 85 | 86 | # Create and compile our GLSL program from the shaders 87 | program_id = common.LoadShaders( ".\\shaders\\common\\StandardShading.vertexshader", 88 | ".\\shaders\\common\\StandardShading.fragmentshader" ) 89 | 90 | # Get a handle for our "MVP" uniform 91 | matrix_id = glGetUniformLocation(program_id, "MVP") 92 | view_matrix_id = glGetUniformLocation(program_id, "V") 93 | model_matrix_id = glGetUniformLocation(program_id, "M") 94 | 95 | # Read our OBJ file 96 | vertices,faces,uvs,normals,colors = objloader.load(".\\content\\male_apose_closed2.obj") 97 | vertex_data,uv_data,normal_data = objloader.process_obj( vertices,faces,uvs,normals,colors) 98 | 99 | # Our OBJ loader uses Python lists, convert to ctype arrays before sending to OpenGL 100 | vertex_data = objloader.generate_2d_ctypes(vertex_data) 101 | normal_data = objloader.generate_2d_ctypes(normal_data) 102 | 103 | # Load OBJ in to a VBO 104 | vertex_buffer = glGenBuffers(1); 105 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 106 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4 * 3, vertex_data, GL_STATIC_DRAW) 107 | 108 | normal_buffer = glGenBuffers(1) 109 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer) 110 | glBufferData(GL_ARRAY_BUFFER, len(normal_data) * 4 * 3, normal_data, GL_STATIC_DRAW) 111 | 112 | # vsync and glfw do not play nice. when vsync is enabled mouse movement is jittery. 113 | common.disable_vsyc() 114 | 115 | # Get a handle for our "LightPosition" uniform 116 | glUseProgram(program_id); 117 | light_id = glGetUniformLocation(program_id, "LightPosition_worldspace"); 118 | 119 | last_time = glfw.get_time() 120 | frames = 0 121 | 122 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 123 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 124 | 125 | current_time = glfw.get_time() 126 | glUseProgram(program_id) 127 | 128 | controls.computeMatricesFromInputs(window) 129 | ProjectionMatrix = controls.getProjectionMatrix(); 130 | ViewMatrix = controls.getViewMatrix(); 131 | ModelMatrix = mat4.identity(); 132 | mvp = ProjectionMatrix * ViewMatrix * ModelMatrix; 133 | 134 | # Send our transformation to the currently bound shader, 135 | # in the "MVP" uniform 136 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 137 | glUniformMatrix4fv(model_matrix_id, 1, GL_FALSE, ModelMatrix.data); 138 | glUniformMatrix4fv(view_matrix_id, 1, GL_FALSE, ViewMatrix.data); 139 | 140 | lightPos = vec3(0,4,4) 141 | glUniform3f(light_id, lightPos.x, lightPos.y, lightPos.z) 142 | 143 | #1rst attribute buffer : vertices 144 | glEnableVertexAttribArray(0) 145 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 146 | glVertexAttribPointer( 147 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 148 | 3, # len(vertex_data) 149 | GL_FLOAT, # type 150 | GL_FALSE, # ormalized? 151 | 0, # stride 152 | null # array buffer offset (c_type == void*) 153 | ) 154 | 155 | # 2nd attribute buffer : normals 156 | glEnableVertexAttribArray(1); 157 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer); 158 | glVertexAttribPointer( 159 | 1, # attribute 160 | 3, # size 161 | GL_FLOAT, # type 162 | GL_FALSE, # ormalized? 163 | 0, # stride 164 | null # array buffer offset (c_type == void*) 165 | ) 166 | 167 | 168 | # Draw the triangles, vertex data now contains individual vertices 169 | # so use array length 170 | glDrawArrays(GL_TRIANGLES, 0, len(vertex_data)) 171 | 172 | # Not strictly necessary because we only have 173 | glDisableVertexAttribArray(0) 174 | glDisableVertexAttribArray(1) 175 | 176 | 177 | # Swap front and back buffers 178 | glfw.swap_buffers(window) 179 | 180 | 181 | # Take screenshot of active buffer 182 | if glfw.get_key( window, glfw.KEY_P ) == glfw.PRESS: 183 | print("Saving screenshot as 'test.bmp'") 184 | screenshot('test.bmp',1024,768) 185 | 186 | # Dump MVP matrix to the command line 187 | if glfw.get_key( window, glfw.KEY_M ) == glfw.PRESS: 188 | print(mvp) 189 | 190 | # Poll for and process events 191 | glfw.poll_events() 192 | 193 | frames += 1 194 | 195 | # !Note braces around vertex_buffer and uv_buffer. 196 | # glDeleteBuffers expects a list of buffers to delete 197 | glDeleteBuffers(1, [vertex_buffer]) 198 | glDeleteBuffers(1, [normal_buffer]) 199 | glDeleteProgram(program_id) 200 | glDeleteVertexArrays(1, [vertex_array_id]) 201 | 202 | glfw.terminate() 203 | 204 | if __name__ == "__main__": 205 | main() 206 | -------------------------------------------------------------------------------- /glew.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerdak/opengl_tutorials_python/97ea99a6fe172cf577f5a8150ea3c17edf6e1a0f/glew.py -------------------------------------------------------------------------------- /glew_wish.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ Temporary stand-in for GLEW functionality until a proper Python 3 | module is created 4 | 5 | glew_wish = you wish... get it. Oh my god I'm so funny. 6 | 7 | The naming convention in this script mimics that of the 8 | official GLEW API, that should make switching to the real 9 | thing a little easier. 10 | 11 | TODO: 12 | [] - See http://pyopengl.sourceforge.net/documentation/opengl_diffs.html and consider scrapping the GLEW 13 | lookup or simply wrapping PyOpenGL's native behaviour. 14 | """ 15 | from __future__ import print_function 16 | 17 | from OpenGL.GL import * 18 | from OpenGL.GL.ARB import * 19 | from OpenGL.GLU import * 20 | from OpenGL.GLUT import * 21 | from OpenGL.GLUT.special import * 22 | from OpenGL.GL.shaders import * 23 | from OpenGL.extensions import * 24 | 25 | import collections 26 | import re 27 | import glfw 28 | 29 | GLEW_INITIALIZED = False 30 | GLEW_ERR = 0 31 | GLEW_OK = 1 32 | GLEW_OGL_INFO = None 33 | GL_VERSIONS = "GL_VERSION" 34 | 35 | def glewAreYouKidding(): 36 | """ Ugly ugly ugly hack to push GLEW variables in to the global namespace 37 | 38 | The GLEW header contains macros and definitions to enumerate 39 | available OpenGL functionality. The only way to provide these 40 | variables in the global namespace of Python is to 41 | work backwards through the call stack to add them globally 42 | to each calling stack frame. 43 | 44 | 45 | Note: Seriously, this is a garbage hack. It will *heavily* pollutes 46 | all calling namespaces. 47 | 48 | Some functionality here might be useful later for injecting GLEW-like 49 | definitions in to Python. Alternatively these functions could just 50 | be moved in to a class that adds them to its members 51 | 52 | """ 53 | if not GLEW_INITIALIZED: 54 | print("GLEW not initialized, call glewInit() first",file=stderr) 55 | return 56 | 57 | import inspect 58 | 59 | this_frame = inspect.currentframe() 60 | stack_frames = set() 61 | 62 | # ignore *this* object 63 | for s in inspect.stack(): 64 | if s[0] != this_frame: 65 | stack_frames.add(s[0]) 66 | 67 | def glewIsSupported(var): 68 | """ Return True if var is valid extension/core pair 69 | 70 | Usage: glewIsSupported("GL_VERSION_1_4 GL_ARB_point_sprite") 71 | 72 | Note: GLEW API was not well documented and this function was 73 | written in haste so the actual GLEW format for glewIsSupported 74 | might be different. 75 | 76 | TODO: 77 | - Only the extension parameter is currently checked. Validate 78 | the core as well. Will likely require scraping opengl docs 79 | for supported functionality 80 | """ 81 | var = re.sub(' +',' ',var) 82 | variables = var.split(' ') 83 | for v in variables: 84 | #if v in GLEW_OGL_INFO[GL_VERSIONS]: 85 | # return True 86 | if v in GLEW_OGL_INFO[GL_EXTENSIONS]: 87 | return True 88 | return False 89 | 90 | def glewGetExtension(extension): 91 | """ Return True if valid extension 92 | """ 93 | if extension in GLEW_OGL_INFO[GL_EXTENSIONS]: 94 | return True 95 | return False 96 | 97 | def glewInit(unsafe=False): 98 | """ GLEW initialization hack 99 | 100 | Glew Python packages are severely stale. PyOpenGL does a 101 | good job of exposing available functionality for OpenGL. 102 | It's likely GLEW is part of the PyOpenGL source (TODO: verify) 103 | 104 | This glewInit works and will actually come up with a set of valid 105 | extensions supported by user's graphics card. 106 | 107 | Input 108 | unsafe (bool): if true, calls glewAreYouKidding to add glew definitions 109 | to Pythons call stack (every single file) 110 | """ 111 | global GLEW_OK 112 | global GLEW_INITIALIZED 113 | global GLEW_OGL_INFO 114 | 115 | GLEW_OGL_INFO = collections.defaultdict(list) 116 | for name in (GL_VENDOR,GL_RENDERER,GL_VERSION,GL_SHADING_LANGUAGE_VERSION,GL_EXTENSIONS): 117 | GLEW_OGL_INFO[name] = glGetString(name).decode().split(' ') 118 | 119 | #It might be necessariy to use glGetStringi for extensions, so far glGetString has worked 120 | #GLEW_OGL_INFO[GL_EXTENSIONS] = glGetStringi(GL_EXTENSIONS,) 121 | 122 | # unique-ify extensions in set making the 'in' operator 123 | # O(1) average case. 124 | GLEW_OGL_INFO[GL_EXTENSIONS] = set(GLEW_OGL_INFO[GL_EXTENSIONS]) 125 | 126 | # opengl versions as of 2014 127 | ogl_version_history = { 128 | 1:[1,2,3,4,5], 129 | 2:[0,1], 130 | 3:[0,1,2,3], 131 | 4:[0,1,2,3,4,5] 132 | } 133 | 134 | # Extract supported versions using *this* mnachine's graphics settings 135 | # to infer backwards compatibility (not necessarily true but good for a placeholder) 136 | GLEW_OGL_INFO[GL_VERSIONS] = set() 137 | this_major = int(GLEW_OGL_INFO[GL_VERSION][0].split('.')[0]) 138 | this_minor = int(GLEW_OGL_INFO[GL_VERSION][0].split('.')[1]) 139 | 140 | for major in range(1,this_major+1): 141 | for minor in ogl_version_history[major]: 142 | if major == this_major and minor <= this_minor: 143 | GLEW_OGL_INFO[GL_VERSIONS].add("GL_VERSION_%d_%d"%(major,minor)) 144 | elif major != this_major: 145 | GLEW_OGL_INFO[GL_VERSIONS].add("GL_VERSION_%d_%d"%(major,minor)) 146 | 147 | GLEW_INITIALIZED = True 148 | 149 | if unsafe: 150 | glewAreYouKidding() 151 | 152 | return GLEW_OK 153 | 154 | def opengl_init(): 155 | global window 156 | # Initialize the library 157 | if not glfw.init(): 158 | print("Failed to initialize GLFW\n",file=sys.stderr) 159 | return False 160 | 161 | # Open Window and create its OpenGL context 162 | window = glfw.create_window(1024, 768, "Tutorial 02", None, None) #(in the accompanying source code this variable will be global) 163 | glfw.window_hint(glfw.SAMPLES, 4) 164 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 165 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 166 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 167 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 168 | 169 | if not window: 170 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 171 | glfw.terminate() 172 | return False 173 | 174 | # Initialize GLEW 175 | glfw.make_context_current(window) 176 | glewExperimental = True 177 | 178 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 179 | # more information including why can remove this code. 180 | if glewInit() != GLEW_OK: 181 | print("Failed to initialize GLEW\n",file=sys.stderr); 182 | return False 183 | return True 184 | 185 | def main(): 186 | opengl_init() 187 | glewInit() 188 | print(GLEW_OGL_INFO[GL_EXTENSIONS]) 189 | print(AVAILABLE_GLU_EXTENSIONS) 190 | 191 | if __name__=='__main__': 192 | main() 193 | -------------------------------------------------------------------------------- /multimethods.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ Multiple method utility decorator 3 | 4 | C++ Users: This is the simplest way of mimicing 5 | operator overloading. 6 | 7 | Note: As written this decorator only supports 8 | positional arguments. 9 | """ 10 | 11 | # http://www.artima.com/weblogs/viewpost.jsp?thread=101605 12 | registry = {} 13 | 14 | class MultiMethod(object): 15 | def __init__(self, name): 16 | self.name = name 17 | self.typemap = {} 18 | def __call__(self, *args): 19 | types = tuple(arg.__class__ for arg in args) # a generator expression! 20 | function = self.typemap.get(types) 21 | if function is None: 22 | raise TypeError("no match") 23 | return function(*args) 24 | def register(self, types, function): 25 | if types in self.typemap: 26 | raise TypeError("duplicate registration") 27 | self.typemap[types] = function 28 | 29 | def multimethod(*types): 30 | def register(function): 31 | name = function.__name__ 32 | mm = registry.get(name) 33 | if mm is None: 34 | mm = registry[name] = MultiMethod(name) 35 | mm.register(types, function) 36 | return mm 37 | return register -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | Tutorial 1 notes 2 | ---------------- 3 | 1. download glfw for python 4 | 2. download glfw libraries 5 | *- https://github.com/FlorianRhiem/pyGLFW is the version used: (https://pypi.python.org/pypi/pyglfw/0.1.0) 6 | - https://github.com/pyglfw/pyglfw is another version w/ better documentation but the Florian one 7 | above does a nicer job of wrapping functionality. 8 | 3. original tuts call for glew, pyopengl seems to include ARB extensions as standard. 9 | a. [] Research this a bit, does pyopengl expose glew. Review pyopengl source. 10 | b. [] figure out how to query for ARB extension existence in pyopengl. (Maybe http://pyopengl.sourceforge.net/pydoc/OpenGL.extensions.html\ 11 | 4. Installation 12 | 1. pip3.4.exe install PyOpenGL PyOpenGL_accelerate 13 | 2. pip3.4.exe install Pillow 14 | 3. download https://github.com/FlorianRhiem/pyGLFW, python setup.py install 15 | a. Use the one included with this 16 | 5. Include link to PyOpenGL performance document: http://pyopengl.sourceforge.net/documentation/opengl_diffs.html 17 | * This page does a nice job of summarzing speedup/debugging tradeoffs 18 | * **NOTE: The section `Extensions and Conditional Functionality` is effectively GLEW minus the 19 | profile specific querying. 20 | 21 | Tutorial 2 notes: 22 | 1. !!!!! If you mix tabs and spaces the usual indentation error gets silently squelched by 23 | what I assume is OpenGL. It's not entirely clear why the error goes missing. 24 | 2. [] Create a custom tutorial describing the differences between VAO's, VBO's, and so on. 25 | - http://www.opengl.org/wiki/Tutorial2:_VAOs,_VBOs,_Vertex_and_Fragment_Shaders_(C_/_SDL) 26 | - http://www.arcsynthesis.org/gltut/Positioning/Tutorial%2005.html 27 | - http://antongerdelan.net/opengl/vertexbuffers.html * 28 | - *This tutorial lead to vao_test.py which properly uses the 29 | 3. Note the creation of a glew standin (used by tuts 1 and 2) 30 | * See http://pyopengl.sourceforge.net/documentation/opengl_diffs.html and consider scrapping the GLEW 31 | lookup or simply wrapping PyOpenGL's native behaviour. 32 | 33 | Tutorial 3 notes: 34 | 1. Created a replacement for glm (not yet complete.) 35 | - Python operator overload http://rgruet.free.fr/PQR26/PQR2.6.html#SpecialMethods 36 | - glm source (headers only, easy to read): https://github.com/g-truc/glm 37 | 2. glm matrices are defined in terms of columns, copy their source directly. 38 | 3. Matrix math with glm works differently because matrices are applied from right to left 39 | - e.g. MatrixA * MatrixB * V results is MatrixB * V = V` then MatrixA * V` = V`` 40 | 41 | Tutorial 4 Notes: 42 | 1. Added key event for 'D' to enable/disable depth to allow the user to see the effects 43 | depth testing 44 | 2. Realized I wasn't clearing data. Added that to Tuts 2-4 (Tut1 doesn't allocate anything.) 45 | 46 | Tutorial 5 Notes: 47 | 1. Note for the user that there are several image processing libraries. Some better than others. :) 48 | 2. Use Pillow (PIL fork) to load images 49 | 3. Research why glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) is necessary for 50 | displaying our image. I'm assuming it's an issue with mipmapping but who knows. 51 | 4. Dig in to mipmapping, see if there is a way to generate your own mipmapped texturing using Pillow 52 | operations 53 | - http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ 54 | 5. Rename `bind_texture` modes to something resembling useful. 55 | 56 | Tutorial 6 notes: 57 | 1. Seriously review Sin/Cos and how they relate to spherical coordinates 58 | 2. Added a common.disable_vsyc() method. Calling this will stabilize mouse look, 59 | otherwise it gets all jittery (will need to be fixed in later tuts.) 60 | 61 | Tutorial 7 notes: 62 | 1. OBJ loader created. Some functionality is lacking and the loader requires normals and uvs 63 | or it gets all stupid 64 | 2. Don't go too far making the obj loader work. It just needs to function w/ the given content. 65 | Let the user handle the rest. 66 | 67 | Tutorial 9 notes: 68 | 1. Added texture.py to handle various texture/image related functionality. Review original C++ tuts 69 | to figure out when those authors move texture functions to a dedicated file. 70 | 2. Added vboindexer.py with some additional, possibly confusing, code for mimicing 71 | C++'s std::map::find behaviour. Python's default dict is unordered so the original vboindexer.cpp 72 | had to be modified to do an equality, not a greater than, memory block comparison. This requires loading 73 | the "c" library. This probably isn't worth explaining in the Python tuts. 74 | 75 | Byte Sizes: 76 | ----------- 77 | Byte lengths are hardcoded right now 78 | 79 | e.g. glBufferData(GL_ARRAY_BUFFER, len(data) * 4 * 3, indexed_normals, GL_STATIC_DRAW) 80 | 81 | Here, (len(data) * 4 * 3) = number of objects * 4 bytes per float * 3 floats per objects. 82 | 83 | ctypes.sizeof is one possible solution for automate byte-size detection. 84 | 85 | Relative imports (unit tests) 86 | ------------------------------ 87 | - Unit tests are inline w/ the module they are testing. This was supposed to keep the primary source 88 | directory clean of faff but I didn't realize that relative imports are not directly supported when 89 | scripts are run inside module folders (https://mail.python.org/pipermail/python-3000/2007-April/006793.html) 90 | 91 | To run them either use `nosetests` (https://nose.readthedocs.org/en/latest/) inside the mod folder or call 92 | the tests directly w/ `python -m csgl.test_example` 93 | 94 | Better explanation: http://stackoverflow.com/questions/72852/how-to-do-relative-imports-in-python 95 | -------------------------------------------------------------------------------- /objloader.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import math 5 | import sys 6 | 7 | from collections import defaultdict 8 | from OpenGL.GL import * 9 | 10 | def normalize_vertex_list(verts): 11 | """ Uniformly rescale 3D vertex data so that its axis of 12 | greatest change is normalized to the range [0,1] 13 | 14 | Original input is modified 15 | """ 16 | 17 | # Set initial min/max values to their inverse extremes so 18 | # the loop below will change their values no matter what. 19 | v_max = [sys.float_info.min] * 3 20 | v_min = [sys.float_info.max] * 3 21 | for v in verts: 22 | for i in range(0,3): 23 | if v[i] > v_max[i]: 24 | v_max[i] = v[i] 25 | if v[i] {'wavefront line element':token_parser()} 176 | obj_line_parsers = defaultdict(lambda : lambda a: None,{ 177 | 'v':parse_vertex_line, 178 | 'V':parse_vertex_line, 179 | 'f':parse_face_line, 180 | 'F':parse_face_line, 181 | 'vt':parse_uv_line, 182 | 'vn':parse_normal_line, 183 | }) 184 | obj_parse_assignment = defaultdict(lambda : lambda a: None,{ 185 | 'v':lambda b:verts.append(b), 186 | 'V':lambda b:verts.append(b), 187 | 'f':lambda b:faces.append(b), 188 | 'F':lambda b:faces.append(b), 189 | 'vt':lambda b:uvs.append(b), 190 | 'vn':lambda b:norms.append(b) 191 | }) 192 | 193 | verts = [] 194 | colors = None 195 | faces = [] 196 | norms = [] 197 | uvs = [] 198 | 199 | with open(file_name,'r') as fr: 200 | for line_index,line in enumerate(fr): 201 | # tokenize each line (ie. Split lines up in to lists of elements) 202 | # e.g. f 1//1 2//2 3//3 => [f,1//1,2//2,3//3] 203 | tokens = line.strip().split(' ') 204 | if tokens == None: continue 205 | if tokens[0] == '': continue 206 | 207 | try: 208 | key = tokens[0] 209 | value = obj_line_parsers[key](tokens) 210 | obj_parse_assignment[key](value) 211 | except Exception as err: 212 | print("Ill formed line[%d]: %s"%(line_index,line)) 213 | print("Err: ",err) 214 | 215 | if normalize: 216 | verts = normalize_obj(verts) 217 | 218 | return verts,faces,uvs,norms,colors 219 | 220 | def cross(a,b): 221 | return [a[1]*b[2] - a[2]*b[1],a[0]*b[2] - a[2]*b[0],a[0]*b[1] - a[1]*b[0]] 222 | def vlen(a): 223 | return math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]) 224 | def vsub(a,b): 225 | return [a[0]-b[0],a[1]-b[1],a[2]-b[2]] 226 | 227 | def process_obj(verts,faces,uvs,normals,colors): 228 | """ Split 6-component facial data in to individual triangles 229 | for OpenGL 230 | 231 | Note: Vertex indices are not used directly by OpenGL. 232 | They are used here to create single, large, vertex array 233 | grouped by triangle. This means each very will likely 234 | be used several times. 235 | """ 236 | # Faces currently store 6 values, split them up a bit 237 | out_verts = [] 238 | out_uvs = [] 239 | out_normals = [] 240 | for face in faces: 241 | if len(face) == 9: 242 | out_verts.append(verts[face[0]-1]) 243 | out_verts.append(verts[face[1]-1]) 244 | out_verts.append(verts[face[2]-1]) 245 | 246 | if len(uvs) == len(verts): 247 | out_uvs.append(uvs[face[3]-1]) 248 | out_uvs.append(uvs[face[4]-1]) 249 | out_uvs.append(uvs[face[5]-1]) 250 | else: 251 | out_uvs.append(0) 252 | out_uvs.append(0) 253 | out_uvs.append(0) 254 | 255 | out_normals.append(normals[face[6]-1]) 256 | out_normals.append(normals[face[7]-1]) 257 | out_normals.append(normals[face[8]-1]) 258 | elif len(face) == 3: 259 | out_verts.append(verts[face[0]-1]) 260 | out_verts.append(verts[face[1]-1]) 261 | out_verts.append(verts[face[2]-1]) 262 | 263 | edge1 = vsub(verts[face[2]-1],verts[face[0]-1]) 264 | edge2 = vsub(verts[face[1]-1],verts[face[0]-1]) 265 | 266 | normal = cross(edge1,edge2) 267 | length = vlen(normal) 268 | 269 | normal[0] /= length 270 | normal[1] /= length 271 | normal[2] /= length 272 | 273 | out_normals.append(normal) 274 | out_normals.append(normal) 275 | out_normals.append(normal) 276 | return out_verts,out_uvs,out_normals 277 | 278 | def generate_2d_ctypes(data): 279 | """ Covert 2D Python list of lists to 2D ctype array 280 | 281 | Input: 282 | data - 2D array like vertices[36][3] 283 | Format: array[rows][cols] where rows are individual elements 284 | and cols are components of each element. 285 | """ 286 | c = len(data[0]) 287 | r = len(data) 288 | 289 | # multidimensional ctype arrays require parens 290 | # or the array type below would become float[r*c] 291 | # instead of float[r][c]. Alternative notation: 292 | # array_type = GLfloat*c*r 293 | array_type = r * (c*GLfloat) 294 | ret = array_type() 295 | 296 | for i in range(0,len(data)): 297 | for j in range(0,c): 298 | ret[i][j] = data[i][j] 299 | 300 | return ret 301 | def main(): 302 | print (10 * (3 * GLfloat)) 303 | print (GLfloat * 3 * 10) 304 | # Note: Compare return value to vertex index - 1 305 | # e.g. f 1 2 3 => [0,1,2] 306 | # f = parse_face_line(['f','1','2','3']) 307 | # assert(f[0] == 0 and f[1] == 1 and f[2] == 2) 308 | 309 | # f = parse_face_line(['f','1//12','2//8','3//5']) 310 | # assert(f[0] == 0 and f[1] == 1 and f[2] == 2) 311 | 312 | # f = parse_face_line(['f','1/4/12','2/6/8','3/7/5']) 313 | # assert(f[0] == 0 and f[1] == 1 and f[2] == 2) 314 | 315 | # f = parse_face_line(['1//12','2//8','3//5']) 316 | # assert(f==None) 317 | 318 | # v = parse_vertex_line(['v','0.1','0.2','0.3']) 319 | # assert(v[0] == 0.1 and v[1] == 0.2 and v[2] == 0.3) 320 | 321 | # v = parse_vertex_line(['v','0.1','0.2','0.3','1','2','3']) 322 | # assert(v[0] == 0.1 and v[1] == 0.2 and v[2] == 0.3) 323 | 324 | # v = parse_vertex_line(['v','0.1','0.2']) 325 | # assert(v == None) 326 | 327 | # c = obj_line_parsers["#"](["asdasd"]) 328 | # #obj_parse_assignment["#"](c) 329 | # print(c) 330 | 331 | v,f,uv,n,c = load(".\\content\\suzanne.obj"); 332 | v,uv,n = process_obj(v,f,uv,n,c) 333 | print(v[0]) 334 | print(uv[0]) 335 | print(n[0]) 336 | if __name__ == '__main__': 337 | main() 338 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # OpenGL-Tutorials in Python 3 | 4 | This repository contains Python versions of the C++ tutorials at [opengl-tutorials](www.opengl-tutorials.org). 5 | 6 | The Python code was written to be as close to the original C++ code as possible. Certain concessions were made to accomodate missing libraries and to match Python's style guide wherever possible. One good example of missing functionality is the [glew](http://glew.sourceforge.net/) library. The most up to date Python-glew wrapper is almost 10 years old and it does not play nicely with Python 3.x. 7 | 8 | Currently only the source code has been converted. The actual tutorials are not finished but will be coming soon and with the original author's permission. 9 | 10 | ##Requirements 11 | --- 12 | 1. Python 3.X [[download](https://www.python.org/download)] 13 | 1. PyOpengl && PyOpenGL_Accelerate: `pip install pyopengl pyopengl_accelerate` 14 | 1. Pillow (a Python Image Library (PIL) fork): `pip install pillow` 15 | 1. pyglfw (a glfw wrapper) 16 | * Download [pyglfw](https://github.com/FlorianRhiem/pyGLFW), unpack, and run `python setup.py install` 17 | * Download [glfw](http://www.glfw.org/) binaries for your OS. 18 | * Linux users: You should be all set after installation. 19 | * Windows users: Copy the **glfw** binaries in the tutorials folder or modify `pyglfw.py` to use Windows paths, by default `pyglfw.py` is set to common Linux paths. 20 | 21 | ##Replacements 22 | --- 23 | A few of the original C++ tutorials contain references to libraries that don't have Python equivalents or the equivalent libraries were beyond the scope of a simple tutorial. Here is a quick table showing what has been replaced: 24 | 25 | 26 | CPP Tutorials | Python Tutorials 27 | ------------- | ---------------- 28 | glm | cgsl 29 | glew | glew_wish 30 | obj loader | objloader.py 31 | 32 | 33 | * **cgsl**: Curiously simple graphics library (CSGL) is a hacky little bit of code I threw together to mimic GLM from the original C++ tutorials. Given more time I may go back and do a proper port. The idea with CSGL was to avoid using complex modules like Numpy that are beyond the scope of these tutorials. 34 | * **glew_wish**: Very much a tongue in cheek kludge to support GLEW's function querying. GLEW is rarely used in the beginner tutorials so this code was never finished. It will handle the basics and the reason can take it as an exercise to finish what I started or do a proper GLEW port. In a perfect world I would do something similar to `pyglfw` like wrapping the GLEW binaries. 35 | * **objloader.py**: The original C++ code creates its own Wavefront OBJ loader that supports a very specific output format from Blender. I copied the original tutorials exactly, meaning you need both UVs and Normals in your Blender output or `objloader.py` won't know what to do. 36 | 37 | 38 | ## Completed Conversions 39 | 40 | * Basic OpenGL 41 | * Tutorial 1 : Opening a window [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial1.py)] 42 | * Tutorial 2 : The first triangle [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial2.py)] 43 | * Tutorial 3 : Matrices [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial3.py)] 44 | * Tutorial 4 : A Colored Cube [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial4.py)] 45 | * Tutorial 5 : A Textured Cube [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial5.py)] 46 | * Tutorial 6 : Keyboard and Mouse [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial6.py)] 47 | * Tutorial 7 : Model loading [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial7.py)] 48 | * Tutorial 8 : Basic shading [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial8.py)] 49 | * Intermediate Tutorials 50 | * Tutorial 9 : VBO Indexing [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial9.py)] 51 | * Tutorial 10 : Transparency [[source](https://github.com/Jerdak/opengl_tutorials_python/blob/master/tutorial10.py)] 52 | 53 | ## Ongoing Conversions 54 | * Tutorial 11 : 2D text 55 | * Tutorial 12 : OpenGL Extensions 56 | * Tutorial 13 : Normal Mapping 57 | * Tutorial 14 : Render To Texture 58 | * Tutorial 15 : Lightmaps 59 | * Tutorial 16 : Shadow mapping 60 | * Tutorial 17 : Rotations 61 | * Tutorial 18 : Billboards & Particles 62 | 63 | # License(s) 64 | 65 | ### Original 66 | [WTFPL Public License Version 2](http://www.opengl-tutorial.org/download/)* 67 | 68 | *Note: The language of WTFPL is unprofessional and will not be restated here. Initially I released my code under WTFPL but after some deliberation I decided against using WTFPL. 69 | 70 | ### Current 71 | The MIT License (MIT) 72 | 73 | Copyright (c) 2014 Jeremy Carson 74 | 75 | Permission is hereby granted, free of charge, to any person obtaining a copy 76 | of this software and associated documentation files (the "Software"), to deal 77 | in the Software without restriction, including without limitation the rights 78 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 79 | copies of the Software, and to permit persons to whom the Software is 80 | furnished to do so, subject to the following conditions: 81 | 82 | The above copyright notice and this permission notice shall be included in 83 | all copies or substantial portions of the Software. 84 | 85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 86 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 87 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 88 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 89 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 90 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 91 | THE SOFTWARE. 92 | -------------------------------------------------------------------------------- /texture.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | from OpenGL.GL import * 6 | from OpenGL.GL.ARB import * 7 | from OpenGL.GLU import * 8 | from OpenGL.GLUT import * 9 | from OpenGL.GLUT.special import * 10 | from OpenGL.GL.shaders import * 11 | from glew_wish import * 12 | from csgl import * 13 | from PIL.Image import open as pil_open 14 | 15 | def bind_texture(texture_id,mode): 16 | """ Bind texture_id using several different modes 17 | 18 | Notes: 19 | Without mipmapping the texture is incomplete 20 | and requires additional constraints on OpenGL 21 | to properly render said texture. 22 | 23 | Use 'MIN_FILTER" or 'MAX_LEVEL' to render 24 | a generic texture with a single resolution 25 | Ref: 26 | [] - http://www.opengl.org/wiki/Common_Mistakes#Creating_a_complete_texture 27 | [] - http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ 28 | TODO: 29 | - Rename modes to something useful 30 | """ 31 | if mode == 'DEFAULT': 32 | glBindTexture(GL_TEXTURE_2D, texture_id) 33 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 34 | elif mode == 'MIN_FILTER': 35 | glBindTexture(GL_TEXTURE_2D, texture_id) 36 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 37 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) 38 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) 39 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 40 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 41 | elif mode == 'MAX_LEVEL': 42 | glBindTexture(GL_TEXTURE_2D, texture_id) 43 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 44 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0) 45 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0) 46 | else: 47 | glBindTexture(GL_TEXTURE_2D, texture_id) 48 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 49 | 50 | # Generate mipmaps? Doesn't seem to work 51 | glGenerateMipmap(GL_TEXTURE_2D) 52 | 53 | def load_image(file_name): 54 | im = pil_open(file_name) 55 | try: 56 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1) 57 | except SystemError: 58 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1) 59 | 60 | texture_id = glGenTextures(1) 61 | 62 | # To use OpenGL 4.2 ARB_texture_storage to automatically generate a single mipmap layer 63 | # uncomment the 3 lines below. Note that this should replaced glTexImage2D below. 64 | #bind_texture(texture_id,'DEFAULT') 65 | #glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); 66 | #glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image) 67 | 68 | # "Bind" the newly created texture : all future texture functions will modify this texture 69 | bind_texture(texture_id,'MIN_FILTER') 70 | glTexImage2D( 71 | GL_TEXTURE_2D, 0, 3, width, height, 0, 72 | GL_RGBA, GL_UNSIGNED_BYTE, image 73 | ) 74 | return texture_id -------------------------------------------------------------------------------- /tutorial1.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 1: Opening a Window 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GLU import * 10 | from OpenGL.GLUT import * 11 | from OpenGL.GLUT.special import * 12 | from OpenGL.GL.shaders import * 13 | 14 | from glew_wish import * 15 | import glfw 16 | import sys 17 | import os 18 | 19 | def main(): 20 | # Initialize the library 21 | if not glfw.init(): 22 | return 23 | 24 | # Open Window and create its OpenGL context 25 | window = glfw.create_window(1024, 768, "Tutorial 01", None, None) 26 | 27 | # 28 | glfw.window_hint(glfw.SAMPLES, 4) 29 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 30 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 31 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 32 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 33 | 34 | if not window: 35 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 36 | glfw.terminate() 37 | return 38 | 39 | # Initialize GLEW 40 | glfw.make_context_current(window) 41 | glewExperimental = True 42 | 43 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 44 | # more information including why can remove this code. 45 | if glewInit() != GLEW_OK: 46 | print("Failed to initialize GLEW\n",file=sys.stderr); 47 | return 48 | 49 | glfw.set_input_mode(window,glfw.STICKY_KEYS,True) 50 | 51 | 52 | # Loop until the user closes the window 53 | 54 | #while not glfw.window_should_close(window): 55 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 56 | 57 | # Draw nothing sucker 58 | 59 | # Swap front and back buffers 60 | glfw.swap_buffers(window) 61 | 62 | # Poll for and process events 63 | glfw.poll_events() 64 | 65 | glfw.terminate() 66 | 67 | if __name__ == "__main__": 68 | main() 69 | 70 | -------------------------------------------------------------------------------- /tutorial10.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 10: Transparency 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | 18 | import texture as textureutils 19 | import common 20 | import glfw 21 | import sys 22 | import os 23 | import controls 24 | import objloader 25 | import vboindexer 26 | 27 | # Global window 28 | window = None 29 | null = c_void_p(0) 30 | 31 | def opengl_init(): 32 | global window 33 | # Initialize the library 34 | if not glfw.init(): 35 | print("Failed to initialize GLFW\n",file=sys.stderr) 36 | return False 37 | 38 | # Open Window and create its OpenGL context 39 | window = glfw.create_window(1024, 768, "Tutorial 10", None, None) #(in the accompanying source code this variable will be global) 40 | glfw.window_hint(glfw.SAMPLES, 4) 41 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 42 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 43 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 44 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 45 | 46 | if not window: 47 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 48 | glfw.terminate() 49 | return False 50 | 51 | # Initialize GLEW 52 | glfw.make_context_current(window) 53 | glewExperimental = True 54 | 55 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 56 | # more information including why can remove this code.a 57 | if glewInit() != GLEW_OK: 58 | print("Failed to initialize GLEW\n",file=stderropen.sys); 59 | return False 60 | return True 61 | 62 | def c_type_fill(data,data_type): 63 | rows = len(data) 64 | cols = len(data[0]) 65 | t = rows * (cols * data_type) 66 | tmp = t() 67 | for r in range(rows): 68 | for c in range(cols): 69 | tmp[r][c] = data[r][c] 70 | return tmp 71 | 72 | def c_type_fill_1D(data,data_type): 73 | rows = len(data) 74 | 75 | t = rows * data_type 76 | tmp = t() 77 | for r in range(rows): 78 | tmp[r] = data[r] 79 | return tmp 80 | 81 | def main(): 82 | 83 | # Initialize GLFW and open a window 84 | if not opengl_init(): 85 | return 86 | 87 | # Enable key events 88 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 89 | glfw.set_cursor_pos(window, 1024/2, 768/2) 90 | 91 | # Set opengl clear color to something other than red (color used by the fragment shader) 92 | glClearColor(0.0,0.0,0.4,0.0) 93 | 94 | # Enable depth test 95 | glEnable(GL_DEPTH_TEST) 96 | 97 | # Accept fragment if it closer to the camera than the former one 98 | glDepthFunc(GL_LESS) 99 | 100 | # Cull triangles which normal is not towards the camera 101 | glEnable(GL_CULL_FACE) 102 | 103 | vertex_array_id = glGenVertexArrays(1) 104 | glBindVertexArray( vertex_array_id ) 105 | 106 | # Create and compile our GLSL program from the shaders 107 | program_id = common.LoadShaders( ".\\shaders\\Tutorial10\\StandardShading.vertexshader", 108 | ".\\shaders\\Tutorial10\\StandardTransparentShading.fragmentshader" ) 109 | 110 | # Get a handle for our "MVP" uniform 111 | matrix_id = glGetUniformLocation(program_id, "MVP") 112 | view_matrix_id = glGetUniformLocation(program_id, "V") 113 | model_matrix_id = glGetUniformLocation(program_id, "M") 114 | 115 | # Load the texture 116 | texture = textureutils.load_image(".\\content\\uvmap_suzanne.bmp") 117 | 118 | # Get a handle for our "myTextureSampler" uniform 119 | texture_id = glGetUniformLocation(program_id, "myTextureSampler") 120 | 121 | # Read our OBJ file 122 | vertices,faces,uvs,normals,colors = objloader.load(".\\content\\suzanne.obj") 123 | vertex_data,uv_data,normal_data = objloader.process_obj( vertices,faces,uvs,normals,colors) 124 | 125 | # Our OBJ loader uses Python lists, convert to ctype arrays before sending to OpenGL 126 | vertex_data = objloader.generate_2d_ctypes(vertex_data) 127 | uv_data = objloader.generate_2d_ctypes(uv_data) 128 | normal_data = objloader.generate_2d_ctypes(normal_data) 129 | 130 | indexed_vertices, indexed_uvs, indexed_normals, indices = vboindexer.indexVBO(vertex_data,uv_data,normal_data) 131 | 132 | indexed_vertices = c_type_fill(indexed_vertices,GLfloat) 133 | indexed_uvs = c_type_fill(indexed_uvs,GLfloat) 134 | indexed_normals = c_type_fill(indexed_normals,GLfloat) 135 | indices = c_type_fill_1D(indices,GLushort) 136 | 137 | 138 | # Load OBJ in to a VBO 139 | vertex_buffer = glGenBuffers(1); 140 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 141 | glBufferData(GL_ARRAY_BUFFER, len(indexed_vertices) * 4 * 3, indexed_vertices, GL_STATIC_DRAW) 142 | 143 | uv_buffer = glGenBuffers(1) 144 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 145 | glBufferData(GL_ARRAY_BUFFER, len(indexed_uvs) * 4 * 2, indexed_uvs, GL_STATIC_DRAW) 146 | 147 | normal_buffer = glGenBuffers(1) 148 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer) 149 | glBufferData(GL_ARRAY_BUFFER, len(indexed_normals) * 4 * 3, indexed_normals, GL_STATIC_DRAW) 150 | 151 | # Generate a buffer for the indices as well 152 | elementbuffer = glGenBuffers(1) 153 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer) 154 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(indices) * 2, indices , GL_STATIC_DRAW); 155 | 156 | # Enable blending 157 | glEnable(GL_BLEND) 158 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 159 | 160 | # vsync and glfw do not play nice. when vsync is enabled mouse movement is jittery. 161 | common.disable_vsyc() 162 | 163 | # Get a handle for our "LightPosition" uniform 164 | glUseProgram(program_id); 165 | light_id = glGetUniformLocation(program_id, "LightPosition_worldspace"); 166 | 167 | last_time = glfw.get_time() 168 | frames = 0 169 | 170 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 171 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 172 | 173 | current_time = glfw.get_time() 174 | if current_time - last_time >= 1.0: 175 | glfw.set_window_title(window,"Tutorial 10. FPS: %d"%(frames)) 176 | frames = 0 177 | last_time = current_time 178 | 179 | glUseProgram(program_id) 180 | 181 | controls.computeMatricesFromInputs(window) 182 | ProjectionMatrix = controls.getProjectionMatrix(); 183 | ViewMatrix = controls.getViewMatrix(); 184 | ModelMatrix = mat4.identity(); 185 | mvp = ProjectionMatrix * ViewMatrix * ModelMatrix; 186 | 187 | # Send our transformation to the currently bound shader, 188 | # in the "MVP" uniform 189 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 190 | glUniformMatrix4fv(model_matrix_id, 1, GL_FALSE, ModelMatrix.data); 191 | glUniformMatrix4fv(view_matrix_id, 1, GL_FALSE, ViewMatrix.data); 192 | 193 | lightPos = vec3(4,4,4) 194 | glUniform3f(light_id, lightPos.x, lightPos.y, lightPos.z) 195 | 196 | # Bind our texture in Texture Unit 0 197 | glActiveTexture(GL_TEXTURE0); 198 | glBindTexture(GL_TEXTURE_2D, texture); 199 | # Set our "myTextureSampler" sampler to user Texture Unit 0 200 | glUniform1i(texture_id, 0); 201 | 202 | #1rst attribute buffer : vertices 203 | glEnableVertexAttribArray(0) 204 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 205 | glVertexAttribPointer( 206 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 207 | 3, # len(vertex_data) 208 | GL_FLOAT, # type 209 | GL_FALSE, # ormalized? 210 | 0, # stride 211 | null # array buffer offset (c_type == void*) 212 | ) 213 | 214 | # 2nd attribute buffer : colors 215 | glEnableVertexAttribArray(1) 216 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 217 | glVertexAttribPointer( 218 | 1, # attribute 1. No particular reason for 1, but must match the layout in the shader. 219 | 2, # len(vertex_data) 220 | GL_FLOAT, # type 221 | GL_FALSE, # ormalized? 222 | 0, # stride 223 | null # array buffer offset (c_type == void*) 224 | ) 225 | 226 | # 3rd attribute buffer : normals 227 | glEnableVertexAttribArray(2); 228 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer); 229 | glVertexAttribPointer( 230 | 2, # attribute 231 | 3, # size 232 | GL_FLOAT, # type 233 | GL_FALSE, # ormalized? 234 | 0, # stride 235 | null # array buffer offset (c_type == void*) 236 | ) 237 | 238 | # Index buffer 239 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer) 240 | 241 | # Draw the triangles ! 242 | glDrawElements( 243 | GL_TRIANGLES, # mode 244 | len(indices), # count 245 | GL_UNSIGNED_SHORT, # type 246 | null # element array buffer offset 247 | ) 248 | # Not strictly necessary because we only have 249 | glDisableVertexAttribArray(0) 250 | glDisableVertexAttribArray(1) 251 | glDisableVertexAttribArray(2) 252 | 253 | 254 | # Swap front and back buffers 255 | glfw.swap_buffers(window) 256 | 257 | # Poll for and process events 258 | glfw.poll_events() 259 | 260 | frames += 1 261 | 262 | # !Note braces around vertex_buffer and uv_buffer. 263 | # glDeleteBuffers expects a list of buffers to delete 264 | glDeleteBuffers(1, [vertex_buffer]) 265 | glDeleteBuffers(1, [uv_buffer]) 266 | glDeleteBuffers(1, [normal_buffer]) 267 | glDeleteProgram(program_id) 268 | glDeleteTextures([texture_id]) 269 | glDeleteVertexArrays(1, [vertex_array_id]) 270 | 271 | glfw.terminate() 272 | 273 | if __name__ == "__main__": 274 | main() 275 | -------------------------------------------------------------------------------- /tutorial2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 2: Drawing the triangle 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | 16 | import common 17 | import glfw 18 | import sys 19 | import os 20 | 21 | # Global window 22 | window = None 23 | null = c_void_p(0) 24 | 25 | def opengl_init(): 26 | global window 27 | # Initialize the library 28 | if not glfw.init(): 29 | print("Failed to initialize GLFW\n",file=sys.stderr) 30 | return False 31 | 32 | # Open Window and create its OpenGL context 33 | window = glfw.create_window(1024, 768, "Tutorial 02", None, None) #(in the accompanying source code this variable will be global) 34 | glfw.window_hint(glfw.SAMPLES, 4) 35 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 36 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 37 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 38 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 39 | 40 | if not window: 41 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 42 | glfw.terminate() 43 | return False 44 | 45 | # Initialize GLEW 46 | glfw.make_context_current(window) 47 | glewExperimental = True 48 | 49 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 50 | # more information including why can remove this code. 51 | if glewInit() != GLEW_OK: 52 | print("Failed to initialize GLEW\n",file=sys.stderr); 53 | return False 54 | return True 55 | 56 | def main(): 57 | if not opengl_init(): 58 | return 59 | 60 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 61 | 62 | # Set opengl clear color to something other than red (color used by the fragment shader) 63 | glClearColor(0,0,0.4,0) 64 | 65 | 66 | vertex_array_id = glGenVertexArrays(1) 67 | glBindVertexArray( vertex_array_id ) 68 | 69 | program_id = common.LoadShaders( ".\\shaders\\Tutorial2\\SimpleVertexShader.vertexshader", 70 | ".\\shaders\\Tutorial2\\SimpleFragmentShader.fragmentshader" ) 71 | 72 | vertex_data = [-1.0, -1.0, 0.0, 73 | 1.0, -1.0, 0.0, 74 | 0.0, 1.0, 0.0] 75 | 76 | vertex_buffer = glGenBuffers(1); 77 | 78 | # GLFloat = c_types.c_float 79 | array_type = GLfloat * len(vertex_data) 80 | 81 | # array_type = c_types.c_float_array_9 so unpack values of vertex array 82 | x = array_type(*vertex_data) 83 | 84 | 85 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 86 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4, array_type(*vertex_data), GL_STATIC_DRAW) 87 | 88 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 89 | glClear(GL_COLOR_BUFFER_BIT) 90 | 91 | glUseProgram(program_id) 92 | 93 | # Bind vertex buffer data to the attribute 0 in our shader. 94 | # Note: This can also be done in the VAO itself (see vao_test.py) 95 | 96 | # Enable the vertex attribute at element[0], in this case that's the triangle's vertices 97 | # this could also be color, normals, etc. It isn't necessary to disable these 98 | # 99 | glEnableVertexAttribArray(0) 100 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 101 | glVertexAttribPointer( 102 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 103 | 3, # len(vertex_data) 104 | GL_FLOAT, # type 105 | GL_FALSE, # ormalized? 106 | 0, # stride 107 | null # array buffer offset (c_type == void*) 108 | ) 109 | 110 | # Draw the triangle ! 111 | glDrawArrays(GL_TRIANGLES, 0, 3) #3 indices starting at 0 -> 1 triangle 112 | 113 | # Not strictly necessary because we only have 114 | glDisableVertexAttribArray(0) 115 | 116 | # Swap front and back buffers 117 | glfw.swap_buffers(window) 118 | 119 | # Poll for and process events 120 | glfw.poll_events() 121 | 122 | # note braces around vertex_buffer and vertex_array_id. 123 | # These 2 functions expect arrays of values 124 | glDeleteBuffers(1, [vertex_buffer]) 125 | glDeleteProgram(program_id) 126 | glDeleteVertexArrays(1, [vertex_array_id]) 127 | glfw.terminate() 128 | 129 | if __name__ == "__main__": 130 | main() 131 | -------------------------------------------------------------------------------- /tutorial3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 3: Matrices 4 | 5 | Note(s): 6 | - Spend several days/weeks going over matrices. If your linear-algebra-foo 7 | is weak you are going to have a painful time with OpenGL. 8 | """ 9 | 10 | from __future__ import print_function 11 | 12 | from OpenGL.GL import * 13 | from OpenGL.GL.ARB import * 14 | from OpenGL.GLU import * 15 | from OpenGL.GLUT import * 16 | from OpenGL.GLUT.special import * 17 | from OpenGL.GL.shaders import * 18 | from glew_wish import * 19 | from csgl import * 20 | 21 | import common 22 | import glfw 23 | import sys 24 | import os 25 | 26 | # Global window 27 | window = None 28 | null = c_void_p(0) 29 | 30 | def opengl_init(): 31 | global window 32 | # Initialize the library 33 | if not glfw.init(): 34 | print("Failed to initialize GLFW\n",file=sys.stderr) 35 | return False 36 | 37 | # Open Window and create its OpenGL context 38 | window = glfw.create_window(1024, 768, "Tutorial 03", None, None) #(in the accompanying source code this variable will be global) 39 | glfw.window_hint(glfw.SAMPLES, 4) 40 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 41 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 42 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 43 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 44 | 45 | if not window: 46 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 47 | glfw.terminate() 48 | return False 49 | 50 | # Initialize GLEW 51 | glfw.make_context_current(window) 52 | glewExperimental = True 53 | 54 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 55 | # more information including why can remove this code. 56 | if glewInit() != GLEW_OK: 57 | print("Failed to initialize GLEW\n",file=sys.stderr); 58 | return False 59 | return True 60 | 61 | def main(): 62 | if not opengl_init(): 63 | return 64 | 65 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 66 | 67 | # Set opengl clear color to something other than red (color used by the fragment shader) 68 | glClearColor(0,0,0.4,0) 69 | 70 | 71 | vertex_array_id = glGenVertexArrays(1) 72 | glBindVertexArray( vertex_array_id ) 73 | 74 | program_id = common.LoadShaders( ".\\shaders\\Tutorial3\\SimpleTransform.vertexshader", 75 | ".\\shaders\\Tutorial3\\SingleColor.fragmentshader" ) 76 | 77 | # Get a handle for our "MVP" uniform 78 | matrix_id= glGetUniformLocation(program_id, "MVP"); 79 | 80 | # Projection matrix : 45 Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units 81 | projection = mat4.perspective(45.0, 4.0 / 3.0, 0.1, 100.0) 82 | 83 | # Camera matrix 84 | view = mat4.lookat(vec3(4,3,3), # Camera is at (4,3,3), in World Space 85 | vec3(0,0,0), # and looks at the origin 86 | vec3(0,1,0)) 87 | 88 | # Model matrix : an identity matrix (model will be at the origin) 89 | model = mat4.identity() 90 | 91 | # Our ModelViewProjection : multiplication of our 3 matrices 92 | mvp = projection * view * model 93 | 94 | vertex_data = [-1.0, -1.0, 0.0, 95 | 1.0, -1.0, 0.0, 96 | 0.0, 1.0, 0.0] 97 | 98 | vertex_buffer = glGenBuffers(1); 99 | 100 | # GLFloat = c_types.c_float 101 | array_type = GLfloat * len(vertex_data) 102 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 103 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4, array_type(*vertex_data), GL_STATIC_DRAW) 104 | 105 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 106 | glClear(GL_COLOR_BUFFER_BIT) 107 | 108 | glUseProgram(program_id) 109 | 110 | # Send our transformation to the currently bound shader, 111 | # in the "MVP" uniform 112 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 113 | # Bind vertex buffer data to the attribute 0 in our shader. 114 | # Note: This can also be done in the VAO itself (see vao_test.py) 115 | 116 | # Enable the vertex attribute at element[0], in this case that's the triangle's vertices 117 | # this could also be color, normals, etc. It isn't necessary to disable these 118 | # 119 | glEnableVertexAttribArray(0) 120 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 121 | glVertexAttribPointer( 122 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 123 | 3, # len(vertex_data) 124 | GL_FLOAT, # type 125 | GL_FALSE, # ormalized? 126 | 0, # stride 127 | null # array buffer offset (c_type == void*) 128 | ) 129 | 130 | # Draw the triangle ! 131 | glDrawArrays(GL_TRIANGLES, 0, 3) #3 indices starting at 0 -> 1 triangle 132 | 133 | # Not strictly necessary because we only have 134 | glDisableVertexAttribArray(0) 135 | 136 | # Swap front and back buffers 137 | glfw.swap_buffers(window) 138 | 139 | # Poll for and process events 140 | glfw.poll_events() 141 | 142 | # note braces around vertex_buffer and vertex_array_id. 143 | # These 2 functions expect arrays of values 144 | glDeleteBuffers(1, [vertex_buffer]) 145 | glDeleteProgram(program_id) 146 | glDeleteVertexArrays(1, [vertex_array_id]) 147 | glfw.terminate() 148 | 149 | if __name__ == "__main__": 150 | main() 151 | -------------------------------------------------------------------------------- /tutorial4.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 4: A Colored Cube 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | 16 | from csgl import * 17 | 18 | import common 19 | import glfw 20 | import sys 21 | import os 22 | 23 | # Global window 24 | window = None 25 | null = c_void_p(0) 26 | 27 | def opengl_init(): 28 | global window 29 | # Initialize the library 30 | if not glfw.init(): 31 | print("Failed to initialize GLFW\n",file=sys.stderr) 32 | return False 33 | 34 | # Open Window and create its OpenGL context 35 | window = glfw.create_window(1024, 768, "Tutorial 04", None, None) #(in the accompanying source code this variable will be global) 36 | glfw.window_hint(glfw.SAMPLES, 4) 37 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 38 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 39 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 40 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 41 | 42 | if not window: 43 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 44 | glfw.terminate() 45 | return False 46 | 47 | # Initialize GLEW 48 | glfw.make_context_current(window) 49 | glewExperimental = True 50 | 51 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 52 | # more information including why can remove this code. 53 | if glewInit() != GLEW_OK: 54 | print("Failed to initialize GLEW\n",file=sys.stderr); 55 | return False 56 | return True 57 | 58 | 59 | def key_event(window,key,scancode,action,mods): 60 | """ Handle keyboard events 61 | 62 | Note: It's not important to understand how this works just yet. 63 | Keyboard and mouse inputs are covered in Tutorial 6 64 | """ 65 | if action == glfw.PRESS and key == glfw.KEY_D: 66 | if glIsEnabled (GL_DEPTH_TEST): glDisable(GL_DEPTH_TEST) 67 | else: glEnable(GL_DEPTH_TEST) 68 | 69 | glDepthFunc(GL_LESS) 70 | 71 | def main(): 72 | if not opengl_init(): 73 | return 74 | 75 | # Enable key events 76 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 77 | 78 | # Enable key event callback 79 | glfw.set_key_callback(window,key_event) 80 | 81 | # Set opengl clear color to something other than red (color used by the fragment shader) 82 | glClearColor(0,0,0.4,0) 83 | 84 | vertex_array_id = glGenVertexArrays(1) 85 | glBindVertexArray( vertex_array_id ) 86 | 87 | program_id = common.LoadShaders( ".\\shaders\\Tutorial4\\TransformVertexShader.vertexshader", 88 | ".\\shaders\\Tutorial4\\ColorFragmentShader.fragmentshader" ) 89 | 90 | # Get a handle for our "MVP" uniform 91 | matrix_id= glGetUniformLocation(program_id, "MVP"); 92 | 93 | # Projection matrix : 45 Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units 94 | projection = mat4.perspective(45.0, 4.0 / 3.0, 0.1, 100.0) 95 | 96 | # Camera matrix 97 | view = mat4.lookat(vec3(4,3,-3), # Camera is at (4,3,3), in World Space 98 | vec3(0,0,0), # and looks at the origin 99 | vec3(0,1,0)) 100 | 101 | # Model matrix : an identity matrix (model will be at the origin) 102 | model = mat4.identity() 103 | 104 | # Our ModelViewProjection : multiplication of our 3 matrices 105 | mvp = projection * view * model 106 | 107 | # Our vertices. Tree consecutive floats give a 3D vertex; Three consecutive vertices give a triangle. 108 | # A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices 109 | vertex_data = [ 110 | -1.0,-1.0,-1.0, 111 | -1.0,-1.0, 1.0, 112 | -1.0, 1.0, 1.0, 113 | 1.0, 1.0,-1.0, 114 | -1.0,-1.0,-1.0, 115 | -1.0, 1.0,-1.0, 116 | 1.0,-1.0, 1.0, 117 | -1.0,-1.0,-1.0, 118 | 1.0,-1.0,-1.0, 119 | 1.0, 1.0,-1.0, 120 | 1.0,-1.0,-1.0, 121 | -1.0,-1.0,-1.0, 122 | -1.0,-1.0,-1.0, 123 | -1.0, 1.0, 1.0, 124 | -1.0, 1.0,-1.0, 125 | 1.0,-1.0, 1.0, 126 | -1.0,-1.0, 1.0, 127 | -1.0,-1.0,-1.0, 128 | -1.0, 1.0, 1.0, 129 | -1.0,-1.0, 1.0, 130 | 1.0,-1.0, 1.0, 131 | 1.0, 1.0, 1.0, 132 | 1.0,-1.0,-1.0, 133 | 1.0, 1.0,-1.0, 134 | 1.0,-1.0,-1.0, 135 | 1.0, 1.0, 1.0, 136 | 1.0,-1.0, 1.0, 137 | 1.0, 1.0, 1.0, 138 | 1.0, 1.0,-1.0, 139 | -1.0, 1.0,-1.0, 140 | 1.0, 1.0, 1.0, 141 | -1.0, 1.0,-1.0, 142 | -1.0, 1.0, 1.0, 143 | 1.0, 1.0, 1.0, 144 | -1.0, 1.0, 1.0, 145 | 1.0,-1.0, 1.0] 146 | 147 | # One color for each vertex. They were generated randomly. 148 | color_data = [ 149 | 0.583, 0.771, 0.014, 150 | 0.609, 0.115, 0.436, 151 | 0.327, 0.483, 0.844, 152 | 0.822, 0.569, 0.201, 153 | 0.435, 0.602, 0.223, 154 | 0.310, 0.747, 0.185, 155 | 0.597, 0.770, 0.761, 156 | 0.559, 0.436, 0.730, 157 | 0.359, 0.583, 0.152, 158 | 0.483, 0.596, 0.789, 159 | 0.559, 0.861, 0.639, 160 | 0.195, 0.548, 0.859, 161 | 0.014, 0.184, 0.576, 162 | 0.771, 0.328, 0.970, 163 | 0.406, 0.615, 0.116, 164 | 0.676, 0.977, 0.133, 165 | 0.971, 0.572, 0.833, 166 | 0.140, 0.616, 0.489, 167 | 0.997, 0.513, 0.064, 168 | 0.945, 0.719, 0.592, 169 | 0.543, 0.021, 0.978, 170 | 0.279, 0.317, 0.505, 171 | 0.167, 0.620, 0.077, 172 | 0.347, 0.857, 0.137, 173 | 0.055, 0.953, 0.042, 174 | 0.714, 0.505, 0.345, 175 | 0.783, 0.290, 0.734, 176 | 0.722, 0.645, 0.174, 177 | 0.302, 0.455, 0.848, 178 | 0.225, 0.587, 0.040, 179 | 0.517, 0.713, 0.338, 180 | 0.053, 0.959, 0.120, 181 | 0.393, 0.621, 0.362, 182 | 0.673, 0.211, 0.457, 183 | 0.820, 0.883, 0.371, 184 | 0.982, 0.099, 0.879] 185 | 186 | vertex_buffer = glGenBuffers(1); 187 | array_type = GLfloat * len(vertex_data) 188 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 189 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4, array_type(*vertex_data), GL_STATIC_DRAW) 190 | 191 | color_buffer = glGenBuffers(1); 192 | array_type = GLfloat * len(color_data) 193 | glBindBuffer(GL_ARRAY_BUFFER, color_buffer) 194 | glBufferData(GL_ARRAY_BUFFER, len(color_data) * 4, array_type(*color_data), GL_STATIC_DRAW) 195 | 196 | 197 | 198 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 199 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 200 | 201 | glUseProgram(program_id) 202 | 203 | # Send our transformation to the currently bound shader, 204 | # in the "MVP" uniform 205 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 206 | # Bind vertex buffer data to the attribute 0 in our shader. 207 | # Note: This can also be done in the VAO itself (see vao_test.py) 208 | 209 | # Enable the vertex attribute at element[0], in this case that's the triangle's vertices 210 | # this could also be color, normals, etc. It isn't necessary to disable these 211 | # 212 | #1rst attribute buffer : vertices 213 | glEnableVertexAttribArray(0) 214 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 215 | glVertexAttribPointer( 216 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 217 | 3, # len(vertex_data) 218 | GL_FLOAT, # type 219 | GL_FALSE, # ormalized? 220 | 0, # stride 221 | null # array buffer offset (c_type == void*) 222 | ) 223 | 224 | # 2nd attribute buffer : colors 225 | glEnableVertexAttribArray(1) 226 | glBindBuffer(GL_ARRAY_BUFFER, color_buffer); 227 | glVertexAttribPointer( 228 | 1, # attribute 0. No particular reason for 0, but must match the layout in the shader. 229 | 3, # len(vertex_data) 230 | GL_FLOAT, # type 231 | GL_FALSE, # ormalized? 232 | 0, # stride 233 | null # array buffer offset (c_type == void*) 234 | ) 235 | 236 | # Draw the triangle ! 237 | glDrawArrays(GL_TRIANGLES, 0, 12*3) #3 indices starting at 0 -> 1 triangle 238 | 239 | # Not strictly necessary because we only have 240 | glDisableVertexAttribArray(0) 241 | glDisableVertexAttribArray(1) 242 | 243 | 244 | # Swap front and back buffers 245 | glfw.swap_buffers(window) 246 | 247 | # Poll for and process events 248 | glfw.poll_events() 249 | 250 | # note braces around vertex_buffer and vertex_array_id. 251 | # These 2 functions expect arrays of values 252 | glDeleteBuffers(1, [vertex_buffer]) 253 | glDeleteBuffers(1, [color_buffer]) 254 | glDeleteProgram(program_id) 255 | glDeleteVertexArrays(1, [vertex_array_id]) 256 | 257 | glfw.terminate() 258 | 259 | if __name__ == "__main__": 260 | main() 261 | -------------------------------------------------------------------------------- /tutorial5.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 5: Textured Cube 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | 18 | import common 19 | import glfw 20 | import sys 21 | import os 22 | 23 | # Global window 24 | window = None 25 | null = c_void_p(0) 26 | 27 | def opengl_init(): 28 | global window 29 | # Initialize the library 30 | if not glfw.init(): 31 | print("Failed to initialize GLFW\n",file=sys.stderr) 32 | return False 33 | 34 | # Open Window and create its OpenGL context 35 | window = glfw.create_window(1024, 768, "Tutorial 05", None, None) #(in the accompanying source code this variable will be global) 36 | glfw.window_hint(glfw.SAMPLES, 4) 37 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 38 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 39 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 40 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 41 | 42 | if not window: 43 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 44 | glfw.terminate() 45 | return False 46 | 47 | # Initialize GLEW 48 | glfw.make_context_current(window) 49 | glewExperimental = True 50 | 51 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 52 | # more information including why can remove this code. 53 | if glewInit() != GLEW_OK: 54 | print("Failed to initialize GLEW\n",file=sys.stderr); 55 | return False 56 | return True 57 | 58 | def bind_texture(texture_id,mode): 59 | """ Bind texture_id using several different modes 60 | 61 | Notes: 62 | Without mipmapping the texture is incomplete 63 | and requires additional constraints on OpenGL 64 | to properly render said texture. 65 | 66 | Use 'MIN_FILTER" or 'MAX_LEVEL' to render 67 | a generic texture with a single resolution 68 | Ref: 69 | [] - http://www.opengl.org/wiki/Common_Mistakes#Creating_a_complete_texture 70 | [] - http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ 71 | TODO: 72 | - Rename modes to something useful 73 | """ 74 | if mode == 'DEFAULT': 75 | glBindTexture(GL_TEXTURE_2D, texture_id) 76 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 77 | elif mode == 'MIN_FILTER': 78 | glBindTexture(GL_TEXTURE_2D, texture_id) 79 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 80 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) 81 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) 82 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 83 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 84 | elif mode == 'MAX_LEVEL': 85 | glBindTexture(GL_TEXTURE_2D, texture_id) 86 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 87 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0) 88 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0) 89 | else: 90 | glBindTexture(GL_TEXTURE_2D, texture_id) 91 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 92 | 93 | # Generate mipmaps? Doesn't seem to work 94 | glGenerateMipmap(GL_TEXTURE_2D) 95 | 96 | def load_image(file_name): 97 | im = pil_open(file_name) 98 | try: 99 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1) 100 | except SystemError: 101 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1) 102 | 103 | texture_id = glGenTextures(1) 104 | 105 | # To use OpenGL 4.2 ARB_texture_storage to automatically generate a single mipmap layer 106 | # uncomment the 3 lines below. Note that this should replaced glTexImage2D below. 107 | #bind_texture(texture_id,'DEFAULT') 108 | #glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); 109 | #glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image) 110 | 111 | # "Bind" the newly created texture : all future texture functions will modify this texture 112 | bind_texture(texture_id,'MIN_FILTER') 113 | glTexImage2D( 114 | GL_TEXTURE_2D, 0, 3, width, height, 0, 115 | GL_RGBA, GL_UNSIGNED_BYTE, image 116 | ) 117 | return texture_id 118 | 119 | def main(): 120 | if not opengl_init(): 121 | return 122 | 123 | # Enable key events 124 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 125 | 126 | # Set opengl clear color to something other than red (color used by the fragment shader) 127 | glClearColor(0.0,0.0,0.4,0.0) 128 | 129 | # Enable depth test 130 | glEnable(GL_DEPTH_TEST); 131 | # Accept fragment if it closer to the camera than the former one 132 | glDepthFunc(GL_LESS); 133 | 134 | vertex_array_id = glGenVertexArrays(1) 135 | glBindVertexArray( vertex_array_id ) 136 | 137 | 138 | program_id = common.LoadShaders( ".\\shaders\\Tutorial5\\TransformVertexShader.vertexshader", 139 | ".\\shaders\\Tutorial5\\TextureFragmentShader.fragmentshader" ) 140 | 141 | # Get a handle for our "MVP" uniform 142 | matrix_id = glGetUniformLocation(program_id, "MVP"); 143 | 144 | # Projection matrix : 45 Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units 145 | projection = mat4.perspective(45.0, 4.0 / 3.0, 0.1, 100.0) 146 | 147 | # Camera matrix 148 | view = mat4.lookat(vec3(4,3,-3), # Camera is at (4,3,3), in World Space 149 | vec3(0,0,0), # and looks at the origin 150 | vec3(0,1,0)) 151 | 152 | # Model matrix : an identity matrix (model will be at the origin) 153 | model = mat4.identity() 154 | 155 | # Our ModelViewProjection : multiplication of our 3 matrices 156 | mvp = projection * view * model 157 | 158 | 159 | texture = load_image(".\\content\\uvtemplate.bmp") 160 | texture_id = glGetUniformLocation(program_id, "myTextureSampler") 161 | 162 | # Our vertices. Tree consecutive floats give a 3D vertex; Three consecutive vertices give a triangle. 163 | # A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices 164 | vertex_data = [ 165 | -1.0,-1.0,-1.0, 166 | -1.0,-1.0, 1.0, 167 | -1.0, 1.0, 1.0, 168 | 1.0, 1.0,-1.0, 169 | -1.0,-1.0,-1.0, 170 | -1.0, 1.0,-1.0, 171 | 1.0,-1.0, 1.0, 172 | -1.0,-1.0,-1.0, 173 | 1.0,-1.0,-1.0, 174 | 1.0, 1.0,-1.0, 175 | 1.0,-1.0,-1.0, 176 | -1.0,-1.0,-1.0, 177 | -1.0,-1.0,-1.0, 178 | -1.0, 1.0, 1.0, 179 | -1.0, 1.0,-1.0, 180 | 1.0,-1.0, 1.0, 181 | -1.0,-1.0, 1.0, 182 | -1.0,-1.0,-1.0, 183 | -1.0, 1.0, 1.0, 184 | -1.0,-1.0, 1.0, 185 | 1.0,-1.0, 1.0, 186 | 1.0, 1.0, 1.0, 187 | 1.0,-1.0,-1.0, 188 | 1.0, 1.0,-1.0, 189 | 1.0,-1.0,-1.0, 190 | 1.0, 1.0, 1.0, 191 | 1.0,-1.0, 1.0, 192 | 1.0, 1.0, 1.0, 193 | 1.0, 1.0,-1.0, 194 | -1.0, 1.0,-1.0, 195 | 1.0, 1.0, 1.0, 196 | -1.0, 1.0,-1.0, 197 | -1.0, 1.0, 1.0, 198 | 1.0, 1.0, 1.0, 199 | -1.0, 1.0, 1.0, 200 | 1.0,-1.0, 1.0] 201 | 202 | # Two UV coordinatesfor each vertex. They were created withe Blender. 203 | uv_data = [ 204 | 0.000059, 1.0-0.000004, 205 | 0.000103, 1.0-0.336048, 206 | 0.335973, 1.0-0.335903, 207 | 1.000023, 1.0-0.000013, 208 | 0.667979, 1.0-0.335851, 209 | 0.999958, 1.0-0.336064, 210 | 0.667979, 1.0-0.335851, 211 | 0.336024, 1.0-0.671877, 212 | 0.667969, 1.0-0.671889, 213 | 1.000023, 1.0-0.000013, 214 | 0.668104, 1.0-0.000013, 215 | 0.667979, 1.0-0.335851, 216 | 0.000059, 1.0-0.000004, 217 | 0.335973, 1.0-0.335903, 218 | 0.336098, 1.0-0.000071, 219 | 0.667979, 1.0-0.335851, 220 | 0.335973, 1.0-0.335903, 221 | 0.336024, 1.0-0.671877, 222 | 1.000004, 1.0-0.671847, 223 | 0.999958, 1.0-0.336064, 224 | 0.667979, 1.0-0.335851, 225 | 0.668104, 1.0-0.000013, 226 | 0.335973, 1.0-0.335903, 227 | 0.667979, 1.0-0.335851, 228 | 0.335973, 1.0-0.335903, 229 | 0.668104, 1.0-0.000013, 230 | 0.336098, 1.0-0.000071, 231 | 0.000103, 1.0-0.336048, 232 | 0.000004, 1.0-0.671870, 233 | 0.336024, 1.0-0.671877, 234 | 0.000103, 1.0-0.336048, 235 | 0.336024, 1.0-0.671877, 236 | 0.335973, 1.0-0.335903, 237 | 0.667969, 1.0-0.671889, 238 | 1.000004, 1.0-0.671847, 239 | 0.667979, 1.0-0.335851] 240 | 241 | vertex_buffer = glGenBuffers(1); 242 | array_type = GLfloat * len(vertex_data) 243 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 244 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4, array_type(*vertex_data), GL_STATIC_DRAW) 245 | 246 | uv_buffer = glGenBuffers(1); 247 | array_type = GLfloat * len(uv_data) 248 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 249 | glBufferData(GL_ARRAY_BUFFER, len(uv_data) * 4, array_type(*uv_data), GL_STATIC_DRAW) 250 | 251 | 252 | 253 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 254 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 255 | 256 | glUseProgram(program_id) 257 | 258 | # Send our transformation to the currently bound shader, 259 | # in the "MVP" uniform 260 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 261 | 262 | # Bind our texture in Texture Unit 0 263 | glActiveTexture(GL_TEXTURE0); 264 | glBindTexture(GL_TEXTURE_2D, texture); 265 | # Set our "myTextureSampler" sampler to user Texture Unit 0 266 | glUniform1i(texture_id, 0); 267 | 268 | #1rst attribute buffer : vertices 269 | glEnableVertexAttribArray(0) 270 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 271 | glVertexAttribPointer( 272 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 273 | 3, # len(vertex_data) 274 | GL_FLOAT, # type 275 | GL_FALSE, # ormalized? 276 | 0, # stride 277 | null # array buffer offset (c_type == void*) 278 | ) 279 | 280 | # 2nd attribute buffer : colors 281 | glEnableVertexAttribArray(1) 282 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 283 | glVertexAttribPointer( 284 | 1, # attribute 0. No particular reason for 0, but must match the layout in the shader. 285 | 2, # len(vertex_data) 286 | GL_FLOAT, # type 287 | GL_FALSE, # ormalized? 288 | 0, # stride 289 | null # array buffer offset (c_type == void*) 290 | ) 291 | 292 | # Draw the triangle ! 293 | glDrawArrays(GL_TRIANGLES, 0, 12*3) #3 indices starting at 0 -> 1 triangle 294 | 295 | # Not strictly necessary because we only have 296 | glDisableVertexAttribArray(0) 297 | glDisableVertexAttribArray(1) 298 | 299 | 300 | # Swap front and back buffers 301 | glfw.swap_buffers(window) 302 | 303 | # Poll for and process events 304 | glfw.poll_events() 305 | 306 | # note braces around vertex_buffer and vertex_array_id. 307 | # These 2 functions expect arrays of values 308 | glDeleteBuffers(1, [vertex_buffer]) 309 | glDeleteBuffers(1, [uv_buffer]) 310 | glDeleteProgram(program_id) 311 | glDeleteTextures([texture_id]) 312 | glDeleteVertexArrays(1, [vertex_array_id]) 313 | 314 | glfw.terminate() 315 | 316 | if __name__ == "__main__": 317 | main() 318 | -------------------------------------------------------------------------------- /tutorial6.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 6: keyboard and mouse 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | 18 | import common 19 | import glfw 20 | import sys 21 | import os 22 | import controls 23 | 24 | # Global window 25 | window = None 26 | null = c_void_p(0) 27 | 28 | def opengl_init(): 29 | global window 30 | # Initialize the library 31 | if not glfw.init(): 32 | print("Failed to initialize GLFW\n",file=sys.stderr) 33 | return False 34 | 35 | # Open Window and create its OpenGL context 36 | window = glfw.create_window(1024, 768, "Tutorial 06", None, None) #(in the accompanying source code this variable will be global) 37 | glfw.window_hint(glfw.SAMPLES, 4) 38 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 39 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 40 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 41 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 42 | 43 | if not window: 44 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 45 | glfw.terminate() 46 | return False 47 | 48 | # Initialize GLEW 49 | glfw.make_context_current(window) 50 | glewExperimental = True 51 | 52 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 53 | # more information including why can remove this code.a 54 | if glewInit() != GLEW_OK: 55 | print("Failed to initialize GLEW\n",file=sys.stderr); 56 | return False 57 | return True 58 | 59 | def bind_texture(texture_id,mode): 60 | """ Bind texture_id using several different modes 61 | 62 | Notes: 63 | Without mipmapping the texture is incomplete 64 | and requires additional constraints on OpenGL 65 | to properly render said texture. 66 | 67 | Use 'MIN_FILTER" or 'MAX_LEVEL' to render 68 | a generic texture with a single resolution 69 | Ref: 70 | [] - http://www.opengl.org/wiki/Common_Mistakes#Creating_a_complete_texture 71 | [] - http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ 72 | TODO: 73 | - Rename modes to something useful 74 | """ 75 | if mode == 'DEFAULT': 76 | glBindTexture(GL_TEXTURE_2D, texture_id) 77 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 78 | elif mode == 'MIN_FILTER': 79 | glBindTexture(GL_TEXTURE_2D, texture_id) 80 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 81 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) 82 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) 83 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 84 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 85 | elif mode == 'MAX_LEVEL': 86 | glBindTexture(GL_TEXTURE_2D, texture_id) 87 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 88 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0) 89 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0) 90 | else: 91 | glBindTexture(GL_TEXTURE_2D, texture_id) 92 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 93 | 94 | # Generate mipmaps? Doesn't seem to work 95 | glGenerateMipmap(GL_TEXTURE_2D) 96 | 97 | def load_image(file_name): 98 | im = pil_open(file_name) 99 | try: 100 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1) 101 | except SystemError: 102 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1) 103 | 104 | texture_id = glGenTextures(1) 105 | 106 | # To use OpenGL 4.2 ARB_texture_storage to automatically generate a single mipmap layer 107 | # uncomment the 3 lines below. Note that this should replaced glTexImage2D below. 108 | #bind_texture(texture_id,'DEFAULT') 109 | #glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); 110 | #glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image) 111 | 112 | # "Bind" the newly created texture : all future texture functions will modify this texture 113 | bind_texture(texture_id,'MIN_FILTER') 114 | glTexImage2D( 115 | GL_TEXTURE_2D, 0, 3, width, height, 0, 116 | GL_RGBA, GL_UNSIGNED_BYTE, image 117 | ) 118 | return texture_id 119 | 120 | def main(): 121 | if not opengl_init(): 122 | return 123 | 124 | # Enable key events 125 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 126 | glfw.set_cursor_pos(window, 1024/2, 768/2) 127 | 128 | # Set opengl clear color to something other than red (color used by the fragment shader) 129 | glClearColor(0.0,0.0,0.4,0.0) 130 | 131 | # Enable depth test 132 | glEnable(GL_DEPTH_TEST) 133 | # Accept fragment if it closer to the camera than the former one 134 | glDepthFunc(GL_LESS); 135 | glEnable(GL_CULL_FACE) 136 | 137 | vertex_array_id = glGenVertexArrays(1) 138 | glBindVertexArray( vertex_array_id ) 139 | 140 | program_id = common.LoadShaders( ".\\shaders\\Tutorial6\\TransformVertexShader.vertexshader", 141 | ".\\shaders\\Tutorial6\\TextureFragmentShader.fragmentshader" ) 142 | 143 | # Get a handle for our "MVP" uniform 144 | matrix_id = glGetUniformLocation(program_id, "MVP"); 145 | 146 | texture = load_image(".\\content\\uvtemplate.bmp") 147 | texture_id = glGetUniformLocation(program_id, "myTextureSampler") 148 | 149 | # Our vertices. Tree consecutive floats give a 3D vertex; Three consecutive vertices give a triangle. 150 | # A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices 151 | vertex_data = [ 152 | -1.0,-1.0,-1.0, 153 | -1.0,-1.0, 1.0, 154 | -1.0, 1.0, 1.0, 155 | 1.0, 1.0,-1.0, 156 | -1.0,-1.0,-1.0, 157 | -1.0, 1.0,-1.0, 158 | 1.0,-1.0, 1.0, 159 | -1.0,-1.0,-1.0, 160 | 1.0,-1.0,-1.0, 161 | 1.0, 1.0,-1.0, 162 | 1.0,-1.0,-1.0, 163 | -1.0,-1.0,-1.0, 164 | -1.0,-1.0,-1.0, 165 | -1.0, 1.0, 1.0, 166 | -1.0, 1.0,-1.0, 167 | 1.0,-1.0, 1.0, 168 | -1.0,-1.0, 1.0, 169 | -1.0,-1.0,-1.0, 170 | -1.0, 1.0, 1.0, 171 | -1.0,-1.0, 1.0, 172 | 1.0,-1.0, 1.0, 173 | 1.0, 1.0, 1.0, 174 | 1.0,-1.0,-1.0, 175 | 1.0, 1.0,-1.0, 176 | 1.0,-1.0,-1.0, 177 | 1.0, 1.0, 1.0, 178 | 1.0,-1.0, 1.0, 179 | 1.0, 1.0, 1.0, 180 | 1.0, 1.0,-1.0, 181 | -1.0, 1.0,-1.0, 182 | 1.0, 1.0, 1.0, 183 | -1.0, 1.0,-1.0, 184 | -1.0, 1.0, 1.0, 185 | 1.0, 1.0, 1.0, 186 | -1.0, 1.0, 1.0, 187 | 1.0,-1.0, 1.0] 188 | 189 | # Two UV coordinatesfor each vertex. They were created withe Blender. 190 | uv_data = [ 191 | 0.000059, 1.0-0.000004, 192 | 0.000103, 1.0-0.336048, 193 | 0.335973, 1.0-0.335903, 194 | 1.000023, 1.0-0.000013, 195 | 0.667979, 1.0-0.335851, 196 | 0.999958, 1.0-0.336064, 197 | 0.667979, 1.0-0.335851, 198 | 0.336024, 1.0-0.671877, 199 | 0.667969, 1.0-0.671889, 200 | 1.000023, 1.0-0.000013, 201 | 0.668104, 1.0-0.000013, 202 | 0.667979, 1.0-0.335851, 203 | 0.000059, 1.0-0.000004, 204 | 0.335973, 1.0-0.335903, 205 | 0.336098, 1.0-0.000071, 206 | 0.667979, 1.0-0.335851, 207 | 0.335973, 1.0-0.335903, 208 | 0.336024, 1.0-0.671877, 209 | 1.000004, 1.0-0.671847, 210 | 0.999958, 1.0-0.336064, 211 | 0.667979, 1.0-0.335851, 212 | 0.668104, 1.0-0.000013, 213 | 0.335973, 1.0-0.335903, 214 | 0.667979, 1.0-0.335851, 215 | 0.335973, 1.0-0.335903, 216 | 0.668104, 1.0-0.000013, 217 | 0.336098, 1.0-0.000071, 218 | 0.000103, 1.0-0.336048, 219 | 0.000004, 1.0-0.671870, 220 | 0.336024, 1.0-0.671877, 221 | 0.000103, 1.0-0.336048, 222 | 0.336024, 1.0-0.671877, 223 | 0.335973, 1.0-0.335903, 224 | 0.667969, 1.0-0.671889, 225 | 1.000004, 1.0-0.671847, 226 | 0.667979, 1.0-0.335851] 227 | 228 | vertex_buffer = glGenBuffers(1); 229 | array_type = GLfloat * len(vertex_data) 230 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 231 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4, array_type(*vertex_data), GL_STATIC_DRAW) 232 | 233 | uv_buffer = glGenBuffers(1); 234 | array_type = GLfloat * len(uv_data) 235 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 236 | glBufferData(GL_ARRAY_BUFFER, len(uv_data) * 4, array_type(*uv_data), GL_STATIC_DRAW) 237 | 238 | # vsync and glfw do not play nice. when vsync is enabled mouse movement is jittery. 239 | common.disable_vsyc() 240 | 241 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 242 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 243 | 244 | glUseProgram(program_id) 245 | 246 | controls.computeMatricesFromInputs(window) 247 | ProjectionMatrix = controls.getProjectionMatrix(); 248 | ViewMatrix = controls.getViewMatrix(); 249 | ModelMatrix = mat4.identity(); 250 | mvp = ProjectionMatrix * ViewMatrix * ModelMatrix; 251 | 252 | # Send our transformation to the currently bound shader, 253 | # in the "MVP" uniform 254 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 255 | 256 | # Bind our texture in Texture Unit 0 257 | glActiveTexture(GL_TEXTURE0); 258 | glBindTexture(GL_TEXTURE_2D, texture); 259 | # Set our "myTextureSampler" sampler to user Texture Unit 0 260 | glUniform1i(texture_id, 0); 261 | 262 | #1rst attribute buffer : vertices 263 | glEnableVertexAttribArray(0) 264 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 265 | glVertexAttribPointer( 266 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 267 | 3, # len(vertex_data) 268 | GL_FLOAT, # type 269 | GL_FALSE, # ormalized? 270 | 0, # stride 271 | null # array buffer offset (c_type == void*) 272 | ) 273 | 274 | # 2nd attribute buffer : colors 275 | glEnableVertexAttribArray(1) 276 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 277 | glVertexAttribPointer( 278 | 1, # attribute 1. No particular reason for 1, but must match the layout in the shader. 279 | 2, # len(vertex_data) 280 | GL_FLOAT, # type 281 | GL_FALSE, # ormalized? 282 | 0, # stride 283 | null # array buffer offset (c_type == void*) 284 | ) 285 | 286 | # Draw the triangle ! 287 | glDrawArrays(GL_TRIANGLES, 0, 12*3) #3 indices starting at 0 -> 1 triangle 288 | 289 | # Not strictly necessary because we only have 290 | glDisableVertexAttribArray(0) 291 | glDisableVertexAttribArray(1) 292 | 293 | 294 | # Swap front and back buffers 295 | glfw.swap_buffers(window) 296 | 297 | # Poll for and process events 298 | glfw.poll_events() 299 | 300 | # !Note braces around vertex_buffer and uv_buffer. 301 | # glDeleteBuffers expects a list of buffers to delete 302 | glDeleteBuffers(1, [vertex_buffer]) 303 | glDeleteBuffers(1, [uv_buffer]) 304 | glDeleteProgram(program_id) 305 | glDeleteTextures([texture_id]) 306 | glDeleteVertexArrays(1, [vertex_array_id]) 307 | 308 | glfw.terminate() 309 | 310 | if __name__ == "__main__": 311 | main() 312 | -------------------------------------------------------------------------------- /tutorial7.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 7: Model Loading 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | 18 | import common 19 | import glfw 20 | import sys 21 | import os 22 | import controls 23 | import objloader 24 | 25 | # Global window 26 | window = None 27 | null = c_void_p(0) 28 | 29 | def opengl_init(): 30 | global window 31 | # Initialize the library 32 | if not glfw.init(): 33 | print("Failed to initialize GLFW\n",file=sys.stderr) 34 | return False 35 | 36 | # Open Window and create its OpenGL context 37 | window = glfw.create_window(1024, 768, "Tutorial 07", None, None) #(in the accompanying source code this variable will be global) 38 | glfw.window_hint(glfw.SAMPLES, 4) 39 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 40 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 41 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 42 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 43 | 44 | if not window: 45 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 46 | glfw.terminate() 47 | return False 48 | 49 | # Initialize GLEW 50 | glfw.make_context_current(window) 51 | glewExperimental = True 52 | 53 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 54 | # more information including why can remove this code.a 55 | if glewInit() != GLEW_OK: 56 | print("Failed to initialize GLEW\n",file=stderropen.sys) 57 | return False 58 | return True 59 | 60 | def bind_texture(texture_id,mode): 61 | """ Bind texture_id using several different modes 62 | 63 | Notes: 64 | Without mipmapping the texture is incomplete 65 | and requires additional constraints on OpenGL 66 | to properly render said texture. 67 | 68 | Use 'MIN_FILTER" or 'MAX_LEVEL' to render 69 | a generic texture with a single resolution 70 | Ref: 71 | [] - http://www.opengl.org/wiki/Common_Mistakes#Creating_a_complete_texture 72 | [] - http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ 73 | TODO: 74 | - Rename modes to something useful 75 | """ 76 | if mode == 'DEFAULT': 77 | glBindTexture(GL_TEXTURE_2D, texture_id) 78 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 79 | elif mode == 'MIN_FILTER': 80 | glBindTexture(GL_TEXTURE_2D, texture_id) 81 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 82 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) 83 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) 84 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 85 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 86 | elif mode == 'MAX_LEVEL': 87 | glBindTexture(GL_TEXTURE_2D, texture_id) 88 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 89 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0) 90 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0) 91 | else: 92 | glBindTexture(GL_TEXTURE_2D, texture_id) 93 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 94 | 95 | # Generate mipmaps? Doesn't seem to work 96 | glGenerateMipmap(GL_TEXTURE_2D) 97 | 98 | def load_image(file_name): 99 | im = pil_open(file_name) 100 | try: 101 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1) 102 | except SystemError: 103 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1) 104 | 105 | texture_id = glGenTextures(1) 106 | 107 | # To use OpenGL 4.2 ARB_texture_storage to automatically generate a single mipmap layer 108 | # uncomment the 3 lines below. Note that this should replaced glTexImage2D below. 109 | #bind_texture(texture_id,'DEFAULT') 110 | #glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height) 111 | #glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image) 112 | 113 | # "Bind" the newly created texture : all future texture functions will modify this texture 114 | bind_texture(texture_id,'MIN_FILTER') 115 | glTexImage2D( 116 | GL_TEXTURE_2D, 0, 3, width, height, 0, 117 | GL_RGBA, GL_UNSIGNED_BYTE, image 118 | ) 119 | return texture_id 120 | 121 | def main(): 122 | 123 | # Initialize GLFW and open a window 124 | if not opengl_init(): 125 | return 126 | 127 | # Enable key events 128 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 129 | glfw.set_cursor_pos(window, 1024/2, 768/2) 130 | 131 | # Set opengl clear color to something other than red (color used by the fragment shader) 132 | glClearColor(0.0,0.0,0.4,0.0) 133 | 134 | # Enable depth test 135 | glEnable(GL_DEPTH_TEST) 136 | 137 | # Accept fragment if it closer to the camera than the former one 138 | glDepthFunc(GL_LESS) 139 | 140 | # Cull triangles which normal is not towards the camera 141 | glEnable(GL_CULL_FACE) 142 | 143 | vertex_array_id = glGenVertexArrays(1) 144 | glBindVertexArray( vertex_array_id ) 145 | 146 | # Create and compile our GLSL program from the shaders 147 | program_id = common.LoadShaders( ".\\shaders\\Tutorial7\\TransformVertexShader.vertexshader", 148 | ".\\shaders\\Tutorial7\\TextureFragmentShader.fragmentshader" ) 149 | 150 | # Get a handle for our "MVP" uniform 151 | matrix_id = glGetUniformLocation(program_id, "MVP") 152 | 153 | # Load the texture 154 | texture = load_image(".\\content\\uvmap.bmp") 155 | 156 | # Get a handle for our "myTextureSampler" uniform 157 | texture_id = glGetUniformLocation(program_id, "myTextureSampler") 158 | 159 | # Read our OBJ file 160 | vertices,faces,uvs,normals,colors = objloader.load(".\\content\\cube.obj") 161 | vertex_data,uv_data,normal_data = objloader.process_obj( vertices,faces,uvs,normals,colors) 162 | 163 | # Our OBJ loader uses Python lists, convert to ctype arrays before sending to OpenGL 164 | vertex_data = objloader.generate_2d_ctypes(vertex_data) 165 | uv_data = objloader.generate_2d_ctypes(uv_data) 166 | 167 | # Load OBJ in to a VBO 168 | vertex_buffer = glGenBuffers(1) 169 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 170 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4 * 3, vertex_data, GL_STATIC_DRAW) 171 | 172 | uv_buffer = glGenBuffers(1) 173 | array_type = GLfloat * len(uv_data) 174 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 175 | glBufferData(GL_ARRAY_BUFFER, len(uv_data) * 4 * 2, uv_data, GL_STATIC_DRAW) 176 | 177 | # vsync and glfw do not play nice. when vsync is enabled mouse movement is jittery. 178 | common.disable_vsyc() 179 | 180 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 181 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 182 | 183 | glUseProgram(program_id) 184 | 185 | controls.computeMatricesFromInputs(window) 186 | ProjectionMatrix = controls.getProjectionMatrix() 187 | ViewMatrix = controls.getViewMatrix() 188 | ModelMatrix = mat4.identity() 189 | mvp = ProjectionMatrix * ViewMatrix * ModelMatrix 190 | 191 | # Send our transformation to the currently bound shader, 192 | # in the "MVP" uniform 193 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 194 | 195 | # Bind our texture in Texture Unit 0 196 | glActiveTexture(GL_TEXTURE0) 197 | glBindTexture(GL_TEXTURE_2D, texture) 198 | # Set our "myTextureSampler" sampler to user Texture Unit 0 199 | glUniform1i(texture_id, 0) 200 | 201 | #1rst attribute buffer : vertices 202 | glEnableVertexAttribArray(0) 203 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 204 | glVertexAttribPointer( 205 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 206 | 3, # len(vertex_data) 207 | GL_FLOAT, # type 208 | GL_FALSE, # ormalized? 209 | 0, # stride 210 | null # array buffer offset (c_type == void*) 211 | ) 212 | 213 | # 2nd attribute buffer : colors 214 | glEnableVertexAttribArray(1) 215 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 216 | glVertexAttribPointer( 217 | 1, # attribute 1. No particular reason for 1, but must match the layout in the shader. 218 | 2, # len(vertex_data) 219 | GL_FLOAT, # type 220 | GL_FALSE, # ormalized? 221 | 0, # stride 222 | null # array buffer offset (c_type == void*) 223 | ) 224 | 225 | # Draw the triangles, vertex data now contains individual vertices 226 | # so use array length 227 | glDrawArrays(GL_TRIANGLES, 0, len(vertex_data)) 228 | 229 | # Not strictly necessary because we only have 230 | glDisableVertexAttribArray(0) 231 | glDisableVertexAttribArray(1) 232 | 233 | 234 | # Swap front and back buffers 235 | glfw.swap_buffers(window) 236 | 237 | # Poll for and process events 238 | glfw.poll_events() 239 | 240 | # !Note braces around vertex_buffer and uv_buffer. 241 | # glDeleteBuffers expects a list of buffers to delete 242 | glDeleteBuffers(1, [vertex_buffer]) 243 | glDeleteBuffers(1, [uv_buffer]) 244 | glDeleteProgram(program_id) 245 | glDeleteTextures([texture_id]) 246 | glDeleteVertexArrays(1, [vertex_array_id]) 247 | 248 | glfw.terminate() 249 | 250 | if __name__ == "__main__": 251 | main() 252 | -------------------------------------------------------------------------------- /tutorial8.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 8: Basic Shading 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | 18 | import common 19 | import glfw 20 | import sys 21 | import os 22 | import controls 23 | import objloader 24 | 25 | # Global window 26 | window = None 27 | null = c_void_p(0) 28 | 29 | def opengl_init(): 30 | global window 31 | # Initialize the library 32 | if not glfw.init(): 33 | print("Failed to initialize GLFW\n",file=sys.stderr) 34 | return False 35 | 36 | # Open Window and create its OpenGL context 37 | window = glfw.create_window(1024, 768, "Tutorial 08", None, None) #(in the accompanying source code this variable will be global) 38 | glfw.window_hint(glfw.SAMPLES, 4) 39 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 40 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 41 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 42 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 43 | 44 | if not window: 45 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 46 | glfw.terminate() 47 | return False 48 | 49 | # Initialize GLEW 50 | glfw.make_context_current(window) 51 | glewExperimental = True 52 | 53 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 54 | # more information including why can remove this code.a 55 | if glewInit() != GLEW_OK: 56 | print("Failed to initialize GLEW\n",file=stderropen.sys); 57 | return False 58 | return True 59 | 60 | def bind_texture(texture_id,mode): 61 | """ Bind texture_id using several different modes 62 | 63 | Notes: 64 | Without mipmapping the texture is incomplete 65 | and requires additional constraints on OpenGL 66 | to properly render said texture. 67 | 68 | Use 'MIN_FILTER" or 'MAX_LEVEL' to render 69 | a generic texture with a single resolution 70 | Ref: 71 | [] - http://www.opengl.org/wiki/Common_Mistakes#Creating_a_complete_texture 72 | [] - http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/ 73 | TODO: 74 | - Rename modes to something useful 75 | """ 76 | if mode == 'DEFAULT': 77 | glBindTexture(GL_TEXTURE_2D, texture_id) 78 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 79 | elif mode == 'MIN_FILTER': 80 | glBindTexture(GL_TEXTURE_2D, texture_id) 81 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 82 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) 83 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) 84 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 85 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 86 | elif mode == 'MAX_LEVEL': 87 | glBindTexture(GL_TEXTURE_2D, texture_id) 88 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 89 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0) 90 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0) 91 | else: 92 | glBindTexture(GL_TEXTURE_2D, texture_id) 93 | glPixelStorei(GL_UNPACK_ALIGNMENT,1) 94 | 95 | # Generate mipmaps? Doesn't seem to work 96 | glGenerateMipmap(GL_TEXTURE_2D) 97 | 98 | def load_image(file_name): 99 | im = pil_open(file_name) 100 | try: 101 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1) 102 | except SystemError: 103 | width,height,image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1) 104 | 105 | texture_id = glGenTextures(1) 106 | 107 | # To use OpenGL 4.2 ARB_texture_storage to automatically generate a single mipmap layer 108 | # uncomment the 3 lines below. Note that this should replaced glTexImage2D below. 109 | #bind_texture(texture_id,'DEFAULT') 110 | #glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); 111 | #glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image) 112 | 113 | # "Bind" the newly created texture : all future texture functions will modify this texture 114 | bind_texture(texture_id,'MIN_FILTER') 115 | glTexImage2D( 116 | GL_TEXTURE_2D, 0, 3, width, height, 0, 117 | GL_RGBA, GL_UNSIGNED_BYTE, image 118 | ) 119 | return texture_id 120 | 121 | def main(): 122 | 123 | # Initialize GLFW and open a window 124 | if not opengl_init(): 125 | return 126 | 127 | # Enable key events 128 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 129 | glfw.set_cursor_pos(window, 1024/2, 768/2) 130 | 131 | # Set opengl clear color to something other than red (color used by the fragment shader) 132 | glClearColor(0.0,0.0,0.4,0.0) 133 | 134 | # Enable depth test 135 | glEnable(GL_DEPTH_TEST) 136 | 137 | # Accept fragment if it closer to the camera than the former one 138 | glDepthFunc(GL_LESS) 139 | 140 | # Cull triangles which normal is not towards the camera 141 | glEnable(GL_CULL_FACE) 142 | 143 | vertex_array_id = glGenVertexArrays(1) 144 | glBindVertexArray( vertex_array_id ) 145 | 146 | # Create and compile our GLSL program from the shaders 147 | program_id = common.LoadShaders( ".\\shaders\\Tutorial8\\StandardShading.vertexshader", 148 | ".\\shaders\\Tutorial8\\StandardShading.fragmentshader" ) 149 | 150 | # Get a handle for our "MVP" uniform 151 | matrix_id = glGetUniformLocation(program_id, "MVP") 152 | view_matrix_id = glGetUniformLocation(program_id, "V") 153 | model_matrix_id = glGetUniformLocation(program_id, "M") 154 | 155 | # Load the texture 156 | texture = load_image(".\\content\\uvmap_suzanne.bmp") 157 | 158 | # Get a handle for our "myTextureSampler" uniform 159 | texture_id = glGetUniformLocation(program_id, "myTextureSampler") 160 | 161 | # Read our OBJ file 162 | vertices,faces,uvs,normals,colors = objloader.load(".\\content\\suzanne.obj") 163 | vertex_data,uv_data,normal_data = objloader.process_obj( vertices,faces,uvs,normals,colors) 164 | 165 | # Our OBJ loader uses Python lists, convert to ctype arrays before sending to OpenGL 166 | vertex_data = objloader.generate_2d_ctypes(vertex_data) 167 | uv_data = objloader.generate_2d_ctypes(uv_data) 168 | normal_data = objloader.generate_2d_ctypes(normal_data) 169 | 170 | # Load OBJ in to a VBO 171 | vertex_buffer = glGenBuffers(1); 172 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 173 | glBufferData(GL_ARRAY_BUFFER, len(vertex_data) * 4 * 3, vertex_data, GL_STATIC_DRAW) 174 | 175 | uv_buffer = glGenBuffers(1) 176 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 177 | glBufferData(GL_ARRAY_BUFFER, len(uv_data) * 4 * 2, uv_data, GL_STATIC_DRAW) 178 | 179 | normal_buffer = glGenBuffers(1) 180 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer) 181 | glBufferData(GL_ARRAY_BUFFER, len(normal_data) * 4 * 3, normal_data, GL_STATIC_DRAW) 182 | 183 | # vsync and glfw do not play nice. when vsync is enabled mouse movement is jittery. 184 | common.disable_vsyc() 185 | 186 | # Get a handle for our "LightPosition" uniform 187 | glUseProgram(program_id); 188 | light_id = glGetUniformLocation(program_id, "LightPosition_worldspace"); 189 | 190 | last_time = glfw.get_time() 191 | frames = 0 192 | 193 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 194 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 195 | 196 | current_time = glfw.get_time() 197 | if current_time - last_time >= 1.0: 198 | glfw.set_window_title(window,"Tutorial 8. FPS: %d"%(frames)) 199 | frames = 0 200 | last_time = current_time 201 | 202 | glUseProgram(program_id) 203 | 204 | controls.computeMatricesFromInputs(window) 205 | ProjectionMatrix = controls.getProjectionMatrix(); 206 | ViewMatrix = controls.getViewMatrix(); 207 | ModelMatrix = mat4.identity(); 208 | mvp = ProjectionMatrix * ViewMatrix * ModelMatrix; 209 | 210 | # Send our transformation to the currently bound shader, 211 | # in the "MVP" uniform 212 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 213 | glUniformMatrix4fv(model_matrix_id, 1, GL_FALSE, ModelMatrix.data); 214 | glUniformMatrix4fv(view_matrix_id, 1, GL_FALSE, ViewMatrix.data); 215 | 216 | lightPos = vec3(4,4,4) 217 | glUniform3f(light_id, lightPos.x, lightPos.y, lightPos.z) 218 | 219 | # Bind our texture in Texture Unit 0 220 | glActiveTexture(GL_TEXTURE0); 221 | glBindTexture(GL_TEXTURE_2D, texture); 222 | # Set our "myTextureSampler" sampler to user Texture Unit 0 223 | glUniform1i(texture_id, 0); 224 | 225 | #1rst attribute buffer : vertices 226 | glEnableVertexAttribArray(0) 227 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 228 | glVertexAttribPointer( 229 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 230 | 3, # len(vertex_data) 231 | GL_FLOAT, # type 232 | GL_FALSE, # ormalized? 233 | 0, # stride 234 | null # array buffer offset (c_type == void*) 235 | ) 236 | 237 | # 2nd attribute buffer : colors 238 | glEnableVertexAttribArray(1) 239 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 240 | glVertexAttribPointer( 241 | 1, # attribute 1. No particular reason for 1, but must match the layout in the shader. 242 | 2, # len(vertex_data) 243 | GL_FLOAT, # type 244 | GL_FALSE, # ormalized? 245 | 0, # stride 246 | null # array buffer offset (c_type == void*) 247 | ) 248 | 249 | # 3rd attribute buffer : normals 250 | glEnableVertexAttribArray(2); 251 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer); 252 | glVertexAttribPointer( 253 | 2, # attribute 254 | 3, # size 255 | GL_FLOAT, # type 256 | GL_FALSE, # ormalized? 257 | 0, # stride 258 | null # array buffer offset (c_type == void*) 259 | ) 260 | 261 | 262 | # Draw the triangles, vertex data now contains individual vertices 263 | # so use array length 264 | glDrawArrays(GL_TRIANGLES, 0, len(vertex_data)) 265 | 266 | # Not strictly necessary because we only have 267 | glDisableVertexAttribArray(0) 268 | glDisableVertexAttribArray(1) 269 | glDisableVertexAttribArray(2) 270 | 271 | 272 | # Swap front and back buffers 273 | glfw.swap_buffers(window) 274 | 275 | # Poll for and process events 276 | glfw.poll_events() 277 | 278 | frames += 1 279 | 280 | # !Note braces around vertex_buffer and uv_buffer. 281 | # glDeleteBuffers expects a list of buffers to delete 282 | glDeleteBuffers(1, [vertex_buffer]) 283 | glDeleteBuffers(1, [uv_buffer]) 284 | glDeleteBuffers(1, [normal_buffer]) 285 | glDeleteProgram(program_id) 286 | glDeleteTextures([texture_id]) 287 | glDeleteVertexArrays(1, [vertex_array_id]) 288 | 289 | glfw.terminate() 290 | 291 | if __name__ == "__main__": 292 | main() 293 | -------------------------------------------------------------------------------- /tutorial9.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Tutorial 9: Basic Shading 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from OpenGL.GL import * 9 | from OpenGL.GL.ARB import * 10 | from OpenGL.GLU import * 11 | from OpenGL.GLUT import * 12 | from OpenGL.GLUT.special import * 13 | from OpenGL.GL.shaders import * 14 | from glew_wish import * 15 | from csgl import * 16 | from PIL.Image import open as pil_open 17 | 18 | import texture as textureutils 19 | import common 20 | import glfw 21 | import sys 22 | import os 23 | import controls 24 | import objloader 25 | import vboindexer 26 | 27 | # Global window 28 | window = None 29 | null = c_void_p(0) 30 | 31 | def opengl_init(): 32 | global window 33 | # Initialize the library 34 | if not glfw.init(): 35 | print("Failed to initialize GLFW\n",file=sys.stderr) 36 | return False 37 | 38 | # Open Window and create its OpenGL context 39 | window = glfw.create_window(1024, 768, "Tutorial 09", None, None) #(in the accompanying source code this variable will be global) 40 | glfw.window_hint(glfw.SAMPLES, 4) 41 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 42 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 43 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 44 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 45 | 46 | if not window: 47 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 48 | glfw.terminate() 49 | return False 50 | 51 | # Initialize GLEW 52 | glfw.make_context_current(window) 53 | glewExperimental = True 54 | 55 | # GLEW is a framework for testing extension availability. Please see tutorial notes for 56 | # more information including why can remove this code.a 57 | if glewInit() != GLEW_OK: 58 | print("Failed to initialize GLEW\n",file=stderropen.sys); 59 | return False 60 | return True 61 | 62 | def c_type_fill(data,data_type): 63 | rows = len(data) 64 | cols = len(data[0]) 65 | t = rows * (cols * data_type) 66 | tmp = t() 67 | for r in range(rows): 68 | for c in range(cols): 69 | tmp[r][c] = data[r][c] 70 | return tmp 71 | 72 | def c_type_fill_1D(data,data_type): 73 | rows = len(data) 74 | 75 | t = rows * data_type 76 | tmp = t() 77 | for r in range(rows): 78 | tmp[r] = data[r] 79 | return tmp 80 | 81 | def main(): 82 | 83 | # Initialize GLFW and open a window 84 | if not opengl_init(): 85 | return 86 | 87 | # Enable key events 88 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 89 | glfw.set_cursor_pos(window, 1024/2, 768/2) 90 | 91 | # Set opengl clear color to something other than red (color used by the fragment shader) 92 | glClearColor(0.0,0.0,0.4,0.0) 93 | 94 | # Enable depth test 95 | glEnable(GL_DEPTH_TEST) 96 | 97 | # Accept fragment if it closer to the camera than the former one 98 | glDepthFunc(GL_LESS) 99 | 100 | # Cull triangles which normal is not towards the camera 101 | glEnable(GL_CULL_FACE) 102 | 103 | vertex_array_id = glGenVertexArrays(1) 104 | glBindVertexArray( vertex_array_id ) 105 | 106 | # Create and compile our GLSL program from the shaders 107 | program_id = common.LoadShaders( ".\\shaders\\Tutorial9\\StandardShading.vertexshader", 108 | ".\\shaders\\Tutorial9\\StandardShading.fragmentshader" ) 109 | 110 | # Get a handle for our "MVP" uniform 111 | matrix_id = glGetUniformLocation(program_id, "MVP") 112 | view_matrix_id = glGetUniformLocation(program_id, "V") 113 | model_matrix_id = glGetUniformLocation(program_id, "M") 114 | 115 | # Load the texture 116 | texture = textureutils.load_image(".\\content\\uvmap_suzanne.bmp") 117 | 118 | # Get a handle for our "myTextureSampler" uniform 119 | texture_id = glGetUniformLocation(program_id, "myTextureSampler") 120 | 121 | # Read our OBJ file 122 | vertices,faces,uvs,normals,colors = objloader.load(".\\content\\suzanne.obj") 123 | vertex_data,uv_data,normal_data = objloader.process_obj( vertices,faces,uvs,normals,colors) 124 | 125 | # Our OBJ loader uses Python lists, convert to ctype arrays before sending to OpenGL 126 | vertex_data = objloader.generate_2d_ctypes(vertex_data) 127 | uv_data = objloader.generate_2d_ctypes(uv_data) 128 | normal_data = objloader.generate_2d_ctypes(normal_data) 129 | 130 | indexed_vertices, indexed_uvs, indexed_normals, indices = vboindexer.indexVBO(vertex_data,uv_data,normal_data) 131 | 132 | indexed_vertices = c_type_fill(indexed_vertices,GLfloat) 133 | indexed_uvs = c_type_fill(indexed_uvs,GLfloat) 134 | indexed_normals = c_type_fill(indexed_normals,GLfloat) 135 | indices = c_type_fill_1D(indices,GLushort) 136 | 137 | 138 | # Load OBJ in to a VBO 139 | vertex_buffer = glGenBuffers(1); 140 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer) 141 | glBufferData(GL_ARRAY_BUFFER, len(indexed_vertices) * 4 * 3, indexed_vertices, GL_STATIC_DRAW) 142 | 143 | uv_buffer = glGenBuffers(1) 144 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer) 145 | glBufferData(GL_ARRAY_BUFFER, len(indexed_uvs) * 4 * 2, indexed_uvs, GL_STATIC_DRAW) 146 | 147 | normal_buffer = glGenBuffers(1) 148 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer) 149 | glBufferData(GL_ARRAY_BUFFER, len(indexed_normals) * 4 * 3, indexed_normals, GL_STATIC_DRAW) 150 | 151 | # Generate a buffer for the indices as well 152 | elementbuffer = glGenBuffers(1) 153 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer) 154 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(indices) * 2, indices , GL_STATIC_DRAW); 155 | 156 | # vsync and glfw do not play nice. when vsync is enabled mouse movement is jittery. 157 | common.disable_vsyc() 158 | 159 | # Get a handle for our "LightPosition" uniform 160 | glUseProgram(program_id); 161 | light_id = glGetUniformLocation(program_id, "LightPosition_worldspace"); 162 | 163 | last_time = glfw.get_time() 164 | frames = 0 165 | 166 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 167 | glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT) 168 | 169 | current_time = glfw.get_time() 170 | if current_time - last_time >= 1.0: 171 | glfw.set_window_title(window,"Tutorial 9. FPS: %d"%(frames)) 172 | frames = 0 173 | last_time = current_time 174 | 175 | glUseProgram(program_id) 176 | 177 | controls.computeMatricesFromInputs(window) 178 | ProjectionMatrix = controls.getProjectionMatrix(); 179 | ViewMatrix = controls.getViewMatrix(); 180 | ModelMatrix = mat4.identity(); 181 | mvp = ProjectionMatrix * ViewMatrix * ModelMatrix; 182 | 183 | # Send our transformation to the currently bound shader, 184 | # in the "MVP" uniform 185 | glUniformMatrix4fv(matrix_id, 1, GL_FALSE,mvp.data) 186 | glUniformMatrix4fv(model_matrix_id, 1, GL_FALSE, ModelMatrix.data); 187 | glUniformMatrix4fv(view_matrix_id, 1, GL_FALSE, ViewMatrix.data); 188 | 189 | lightPos = vec3(4,4,4) 190 | glUniform3f(light_id, lightPos.x, lightPos.y, lightPos.z) 191 | 192 | # Bind our texture in Texture Unit 0 193 | glActiveTexture(GL_TEXTURE0); 194 | glBindTexture(GL_TEXTURE_2D, texture); 195 | # Set our "myTextureSampler" sampler to user Texture Unit 0 196 | glUniform1i(texture_id, 0); 197 | 198 | #1rst attribute buffer : vertices 199 | glEnableVertexAttribArray(0) 200 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 201 | glVertexAttribPointer( 202 | 0, # attribute 0. No particular reason for 0, but must match the layout in the shader. 203 | 3, # len(vertex_data) 204 | GL_FLOAT, # type 205 | GL_FALSE, # ormalized? 206 | 0, # stride 207 | null # array buffer offset (c_type == void*) 208 | ) 209 | 210 | # 2nd attribute buffer : colors 211 | glEnableVertexAttribArray(1) 212 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 213 | glVertexAttribPointer( 214 | 1, # attribute 1. No particular reason for 1, but must match the layout in the shader. 215 | 2, # len(vertex_data) 216 | GL_FLOAT, # type 217 | GL_FALSE, # ormalized? 218 | 0, # stride 219 | null # array buffer offset (c_type == void*) 220 | ) 221 | 222 | # 3rd attribute buffer : normals 223 | glEnableVertexAttribArray(2); 224 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer); 225 | glVertexAttribPointer( 226 | 2, # attribute 227 | 3, # size 228 | GL_FLOAT, # type 229 | GL_FALSE, # ormalized? 230 | 0, # stride 231 | null # array buffer offset (c_type == void*) 232 | ) 233 | 234 | 235 | # Draw the triangles, vertex data now contains individual vertices 236 | # so use array length 237 | # glDrawArrays(GL_TRIANGLES, 0, len(vertex_data)) 238 | # Index buffer 239 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer) 240 | 241 | # Draw the triangles ! 242 | glDrawElements( 243 | GL_TRIANGLES, # mode 244 | len(indices), # count 245 | GL_UNSIGNED_SHORT, # type 246 | null # element array buffer offset 247 | ) 248 | # Not strictly necessary because we only have 249 | glDisableVertexAttribArray(0) 250 | glDisableVertexAttribArray(1) 251 | glDisableVertexAttribArray(2) 252 | 253 | 254 | # Swap front and back buffers 255 | glfw.swap_buffers(window) 256 | 257 | # Poll for and process events 258 | glfw.poll_events() 259 | 260 | frames += 1 261 | 262 | # !Note braces around vertex_buffer and uv_buffer. 263 | # glDeleteBuffers expects a list of buffers to delete 264 | glDeleteBuffers(1, [vertex_buffer]) 265 | glDeleteBuffers(1, [uv_buffer]) 266 | glDeleteBuffers(1, [normal_buffer]) 267 | glDeleteProgram(program_id) 268 | glDeleteTextures([texture_id]) 269 | glDeleteVertexArrays(1, [vertex_array_id]) 270 | 271 | glfw.terminate() 272 | 273 | if __name__ == "__main__": 274 | main() 275 | -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ Various Utilities 3 | """ 4 | from __future__ import print_function 5 | 6 | from OpenGL.GL import * 7 | from csgl import * 8 | 9 | import glfw 10 | import math as mathf 11 | 12 | from PIL import Image 13 | from PIL.Image import open as pil_open 14 | 15 | def screenshot(file_name_out,width,height): 16 | data = glReadPixels(0,0,width,height,GL_RGB,GL_UNSIGNED_BYTE,outputType=None) 17 | image = Image.fromstring(mode="RGB", size=(width, height), data=data) 18 | image = image.transpose(Image.FLIP_TOP_BOTTOM) 19 | image.save(file_name_out) 20 | -------------------------------------------------------------------------------- /vao_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ Tutorial 2: Drawing the triangle 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from OpenGL.GL import * 8 | from OpenGL.GL.ARB import * 9 | from OpenGL.GLU import * 10 | from OpenGL.GLUT import * 11 | from OpenGL.GLUT.special import * 12 | from OpenGL.GL.shaders import * 13 | 14 | from test import * 15 | from glew_wish import * 16 | import common 17 | import glfw 18 | import sys 19 | import os 20 | 21 | # Global window 22 | window = None 23 | null = c_void_p(0) 24 | 25 | def opengl_init(): 26 | global window 27 | # Initialize the library 28 | if not glfw.init(): 29 | print("Failed to initialize GLFW\n",file=sys.stderr) 30 | return False 31 | 32 | # Open Window and create its OpenGL context 33 | window = glfw.create_window(1024, 768, "VAO Test", None, None) #(in the accompanying source code this variable will be global) 34 | glfw.window_hint(glfw.SAMPLES, 4) 35 | glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) 36 | glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) 37 | glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE) 38 | glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 39 | 40 | if not window: 41 | print("Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n",file=sys.stderr) 42 | glfw.terminate() 43 | return False 44 | 45 | # Initialize GLEW 46 | glfw.make_context_current(window) 47 | glewExperimental = True 48 | 49 | 50 | # !!NOTE: The most recent GLEW package for python is 8 years old, ARB functionality 51 | # is available in Pyopengl natively. 52 | 53 | if glewInit() != GLEW_OK: 54 | print("Failed to initialize GLEW\n",file=sys.stderr); 55 | return False 56 | return True 57 | 58 | 59 | current_vao = 0 60 | vaos = [] 61 | def key_event(window,key,scancode,action,mods): 62 | global current_vao 63 | if action == glfw.PRESS and key == glfw.KEY_W: 64 | current_vao+=1 65 | current_vao = current_vao % len(vaos) 66 | 67 | 68 | def init_object(vertices): 69 | array_type = GLfloat * len(vertex_data) 70 | 71 | glBindBuffer(GL_ARRAY_BUFFER, glGenBuffers(1)) 72 | glBufferData(GL_ARRAY_BUFFER, len(vertices) * 4, array_type(*vertices), GL_STATIC_DRAW) 73 | 74 | glEnableVertexAttribArray(0) 75 | glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,null) 76 | 77 | 78 | vertex_data = [-1.0, -1.0, 0.0, 79 | 1.0, -1.0, 0.0, 80 | 0.0, 1.0, 0.0] 81 | 82 | vertex_data2 = [0.0, -1.0, 0.0, 83 | 1.0, -1.0, 0.0, 84 | 0.0, 1.0, 0.0] 85 | 86 | 87 | def main(): 88 | global current_vao 89 | global vaos 90 | 91 | if not opengl_init(): 92 | return 93 | 94 | glfw.set_input_mode(window,glfw.STICKY_KEYS,GL_TRUE) 95 | glfw.set_key_callback(window,key_event) 96 | # Set opengl clear color to something other than red (color used by the fragment shader) 97 | glClearColor(0,0,0.4,0) 98 | 99 | # Create vertex array object (VAO) 1: Full Triangle 100 | vao = glGenVertexArrays(1) 101 | glBindVertexArray(vao) 102 | init_object(vertex_data) 103 | glBindVertexArray(0) 104 | 105 | # Create vertex array object (VAO) 2: 1/2 Triangle 106 | vao2 = glGenVertexArrays(1) 107 | glBindVertexArray(vao2) 108 | init_object(vertex_data2) 109 | glBindVertexArray(0) 110 | 111 | program_id = common.LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" ) 112 | vertex_buffer = glGenBuffers(1) 113 | 114 | current_vao = 0 115 | vaos = [vao,vao2] 116 | glewInit() 117 | 118 | 119 | while glfw.get_key(window,glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window): 120 | glClear(GL_COLOR_BUFFER_BIT) 121 | 122 | glUseProgram(program_id) 123 | glBindVertexArray(vaos[current_vao]) 124 | 125 | # Draw the triangle ! 126 | glDrawArrays (GL_TRIANGLES, 0, 3)#3 indices starting at 0 -> 1 triangle 127 | 128 | glBindVertexArray(0) 129 | 130 | # Swap front and back buffers 131 | glfw.swap_buffers(window) 132 | 133 | # Poll for and process events 134 | glfw.poll_events() 135 | 136 | glfw.terminate() 137 | 138 | if __name__ == "__main__": 139 | main() 140 | -------------------------------------------------------------------------------- /vboindexer.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import ctypes 5 | 6 | class PackedVertex(object): 7 | libc_name = ctypes.util.find_library("c") 8 | libc = ctypes.CDLL(libc_name) 9 | 10 | libc.memcmp.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t) 11 | def __init__(self,pos,uv,norm): 12 | self.position = pos 13 | self.uv = uv 14 | self.normal = norm 15 | 16 | def __hash__(self): 17 | return hash((tuple(self.position),tuple(self.uv),tuple(self.normal))) 18 | 19 | def __eq__(self,other): 20 | """ Compare PackedVertex objects at the byte level 21 | 22 | ctype arrays don't allow direct comparisons ([1]) so instead 23 | do a memory comparison ([2]), similar to what was done in the original 24 | .cpp tutorials. 25 | 26 | The main difference is that std::map defines equality as a 27 | reflexive comparison because std::map's are ordered, 28 | unlike Python dict. That means instead of looking for values 29 | >0 like in the original vboindexer.cpp you need to look for 30 | values == 0 which denote memory block equality. 31 | 32 | refs: 33 | [1] - http://stackoverflow.com/questions/25159248/compare-ctypes-arrays-without-additional-memory 34 | [2] - http://www.cplusplus.com/reference/cstring/memcmp/ 35 | """ 36 | a = self.libc.memcmp(self.position, other.position, ctypes.sizeof(self.position)) 37 | b = self.libc.memcmp(self.normal, other.normal, ctypes.sizeof(self.normal)) 38 | c = self.libc.memcmp(self.uv, other.uv, ctypes.sizeof(self.uv)) 39 | return (a==0 and b == 0 and c == 0) 40 | 41 | 42 | def getSimilarVertexIndex_fast(packed,vertex_to_out_index): 43 | if packed not in vertex_to_out_index: 44 | return False,None 45 | else: 46 | return True,vertex_to_out_index[packed] 47 | 48 | def indexVBO(vertices,uvs,normals): 49 | vertex_to_out_index = {} 50 | 51 | out_verts = [] 52 | out_uvs = [] 53 | out_normals = [] 54 | out_indices = [] 55 | 56 | found_count = 0 57 | for i,vertex in enumerate(vertices): 58 | packed = PackedVertex(vertices[i],uvs[i],normals[i]) 59 | found,index = getSimilarVertexIndex_fast(packed,vertex_to_out_index) 60 | 61 | 62 | if found: 63 | out_indices.append(index) 64 | found_count += 1 65 | else: 66 | out_verts.append(vertices[i]) 67 | out_uvs.append(uvs[i]) 68 | out_normals.append(normals[i]) 69 | new_index = len(out_verts)-1 70 | out_indices.append(new_index) 71 | 72 | vertex_to_out_index[packed] = new_index 73 | 74 | print("Found %d duplicate vertices creating the VBO"%(found_count)) 75 | return out_verts,out_uvs,out_normals,out_indices 76 | 77 | 78 | 79 | if __name__ == "__main__": 80 | p1 = PackedVertex([1,2,3],[4,5,6],[7,8,9]) 81 | p2 = PackedVertex([1,2,3],[4,5,6],[7,8,9]) 82 | p3 = PackedVertex([1,2,3],[4,5,6],[7,8,10]) 83 | 84 | 85 | d = {} 86 | d[p1] = 0 87 | assert(d[p1] == 0) 88 | assert(d[PackedVertex([1,2,3],[4,5,6],[7,8,9])] == 0) 89 | assert(p1 in d) 90 | assert(p2 in d) 91 | 92 | assert(p3 not in d) 93 | d[p3] = 1 94 | assert(p3 in d) 95 | 96 | del d[p1] 97 | assert(p1 not in d) 98 | 99 | del d[p3] 100 | assert(p3 not in d) --------------------------------------------------------------------------------