Fixed Function to Modern OpenGL (Part 3 of 4)

John Stone


John Stone is a seasoned software developer with an extensive computer science education and entrepreneurial background and brings a wealth of practical real-world experience working with graphical visualization and virtual reality (VR). As a former technical director of research for a VR laboratory at the University of Texas at Austin, he spent years honing his craft developing VR-based data acquisition systems in OpenGL for scientific experiments and projects.

By John Stone | Monday, February 29, 2016

The code referenced in this blog is available here.

In our last blog, we re-implemented the famous OpenGL triangle in world space and compared and contrasted the fixed-function GLUT program with an equivalent Modern OpenGL program.  We covered how to use Qt's math functions to replace the matrix mode that was removed from Fixed-Function OpenGL and replaced with code you write in your vertex shader.   We talked about the Model, View, Projection idioms and how you can use uniforms to pass these variables (and others) to your shaders programs between draw calls.   Next up, we're going to talk about OpenGL lighting , it's origins and theory and how to implement that theory in your shader programs.

Lighting

Let us now move onto the second program that you all probably wrote when learning OpenGL, rendering the standard cube.  There are a few different varieties of this exercise, specifically, rendering the cube with uniquely colored facets without lighting, rendering the cube with a uniform color with lighting and rendering the cube with lighted color facets. 

The first is accomplished by glDisable(GL_LIGHTING).  The vertex attribute color values use glColor between glBegin/glEnd.  The second is accomplished by glEnable(GL_LIGHTING) and setting color lighting parameters with glMaterial, and the third is accomplished by glEnable(GL_LIGHTING), glEnable(GL_COLOR_MATERIAL) and glColorMaterial(GL_AMBIENT_DIFFUSE) with vertex attribute color values again set by glColor between the glBegin/glEnd pair.   

