Search code examples
openglmatrixtransformationprojectionpyglet

Applying GLM like "perspective" and "lookat" calculations results in my shape disappearing


I'm new to OpenGL and am working my way through translating the C++ OpenGL tutorials at http://open.gl/ to Python and Pyglet (https://github.com/01AutoMonkey/open.gl-tutorials-to-pyglet) and am currently at the second example in the "Transformation" section (http://open.gl/transformations).

It's done, except I just get a black screen instead of an image moving in a particular way (two triangles making up a rectangle are suppose to rotate and the camera is suppose to tilt a little). The problem seems to stem from matrix calculations, more specifically the applying of "perspective" and "lookat" to gain a "projection". If I remove those two from my vertex shader calculations it works, I get a rotating image, but without the tilting of the camera which the two matrixes were suppose to deliver.

I've compared my code with the C++ code and I don't see any discrepancies and I fail to see where the problem could lie.

How I'm translating the code:

  • C++ to Python and ctypes
  • SFML to Pyglet
  • GLEW to pyglet.gl
  • SOIL to pyglet.image
  • GLM to pyeuclid <-- note that pyeuclid is what I'm using to calculate the matrices

Could it be I'm suppose to send the matrices in some other form then a flat list? Also maybe the code is working but I'm doing something wrong or pyglet works differently and therefore the image is displayed outside the screen?

The C++ code:

// Set up projection
glm::mat4 view = glm::lookAt(
    glm::vec3(1.2f, 1.2f, 1.2f),
    glm::vec3(0.0f, 0.0f, 0.0f),
    glm::vec3(0.0f, 0.0f, 1.0f)
);
GLint uniView = glGetUniformLocation(shaderProgram, "view");
glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));

glm::mat4 proj = glm::perspective(45.0f, 800.0f / 600.0f, 1.0f, 10.0f);
GLint uniProj = glGetUniformLocation(shaderProgram, "proj");
glUniformMatrix4fv(uniProj, 1, GL_FALSE, glm::value_ptr(proj));

My Python, Pyglet code equivilant:

# Set up projection
eye = Vector3(1.2, 1.2, 1.2)
at = Vector3(0.0, 0.0, 0.0)
up = Vector3(0.0, 0.0, 1.0)
view = Matrix4.new_look_at(eye, at, up)
view = view[:]
view_ctype = (GLfloat * len(view))(*view)
uniView = glGetUniformLocation(shader.handle, "view")
glUniformMatrix4fv(uniView, 1, GL_FALSE, view_ctype)

proj = Matrix4.new_perspective(45.0, 800.0 / 600.0, 1.0, 10.0)
proj = proj[:]
proj_ctype = (GLfloat * len(proj))(*proj)
uniProj = glGetUniformLocation(shader.handle, "proj")
glUniformMatrix4fv(uniProj, 1, GL_FALSE, proj_ctype)

The full script:

import pyglet
from pyglet.gl import *
from shader import Shader
from ctypes import pointer, sizeof
import math
import time
from euclid import *


window = pyglet.window.Window(800, 600, "OpenGL")
window.set_location(100, 100)


# Shaders (Vertex and Fragment shaders)
vertex = """
#version 150

in vec2 position;
in vec2 texcoord;

out vec2 Texcoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main() {
    Texcoord = texcoord;
    gl_Position = proj * view * model * vec4(position, 0.0, 1.0);
}
"""
fragment = """
#version 150 core

in vec2 Texcoord;

out vec4 outColor;

uniform sampler2D texKitten;
uniform sampler2D texPuppy;

void main() {
   outColor = mix(texture(texKitten, Texcoord), texture(texPuppy, Texcoord), 0.5);
}
"""
## Compiling shaders and combining them into a program 
shader = Shader(vertex, fragment)
shader.bind() #glUseProgram


# Vertex Input
## Vertex Array Objects
vao = GLuint()
glGenVertexArrays(1, pointer(vao))
glBindVertexArray(vao)

