Search code examples
pythonpython-3.xopenglgluttexture-mapping

Modern opengl python program has cube appear but no textures


The latest code below is a ported python program of the tunnel.cpp program from Superbible OpenGL 7th edition.

The cube appears, however the textures do not. There is also supposed to be slight movement toward the tunnel, and that's not happening either. Any ideas what could be causing this?

Update: Thanks to Rabbid76 the textures now appear and they are rendering correctly.

#!/usr/bin/python3

import sys
import time

sys.path.append("./shared")

#from sbmloader import SBMObject    # location of sbm file format loader
from ktxloader import KTXObject    # location of ktx file format loader

from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44

fullscreen = True

#import numpy.matlib
#import numpy as np

try:
    from OpenGL.GLUT import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
    print ('''
    ERROR: PyOpenGL not installed properly.
        ''')
    sys.exit()

identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]

render_prog = GLuint(0)
render_vao = GLuint(0)

class uniforms:
    mvp = GLint
    offset = GLint

tex_wall = GLuint(0)
tex_ceiling = GLuint(0)
tex_floor = GLuint(0)

uniform = uniforms()

class Scene:

    def __init__(self, width, height):
        global render_prog
        global render_vao
        global uniform

        global tex_wall, tex_ceiling, tex_floor

        self.width = width
        self.height = height

        vs = GLuint(0)
        fs = GLuint(0)

        vs_source = '''
#version 420 core

out VS_OUT
{
    vec2 tc;
} vs_out;

uniform mat4 mvp;
uniform float offset;

void main(void)
{
    const vec2[4] position = vec2[4](vec2(-0.5, -0.5),
                                     vec2( 0.5, -0.5),
                                     vec2(-0.5,  0.5),
                                     vec2( 0.5,  0.5));
    vs_out.tc = (position[gl_VertexID].xy + vec2(offset, 0.5)) * vec2(30.0, 1.0);
    gl_Position = mvp * vec4(position[gl_VertexID], 0.0, 1.0);
}
'''

        fs_source = '''
#version 420 core

layout (location = 0) out vec4 color;

in VS_OUT
{
    vec2 tc;
} fs_in;

layout (binding = 0) uniform sampler2D tex;

void main(void)
{
    color = texture(tex, fs_in.tc);
}
'''

        vs = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vs, vs_source)
        glCompileShader(vs)

        glGetShaderInfoLog(vs)

        fs = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fs, fs_source)
        glCompileShader(fs)

        glGetShaderInfoLog(vs)

        render_prog = glCreateProgram()
        glAttachShader(render_prog, vs)
        glAttachShader(render_prog, fs)
        glLinkProgram(render_prog)

        glDeleteShader(vs)
        glDeleteShader(fs)

        glGetProgramInfoLog(render_prog)

        uniform.mvp = glGetUniformLocation(render_prog, "mvp")
        uniform.offset = glGetUniformLocation(render_prog, "offset")

        glGenVertexArrays(1, render_vao)
        glBindVertexArray(render_vao)

        ktxobj = KTXObject()

        tex_wall = ktxobj.ktx_load("brick.ktx")
        tex_ceiling = ktxobj.ktx_load("ceiling.ktx")
        tex_floor = ktxobj.ktx_load("floor.ktx")


        textures = [ tex_floor, tex_wall, tex_ceiling ]

        for i in range (0, 3):
            glBindTexture(GL_TEXTURE_2D, textures[i])
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

        glBindVertexArray(render_vao)


    def display(self):

        green = [ 0.0, 0.1, 0.0, 0.0 ]
        currentTime = time.time()

        glViewport(0, 0, self.width, self.height)
        glClearBufferfv(GL_COLOR, 0, green)

        glUseProgram(render_prog)

        proj_matrix = (GLfloat * 16)(*identityMatrix)
        proj_matrix = m3dPerspective(m3dDegToRad(60.0), float(self.width) / float(self.height), 0.1, 100.0)

        glUniform1f(uniform.offset, -(currentTime * 0.03) % 1) # negative sign to postive changes direction

        textures = [ tex_wall, tex_ceiling, tex_wall, tex_floor ]

        for i in range(0, 4):

            RZ = (GLfloat * 16)(*identityMatrix)
            m3dRotationMatrix44(RZ, i * m3dDegToRad(90.0), 0.0, 0.0, 1.0)

            T = (GLfloat * 16)(*identityMatrix)
            m3dTranslateMatrix44(T, -5, 0, -10)

            RY = (GLfloat * 16)(*identityMatrix)
            m3dRotationMatrix44(RY, m3dDegToRad(90.0), 0.0, 1.0, 0.0)

            S = (GLfloat * 16)(*identityMatrix)
            m3dScaleMatrix44(S, 300.0, 10.0, 1.0)

            mv_matrix = (GLfloat * 16)(*identityMatrix)
            mv_matrix = m3dMultiply(RZ, m3dMultiply(T, m3dMultiply(RY, S)))

            mvp = (GLfloat * 16)(*identityMatrix)
            mvp = m3dMultiply(proj_matrix , mv_matrix )

            glUniformMatrix4fv(uniform.mvp, 1, GL_FALSE, mvp)

            glBindTexture(GL_TEXTURE_2D, textures[i]);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        glutSwapBuffers()

    def reshape(self, width, height):
        self.width = width
        self.height = height

    def keyboard(self, key, x, y ):
        global fullscreen

        print ('key:' , key)
        if key == b'\x1b': # ESC
            sys.exit()

        elif key == b'f' or key == b'F': #fullscreen toggle

            if (fullscreen == True):
                glutReshapeWindow(512, 512)
                glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
                fullscreen = False
            else:
                glutFullScreen()
                fullscreen = True

        print('done')

    def init(self):
        pass

    def timer(self, blah):

        glutPostRedisplay()
        glutTimerFunc( int(1/60), self.timer, 0)
        time.sleep(1/60.0)


