Perspective and LookAt matrices: Python implementation renders nothing

Florian NICOLAS <[email protected]> Sat, 28 Nov 2015 12:30:00 +0100
Newsgroups gmane.comp.python.opengl.user
Message-ID <[email protected]>
Hi!

I am still experiencing PyOpenGL and today I implemented Perspective and LookAt matrices based on gluLookAt and gluPerspective (frustum):



def perspectiveMatrix2(fovDeg, aspectRatio, near, far):

   ymax = near*np.tan(fovDeg*np.pi/360)
   xmax = ymax*aspectRatio

   return perspectiveMatrix(-xmax, xmax, -ymax, ymax, near, far)


def perspectiveMatrix(left, right, bottom, top, near, far):

   mat = np.array([[2*near/(right-left), 0, (right+left)/(right-left), 0],
                   [0, 2*near/(top-bottom), (top+bottom)/(top-bottom), 0],
                   [0, 0, (near+far)/(near-far), 2*near*far/(near-far)],
                   [0, 0, -1, 0]])

   return mat


def lookAt(eye, center, up):

   E = eye
   C = center
   U = up

   L = C-E
   L = L/np.linalg.norm(L)
   S = np.cross(L,U)
   S = S/np.linalg.norm(S)
   Uprim = np.cross(S,L)

   mat = np.zeros(shape=(4,4))

   mat[:,0] = np.hstack([S, 0])
   mat[:,1] = np.hstack([Uprim, 0])
   mat[:,2] = np.hstack([-L, 0])
   mat[:,3] = np.hstack([-E, 1])

   return mat

However it renders strange things or sometimes nothing depending on the input parameters.

I also provide my running code (where getXRotation, getYRotation and getZRotation behave as expected) so I suppose the issue com from my perspective and lookAt matrices.

I have two last questions about matrices in GLSL: 
- I have found that mat*vec4(position, 1) and vec4(position,1)*mat do not trigger any errors while the latter, in my opinion, should fail, why?
- As OpenGL and bumpy are not identically ordered (column/row major), should I always have to transpose my matrix before using OpenGL functions?


Thanks in advance for your help!

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
# Distributed under the terms of the new BSD License.
# -----------------------------------------------------------------------------
import sys
import ctypes
import numpy as np
import OpenGL.GL as gl
import OpenGL.GLU as glu
import OpenGL.GLUT as glut

vertex_code = """
   uniform float scale;
   uniform mat4 matCam;
   attribute vec4 color;
   attribute vec3 position;
   varying vec4 v_color;
   void main()
   {
       gl_Position = matCam*vec4(scale*position, 1.0);
       v_color = color;
   } """

fragment_code = """
   varying vec4 v_color;
   void main()
   {
       gl_FragColor = v_color;
   } """

def display():
   gl.glEnable(gl.GL_DEPTH_TEST)
   gl.glDepthFunc(gl.GL_LESS)
   gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
   #gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 9)
   gl.glDrawElements(gl.GL_TRIANGLES, len(index), gl.GL_UNSIGNED_INT, None)
   glut.glutSwapBuffers()



def reshape(width,height):
   gl.glViewport(0, 0, width, height)

def keyboard( key, x, y ):
   if key == '\033':
       sys.exit( )


def perspectiveMatrix(left, right, bottom, top, near, far):

   mat = np.array([[2*near/(right-left), 0, (right+left)/(right-left), 0],
                   [0, 2*near/(top-bottom), (top+bottom)/(top-bottom), 0],
                   [0, 0, (near+far)/(near-far), 2*near*far/(near-far)],
                   [0, 0, -1, 0]])

   return mat

def perspectiveMatrix2(fovDeg, aspectRatio, near, far):

   ymax = near*np.tan(fovDeg*np.pi/360)
   xmax = ymax*aspectRatio

   return perspectiveMatrix(-xmax, xmax, -ymax, ymax, near, far)


def lookAt(eye, center, up):

   E = eye
   C = center
   U = up

   L = C-E
   L = L/np.linalg.norm(L)
   S = np.cross(L,U)
   S = S/np.linalg.norm(S)
   Uprim = np.cross(S,L)

   mat = np.zeros(shape=(4,4))

   mat[:,0] = np.hstack([S, 0])
   mat[:,1] = np.hstack([Uprim, 0])
   mat[:,2] = np.hstack([-L, 0])
   mat[:,3] = np.hstack([-E, 1])

   return mat

def getXRotation(theta):

   matX = np.array([[1, 0, 0, 0],
                   [0, np.cos(theta), -np.sin(theta), 0],
                   [0, np.sin(theta), np.cos(theta), 0],
                   [0, 0, 0, 1],
                   ])

   return matX