What you may not know is that there are many different mathematical models that describe lighting a 3D scene:  Phong, Gourad, Blinn-Phong, Cook-Torrance, etc. (the list goes on).  In fixed-function OpenGL the lighting model, built into the graphics chips, was based on Blinn-Phong (alternatively named ADS) equations that model light using ambient, diffuse, specular and emissive terms(https://en.wikipedia.org/wiki/Blinn-Phong_shading_model). ¹  The whole of the fixed-function OpenGL lighting API was concerned with setting constant parameters on the various Blinn-Phong equations.  Now for the bad news, this is all gone now.  It is now up to you to calculate whatever lighting effects you want in your vertex/pixel shaders, perform your own math with your own set of parameters, and then set the resulting color on the pixel by assigning to gl_FragColor.  The good news is that now you can create any kind of lighting model you would like if you were not happy with Blinn-Phong.

What this means from the point of view of porting an existing fixed-function application is that you have to duplicate particular Blinn-Phong equations in your vertex shader based on your fixed-function lighting parameter values in your code.  In our examples, we are going to present how to do this for each of the cube rendering cases described earlier.

Listing 4.1a Fixed-Function OpenGL Cube with Colored Facets

#include <GL/glut.h>
#include <stdlib.h>

void init(void)
{
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    glEnable(GL_DEPTH_TEST);
}

void display(void)
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    static unsigned cnt;

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix ();
   glRotatef(float(cnt%360), 1,0,0);
   glRotatef(45.f, 0,0,1);

   glBegin(GL_QUADS);                // Begin drawing the color cube with 6 quads
      // Top face (y = 1.0f)
      glColor3f(0.0f, 1.0f, 0.0f);     // Green
      glVertex3f( 1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f,  1.0f);
      glVertex3f( 1.0f, 1.0f,  1.0f);

      // Bottom face (y = -1.0f)
      glColor3f(1.0f, 0.5f, 0.0f);     // Orange
      glVertex3f( 1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);

      // Front face  (z = 1.0f)
      glColor3f(1.0f, 0.0f, 0.0f);     // Red
      glVertex3f( 1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f, -1.0f, 1.0f);
      glVertex3f( 1.0f, -1.0f, 1.0f);

      // Back face (z = -1.0f)
      glColor3f(1.0f, 1.0f, 0.0f);     // Yellow
      glVertex3f( 1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f( 1.0f,  1.0f, -1.0f);

      // Left face (x = -1.0f)
      glColor3f(0.0f, 0.0f, 1.0f);     // Blue
      glVertex3f(-1.0f,  1.0f,  1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);

      // Right face (x = 1.0f)
      glColor3f(1.0f, 0.0f, 1.0f);     // Magenta
      glVertex3f(1.0f,  1.0f, -1.0f);
      glVertex3f(1.0f,  1.0f,  1.0f);
      glVertex3f(1.0f, -1.0f,  1.0f);
      glVertex3f(1.0f, -1.0f, -1.0f);
   glEnd();  // End of drawing color-cube

   glPopMatrix ();

   ++cnt;

   glutSwapBuffers();
}

void timeout (int)
{
    glutPostRedisplay();
    glutTimerFunc(1000/60, timeout, 1);
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   if (w <= h)
      glOrtho (-2.f, 2.f, -2.f*h/w, 2.f*h/w, -10.f, 10.f);
   else
      glOrtho (-2.f*w/h, 2.f*w/h, -2.f, 2.f, -10.f, 10.f);
}

void keyboard (unsigned char key, int , int )
{
   switch (key) {
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (640, 480);
   glutInitWindowPosition (50, 50);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutTimerFunc(1000/60, timeout, 1);
   glutMainLoop();
   return 0;
}

Listing 4.1b Modern OpenGL Cube with Colored Facets

#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>

#include <QtGui/QOpenGLWindow>
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLVertexArrayObject>

static QString vertexShader =
        "#version 100\n"
        "\n"
        "attribute vec3 position;\n"
        "attribute vec3 color;\n"
        "\n"
        "uniform mat4 mvp;\n"
        "\n"
        "varying vec3 v_color;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    v_color = color;\n"
        "    gl_Position = mvp * vec4(position, 1);\n"
        "}\n"
        ;

static QString fragmentShader =
        "#version 100\n"
        "\n"
        "varying vec3 v_color;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = vec4(v_color, 1);\n"
        "}\n"
        ;

struct Window : QOpenGLWindow, QOpenGLFunctions
{
    Window() :
        m_vbo(QOpenGLBuffer::VertexBuffer),
        m_ibo(QOpenGLBuffer::IndexBuffer)
    {
    }

    void createShaderProgram()
    {
        if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Vertex, vertexShader)) {
            qDebug() << "Error in vertex shader:" << m_pgm.log();
            exit(1);
        }
        if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Fragment, fragmentShader)) {
            qDebug() << "Error in fragment shader:" << m_pgm.log();
            exit(1);
        }
        if ( !m_pgm.link() ) {
            qDebug() << "Error linking shader program:" << m_pgm.log();
            exit(1);
        }
    }

    void createGeometry()
    {
        // Initialize and bind the VAO that's going to capture all this vertex state
        m_vao.create();
        m_vao.bind();

        // we need 24 vertices, 24 normals, and 24 colors (6 faces, 4 vertices per face)
        // since we can't share normal data at the corners (each corner gets 3 normals)
        // and since we're not using glVertexAttribDivisor (not available in ES 2.0)

         struct Vertex {
             GLfloat position[3],
                     color   [3];
         } attribs[24]= {
            // Top face (y = 1.0f)
            { { 1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f} },     // Green
            { {-1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f} },     // Green,
            { {-1.0f, 1.0f,  1.0f}, {0.0f, 1.0f, 0.0f} },     // Green,
            { { 1.0f, 1.0f,  1.0f}, {0.0f, 1.0f, 0.0f} },     // Green,

            // Bottom face (y = -1.0f)
            { { 1.0f, -1.0f,  1.0f}, {1.0f, 0.5f, 0.0f} },    // Orange
            { {-1.0f, -1.0f,  1.0f}, {1.0f, 0.5f, 0.0f} },    // Orange
            { {-1.0f, -1.0f, -1.0f}, {1.0f, 0.5f, 0.0f} },    // Orange
            { { 1.0f, -1.0f, -1.0f}, {1.0f, 0.5f, 0.0f} },    // Orange

            // Front face  (z = 1.0f)
            { { 1.0f,  1.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red
            { {-1.0f,  1.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red
            { {-1.0f, -1.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red
            { { 1.0f, -1.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red

            // Back face (z = -1.0f)
            { { 1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow
            { {-1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow
            { {-1.0f,  1.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow
            { { 1.0f,  1.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow

            // Left face (x = -1.0f)
            { {-1.0f,  1.0f,  1.0f}, {0.0f, 0.0f, 1.0f} },    // Blue
            { {-1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, 1.0f} },    // Blue
            { {-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, 1.0f} },    // Blue
            { {-1.0f, -1.0f,  1.0f}, {0.0f, 0.0f, 1.0f} },    // Blue

            // Right face (x = 1.0f)
            { {1.0f,  1.0f, -1.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
            { {1.0f,  1.0f,  1.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
            { {1.0f, -1.0f,  1.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
            { {1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
        };

        // Put all the attribute data in a FBO
        m_vbo.create();
        m_vbo.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_vbo.bind();
        m_vbo.allocate(&attribs, sizeof(attribs));

        // Configure the vertex streams for this attribute data layout
        m_pgm.enableAttributeArray("position");
        m_pgm.setAttributeBuffer("position", GL_FLOAT, offsetof(Vertex, position), 3, sizeof(Vertex) );

        m_pgm.enableAttributeArray("color");
        m_pgm.setAttributeBuffer("color",   GL_FLOAT, offsetof(Vertex, color), 3, sizeof(Vertex) );

        // we need 36 indices (6 faces, 2 triangles per face, 3 vertices per triangle)
        struct {
            GLubyte cube[36];
        } indices;
        m_cnt=0; for (GLsizei i=0, v=0; v<6*4; v+=4)
        {
            // first triangle (ccw winding)
            indices.cube[i++] = v + 0;
            indices.cube[i++] = v + 1;
            indices.cube[i++] = v + 2;

             // second triangle (ccw winding)
            indices.cube[i++] = v + 0;
            indices.cube[i++] = v + 2;
            indices.cube[i++] = v + 3;

            m_cnt = i;
        }

        // Put all the index data in a IBO
        m_ibo.create();
        m_ibo.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_ibo.bind();
        m_ibo.allocate(&indices, sizeof(indices));

        // Okay, we've finished setting up the vao
        m_vao.release();
    }

    void initializeGL()
    {
        QOpenGLFunctions::initializeOpenGLFunctions();
        createShaderProgram(); m_pgm.bind();
        createGeometry();
        m_view.setToIdentity();
        glEnable(GL_DEPTH_TEST);
    }

    void resizeGL(int w, int h)
    {
        glViewport(0, 0, w, h);
        m_projection.setToIdentity();
        if (w <= h)
            m_projection.ortho(-2.f, 2.f, -2.f*h/w, 2.f*h/w, -2.f, 2.f);
        else
            m_projection.ortho(-2.f*w/h, 2.f*w/h, -2.f, 2.f, -2.f, 2.f);
        update();
    }

    void paintGL()
    {
        static unsigned cnt;

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        QMatrix4x4 model;
        model.rotate(cnt%360, 1,0,0);
        model.rotate(45, 0,0,1);
        m_pgm.bind();
        m_pgm.setUniformValue("mvp", m_projection * m_view * model);
        m_vao.bind();
        glDrawElements(GL_TRIANGLES, m_cnt, GL_UNSIGNED_BYTE, 0);

        update();
        ++cnt;
    }

    void keyPressEvent(QKeyEvent * ev)
    {
        switch (ev->key()) {
           case Qt::Key_Escape:
              exit(0);
              break;
           default:
              QOpenGLWindow::keyPressEvent(ev);
              break;
        }

    }

    QMatrix4x4 m_projection, m_view;
    QOpenGLShaderProgram     m_pgm;
    QOpenGLVertexArrayObject m_vao;
    QOpenGLBuffer            m_vbo;
    QOpenGLBuffer            m_ibo;
    GLsizei                  m_cnt;
};

int main(int argc, char *argv[])
{
    QGuiApplication a(argc,argv);
    Window w;
    w.setWidth(640); w.setHeight(480);
    w.show();
    return a.exec();
}

In Listing 4.1a and Listing 4.1b we see how vertex colors apply to the pixels making up each face of a cube when glDisable(GL_LIGHTING).  Notice how we have to duplicate the vertex positions in order to assign the three different colors for each vertex (one color for each face in which the vertex participates).  It is important to remember that vertex state is the combination of all attribute values assigned to that index in the parallel vertex attribute arrays.  If you need any one value to change then you must duplicate the others in a new index position along with the change you desire. 

Listing 4.2a Fixed-Function OpenGL Cube Lit with Single Color

#include <GL/glut.h>
#include <stdlib.h>

void init(void)
{
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    GLfloat light_ambient     [] = { 0.0f, 0.0f, 0.0f, 1.0f };  /* default value */
    GLfloat light_diffuse     [] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* default value */
    GLfloat light_specular    [] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* default value */
    GLfloat light_position    [] = { 1.0f, 1.0f, 1.0f, 0.0f };  /* NOT default value */
    GLfloat lightModel_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };  /* default value */
    GLfloat material_ambient  [] = { 0.2f, 0.2f, 0.2f, 1.0f };  /* default value */
    GLfloat material_diffuse  [] = { 0.8f, 0.8f, 0.8f, 1.0f };  /* default value */
    GLfloat material_specular [] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* NOT default value */
    GLfloat material_emission [] = { 0.0f, 0.0f, 0.0f, 1.0f };  /* default value */

    glLightfv (GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv (GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv (GL_LIGHT0, GL_POSITION, light_position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightModel_ambient);
    glMaterialfv(GL_FRONT, GL_AMBIENT, material_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, material_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, material_specular);
    glMaterialfv(GL_FRONT, GL_EMISSION, material_emission);
    glMaterialf(GL_FRONT, GL_SHININESS, 10.0);               /* NOT default value	*/

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);
    glEnable (GL_NORMALIZE);
    glEnable (GL_DEPTH_TEST);

    glClearColor(.5f,.5f,.5f,1.f);
}

void display(void)
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    static unsigned cnt;

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix ();
   glRotatef(float(cnt%360), 1,0,0);
   glRotatef(45.f, 0,0,1);

   glBegin(GL_QUADS);                // Begin drawing the color cube with 6 quads
      // Top face (y = 1.0f)
      glNormal3f( 0.0f, 1.0f,  0.0f);
      glVertex3f( 1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f,  1.0f);
      glVertex3f( 1.0f, 1.0f,  1.0f);

      // Bottom face (y = -1.0f)
      glNormal3f( 0.0f, -1.0f,  0.0f);
      glVertex3f( 1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);

      // Front face  (z = 1.0f)
      glNormal3f( 0.0f,  0.0f, 1.0f);
      glVertex3f( 1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f, -1.0f, 1.0f);
      glVertex3f( 1.0f, -1.0f, 1.0f);

      // Back face (z = -1.0f)
      glNormal3f( 0.0f,  0.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f( 1.0f,  1.0f, -1.0f);

      // Left face (x = -1.0f)
      glNormal3f(-1.0f,  0.0f,  0.0f);
      glVertex3f(-1.0f,  1.0f,  1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);

      // Right face (x = 1.0f)
      glNormal3f(1.0f,  0.0f,  0.0f);
      glVertex3f(1.0f,  1.0f, -1.0f);
      glVertex3f(1.0f,  1.0f,  1.0f);
      glVertex3f(1.0f, -1.0f,  1.0f);
      glVertex3f(1.0f, -1.0f, -1.0f);
   glEnd();  // End of drawing color-cube

   glPopMatrix ();

   ++cnt;

   glutSwapBuffers();
}

void timeout (int)
{
    glutPostRedisplay();
    glutTimerFunc(1000/60, timeout, 1);
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   if (w <= h)
      glOrtho (-2.f, 2.f, -2.f*h/w, 2.f*h/w, -10.f, 10.f);
   else
      glOrtho (-2.f*w/h, 2.f*w/h, -2.f, 2.f, -10.f, 10.f);
}

void keyboard (unsigned char key, int , int )
{
   switch (key) {
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (640, 480);
   glutInitWindowPosition (50, 50);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutTimerFunc(1000/60, timeout, 1);
   glutMainLoop();
   return 0;
}

Listing 4.2b Modern OpenGL Cube Lit with Single Color

#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>

#include <QtGui/QOpenGLWindow>
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLVertexArrayObject>

static QString vertexShader =
        "#version 100\n"
        "\n"
        "attribute vec3 vertexPosition;\n"
        "attribute vec3 vertexNormal;\n"
        "\n"
        "uniform mat4 modelViewMatrix;\n"
        "uniform mat3 normalMatrix;\n"
        "uniform mat4 projectionMatrix;\n"
        "\n"
        "struct LightSource\n"
        "{\n"
        "    vec3 ambient;\n"
        "    vec3 diffuse;\n"
        "    vec3 specular;\n"
        "    vec3 position;\n"
        "};\n"
        "uniform LightSource lightSource;\n"
        "\n"
        "struct LightModel\n"
        "{\n"
        "    vec3 ambient;\n"
        "};\n"
        "uniform LightModel lightModel;\n"
        "\n"
        "struct Material {\n"
        "    vec3  emission;\n"
        "    vec3  ambient;\n"
        "    vec3  diffuse;\n"
        "    vec3  specular;\n"
        "    float shininess;\n"
        "};\n"
        "uniform Material material;\n"
        "\n"
        "varying vec3 v_color;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    vec3 normal     = normalize(normalMatrix * vertexNormal);                       // normal vector              \n"
        "    vec3 position   = vec3(modelViewMatrix * vec4(vertexPosition, 1));              // vertex pos in eye coords   \n"
        "    vec3 halfVector = normalize(lightSource.position + vec3(0,0,1));                // light half vector          \n"
        "    float nDotVP    = dot(normal, normalize(lightSource.position));                 // normal . light direction   \n"
        "    float nDotHV    = max(0.f, dot(normal,  halfVector));                           // normal . light half vector \n"
        "    float pf        = mix(0.f, pow(nDotHV, material.shininess), step(0.f, nDotVP)); // power factor               \n"
        "\n"
        "    vec3 ambient    = lightSource.ambient;\n"
        "    vec3 diffuse    = lightSource.diffuse * nDotVP;\n"
        "    vec3 specular   = lightSource.specular * pf;\n"
        "    vec3 sceneColor = material.emission + material.ambient * lightModel.ambient;\n"
        "\n"
        "    v_color = clamp(sceneColor +                             \n"
        "                    ambient  * material.ambient +            \n"
        "                    diffuse  * material.diffuse +            \n"
        "                    specular * material.specular, 0.f, 1.f );\n"
        "\n"
        "    gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1);\n"
        "}\n"
        ;

static QString fragmentShader =
        "#version 100\n"
        "\n"
        "varying vec3 v_color;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = vec4(v_color, 1);\n"
        "}\n"
        ;

struct Window : QOpenGLWindow, QOpenGLFunctions
{
    Window() :
        m_vbo(QOpenGLBuffer::VertexBuffer),
        m_ibo(QOpenGLBuffer::IndexBuffer)
    {
    }

    void createShaderProgram()
    {
        if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Vertex, vertexShader)) {
            qDebug() << "Error in vertex shader:" << m_pgm.log();
            exit(1);
        }
        if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Fragment, fragmentShader)) {
            qDebug() << "Error in fragment shader:" << m_pgm.log();
            exit(1);
        }
        if ( !m_pgm.link() ) {
            qDebug() << "Error linking shader program:" << m_pgm.log();
            exit(1);
        }
    }

    void createGeometry()
    {
        // Initialize and bind the VAO that's going to capture all this vertex state
        m_vao.create();
        m_vao.bind();

        // we need 24 vertices, 24 normals, and 24 colors (6 faces, 4 vertices per face)
        // since we can't share normal data at the corners (each corner gets 3 normals)
        // and since we're not using glVertexAttribDivisor (not available in ES 2.0)

         struct Vertex {
             GLfloat position[3],
                     normal   [3];
         } attribs[24]= {
            // Top face (y = 1.0f)
            { { 1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f} },
            { {-1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f} },
            { {-1.0f, 1.0f,  1.0f}, {0.0f, 1.0f, 0.0f} },
            { { 1.0f, 1.0f,  1.0f}, {0.0f, 1.0f, 0.0f} },

            // Bottom face (y = -1.0f)
            { { 1.0f, -1.0f,  1.0f}, {0.0f, -1.0f, 0.0f} },
            { {-1.0f, -1.0f,  1.0f}, {0.0f, -1.0f, 0.0f} },
            { {-1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f} },
            { { 1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f} },

            // Front face  (z = 1.0f)
            { { 1.0f,  1.0f, 1.0f}, {0.0f, 0.0f, 1.0f} },
            { {-1.0f,  1.0f, 1.0f}, {0.0f, 0.0f, 1.0f} },
            { {-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f} },
            { { 1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f} },

            // Back face (z = -1.0f)
            { { 1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f} },
            { {-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f} },
            { {-1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, -1.0f} },
            { { 1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, -1.0f} },

            // Left face (x = -1.0f)
            { {-1.0f,  1.0f,  1.0f}, {-1.0f, 0.0f, 0.0f} },
            { {-1.0f,  1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f} },
            { {-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f} },
            { {-1.0f, -1.0f,  1.0f}, {-1.0f, 0.0f, 0.0f} },

            // Right face (x = 1.0f)
            { {1.0f,  1.0f, -1.0f}, {1.0f, 0.0f, 0.0f} },
            { {1.0f,  1.0f,  1.0f}, {1.0f, 0.0f, 0.0f} },
            { {1.0f, -1.0f,  1.0f}, {1.0f, 0.0f, 0.0f} },
            { {1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 0.0f} },
        };

        // Put all the attribute data in a FBO
        m_vbo.create();
        m_vbo.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_vbo.bind();
        m_vbo.allocate(&attribs, sizeof(attribs));

        // Configure the vertex streams for this attribute data layout
        m_pgm.enableAttributeArray("vertexPosition");
        m_pgm.setAttributeBuffer("vertexPosition", GL_FLOAT, offsetof(Vertex, position), 3, sizeof(Vertex) );

        m_pgm.enableAttributeArray("vertexNormal");
        m_pgm.setAttributeBuffer("vertexNormal", GL_FLOAT, offsetof(Vertex, normal), 3, sizeof(Vertex) );

        // we need 36 indices (6 faces, 2 triangles per face, 3 vertices per triangle)
        struct {
            GLubyte cube[36];
        } indices;
        m_cnt=0; for (GLsizei i=0, v=0; v<6*4; v+=4)
        {
            // first triangle (ccw winding)
            indices.cube[i++] = v + 0;
            indices.cube[i++] = v + 1;
            indices.cube[i++] = v + 2;

             // second triangle (ccw winding)
            indices.cube[i++] = v + 0;
            indices.cube[i++] = v + 2;
            indices.cube[i++] = v + 3;

            m_cnt = i;
        }

        // Put all the index data in a IBO
        m_ibo.create();
        m_ibo.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_ibo.bind();
        m_ibo.allocate(&indices, sizeof(indices));

        // Okay, we've finished setting up the vao
        m_vao.release();
    }

    void initializeGL()
    {
        QOpenGLFunctions::initializeOpenGLFunctions();
        createShaderProgram(); m_pgm.bind();

        // Set lighting information
        m_pgm.setUniformValue("lightSource.ambient",  QVector3D( 0.0f, 0.0f, 0.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("lightSource.diffuse",  QVector3D( 1.0f, 1.0f, 1.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("lightSource.specular", QVector3D( 1.0f, 1.0f, 1.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("lightSource.position", QVector3D( 1.0f, 1.0f, 1.0f )); // NOT DEFAULT VALUE
        m_pgm.setUniformValue("lightModel.ambient",   QVector3D( 0.2f, 0.2f, 0.2f )); // opengl fixed-function default
        m_pgm.setUniformValue("material.emission",    QVector3D( 0.0f, 0.0f, 0.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("material.ambient",     QVector3D( 0.2f, 0.2f, 0.2f )); // opengl fixed-function default
        m_pgm.setUniformValue("material.diffuse",     QVector3D( 0.8f, 0.8f, 0.8f )); // opengl fixed-function default
        m_pgm.setUniformValue("material.specular",    QVector3D( 1.0f, 1.0f, 1.0f )); // NOT DEFAULT VALUE
        m_pgm.setUniformValue("material.shininess",   10.0f);                         // NOT DEFAULT VALUE

        createGeometry();
        m_view.setToIdentity();
        glEnable(GL_DEPTH_TEST);

        glClearColor(.5f,.5f,.5f,1.f);
    }

    void resizeGL(int w, int h)
    {
        glViewport(0, 0, w, h);
        m_projection.setToIdentity();
        if (w <= h)
            m_projection.ortho(-2.f, 2.f, -2.f*h/w, 2.f*h/w, -2.f, 2.f);
        else
            m_projection.ortho(-2.f*w/h, 2.f*w/h, -2.f, 2.f, -2.f, 2.f);
        update();
    }

    void paintGL()
    {
        static unsigned cnt;

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        QMatrix4x4 model;
        model.rotate(cnt%360, 1,0,0);
        model.rotate(45, 0,0,1);
        QMatrix4x4 mv = m_view * model;
        m_pgm.bind();
        m_pgm.setUniformValue("modelViewMatrix", mv);
        m_pgm.setUniformValue("normalMatrix", mv.normalMatrix());
        m_pgm.setUniformValue("projectionMatrix", m_projection);
        m_vao.bind();
        glDrawElements(GL_TRIANGLES, m_cnt, GL_UNSIGNED_BYTE, 0);

        update();
        ++cnt;
    }

    void keyPressEvent(QKeyEvent * ev)
    {
        switch (ev->key()) {
           case Qt::Key_Escape:
              exit(0);
              break;
           default:
              QOpenGLWindow::keyPressEvent(ev);
              break;
        }
    }

    QMatrix4x4 m_projection, m_view;
    QOpenGLShaderProgram     m_pgm;
    QOpenGLVertexArrayObject m_vao;
    QOpenGLBuffer            m_vbo;
    QOpenGLBuffer            m_ibo;
    GLsizei                  m_cnt;
};

int main(int argc, char *argv[])
{
    QGuiApplication a(argc,argv);
    Window w;
    w.setWidth(640); w.setHeight(480);
    w.show();
    return a.exec();
}

In Listing 4.2a and Listing 4.2b, we see how constant color is applied to the cube using glEnable(GL_LIGHTING) and glMaterial.  Notice the lighting equations are implemented in the first few lines of the vertex shader and again the resulting color is being passed to the pixel shader.  This is in line with the fixed-function lighting model where lighting calculations were performed on the vertices and then interpolated to pixels during rasterization. 

Listing 4.3a Fixed-Function OpenGL Cube Lit with Colored Facets

#include <GL/glut.h>
#include <stdlib.h>

void init(void)
{
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    GLfloat light_ambient     [] = { 0.0f, 0.0f, 0.0f, 1.0f };  /* default value */
    GLfloat light_diffuse     [] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* default value */
    GLfloat light_specular    [] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* default value */
    GLfloat light_position    [] = { 1.0f, 1.0f, 1.0f, 0.0f };  /* NOT default value */
    GLfloat lightModel_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };  /* default value */
    GLfloat material_specular [] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* NOT default value */
    GLfloat material_emission [] = { 0.0f, 0.0f, 0.0f, 1.0f };  /* default value */

    glLightfv (GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv (GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv (GL_LIGHT0, GL_POSITION, light_position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightModel_ambient);
    glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
    glMaterialfv(GL_FRONT, GL_SPECULAR, material_specular);
    glMaterialfv(GL_FRONT, GL_EMISSION, material_emission);
    glMaterialf(GL_FRONT, GL_SHININESS, 10.0);               /* NOT default value	*/

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_NORMALIZE);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_DEPTH_TEST);

    glClearColor(.5f,.5f,.5f,1.f);
}

void display(void)
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    static unsigned cnt;

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix ();
   glRotatef(float(cnt%360), 1,0,0);
   glRotatef(45.f, 0,0,1);

   glBegin(GL_QUADS);                // Begin drawing the color cube with 6 quads
      // Top face (y = 1.0f)
      glColor3f(0.0f, 1.0f, 0.0f);     // Green
      glNormal3f( 0.0f, 1.0f,  0.0f);
      glVertex3f( 1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f, -1.0f);
      glVertex3f(-1.0f, 1.0f,  1.0f);
      glVertex3f( 1.0f, 1.0f,  1.0f);

      // Bottom face (y = -1.0f)
      glColor3f(1.0f, 0.5f, 0.0f);     // Orange
      glNormal3f( 0.0f, -1.0f,  0.0f);
      glVertex3f( 1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);

      // Front face  (z = 1.0f)
      glColor3f(1.0f, 0.0f, 0.0f);     // Red
      glNormal3f( 0.0f,  0.0f, 1.0f);
      glVertex3f( 1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f,  1.0f, 1.0f);
      glVertex3f(-1.0f, -1.0f, 1.0f);
      glVertex3f( 1.0f, -1.0f, 1.0f);

      // Back face (z = -1.0f)
      glColor3f(1.0f, 1.0f, 0.0f);     // Yellow
      glNormal3f( 0.0f,  0.0f, -1.0f);
      glVertex3f( 1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f( 1.0f,  1.0f, -1.0f);

      // Left face (x = -1.0f)
      glColor3f(0.0f, 0.0f, 1.0f);     // Blue
      glNormal3f(-1.0f,  0.0f,  0.0f);
      glVertex3f(-1.0f,  1.0f,  1.0f);
      glVertex3f(-1.0f,  1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f, -1.0f);
      glVertex3f(-1.0f, -1.0f,  1.0f);

      // Right face (x = 1.0f)
      glColor3f(1.0f, 0.0f, 1.0f);     // Magenta
      glNormal3f(1.0f,  0.0f,  0.0f);
      glVertex3f(1.0f,  1.0f, -1.0f);
      glVertex3f(1.0f,  1.0f,  1.0f);
      glVertex3f(1.0f, -1.0f,  1.0f);
      glVertex3f(1.0f, -1.0f, -1.0f);
   glEnd();  // End of drawing color-cube

   glPopMatrix ();

   ++cnt;

   glutSwapBuffers();
}

void timeout (int)
{
    glutPostRedisplay();
    glutTimerFunc(1000/60, timeout, 1);
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   if (w <= h)
      glOrtho (-2.f, 2.f, -2.f*h/w, 2.f*h/w, -10.f, 10.f);
   else
      glOrtho (-2.f*w/h, 2.f*w/h, -2.f, 2.f, -10.f, 10.f);
}

void keyboard (unsigned char key, int , int )
{
   switch (key) {
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (640, 480);
   glutInitWindowPosition (50, 50);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutTimerFunc(1000/60, timeout, 1);
   glutMainLoop();
   return 0;
}

Listing 4.3b Modern OpenGL Cube Lit with Single Colored Facets

#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>

#include <QtGui/QOpenGLWindow>
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLVertexArrayObject>

static QString vertexShader =
        "#version 100\n"
        "\n"
        "attribute vec3 vertexPosition;\n"
        "attribute vec3 vertexNormal;\n"
        "attribute vec3 vertexColor;\n"
        "\n"
        "uniform mat4 modelViewMatrix;\n"
        "uniform mat3 normalMatrix;\n"
        "uniform mat4 projectionMatrix;\n"
        "\n"
        "struct LightSource\n"
        "{\n"
        "    vec3 ambient;\n"
        "    vec3 diffuse;\n"
        "    vec3 specular;\n"
        "    vec3 position;\n"
        "};\n"
        "uniform LightSource lightSource;\n"
        "\n"
        "struct LightModel\n"
        "{\n"
        "    vec3 ambient;\n"
        "};\n"
        "uniform LightModel lightModel;\n"
        "\n"
        "struct Material {\n"
        "    vec3  emission;\n"
        "    vec3  specular;\n"
        "    float shininess;\n"
        "};\n"
        "uniform Material material;\n"
        "\n"
        "varying vec3 v_color;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    vec3 normal     = normalize(normalMatrix * vertexNormal);                       // normal vector              \n"
        "    vec3 position   = vec3(modelViewMatrix * vec4(vertexPosition, 1));              // vertex pos in eye coords   \n"
        "    vec3 halfVector = normalize(lightSource.position + vec3(0,0,1));                // light half vector          \n"
        "    float nDotVP    = dot(normal, normalize(lightSource.position));                 // normal . light direction   \n"
        "    float nDotHV    = max(0.f, dot(normal,  halfVector));                           // normal . light half vector \n"
        "    float pf        = mix(0.f, pow(nDotHV, material.shininess), step(0.f, nDotVP)); // power factor               \n"
        "\n"
        "    vec3 ambient    = lightSource.ambient;\n"
        "    vec3 diffuse    = lightSource.diffuse * nDotVP;\n"
        "    vec3 specular   = lightSource.specular * pf;\n"
        "    vec3 sceneColor = material.emission + vertexColor * lightModel.ambient;\n"
        "\n"
        "    v_color = clamp(sceneColor +                         \n"
        "                    ambient  * vertexColor +             \n"
        "                    diffuse  * vertexColor +             \n"
        "                    specular * material.specular, 0.f, 1.f );\n"
        "\n"
        "    gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1);\n"
        "}\n"
        ;

static QString fragmentShader =
        "#version 100\n"
        "\n"
        "varying vec3 v_color;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = vec4(v_color, 1);\n"
        "}\n"
        ;

struct Window : QOpenGLWindow, QOpenGLFunctions
{
    Window() :
        m_vbo(QOpenGLBuffer::VertexBuffer),
        m_ibo(QOpenGLBuffer::IndexBuffer)
    {
    }

    void createShaderProgram()
    {
        if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Vertex, vertexShader)) {
            qDebug() << "Error in vertex shader:" << m_pgm.log();
            exit(1);
        }
        if ( !m_pgm.addShaderFromSourceCode( QOpenGLShader::Fragment, fragmentShader)) {
            qDebug() << "Error in fragment shader:" << m_pgm.log();
            exit(1);
        }
        if ( !m_pgm.link() ) {
            qDebug() << "Error linking shader program:" << m_pgm.log();
            exit(1);
        }
    }

    void createGeometry()
    {
        // Initialize and bind the VAO that's going to capture all this vertex state
        m_vao.create();
        m_vao.bind();

        // we need 24 vertices, 24 normals, and 24 colors (6 faces, 4 vertices per face)
        // since we can't share normal data at the corners (each corner gets 3 normals)
        // and since we're not using glVertexAttribDivisor (not available in ES 2.0)

         struct Vertex {
             GLfloat position[3],
                     normal  [3],
                     color   [3];
         } attribs[24]= {
            // Top face (y = 1.0f)
            { { 1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f} },     // Green
            { {-1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f} },     // Green
            { {-1.0f, 1.0f,  1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f} },     // Green
            { { 1.0f, 1.0f,  1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f} },     // Green

            // Bottom face (y = -1.0f)
            { { 1.0f, -1.0f,  1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f} },    // Orange
            { {-1.0f, -1.0f,  1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f} },    // Orange
            { {-1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f} },    // Orange
            { { 1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f} },    // Orange

            // Front face  (z = 1.0f)
            { { 1.0f,  1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red
            { {-1.0f,  1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red
            { {-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red
            { { 1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f} },     // Red

            // Back face (z = -1.0f)
            { { 1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow
            { {-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow
            { {-1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow
            { { 1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f} },    // Yellow

            // Left face (x = -1.0f)
            { {-1.0f,  1.0f,  1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f} },    // Blue
            { {-1.0f,  1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f} },    // Blue
            { {-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f} },    // Blue
            { {-1.0f, -1.0f,  1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f} },    // Blue

            // Right face (x = 1.0f)
            { {1.0f,  1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
            { {1.0f,  1.0f,  1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
            { {1.0f, -1.0f,  1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
            { {1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f} },     // Magenta
        };

        // Put all the attribute data in a FBO
        m_vbo.create();
        m_vbo.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_vbo.bind();
        m_vbo.allocate(&attribs, sizeof(attribs));

        // Configure the vertex streams for this attribute data layout
        m_pgm.enableAttributeArray("vertexPosition");
        m_pgm.setAttributeBuffer("vertexPosition", GL_FLOAT, offsetof(Vertex, position), 3, sizeof(Vertex) );

        m_pgm.enableAttributeArray("vertexNormal");
        m_pgm.setAttributeBuffer("vertexNormal", GL_FLOAT, offsetof(Vertex, normal), 3, sizeof(Vertex) );

        m_pgm.enableAttributeArray("vertexColor");
        m_pgm.setAttributeBuffer("vertexColor", GL_FLOAT, offsetof(Vertex, color), 3, sizeof(Vertex) );

        // we need 36 indices (6 faces, 2 triangles per face, 3 vertices per triangle)
        struct {
            GLubyte cube[36];
        } indices;
        m_cnt=0; for (GLsizei i=0, v=0; v<6*4; v+=4)
        {
            // first triangle (ccw winding)
            indices.cube[i++] = v + 0;
            indices.cube[i++] = v + 1;
            indices.cube[i++] = v + 2;

             // second triangle (ccw winding)
            indices.cube[i++] = v + 0;
            indices.cube[i++] = v + 2;
            indices.cube[i++] = v + 3;

            m_cnt = i;
        }

        // Put all the index data in a IBO
        m_ibo.create();
        m_ibo.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_ibo.bind();
        m_ibo.allocate(&indices, sizeof(indices));

        // Okay, we've finished setting up the vao
        m_vao.release();
    }

    void initializeGL()
    {
        QOpenGLFunctions::initializeOpenGLFunctions();
        createShaderProgram(); m_pgm.bind();

        // Set lighting information
        m_pgm.setUniformValue("lightSource.ambient",  QVector3D( 0.0f, 0.0f, 0.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("lightSource.diffuse",  QVector3D( 1.0f, 1.0f, 1.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("lightSource.specular", QVector3D( 1.0f, 1.0f, 1.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("lightSource.position", QVector3D( 1.0f, 1.0f, 1.0f )); // NOT DEFAULT VALUE
        m_pgm.setUniformValue("lightModel.ambient",   QVector3D( 0.2f, 0.2f, 0.2f )); // opengl fixed-function default
        m_pgm.setUniformValue("material.emission",    QVector3D( 0.0f, 0.0f, 0.0f )); // opengl fixed-function default
        m_pgm.setUniformValue("material.specular",    QVector3D( 1.0f, 1.0f, 1.0f )); // NOT DEFAULT VALUE
        m_pgm.setUniformValue("material.shininess",   10.0f);                         // NOT DEFAULT VALUE

        createGeometry();
        m_view.setToIdentity();
        glEnable(GL_DEPTH_TEST);

        glClearColor(.5f,.5f,.5f,1.f);
    }

    void resizeGL(int w, int h)
    {
        glViewport(0, 0, w, h);
        m_projection.setToIdentity();
        if (w <= h)
            m_projection.ortho(-2.f, 2.f, -2.f*h/w, 2.f*h/w, -2.f, 2.f);
        else
            m_projection.ortho(-2.f*w/h, 2.f*w/h, -2.f, 2.f, -2.f, 2.f);
        update();
    }

    void paintGL()
    {
        static unsigned cnt;

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        QMatrix4x4 model;
        model.rotate(cnt%360, 1,0,0);
        model.rotate(45, 0,0,1);
        QMatrix4x4 mv = m_view * model;
        m_pgm.bind();
        m_pgm.setUniformValue("modelViewMatrix", mv);
        m_pgm.setUniformValue("normalMatrix", mv.normalMatrix());
        m_pgm.setUniformValue("projectionMatrix", m_projection);
        m_vao.bind();
        glDrawElements(GL_TRIANGLES, m_cnt, GL_UNSIGNED_BYTE, 0);

        update();
        ++cnt;
    }

    void keyPressEvent(QKeyEvent * ev)
    {
        switch (ev->key()) {
           case Qt::Key_Escape:
              exit(0);
              break;
           default:
              QOpenGLWindow::keyPressEvent(ev);
              break;
        }
    }

    QMatrix4x4 m_projection, m_view;
    QOpenGLShaderProgram     m_pgm;
    QOpenGLVertexArrayObject m_vao;
    QOpenGLBuffer            m_vbo;
    QOpenGLBuffer            m_ibo;
    GLsizei                  m_cnt;
};

int main(int argc, char *argv[])
{
    QGuiApplication a(argc,argv);
    Window w;
    w.setWidth(640); w.setHeight(480);
    w.show();
    return a.exec();
}

Lastly, Listing 4.3a and Listing 4.3b show the effects of enabling glColorMaterial(GL_AMBIENT_DIFFUSE) in which the ambient and diffuse terms of the lighting equations are pulled from vertex attributes instead of values set by glMaterial.

One thing to note is the GLSL ShaderGen program available from github (https://github.com/mojocorp/ShaderGen). ² With it, you can generate shader code for any particular fixed-function lighting configuration.  The bad news is that it was written against OpenGL 2 shaders, which had tight ties to fixed-function lighting parameters in the form of built-in uniforms.  You will have to explicitly define and use these uniforms in your modern shader code like in listing 4.2b and listing 4.3b.  In addition, notice how the “automatic” fixed-function gl_LightModelProducts.sceneColor and gl_LightSourceParameters.halfVector uniform parameters are calculated since they are used in the ShaderGen generated vertex code.

Another thing to note is that each of the three cube rendering examples above used a different vertex shader instead of using one shader with if statements and “mode” parameters.  This is an example of best practices for modern OpenGL, which is to always favor writing multiple shaders, each performing a unique task instead of one shader performing multiple tasks based on input parameters.  It is up to you, the programmer, to decide where the balance lies for your own code base.

Wrapping Up

We have introduced Fixed-Function OpenGL lighting and how to implement these equations in your shader programs.   We noted that Fixed-Function OpenGL lighting is based on Blinn-Phong equations and noticed that with shaders you're not limited to this approach.  We pointed out ShaderGen application that can create pixel correct Fixed-Function lighting shader code for aide in porting your fixed-function applications.  

Next up we'll introduce texturing, how it's used in fixed function applications and the equivalent implementation using modern OpenGL with shaders.  Stay tuned.

References:

  1. Blinn-Phong (alternatively named ADS),  https://en.wikipedia.org/wiki/Blinn-Phong_shading_model
  2. GLSL ShaderGen program available from github, https://github.com/mojocorp/ShaderGen

 

Part 1 | Part 2 | Part 3 | Part 4



Have a question or add to the conversation: Log in Register