## Vertex Buffer Object
vbo = GLuint()
glGenBuffers(1, pointer(vbo)) # Generate 1 buffer

#           Position    Texcoords
vertices = [-0.5,  0.5, 0.0, 1.0,
             0.5,  0.5, 1.0, 1.0,
             0.5, -0.5, 1.0, 0.0,
            -0.5, -0.5, 0.0, 0.0]

## Convert the verteces array to a GLfloat array, usable by glBufferData
vertices_gl = (GLfloat * len(vertices))(*vertices)

## Upload data to GPU
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_gl), vertices_gl, GL_STATIC_DRAW)


# Element array
ebo = GLuint()
glGenBuffers(1, pointer(ebo))

elements = [0, 1, 2,
            2, 3, 0]
elements_gl = (GLuint * len(elements))(*elements)

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements_gl), elements_gl, GL_STATIC_DRAW);


# Making the link between vertex data and attributes
## shader.handle holds the value of glCreateProgram()
posAttrib = glGetAttribLocation(shader.handle, "position");
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);

texAttrib = glGetAttribLocation(shader.handle, "texcoord");
glEnableVertexAttribArray(texAttrib);
glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 2 * sizeof(GLfloat));


# Load textures
textures = [0] * 2
textures_ctype = (GLuint * len(textures))(*textures)
glGenTextures(2, textures_ctype);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures_ctype[0]);

image = pyglet.image.load("sample.png")
width, height = image.width, image.height
image = image.get_data('RGB', width * 3)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

glUniform1i(glGetUniformLocation(shader.handle, "texKitten"), 0);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textures_ctype[1]);

image = pyglet.image.load("sample2.png")
width, height = image.width, image.height
image = image.get_data('RGB', width * 3)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

glUniform1i(glGetUniformLocation(shader.handle, "texPuppy"), 1);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

uniModel = glGetUniformLocation(shader.handle, "model");

# Set up projection
eye = Vector3(1.2, 1.2, 1.2)
at = Vector3(0.0, 0.0, 0.0)
up = Vector3(0.0, 0.0, 1.0)
view = Matrix4.new_look_at(eye, at, up)
view = view[:]
view_ctype = (GLfloat * len(view))(*view)
uniView = glGetUniformLocation(shader.handle, "view")
glUniformMatrix4fv(uniView, 1, GL_FALSE, view_ctype)

proj = Matrix4.new_perspective(45.0, 800.0 / 600.0, 1.0, 10.0)
proj = proj[:]
proj_ctype = (GLfloat * len(proj))(*proj)
uniProj = glGetUniformLocation(shader.handle, "proj")
glUniformMatrix4fv(uniProj, 1, GL_FALSE, proj_ctype)


# Set clear color
glClearColor(0.0, 0.0, 0.0, 1.0)


@window.event
def on_draw():
    # Clear the screen to black
    glClear(GL_COLOR_BUFFER_BIT)

    # Calculate transformation
    model = Quaternion.new_rotate_axis(time.clock() * math.pi, Vector3(0, 0, 1))
    model = model.get_matrix()[:]
    model_ctype = (GLfloat * len(model))(*model)
    glUniformMatrix4fv(uniModel, 1, GL_FALSE, model_ctype);

    # Draw a rectangle from the 2 triangles using 6 indices
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)

@window.event
def on_key_press(symbol, modifiers):
    pass

@window.event
def on_key_release(symbol, modifiers):
    pass

def update(dt):
    pass
pyglet.clock.schedule(update)


pyglet.app.run()

Solution

  • OpenGL expects matrices to be provided in column-major order. GLM uses the same convention, however many matrix math libraries not designed for use with OpenGL use row-major order.

    My python is rusty, but a cursory inspection of the source for pyeuclid seems to confirm that they are using row-major order.

    Therefore, you need to convert from row-major to column major order before passing any matrices to functions like glUniformMatrix4fv. Luckily, this is a simple matter; it's just the transpose of the matrix!