def getYRotation(theta):

   matY = np.array([[np.cos(theta), 0, np.sin(theta), 0],
                   [0, 1, 0, 0],
                   [-np.sin(theta), 0, np.cos(theta), 0],
                   [0, 0, 0, 1],
                   ])

   return matY



def getZRotation(theta):

   matZ = np.array([[np.cos(theta), -np.sin(theta), 0, 0],
                   [np.sin(theta), np.cos(theta), 0, 0],
                   [0, 0, 1, 0],
                   [0, 0, 0, 1],
                   ])

   return matZ    

def timer(fps):

   global clock
   clock += 0.001 * 1000.0/fps
   theta = clock

   eye = np.array([0,0,1])
   center = np.array([0,0,0])
   up = np.array([0,1,0])

   #mat = lookAt(eye, center, up)   
   #mat = getXRotation(clock)
   mat = perspectiveMatrix2(1, 4/3, 100, -5)


   loc = gl.glGetUniformLocation(program, "matCam")
   gl.glUniformMatrix4fv(loc, 1, False, mat.T)
   glut.glutTimerFunc(1000/fps, timer, fps)
   glut.glutPostRedisplay()    


# GLUT init
# --------------------------------------
glut.glutInit()
glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA)
glut.glutCreateWindow('Hello world!')
glut.glutReshapeWindow(512,512)
glut.glutReshapeFunc(reshape)
glut.glutDisplayFunc(display)
glut.glutKeyboardFunc(keyboard)
glut.glutTimerFunc(1000/60, timer, 60)


# Build data
# --------------------------------------
data = np.zeros(8, [("position", np.float32, 3),
                   ("color",    np.float32, 4)])
data['color']    = [ (1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1),
                   (1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1)]

data['position'] = [ (1,1,1),
                      (-1,1,1),
                      (-1,-1,1),
                      (+1,-1,1),
                       (1,-1,-1),
                       (1,1,-1),
                       (-1,1,-1),
                       (-1,-1,-1)
                          ]
index = np.array( [0,1,2, 
                  0,2,3,  
                  0,3,4, 
                  0,4,5,  
                  0,5,6, 
                  0,6,1,
                  1,6,7, 
                  1,7,2,  
                  7,4,3, 
                  7,3,2,  
                  4,7,6, 
                  4,6,5], dtype=np.uint32)



# Build & activate program
# --------------------------------------

# Request a program and shader slots from GPU
program  = gl.glCreateProgram()
vertex   = gl.glCreateShader(gl.GL_VERTEX_SHADER)
fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)

# Set shaders source
gl.glShaderSource(vertex, vertex_code)
gl.glShaderSource(fragment, fragment_code)

# Compile shaders
gl.glCompileShader(vertex)
gl.glCompileShader(fragment)

# Attach shader objects to the program
gl.glAttachShader(program, vertex)
gl.glAttachShader(program, fragment)

# Build program
gl.glLinkProgram(program)

# Get rid of shaders (no more needed)
gl.glDetachShader(program, vertex)
gl.glDetachShader(program, fragment)

# Make program the default program
gl.glUseProgram(program)


# Build buffer
# --------------------------------------

# Request a buffer slot from GPU
buffer = gl.glGenBuffers(1)


# Make this buffer the default one
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)

# Upload data
gl.glBufferData(gl.GL_ARRAY_BUFFER, data.nbytes, data, gl.GL_DYNAMIC_DRAW)

buffer_index = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, buffer_index)
gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, index.nbytes, index, gl.GL_STATIC_DRAW)


# Bind attributes
# --------------------------------------
stride = data.strides[0]
offset = ctypes.c_void_p(0)
loc = gl.glGetAttribLocation(program, "position")
gl.glEnableVertexAttribArray(loc)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)

offset = ctypes.c_void_p(data.dtype["position"].itemsize)
loc = gl.glGetAttribLocation(program, "color")
gl.glEnableVertexAttribArray(loc)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
gl.glVertexAttribPointer(loc, 4, gl.GL_FLOAT, False, stride, offset)

gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, buffer_index)

gl.glEnable(gl.GL_DEPTH_TEST)
gl.glDepthFunc(gl.GL_LESS)
# Bind uniforms
# --------------------------------------
loc = gl.glGetUniformLocation(program, "scale")
gl.glUniform1f(loc, 0.5)
clock = 0

loc = gl.glGetUniformLocation(program, "matCam")
gl.glUniformMatrix4fv(loc, 1, False, np.eye(4))
# Enter mainloop
# --------------------------------------
glut.glutMainLoop()
------------------------------------------------------------------------------
_______________________________________________
PyOpenGL Homepage
http://pyopengl.sourceforge.net