Displaying a simple 2D textured rectangle inside a wxGLCanvas using PyOpenGL

opengl, pyopengl, python, textures, wxpython

Solution

Well, the critical mistake I was making was not scaling the float values of my input data to between 0 and 1 - all my pixels were >1 so the quad rendered as white! I had a bit of trouble finding good examples of how to use `wxGLCanvas`es, so here's a simple texture rendered in a `wxGLCanvas` for anyone who might find it useful.

import numpy as np
from scipy.misc import ascent
import OpenGL.GL as gl
import wx
from wx.glcanvas import GLCanvas


class Canvas(GLCanvas):

    def __init__(self, parent):
        """create the canvas """
        super(Canvas, self).__init__(parent)
        self.texture = None

        # execute self.onPaint whenever the parent frame is repainted
        wx.EVT_PAINT(self, self.onPaint)

    def initTexture(self):
        """init the texture - this has to happen after an OpenGL context
        has been created
        """

        # make the OpenGL context associated with this canvas the current one
        self.SetCurrent()

        data = np.uint8(np.flipud(ascent()))
        w, h = data.shape

        # generate a texture id, make it current
        self.texture = gl.glGenTextures(1)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)

        # texture mode and parameters controlling wrapping and scaling
        gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE)
        gl.glTexParameterf(
            gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_REPEAT)
        gl.glTexParameterf(
            gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_REPEAT)
        gl.glTexParameterf(
            gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
        gl.glTexParameterf(
            gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)

        # map the image data to the texture. note that if the input
        # type is GL_FLOAT, the values must be in the range [0..1]
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, w, h, 0,
                        gl.GL_LUMINANCE, gl.GL_UNSIGNED_BYTE, data)

    def onPaint(self, event):
        """called when window is repainted """
        # make sure we have a texture to draw
        if not self.texture:
            self.initTexture()
        self.onDraw()

    def onDraw(self):
        """draw function """

        # make the OpenGL context associated with this canvas the current one
        self.SetCurrent()

        # set the viewport and projection
        w, h = self.GetSize()
        gl.glViewport(0, 0, w, h)

        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        gl.glOrtho(0, 1, 0, 1, 0, 1)

        gl.glMatrixMode(gl.GL_MODELVIEW)
        gl.glLoadIdentity()
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        # enable textures, bind to our texture
        gl.glEnable(gl.GL_TEXTURE_2D)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)
        gl.glColor3f(1, 1, 1)

        # draw a quad
        gl.glBegin(gl.GL_QUADS)
        gl.glTexCoord2f(0, 1)
        gl.glVertex2f(0, 1)
        gl.glTexCoord2f(0, 0)
        gl.glVertex2f(0, 0)
        gl.glTexCoord2f(1, 0)
        gl.glVertex2f(1, 0)
        gl.glTexCoord2f(1, 1)
        gl.glVertex2f(1, 1)
        gl.glEnd()

        gl.glDisable(gl.GL_TEXTURE_2D)

        # swap the front and back buffers so that the texture is visible
        self.SwapBuffers()


def run():
    app = wx.App()
    fr = wx.Frame(None, size=(512, 512), title='wxPython texture demo')
    canv = Canvas(fr)
    fr.Show()
    app.MainLoop()

if __name__ == "__main__":
    run()

Problem

I'm trying to write some code that will display a single image as a simple 2D textured rectangle within a `wxGLCanvas` container. Here's a runnable example of what I've got so far: ``` import wx from wxPython.glcanvas import wxGLCanvas from OpenGL.GLU import * from OpenGL.GL import * import numpy as np from scipy.misc import ascent def run(): imarray = np.float32(ascent()) imarray = np.repeat(imarray[..., np.newaxis], 3, axis=2) app = wx.PySimpleApp() frame = wx.Frame(None, title='Simple 2D texture') canvas = myGLCanvas(frame, imarray) frame.Show(True) app.MainLoop() class myGLCanvas(wxGLCanvas): def __init__(self, parent, image): attribList = [wx.glcanvas.WX_GL_DOUBLEBUFFER] wxGLCanvas.__init__(self, parent, -1, attribList=attribList) wx.EVT_PAINT(self, self.OnPaint) self.image = np.float32(image) self.InitProjection() self.InitTexture() pass def OnPaint(self, event): """Called whenever the window gets resized, uncovered etc.""" self.SetCurrent() dc = wx.PaintDC(self) self.OnDraw() self.SwapBuffers() pass def InitProjection(self): """Enable the depth buffer and initialize the viewport and projection matrix""" glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LEQUAL) width = self.image.shape[1] height = self.image.shape[0] glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0, width, height, 0, -1, 1) pass def InitTexture(self): """Initializes the texture from a numpy array""" self.texture = glGenTextures(1) glEnable(GL_TEXTURE_2D) glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glBindTexture(GL_TEXTURE_2D, self.texture) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, self.image.shape[1], self.image.shape[0], 0, GL_RGB, GL_FLOAT, self.image) glDisable(GL_TEXTURE_2D) pass def OnDraw(self): """Draw a textured rectangle slightly smaller than the viewport""" glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glClearColor(0., 0., 0., 0.) glClearDepth(1) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glBindTexture(GL_TEXTURE_2D, self.texture) glEnable(GL_TEXTURE_2D) # draw a textured quad, shrink it a bit so the edge is clear glBegin(GL_QUADS) glTexCoord2f(0., 0.) glVertex3f(-0.9, -0.9, 0.) glTexCoord2f(1., 0.) glVertex3f(0.9, -0.9, 0.) glTexCoord2f(1., 1.) glVertex3f(0.9, 0.9, 0.) glTexCoord2f(0., 1.) glVertex3f(-0.9, 0.9, 0.) glEnd() glDisable(GL_TEXTURE_2D) pass if __name__ == '__main__': run() ``` This draws a rectangle successfully, but fails to texture it - all I see is a white rectangle. Any idea what I'm doing wrong?

Original source