OpenGL textures appear just black

Alexander Müller picture Alexander Müller · Nov 8, 2010 · Viewed 7.8k times · Source

I am trying to apply a texture to a quad, but I only get a black box instead of the texture. I am using DevIL to load images from files and OpenGL does the rest.

Here is what I am doing so far:

The following class abstracts the DevIL representation for an image.

#include "Image.h"

Image::Image()
{
    ilGenImages(1, &this->imageId);
}

Image::~Image()
{
    ilDeleteImages(1, &this->imageId);
}

ILint Image::getWidth()
{
    return this->width;
}

ILint Image::getHeight()
{
    return this->height;
}

ILint Image::getDepth()
{
    return this->depth;
}

ILint Image::getBpp()
{
    return this->bpp;
}

ILint Image::getFormat()
{
    return this->format;
}

ILubyte* Image::getData()
{
    return ilGetData();
}

bool Image::loadFromFile(wchar_t *filename)
{
    // Load the image from file.
    ILboolean retval = ilLoadImage(filename);
    if (!retval) {
        ILenum error;
        while ((error = ilGetError()) != IL_NO_ERROR) {
            wcout << error << L" " << iluErrorString(error);
        }
        return false;
    }

    this->width = ilGetInteger(IL_IMAGE_WIDTH);
    this->height = ilGetInteger(IL_IMAGE_HEIGHT);
    this->depth = ilGetInteger(IL_IMAGE_DEPTH);
    this->bpp = ilGetInteger(IL_IMAGE_BPP);
    this->format = ilGetInteger(IL_IMAGE_FORMAT);

    return true;
}

bool Image::convert()
{
    ILboolean retval = ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);
    if (!retval) {
        ILenum error;
        while ((error = ilGetError()) != IL_NO_ERROR) {
            wcout << error << L" " << iluErrorString(error);
        }
        return false;
    }
    return true;
}

bool Image::scale(ILint width, ILint height, ILint depth)
{
    ILboolean retval = iluScale(width, height, depth);
    if (!retval) {
        ILenum error;
        while ((error = ilGetError()) != IL_NO_ERROR) {
            wcout << error << L" " << iluErrorString(error);
        }
        return false;
    }
    return true;
}

void Image::bind()
{
    ilBindImage(this->imageId);
}

This class abstracts the texture representation for OpenGL.

#include "Texture.h"

Texture::Texture(int width, int height)
{
    glGenTextures(1, &this->textureId);

    this->width = width;
    this->height = height;
}

int Texture::getWidth()
{
    return this->width;
}

int Texture::getHeight()
{
    return this->height;
}

void Texture::initFilter()
{
    // We will use linear interpolation for magnification filter.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // We will use linear interpolation for minifying filter.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

}

void Texture::unpack()
{
    glPixelStoref(GL_UNPACK_ALIGNMENT, 1);
}

void Texture::bind()
{
    glBindTexture(GL_TEXTURE_2D, this->textureId);
}

Texture::~Texture()
{
    glDeleteTextures(1, &this->textureId);
}

The following class contains the texture loading process.

#include "TextureLoader.h"

void TextureLoader::initialize()
{
    if (ilGetInteger(IL_VERSION_NUM) < IL_VERSION) {
        debug("Wrong DevIL version detected.");
        return;
    }

    ilInit();
    ilutRenderer(ILUT_OPENGL);
}

Texture* TextureLoader::createTexture(wchar_t *filename, Color *color)
{
    // Generate some space for an image and bind it.
    Image *image = new Image();
    image->bind();

    bool retval = image->loadFromFile(filename);
    if (!retval) {
        debug("Could not load image from file.");
        return 0;
    }

    retval = image->convert();
    if (!retval) {
        debug("Could not convert image from RGBA to unsigned byte");
    }

    int pWidth = getNextPowerOfTwo(image->getWidth());
    int pHeight = getNextPowerOfTwo(image->getHeight());
    int size = pWidth * pHeight;

    retval = image->scale(pWidth, pHeight, image->getDepth());
    if (!retval) {
        debug("Could not scale image from (w: %i, h: %i) to (w: %i, h: %i) with depth %i.", image->getWidth(), image->getHeight(), pWidth, pHeight, image->getDepth());
        return 0;
    }

    // Generate some space for a texture and bind it.
    Texture *texture = new Texture(image->getWidth(), image->getHeight());
    texture->bind();

    // Set the interpolation filters.
    texture->initFilter();

    // Unpack pixels.
    texture->unpack();

    ILubyte *imageData = image->getData();

    TextureLoader::setColorKey(imageData, size, new Color(0, 0, 0));
    TextureLoader::colorize(imageData, size, new Color(255, 0, 0));

    debug("bpp: %i", image->getBpp());
    debug("width: %i", image->getWidth());
    debug("height: %i", image->getHeight());
    debug("format: %i", image->getFormat());

    // Map image data to texture data.
    glTexImage2D(GL_TEXTURE_2D, 0, image->getBpp(), image->getWidth(), image->getHeight(), 0, image->getFormat(), GL_UNSIGNED_BYTE, imageData);

    delete image;

    return texture;
}

