Fixed Function to Modern OpenGL (Part 4 of 4)
The code referenced in this blog is available here.
Texturing
Texture setup in modern OpenGL is nearly equivalent to what you are used to in the fixed-function API. You still have to enable your texture units, create and configure your texture objects (including mipmap chains), upload texture data to them, bind them to a texture target and configure texture coordinates on your vertices as attribute data. However, glTexEnv, glTexGen and the texture matrix stack have been removed.
glTexEnv must be replaced by code in the fragment shader. Recall that a fragment shader typically gets as input an interpolated color (usually calculated from a set of lighting equations) from the vertex shader. What you do is take that input color and then “sample” a color from a texture using texture coordinates (also usually input from the vertex shader) and then perform whatever kind of blending math you want. If you like glTexEnv(GL_REPLACE) then just ignore the color shader input, if you like glTexEnv(GL_MODULATE) then you implement modulation logic in shader code before writing the result to gl_FragColor.
So how do we “sample” a color from a texture in a fragment shader? The answer is we use a new kind of uniform variable called a “texture sampler” to refer to the texture. We pass that variable as a parameter to a built-in texture sampling function. For 2-D textures, we use uniform sampler2D myTexture; to declare the texture and texture2D(myTexture, v_TextureCoord.st) to read a “texel” (texture pixel) from it. This takes care of the fragment shader code, but what about CPU setup?
myTexture is a uniform variable and as we learned earlier, these variables must be assigned before glDraw calls. What value should we assign? The answer is the texture unit number. Recall that OpenGL supports multiple texture units enabled with glActiveTexture(GL_TEXTURE0, … GL_TEXTUREn) and that glTexture calls are applied to the last activated unit. This all remains unchanged, but it is this unit number: 0 for GL_TEXTURE0, 1 for GL_TEXTURE1 that you assign to your texture sampler uniforms before making glDraw calls. So if you have activated GL_TEXTURE0, defined and configured a texture object, bound it to GL_TEXTURE2D and assigned the value zero to your uniform variable, then texture unit 0 is configured for 2-D sampling of that texture object in your fragment shader. What this means is that you can simply change which texture object is bound to GL_TEXTURE2D between draw calls to sample from different textures using the same texture unit, or you can change to entirely different texture unit by assign a new value to your texture sampling uniform, or you can do both. It is completely up to you to manage your texture state and texture unit usage.
glTexGen has also been removed, and it is up to you to write the replacement code in your fragment shader for each of GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, and GL_EYE_PLANE modes you may need.
Multi-Texturing is accomplished by simply defining, and sampling from, two or more “texture samples” (each bound to different texture units) in your fragment shader and then combining their results in whatever fashion you would like. This includes emulating the multi-texture combination logic as implemented in fixed-function OpenGL.
Lastly, the glMatrixMode(GL_TEXTURE) has been removed. Its emulation is accomplished exactly like the Model, View, Projection matrices. You use a mat4 uniform, assign its matrix values before calling glDraw, and then in your fragment shader multiply the texture coordinates with this matrix before using them to sample your textures.
Our last example, Listing 5.1 and Listing 5.2 show the classic textured cube example in both fixed function (5.1) and its modern equivalent (5.2)
Listing 5.1 Fixed-Function OpenGL Cube Lit with Colored Facets and Texture Overlay
#include <GL/glut.h> #include <stdlib.h> static GLuint texName; /* * Texture copied and modifided modified from: * https://www.opengl.org/archives/resources/code/samples/mjktips/TexShadowReflectLight.html */ static char *circles[] = { "................", "................", "......xxxx......", "....xxxxxxxx....", "...xxxxxxxxxx...", "...xxx....xxx...", "..xxx......xxx..", "..xxx......xxx..", "..xxx......xxx..", "..xxx......xxx..", "...xxx....xxx...", "...xxxxxxxxxx...", "....xxxxxxxx....", "......xxxx......", "................", "................", }; static void initTexture(void) { GLubyte floorTexture[16][16][3]; GLubyte *loc; int s, t; /* Setup RGB image for the texture. */ loc = (GLubyte*) floorTexture; for (t = 0; t < 16; t++) { for (s = 0; s < 16; s++) { if (circles[t][s] == 'x') { /* Nice green. */ loc[0] = 0x1f; loc[1] = 0x8f; loc[2] = 0x1f; } else { /* Light gray. */ loc[0] = 0xaa; loc[1] = 0xaa; loc[2] = 0xaa; } loc += 3; } } glGenTextures (1, &texName); glBindTexture (GL_TEXTURE_2D, texName); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); glTexImage2D (GL_TEXTURE_2D, 0, 3, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, floorTexture); 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_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } 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_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); initTexture(); glClearColor(.5f,.5f,.5f,1.f); } void display(void) { glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texName); 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); glTexCoord2f(0.0, 0.0); glVertex3f( 1.0f, 1.0f, -1.0f); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0, 0.0); 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); glTexCoord2f(0.0, 0.0); glVertex3f( 1.0f, -1.0f, 1.0f); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0, 0.0); 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); glTexCoord2f(0.0, 0.0); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 0.0); 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); glTexCoord2f(0.0, 0.0); glVertex3f( 1.0f, -1.0f, -1.0f); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(1.0, 0.0); 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); glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0, 0.0); 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); glTexCoord2f(0.0, 0.0); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(0.0, 1.0); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(1.0, 0.0); 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 5.2 Modern OpenGL Cube Lit with Colored Facets and Texture Overlay
#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" "attribute vec2 texCoord2d;\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" "varying vec2 v_texCoord2d;\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" " v_texCoord2d = texCoord2d;\n" "\n" " gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1);\n" "}\n" ; static QString fragmentShader = "#version 100\n" "uniform sampler2D texUnit;\n" "\n" "varying vec3 v_color;\n" "varying vec2 v_texCoord2d;\n" "\n" "void main()\n" "{\n" " gl_FragColor = vec4(v_color, 1) * texture2D(texUnit, v_texCoord2d);\n" "}\n" ; /* * Texture copied and modifided modified from: * https://www.opengl.org/archives/resources/code/samples/mjktips/TexShadowReflectLight.html */ static char *circles[] = { "................", "................", "......xxxx......", "....xxxxxxxx....", "...xxxxxxxxxx...", "...xxx....xxx...", "..xxx......xxx..", "..xxx......xxx..", "..xxx......xxx..", "..xxx......xxx..", "...xxx....xxx...", "...xxxxxxxxxx...", "....xxxxxxxx....", "......xxxx......", "................", "................", }; 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], texcoord[2]; } 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}, { 0.0f, 0.0f} }, // Green { {-1.0f, 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, { 0.0f, 1.0f} }, // Green { {-1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, { 1.0f, 1.0f} }, // Green { { 1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.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}, { 0.0f, 0.0f} }, // Orange { {-1.0f, -1.0f, 1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f}, { 0.0f, 1.0f} }, // Orange { {-1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f}, { 1.0f, 1.0f} }, // Orange { { 1.0f, -1.0f, -1.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.5f, 0.0f}, { 1.0f, 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}, { 0.0f, 0.0f} }, // Red { {-1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f} }, // Red { {-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, { 1.0f, 1.0f} }, // Red { { 1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, { 1.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}, { 0.0f, 0.0f} }, // Yellow { {-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f}, { 0.0f, 1.0f} }, // Yellow { {-1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f}, { 1.0f, 1.0f} }, // Yellow { { 1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.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}, { 0.0f, 0.0f} }, // Blue { {-1.0f, 1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, { 0.0f, 1.0f} }, // Blue { {-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, { 1.0f, 1.0f} }, // Blue { {-1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, { 1.0f, 0.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}, { 0.0f, 0.0f} }, // Magenta { {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {1.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}, { 1.0f, 1.0f} }, // Magenta { {1.0f, -1.0f, -1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, { 1.0f, 0.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) ); m_pgm.enableAttributeArray("texCoord2d"); m_pgm.setAttributeBuffer("texCoord2d", GL_FLOAT, offsetof(Vertex, texcoord), 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 createTexture(void) { GLubyte image[16][16][3]; GLubyte *loc; int s, t; /* Setup RGB image for the texture. */ loc = (GLubyte*) image; for (t = 0; t < 16; t++) { for (s = 0; s < 16; s++) { if (circles[t][s] == 'x') { /* Nice green. */ loc[0] = 0x1f; loc[1] = 0x8f; loc[2] = 0x1f; } else { /* Light gray. */ loc[0] = 0xaa; loc[1] = 0xaa; loc[2] = 0xaa; } loc += 3; } } glGenTextures (1, &m_tex); glBindTexture (GL_TEXTURE_2D, m_tex); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); glTexImage2D (GL_TEXTURE_2D, 0, 3, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, image); 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_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } 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); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); m_pgm.setUniformValue("texUnit", 0); createTexture(); 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); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_tex); 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; GLuint m_tex; 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(); }
Wrapping Up
We have covered the necessary steps you need to port a typical fixed-function application to modern OpenGL. While not exhaustive, Geometry/Attributes, Lighting and Texturing are the most common features used in fixed-function applications.