pythonopenglmultitexturing

Multitexturing not working as expected


Given the below snippet:

import os
import textwrap

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from PIL import Image

from OpenGL.GL.ARB.multitexture import *
from OpenGL.extensions import alternate


def get_opengl_info():
    return textwrap.dedent("""\
        Vendor: {0}
        Renderer: {1}
        OpenGL Version: {2}
        Shader Version: {3}
        {4:*^80}
        Num Extensions: {5}
        {6}
    """).format(
        glGetString(GL_VENDOR).decode("utf-8"),
        glGetString(GL_RENDERER).decode("utf-8"),
        glGetString(GL_VERSION).decode("utf-8"),
        glGetString(GL_SHADING_LANGUAGE_VERSION).decode("utf-8"),
        "OPENGL EXTENSIONS",
        glGetIntegerv(GL_NUM_EXTENSIONS),
        "\n".join(glGetString(GL_EXTENSIONS).decode("utf-8").split())
    )


def create_gl_texture(use_active_texture, channel, width, height, pbits):
    id_texture = glGenTextures(1)
    if use_active_texture:
        glActiveTexture(GL_TEXTURE0 + channel)
    glBindTexture(GL_TEXTURE_2D, id_texture)
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
    glTexImage2D(GL_TEXTURE_2D, 0, 3, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, pbits)
    glTexParameter(
        GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameter(
        GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
    glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    glGenerateMipmap(GL_TEXTURE_2D)

    return id_texture


def load_texture(use_active_texture, filename, i):
    image = Image.open(filename)
    ix = image.size[0]
    iy = image.size[1]
    pbits = image.convert("RGBA").tobytes("raw", "RGBA")

    id_texture = create_gl_texture(use_active_texture, i, ix, iy, pbits)
    print("Loaded", id_texture)
    return id_texture


X_AXIS = 0.0
Y_AXIS = 0.0
Z_AXIS = 0.0
DIRECTION = 1
id_textures = []


def init_gl(Width, Height):
    global glMultiTexCoord2f, glActiveTexture
    print(get_opengl_info())

    print("Choosing between: ", glMultiTexCoord2f.__name__,
          glMultiTexCoord2fARB.__name__)
    print("Choosing between: ", glActiveTexture.__name__,
          glActiveTextureARB.__name__)

    glMultiTexCoord2f = alternate(
        glMultiTexCoord2f,
        glMultiTexCoord2fARB
    )
    glActiveTexture = alternate(
        glActiveTexture,
        glActiveTextureARB,
    )

    print("Selected: ", glMultiTexCoord2f.__name__)
    print("Selected: ", glActiveTexture.__name__)

    if not glMultiTexCoord2f:
        print('Multitexture not supported!')
        sys.exit(1)

    glClearColor(0.0, 0.0, 0.0, 0.0)
    glClearDepth(1.0)
    glDepthFunc(GL_LESS)
    glEnable(GL_DEPTH_TEST)
    glShadeModel(GL_SMOOTH)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(Width) / float(Height), 0.1, 100.0)
    glMatrixMode(GL_MODELVIEW)
    glEnable(GL_TEXTURE_2D)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)


def keyPressed(*args):
    if args[0] == "\033":
        sys.exit()