void TextureLoader::setColorKey(ILubyte *imageData, int size, Color *color)
{
    for (int i = 0; i < size * 4; i += 4)
    {
        if (imageData[i] == color->r && imageData[i + 1] == color->g && imageData[i + 2] == color->b)
        {
            imageData[i + 3] = 0;
        }
    }
}

void TextureLoader::colorize(ILubyte *imageData, int size, Color *color)
{
    for (int i = 0; i < size * 4; i += 4)
    {
        int rr = (int(imageData[i]) * int(color->r)) >> 8;
        int rg = (int(imageData[i + 1]) * int(color->g)) >> 8;
        int rb = (int(imageData[i + 2]) * int(color->b)) >> 8;
        int fak = int(imageData[i]) * 5 - 4 * 256 - 138;

        if (fak > 0)
        {
            rr += fak;
            rg += fak;
            rb += fak;
        }

        rr = rr < 255 ? rr : 255;
        rg = rg < 255 ? rg : 255;
        rb = rb < 255 ? rb : 255;

        imageData[i] = rr > 0 ? (GLubyte) rr : 1;
        imageData[i + 1] = rg > 0 ? (GLubyte) rg : 1;
        imageData[i + 2] = rb > 0 ? (GLubyte) rb : 1;
    }
}

The last class does the drawing.

#include "Texturizer.h"

void Texturizer::draw(Texture *texture, float x, float y, float angle)
{
    // Enable texturing.
    glEnable(GL_TEXTURE_2D);

    // Bind the texture for drawing.
    texture->bind();

    // Enable alpha blending.
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    int width = texture->getWidth();
    int height = texture->getHeight();

    // Create centered dimension vectors.
    b2Vec2 vertices[4];
    vertices[0] = 0.5f * b2Vec2(- width, - height);
    vertices[1] = 0.5f * b2Vec2(+ width, - height);
    vertices[2] = 0.5f * b2Vec2(+ width, + height);
    vertices[3] = 0.5f * b2Vec2(- width, + height);

    b2Mat22 matrix = b2Mat22();
    matrix.Set(angle);

    glBegin(GL_QUADS);
    for (int i = 0; i < 4; i++) {
        float texCoordX = i == 0 || i == 3 ? 0.0f : 1.0f;
        float texCoordY = i < 2 ? 0.0f : 1.0f;
        glTexCoord2f(texCoordX, texCoordY);

        // Rotate and move vectors.
        b2Vec2 vector = b2Mul(matrix, vertices[i]) + meter2pixel(b2Vec2(x, y));
        glVertex2f(vector.x, vector.y);
    }
    glEnd();

    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
}

Last but not least, the following method initializes OpenGL (and triggers the initialization of DevIL):

void GraphicsEngine::initialize(int argc, char **argv)
{
    // Initialize the window.
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowSize(WIDTH, HEIGHT);

    // Set shading model.
    glShadeModel(GL_SMOOTH);

    // Create the window.
    this->mainWindow = glutCreateWindow(TITLE);

    // Set keyboard methods.
    glutKeyboardFunc(&onKeyDownCallback);
    glutKeyboardUpFunc(&onKeyUpCallback);
    glutSpecialFunc(&onSpecialKeyDownCallback);
    glutSpecialUpFunc(&onSpecialKeyUpCallback);

    // Set mouse callbacks.
    glutMouseFunc(&onMouseButtonCallback);
#ifdef FREEGLUT
    glutMouseWheelFunc(&onMouseWheelCallback);
#endif
    glutMotionFunc(&onMouseMotionCallback);
    glutPassiveMotionFunc(&onMousePassiveMotionCallback);

    // Set display callbacks.
    glutDisplayFunc(&onDrawCallback);
    glutReshapeFunc(&onReshapeCallback);

    // Set a timer to control the frame rate.
    glutTimerFunc(FRAME_PERIOD, onTimerTickCallback, 0);

    // Set clear color.
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    Camera::getInstance()->subscribe(this);

    // Initialize texture loader.
    TextureLoader::initialize();
}

