Saturday, June 12, 2010

GL_TEXTURE_2D

GL_TEXTURE_2D

In this part, we continue with a convenient and powerful method for 2D OpenGL, 2D texture.
The strength of using OpenGL 2D texture over glDrawPixels is that linear filtering can be selected.
Moreover, the task is systematic and not tedious.

I, however, do not know if using RGBA is better in terms of performance or not, but I used it in this example.
Thus, we have to modify a way to create a texture image.  The A element can be set to 255, as shown below.

texImage[nColorBytes*(h*m_nWidth + w) + 3] = (GLubyte) 255;

To initialize 2D texture with linear filtering, the following code can be used to make a texImage an OpenGL texture.

void GLTexture2D::initializeGL()
{
    // Basic initialization
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_FLAT);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // Texture initialization
    glGenTextures(1, &textureName);
    glBindTexture(GL_TEXTURE_2D, textureName);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    // GL_NEAREST is another choice
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_nWidth, m_nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texImage);
}



To draw a 2D image as a texture, we have to map image coordinates to texture coordinates when we draw GL_QUADS.
In the following function, we map the texture created in initializeGL to rectangular coordinates -1.0 to 1.0.

void GLTexture2D::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glBindTexture(GL_TEXTURE_2D, textureName);

    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0);    glVertex3f(-1.0, -1.0, 0.0);
        glTexCoord2f(0.0, 1.0);    glVertex3f(-1.0,  1.0, 0.0);
        glTexCoord2f(1.0, 1.0);    glVertex3f( 1.0,  1.0, 0.0);
        glTexCoord2f(1.0, 0.0);    glVertex3f( 1.0, -1.0, 0.0);
    glEnd();
    glDisable(GL_TEXTURE_2D);
}


===================================

An example project for Eclipse CDT / Qt on Linux is stored at a public folder here.
The project file is valid for Eclipse CDT on Linux with Qt integration (Ubuntu 9.04 Juanty), but the main part should be applicable to most C++ environment.


Pinyo Taeprasartsit
Octobor 2009

glDrawPixels + Qt (Part 3)

glDrawPixels + Qt (Part 3)

In this part, we will discuss the use of a pixel buffer in OpenGL.  A pixel buffer is an advanced feature, which is aimed at increasing screen draw rate.
For OpenGL 2.1, this pixel buffer is a part of a standard extension and not in OpenGL core. 

To use this extension, things become much more complicated, but can still be explained step by step, as follows.
  1. We need to include <GL/glew.h> before any other OpenGL context.  In Qt, we can normally include it in a header file before we include any QGLWidget.
    For your information, this extension is OpenGL Extension Wrangler Library (GLEW).

  2. Since GLEW is a library, we have to declare its use in a .pro file.  Open a .pro file and add a line

    LIBS    += -lGLEW


    Your project can now link to this library properly.

  3. Initialize GLEW before any other OpenGL work.  This should be done in the initializeGL method of QGLWidget.
    Initializing GLEW, however, may fail, especially if we try to initialize it for the second time.
    Thus, it is recommended to prepare for initialization error, as shown below.

    GLenum initStatus = glewInit();
    if (initStatus != GLEW_OK) {
        cout << "Cannot initialize glew." << endl;
        exit(1);
    } else
        cout << "glew is successfully initialized." << endl;


  4. Bind data to a buffer.  We need to provide a non-local GLuint to be employed as a buffer ID.  This may be a class-member or global variable.
    Assume that we declare the ID variable as a class member by

    GLuint pixelBuffer;

    Next, bind the buffer to it.  Note that chkImage contains pixel data and must be already filled.
    These patch of code is still in initializeGL.

    glGenBuffers(1, &pixelBuffer);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, 3* m_nWidth * m_nHeight, chkImage,
            GL_STATIC_DRAW);

  5. Draw pixels in paintGL by

    GLubyte* nBuffOffset = 0;
    glDrawPixels(m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE, nBuffOffset);


    Note that instead of providing a pointer to chkImage, we provide an offset in a buffer.  The zero offset means that we are going to use the first byte of the buffer and so on.

    To make this matter more understandable in terms of actual implementation, methods for initialization and painting are shown below.

    void GLImage2_2::initializeGL()
    {
        GLenum initStatus = glewInit();
        if (initStatus != GLEW_OK) {
            cout << "Cannot initialize glew." << endl;
            exit(1);
        } else
            cout << "glew is successfully initialized." << endl;

        // Set coordinate system
        glOrtho(0, m_nWidth, 0, m_nHeight, 0, 1);

        // Basic initialization
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glShadeModel(GL_FLAT);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

        // Using buffer objects
        glGenBuffers(1, &pixelBuffer);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 3* m_nWidth * m_nHeight, chkImage,
                GL_STATIC_DRAW);
    }




    void GLImage2_2::paintGL() {
        glClear(GL_COLOR_BUFFER_BIT);
        glRasterPos2i(0, 0);

        GLubyte* nBuffOffset = 0;
        glDrawPixels(m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE, nBuffOffset);
    }