# Method0: Using glBindTexture + glTexCoord2f per face
def draw_method_0():
    global X_AXIS, Y_AXIS, Z_AXIS
    global DIRECTION
    global id_textures
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glLoadIdentity()
    glTranslatef(0.0, 0.0, -6.0)

    glRotatef(X_AXIS, 1.0, 0.0, 0.0)
    glRotatef(Y_AXIS, 0.0, 1.0, 0.0)
    glRotatef(Z_AXIS, 0.0, 0.0, 1.0)

    glBindTexture(GL_TEXTURE_2D, id_textures[0])
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(1.0,  1.0,  1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glEnd()

    glBindTexture(GL_TEXTURE_2D, id_textures[1])
    glBegin(GL_QUADS)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(1.0, -1.0, -1.0)
    glEnd()

    glBindTexture(GL_TEXTURE_2D, id_textures[2])
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(1.0,  1.0,  1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glEnd()

    glBindTexture(GL_TEXTURE_2D, id_textures[3])
    glBegin(GL_QUADS)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(1.0, -1.0, -1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glEnd()

    glBindTexture(GL_TEXTURE_2D, id_textures[4])
    glBegin(GL_QUADS)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(1.0,  1.0,  1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glEnd()

    glBindTexture(GL_TEXTURE_2D, id_textures[5])
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glEnd()

    X_AXIS = X_AXIS - 0.030
    Z_AXIS = Z_AXIS - 0.030

    glutSwapBuffers()


# Method1: Using glActiveTexture+glBindTexture+glMultiTexCoord2f per face
def draw_method_1():
    global X_AXIS, Y_AXIS, Z_AXIS
    global DIRECTION
    global id_textures
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glLoadIdentity()
    glTranslatef(0.0, 0.0, -6.0)

    glRotatef(X_AXIS, 1.0, 0.0, 0.0)
    glRotatef(Y_AXIS, 0.0, 1.0, 0.0)
    glRotatef(Z_AXIS, 0.0, 0.0, 1.0)

    glActiveTexture(GL_TEXTURE0 + 0)
    glBindTexture(GL_TEXTURE_2D, id_textures[0])
    glBegin(GL_QUADS)
    glMultiTexCoord2f(GL_TEXTURE0 + 0, 0.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 0, 1.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 0, 1.0, 1.0)
    glVertex3f(1.0,  1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 0, 0.0, 1.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 1)
    glBindTexture(GL_TEXTURE_2D, id_textures[1])
    glBegin(GL_QUADS)
    glMultiTexCoord2f(GL_TEXTURE0 + 1, 1.0, 0.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 1, 1.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 1, 0.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 1, 0.0, 0.0)
    glVertex3f(1.0, -1.0, -1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 2)
    glBindTexture(GL_TEXTURE_2D, id_textures[2])
    glBegin(GL_QUADS)
    glMultiTexCoord2f(GL_TEXTURE0 + 2, 0.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 2, 0.0, 0.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 2, 1.0, 0.0)
    glVertex3f(1.0,  1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 2, 1.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 3)
    glBindTexture(GL_TEXTURE_2D, id_textures[3])
    glBegin(GL_QUADS)
    glMultiTexCoord2f(GL_TEXTURE0 + 3, 1.0, 1.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 3, 0.0, 1.0)
    glVertex3f(1.0, -1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 3, 0.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 3, 1.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 4)
    glBindTexture(GL_TEXTURE_2D, id_textures[4])
    glBegin(GL_QUADS)
    glMultiTexCoord2f(GL_TEXTURE0 + 4, 1.0, 0.0)
    glVertex3f(1.0, -1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 4, 1.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 4, 0.0, 1.0)
    glVertex3f(1.0,  1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 4, 0.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 5)
    glBindTexture(GL_TEXTURE_2D, id_textures[5])
    glBegin(GL_QUADS)
    glMultiTexCoord2f(GL_TEXTURE0 + 5, 0.0, 0.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 5, 1.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 5, 1.0, 1.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glMultiTexCoord2f(GL_TEXTURE0 + 5, 0.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glEnd()

    X_AXIS = X_AXIS - 0.030
    Z_AXIS = Z_AXIS - 0.030

    glutSwapBuffers()

# Method2: Using glActiveTexture+glBindTexture+glTexCoord2f per face
def draw_method_2():
    global X_AXIS, Y_AXIS, Z_AXIS
    global DIRECTION
    global id_textures
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glLoadIdentity()
    glTranslatef(0.0, 0.0, -6.0)

    glRotatef(X_AXIS, 1.0, 0.0, 0.0)
    glRotatef(Y_AXIS, 0.0, 1.0, 0.0)
    glRotatef(Z_AXIS, 0.0, 0.0, 1.0)

    glActiveTexture(GL_TEXTURE0 + 0)
    glBindTexture(GL_TEXTURE_2D, id_textures[0])
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(1.0,  1.0,  1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 1)
    glBindTexture(GL_TEXTURE_2D, id_textures[1])
    glBegin(GL_QUADS)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(1.0, -1.0, -1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 2)
    glBindTexture(GL_TEXTURE_2D, id_textures[2])
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(1.0,  1.0,  1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 3)
    glBindTexture(GL_TEXTURE_2D, id_textures[3])
    glBegin(GL_QUADS)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(1.0, -1.0, -1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 4)
    glBindTexture(GL_TEXTURE_2D, id_textures[4])
    glBegin(GL_QUADS)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(1.0,  1.0, -1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(1.0,  1.0,  1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(1.0, -1.0,  1.0)
    glEnd()

    glActiveTexture(GL_TEXTURE0 + 5)
    glBindTexture(GL_TEXTURE_2D, id_textures[5])
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 0.0)
    glVertex3f(-1.0, -1.0,  1.0)
    glTexCoord2f(1.0, 1.0)
    glVertex3f(-1.0,  1.0,  1.0)
    glTexCoord2f(0.0, 1.0)
    glVertex3f(-1.0,  1.0, -1.0)
    glEnd()

    X_AXIS = X_AXIS - 0.030
    Z_AXIS = Z_AXIS - 0.030

    glutSwapBuffers()

def load_textures(use_active_texture):
    global id_textures

    id_textures.append(load_texture(use_active_texture, "tex00.jpg", 0))
    id_textures.append(load_texture(use_active_texture, "tex01.jpg", 1))
    id_textures.append(load_texture(use_active_texture, "tex02.jpg", 2))
    id_textures.append(load_texture(use_active_texture, "tex03.jpg", 3))
    id_textures.append(load_texture(use_active_texture, "tex04.jpg", 4))
    id_textures.append(load_texture(use_active_texture, "tex05.jpg", 5))


def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
    glutInitWindowSize(640, 480)
    glutInitWindowPosition(200, 200)

    glutCreateWindow(b'OpenGL Python Textured Cube')

    init_gl(640, 480)

    current_method = 2
    draw_methods = {
        0: {"f": draw_method_0, "use_active_texture": False},
        1: {"f": draw_method_1, "use_active_texture": True},
        2: {"f": draw_method_2, "use_active_texture": True}
    }
    draw = draw_methods[current_method]["f"]
    load_textures(draw_methods[current_method]["use_active_texture"])
    glutDisplayFunc(draw)
    glutIdleFunc(draw)
    glutKeyboardFunc(keyPressed)

    glutMainLoop()

if __name__ == "__main__":
    main()

If i set current_method=0 I'll get the output I expect, all cube faces will use a different texture (as intended):

enter image description here

If i set current_method=1 only one face will be textured properly, that's a wrong output:

enter image description here

If i set current_method=2 all faces will be textured with the same texture[0], which is also a wrong output:

enter image description here

I'd like to understand why methods 1 & 2 are not giving the same output as method 0. I know when using shaders using glActiveTexture properly becomes trivial but I'd like to understand what's wrong when using these methods on the old fixed pipeline.


Solution

  • I think the core issue here is a misunderstanding of how multitexturing works.

    Multitexturing applies multiple textures at the same time to the same primitives. You want to apply different textures, one texture at a time, to different primitives. It should be clear from this that multitexturing does not do what you want, so there is no point in using it.

    How multitexturing works

    With multitexturing, multiple texture units combine their results using blending blending functions.

    texture unit 0 --> +---------+
                       |  blend  | --\
    texture unit 1 --> +---------+    \--> +---------+
                                           |  blend  | --> etc.
    texture unit 2 ----------------------> +---------+
    

    The blending function for each stage is set by glTexEnv and can be something like GL_MODULATE, GL_DECAL, or GL_ADD. This is how lightmaps were combined with textures back in the day: one texture would have the diffuse texture, and one texture would have the lightmap texture.

    Again, this is completely different from the effect which you want to achieve, so multitexturing has no point in your application.

    How glActiveTexture works

    glActiveTexture doesn't change which texture unit is drawn to the screen. By default, only texture unit #0 will draw to the screen.

    glActiveTexture just allows you to bind textures to other texture units. However, since texture unit #1 isn't being used, it doesn't matter what texture is bound to unit #1 or what the coordinates are. So you should always be using glActiveTexture(GL_TEXTURE0), since you always want to change texture unit #0.

    Solutions

    So, you have working code that doesn't use multitexturing. Great! You're done. You don't get bonus points for using more OpenGL features.

    Alternatively, you could draw the entire cube in a single draw call if you use a 2D array texture. You simply load your existing textures as planes in a 2D array texture and use 3D texture coordinates instead of 2D texture coordinates. Again, no multitexturing required.