The image I am using already worked for another OpenGL/DevIL project, so it cannot be the source of the problem.

The texture is created inside of every class which represents a world object (it's a game...). The character is called Blobby and here are the most important parts of its implementation:

#include "Blobby.h"

Blobby::Blobby()
{
    this->isJumping = false;
    this->isRotating = false;
    this->isWalking = false;
    this->isDucking = false;
    this->isStandingUp = false;
    this->isOnGround = false;
    this->isTouchingWall = false;
    this->angle = 0;
    this->direction = DIRECTION_UNKNOWN;
    this->wallDirection = DIRECTION_UNKNOWN;

    // Create a red blobby texture.
    this->texture = TextureLoader::createTexture(L"D:/01.bmp", new Color(255, 0, 0));

    ContactListener::getInstance()->subscribe(this);
}

void Blobby::draw()
{
    GraphicsEngine::drawString(35, 40, "isOnGround     = %s", this->isOnGround ? "true" : "false");
    GraphicsEngine::drawString(35, 55, "inJumping      = %s", this->isJumping ? "true" : "false");
    GraphicsEngine::drawString(35, 70, "isRotating     = %s", this->isRotating ? "true" : "false");
    GraphicsEngine::drawString(35, 85, "isTouchingWall = %s (%i)", this->isTouchingWall ? "true" : "false", this->wallDirection);

    Texturizer::draw(this->texture, this->getBody(0)->GetPosition().x, this->getBody(0)->GetPosition().y, this->getBody(0)->GetAngle());

    AbstractEntity::draw(); // draws debug information... not important
}

The OpenGL timer callback calls a step method which ends here:

void Simulator::step()
{
    // Update physics.
    this->gameWorld->step();

    b2Vec2 p = Camera::convertWorldToScreen(meter2pixel(this->cameraBlobby->getBody(0)->GetPosition().x), 300.0f);
    if (p.x < 300) {
        Camera::getInstance()->setViewCenter(Camera::convertScreenToWorld(400 - (300 - int(p.x)), 300));
    } else if (p.x > 500) {
        Camera::getInstance()->setViewCenter(Camera::convertScreenToWorld(400 + (int(p.x) - 500), 300));
    }


    for (unsigned int i = 0; i < this->gameWorld->getEntityCount(); i++) {
        IEntity *entity = this->gameWorld->getEntity(i);
        entity->draw();
    }
}

IEntity is a pure virtual class (i.e. interface), AbstractEntity implements this interface and adds global methods. Blobby inherits from AbstractEntity and adds routines which are special for this world object.

EDIT: I have uploaded a more recent version of the code (the whole project incl. dependencies) here: http://upload.visusnet.de/uploads/BlobbyWarriors-rev19.zip (~9.5 MB)

Answer

slacker picture slacker · Nov 8, 2010

I'm not familiar with DevIL, but... are you providing the right diffuse color for your vertices? If lighting is enabled, are there some lights pointing on the quad? Does the camera look at the front side of the quad?

EDIT:

You got a bug in the code, but not the one you posted here, but in the version in the archive you linked.

You call glColor3i(255, 255, 255), and it sets the diffuse color to (very nearly) black as expected. glColor3i does not accept the color values in the target (calculation or framebuffer) range. The possible values are scaled to the entire range of the int type. This means the maximum value (1.0 in float) is represented by MAX_INT (2,147,483,647) , 0 is 0, and -1.0 is MIN_INT (-2,147,483,648). The 255 value you provided represents about 0.000000118, which is very nearly zero.

I believe you intended one of the following (completely equivalent) forms:

glColor3f(1.0, 1.0, 1.0), glColor3ub(255, 255, 255),

glColor3i(2147483647, 2147483647, 2147483647).