So, what if we have several buffers?  In that case, we have to prepare more buffer-ID variables, bind them, and populate them. 
At the painting time, we specify our target buffer.  For concreateness, see the code below.

void GLImage2_2::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT);
    glRasterPos2i(0, 0);

    if (target = 0)
        
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer1);
    else
        
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer2);

    GLubyte* nBuffOffset = 0;
    glDrawPixels(m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE, nBuffOffset);
}

===================================

An example Eclipse CDT / Qt project is stored at a public folder here.
The project file is valid for Eclipse CDT on Linux with Qt integration (Ubuntu 9.04 Juanty), but the main part should be applicable to most C++ environment.


Pinyo Taeprasartsit
September 2009

glDrawPixels + Qt (Part 2)

glDrawPixels + Qt (Part 2)

In this part, we are going to use Qt to load an image from a file and display it with QGLWidget by using glDrawPixels.
The process is generally the same, except the order of function calls and the use of Qt to load an image file.

First, take a look at the constructor

GLImage2_1::GLImage2_1(QGLWidget *parent)
    : QGLWidget(parent)
{
    makeImage();
    m_nCurrWidth = m_nWidth;
    m_nCurrHeight = m_nHeight;
}


The function makeImage() is called early because we want to know image dimensions and give a correct size hint to a QGLWidget.
initializeGL is, therefore, leaner.

void GLImage2_1::initializeGL()
{
    // Set coordinate system
    glOrtho(0, m_nWidth, 0, m_nHeight, 0, 1);

    // Basic initialization
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_FLAT);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}


Now, let's get to makeImage(), the most important part of this article.

void GLImage2_1::makeImage() {
    string fileName("/home/pinyo/temp/beach.jpg");

    QImage qImage;
    qImage.load(QString(fileName.c_str()));

    m_nWidth = qImage.width();
    m_nHeight = qImage.height();

    chkImage = new GLubyte[m_nHeight * m_nWidth * 3];

    for (int h = 0; h < m_nHeight; ++h) {
        int nImgH = m_nHeight - h - 1;

        for (int w = 0; w < m_nWidth; ++w) {
            QRgb color = qImage.pixel(w, nImgH);

            chkImage[3* (h *m_nWidth + w) + 0] = (GLubyte) qRed(color);
            chkImage[3*(h*m_nWidth + w) + 1] = (GLubyte) qGreen(color);
            chkImage[3*(h*m_nWidth + w) + 2] = (GLubyte) qBlue(color);
        }
    }

    return;
}

There are a few things we have to pay attention to.
  1. QImage is used and (declared in qimage.h)
  2. We have no need to explicitly supply the image file type to its load function.  Qt first relies on file extension (.jpg, .png, for example).  If it fails or there is no file extension, Qt will make a guess based on file header.
  3. Notice the use of int nImgH = m_nHeight - h - 1;  This is essential because the image coordinate system in OpenGL is different from most computer imaging systems, including Qt.  In Qt the origin is at top left and the y-axis points downward, while in OpenGL, the origin is at bottom left and the y-axis points upward.  Since the x-axes in Qt and OpenGL are the same, there is no need to convert x-coordinates.


