In the previous article I wrote that using modern OpenGL (i.e. version 3.0 and above) is possible, although the core profile cannot be used yet. I also mentioned this article which briefly describes how to use the core profile, although in fact this example will also work in the default compatibility mode. In this mode we can use both the fixed pipeline and shaders, but I will focus on the "modern" approach.
Qt has a handy class called
QGLShaderProgram which wraps the OpenGL API related to shaders. A big advantage of this class is that it supports all classes related to 3D graphics provided by Qt, such as
QMatrix4x4, as well as basic types like
QColor. This way we don't have to worry about converting those types to OpenGL types. Internally this class is little more than a
GLuint storing the handle of the shader program and most its methods are simple wrappers around functions like
glUniform3fv so it's very lightweight.
Note, however, that shaders work in quite a different way depending on the version of the GLSL specification. By default version 1.20 is assumed, so your shaders can access all information known from the fixed pipeline - vertex position, normal, texture coordinates, transformation matrices, lighting parameters, etc. Things change dramatically when you put the following declaration at the beginning of the shader:
Any attempt to access these built-in uniforms and attributes will result in an error. It means that you have to pass all information using explicitly declared uniforms and attributes. For example, to define the world-to-camera transformation matrix, you could use the following code:
QMatrix4x4 view; view.translate( 0.0, 0.0, -CameraDistance ); view.rotate( m_angle, 1.0, 0.0, 0.0 ); view.rotate( m_rotation, 0.0, 0.0, 1.0 ); m_program.setUniformValue( "ViewMatrix", view );
This is not only much more elegant than a series of calls to
glRotate etc., but also faster and more flexible. The vector and matrix classes provided by Qt are really handy; the authors of this class even thought about the
normalMatrix method that calculates the transposed inverse (or was it inversed transpose?) for transforming normal vectors.
Similarly, uniforms can be used to pass lighting parameters, materials, blending information and many more things which are not possible to achieve using the fixed pipeline. When it comes to attributes, the
QGLShaderProgram offers a bunch of functions for passing single values to attributes (which are not very useful in most cases) and for passing arrays of various types. However this is not recommended, because OpenGL knows nothing about the contents of these arrays and it cannot assume that they don't change between executions of the shader or between successive frames.
A much better approach is to use the
setAttributeBuffer method in connection with the
QGLBuffer class. Internally this method is a wrapper for
glVertexAttribPointer just like the attribute array methods, but it makes the code much more readable as it explicitly states that vertex buffers are used. In addition there's no need to cast the offset to a pointer because Qt will do that for us.
QGLBuffer class is also a very thin wrapper around a
GLuint representing the vertex buffer object (or index buffer or pixel buffer object). Unlike
QGLShaderProgram it's a value type (it doesn't make sense to copy a program anyway), so we can share buffers without having to worry about tracking and releasing them when they are no longer needed.
In order to use the
QGLBuffer, we need to create it and fill it with data; then we can bind it with the attributes of the shader program. By using appropriate offset and stride, we can easily bind multiple attributes to a single buffer; usually all attributes of a single vertex would be stored together, followed by the remaining vertices. Don't forget about calling
enableAttributeArray for each attribute. We can also use another instance of
QGLBuffer to store the indexes.
When everything is set up like this, the rendering is a matter of binding the program and both buffers to the context and calling
glDrawElements. In more complex scenarios we can use multiple vertex array objects to store the bindings between vertex buffers and attributes. But since we're not using the core profile, OpenGL will create an implicit vertex array object for us.
We can also use uniform buffer objects to simplify passing lots of uniforms to multiple programs. Although Qt doesn't support them at the moment, there is a simple hack which allows us to abuse
QGLBuffer. If you look at the declaration of this class you will notice that the values of the enumeration defining the type of a buffer are the same as the corresponding target constants in OpenGL. So we could simply pass
GL_UNIFORM_BUFFER as the type of the buffer - I haven't tested it yet, but it should work.