if __name__ == '__main__':
    start = time.time()

    glutInit()
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    glutInitWindowSize(512, 512)

    w1 = glutCreateWindow('OpenGL SuperBible - Tunnel')
    glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))

    fullscreen = False
    many_cubes = False
    #glutFullScreen()

    scene = Scene(512,512)
    glutReshapeFunc(scene.reshape)
    glutDisplayFunc(scene.display)
    glutKeyboardFunc(scene.keyboard)

    glutIdleFunc(scene.display)
    #glutTimerFunc( int(1/60), scene.timer, 0)

    scene.init()

    glutMainLoop()

The program is supposed to appear like:

enter image description here

Update: With the amazing code and insights by Rabbid76 the output is now rendering correctly. An animated gif of the output is below.

Dependency files: brick.ktx , ceiling.ktx , floor.ktx , and in 'shared' folder ktxloader.py , sbmath.py


Solution

  • You've to use the global statement to set the variables tex_wall, tex_ceiling, tex_floor in global namespace, in the constructor of Scene:

    class Scene:
    
        def __init__(self, width, height):
            global tex_wall, tex_ceiling, tex_floor
    
            # [...]
    
            tex_wall = ktxobj.ktx_load("brick.ktx")
            tex_ceiling = ktxobj.ktx_load("ceiling.ktx")
            tex_floor = ktxobj.ktx_load("floor.ktx")
    
            # [...]
    

    Further there are some issues when you set the model matrices. The y-scale has to be 10.0, to scale the walls to proper height and width:

    m3dScaleMatrix44(S, 30.0, 1.0, 1.0)
    m3dScaleMatrix44(S, 300.0, 10.0, 1.0)

    The translation has to be done before the rotation around the z-axis, because walls, floor and ceiling should be rotated displaced. Scaling has to be done first:

    mv_matrix = m3dMultiply(T, m3dMultiply(RZ, m3dMultiply(S, RY)))
    mv_matrix = m3dMultiply(RZ, m3dMultiply(T, m3dMultiply(RY, S)))


    In "ktxloader" module ptr is used, when the data bytes are read from the bitmap:

    glTexSubImage2D(GL_TEXTURE_2D, i, 0, 0, width, height, h.glformat, h.gltype, data[ptr:])
    

    So ptr has to be incremented by h.keypairbyte:

    data_start = ptr + h.keypairbytes
    dt = data[data_start:]
    ptr += h.keypairbytes


    The floor and the ceiling are swapped:

    textures = [ tex_wall, tex_floor, tex_wall, tex_ceiling ]
    textures = [ tex_wall, tex_ceiling, tex_wall, tex_floor ]


    Before the texture offset uniform (uniform float offset) is set you've to use % (modulo) operator, because the value in currentTime is top large and doesn't fit in single precision (32 bit) floating point value. Since the texture coordinates are in range [0.0, 1.0], the fraction part of the offset can be calculated by % 1. e.g.:

    glUniform1f(uniform.offset, (currentTime * -0.03) % 1)