========================================

An example Eclipse CDT / Qt project is stored at a public folder here.
The project file is valid for Eclipse CDT on Linux with Qt integration (Ubuntu 9.04 Juanty), but the main part should be applicable to most C++ environment.


Pinyo Taeprasartsit
September 2009

glDrawPixels + Qt (Part 1)

glDrawPixels + Qt (Part 1)

This is an article following GL_POINTS + Qt.  As mentioned earlier, we can use glDrawPixels with glPixelZoom to deal with 2D graphics.
Although it appears to be more complicated than GL_POINTS, glDrawPixels is still another simple method.

The strength of glDrawPixels is its ability to scale an image in a way most people expect.  For example, a pixel in the original scale may occupy more than one pixel in a window, if the window is scaled up.
We can also increase draw rate by using buffer, but Part 1 will not cover that topic.

Now, let's see how to draw an image by using glDrawPixels and glPixelZoom.  Recall that the coordinate system of OpenGL may be different from most imaging systems.  OpenGL set the origin at the bottom left by default.

The major difference between GL_POINTS and glDrawPixels is that GL_POINTS is based on a call list, while glDrawPixels is based on an array.
This array is a flatten 3D or 4D array.  Namely, RGB(A) data is kept together in a 1D array.  We have to tell OpenGL a color model we want to use.

Assume that we want to use the RGB color model and the image is of size 128x128 pixels.  Therefore, we have to create a 1D array of size 128x128x3.  RGB values of one pixels are packed at contiguous array cells, as shown below.

/// Build a checker board, each grid cell is of size 16 x 16 pixels.
///
0x10 is a base-16 number equal to 16.
void GLImage::makeImage() {
    chkImage = new GLubyte[m_nHeight * m_nWidth * 3];

    int c;
    for (int h = 0; h < m_nHeight; ++h)
        for (int w = 0; w < m_nWidth; ++w) {
            c = ( ((h&0x10) == 0)^((w&0x10) == 0) )*255;
            chkImage[3*(h*m_nWidth + w) + 0] = (GLubyte) c;
            chkImage[3*(h*m_nWidth + w) + 1] = (GLubyte) c;
            chkImage[3*(h*m_nWidth + w) + 2] = (GLubyte) c;
        }

    return;
}


The drawing process is super easy.

void GLImage::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT);
    glRasterPos2i(0, 0);
    glDrawPixels(m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE, chkImage);
}

The resize process needs to specify zoom factors for both x- and y-axes.  Note that we may not need to use m_nCurrWidth and m_nCurrHeight, if we do not intend to use them for other proposed.

void GLImage::resizeGL(int width, int height) {
    glViewport(0, 0, width, height);
    m_nCurrWidth = width;
    m_nCurrHeight = height;

    glPixelZoom(m_nCurrWidth / (double) m_nWidth, m_nCurrHeight / (double) m_nHeight);
}


The way for initialization may be a bit more complicated than usual, but it is a pattern we can just follow (somewhat blindly).

void GLImage::initializeGL()
{
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, m_nWidth, 0, m_nHeight, 0, 1);
    glMatrixMode(GL_MODELVIEW);

    glShadeModel(GL_FLAT);
    makeImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}


Notice the use of glShadeModel(GL_FLAT).  glPixelStorei is employed to specify the way we store color data.  The parameter 1 indicates that it is 1-btye alignment.  Basically, there is no restriction of the beginning position of each row with this parameter.  If it is 2, each row must start on even-numbered byte.


========================================

An example Eclipse CDT / Qt project is stored at a public folder here.
The project file is valid for Eclipse CDT on Linux with Qt integration (Ubuntu 9.04 Juanty), but the main part should be applicable to most C++ environment.


Pinyo Taeprasartsit
September 2009