QGLBuilder ClassThe QGLBuilder class constructs geometry for efficient display. More... #include <QGLBuilder> This class was introduced in Qt 4.8. Public Functions
Related Non-Members
Detailed DescriptionThe QGLBuilder class constructs geometry for efficient display. Use a QGLBuilder to build up vertex, index, texture and other data during application initialization. The finalizedSceneNode() function returns an optimized scene which can be efficiently and flexibly displayed during frames of rendering. It is suited to writing loaders for 3D models, and for programatically creating geometry. Geometry BuildingQGLBuilder makes the job of getting triangles on the GPU simple. It calculates indices and normals for you, then uploads the data. While it has addQuads() and other functions to deal with quads, all data is represented as triangles for portability. The simplest way to use QGLBuilder is to send a set of geometry values to it using QGeometryData in the constructor: MyView::MyView() : QGLView() { // in the constructor construct a builder on the stack QGLBuilder builder; QGeometryData triangle; QVector3D a(2, 2, 0); QVector3D b(-2, 2, 0); QVector3D c(0, -2, 0); triangle.appendVertex(a, b, c); // When adding geometry, QGLBuilder automatically creates lighting normals builder << triangle; // obtain the scene from the builder m_scene = builder.finalizedSceneNode(); // apply effects at app initialization time QGLMaterial *mat = new QGLMaterial; mat->setDiffuseColor(Qt::red); m_scene->setMaterial(mat); } Then during rendering the scene is used to display the results: MyView::paintGL(QGLPainter *painter) { m_scene->draw(painter); } QGLBuilder automatically generates index values and normals on-the-fly during geometry building. During building, simply send primitives to the builder as a sequence of vertices, and vertices that are the same will be referenced by a single index automatically. Primitives will have standard normals generated automatically based on vertex winding. Consider the following code for OpenGL to draw a quad with corner points A, B, C and D : float vertices[12] = { -1.0, -1.0, -1.0, // A 1.0, -1.0, -1.0, // B 1.0, 1.0, 1.0, // C -1.0, 1.0, 1.0 // D }; float normals[12] = { 0.0f }; for (int i = 0; i < 12; i += 3) { normals[i] = 0.0; normals[i+1] = -sqrt(2.0); normals[i+2] = sqrt(2.0); } GLuint indices[6] = { 0, 1, 2, // triangle A-B-C 0, 2, 3 // triangle A-C-D }; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vertices); glNormalPointer(3, GL_FLOAT, 0, normals); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices); With QGLBuilder this code becomes: float vertices[12] = { -1.0, -1.0, -1.0, // A 1.0, -1.0, -1.0, // B 1.0, 1.0, 1.0, // C -1.0, 1.0, 1.0 // D }; QGLBuilder quad; QGeometryData data; data.appendVertexArray(QArray<QVector3D>::fromRawData( reinterpret_cast<const QVector3D*>(vertices), 4)); quad.addQuads(data); The data primitive is added to the list, as two triangles, indexed to removed the redundant double storage of B & C - just the same as the OpenGL code. QGLBuilder will also calculate a normal for the quad and apply it to the vertices. In this trivial example the indices are easily calculated, however in more complex geometry it is easy to introduce bugs by trying to manually control indices. Extra work is required to generate, track and store the index values correctly. Bugs such as trying to index two vertices with different data - one with texture data and one without - into one triangle can easily result. The picture becomes more difficult when smoothing groups are introduced - see below. Using indices is always preferred since it saves space on the GPU, and makes the geometry perform faster during application run time. Removing Epsilon ErrorsWhere vertices are generated by modelling packages or tools, or during computation in code, very frequently rounding errors will result in several vertices being generated that are actually the same vertex but are separated by tiny amounts. At best these duplications waste space on the GPU but at worst can introduce visual artifacts that mar the image displayed. Closing paths, generating solids of rotation, or moving model sections out and back can all introduce these types of epsilon errors, resulting in "cracks" or artifacts on display. QGLBuilder's index generation process uses a fuzzy match that coalesces all vertex values at a point - even if they are out by a tiny amount - and references them with a single index. Lighting Normals and Null TrianglesQGLBuilder functions calculate lighting normals, when building geometry. This saves the application programmer from having to write code to calculate them. Normals for each triangle (a, b, c) are calculated as the QVector3D::normal(a, b, c). If lighting normals are explicitly supplied when using QGLBuilder, then this calculation is not done. This may save on build time. As an optimization, QGLBuilder skips null triangles, that is ones with zero area, where it can. Such triangles generate no fragments on the GPU, and thus do not display but nonetheless can take up space and processing power. Null triangles can easily occur when calculating vertices results in two vertices coinciding, or three vertices lying on the same line. This skipping is done using the lighting normals cross-product. If the cross-product is a null vector then the triangle is null. When lighting normals are specified explicitly the skipping optimization is suppressed, so if for some reason null triangles are required to be retained, then specify normals for each logical vertex. See the documentation below of the individual addTriangle() and other functions for more details. Raw Triangle ModeWhere generation of indices and normals is not needed - for example if porting an existing application, it is possible to do a raw import of triangle data, without using any of QGLBuilder's processing. To do this ensure that indices are placed in the QGeometryData passed to the addTriangles() function, and this will trigger raw triangle mode. When adding triangles in this way ensure that all appropriate values have been correctly set, and that the normals, indices and other data are correctly calculated, since no checking is done. When writing new applications, simply leave construction of normals and indices to the QGLBuilder Rendering and QGLSceneNode items.QGLSceneNodes are used to manage application of local transformations, materials and effects. QGLBuilder generates a root level QGLSceneNode, which can be accessed with the sceneNode() function. Under this a new node is created for each section of geometry, and also by using pushNode() and popNode(). To organize geometry for painting with different materials and effects call the newNode() function: QGLSceneNode *box = builder.newNode(); box->setMaterial(wood); Many nodes may be created this way, but they will be optimized into a small number of buffers under the one scene when the finalizedSceneNode() function is called. Here the front can is a set of built geometry and the other two are scene nodes that reference it, without copying any geometry. QGLSceneNode *can = buildGeometry(); canScene->addNode(can); { // rotate the can around so its label shows; and down // so the base is facing down QMatrix4x4 mat; QQuaternion q1 = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 270.0f); QQuaternion q2 = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 100.0f); mat.rotate(q2 * q1); can->setLocalTransform(mat); } // display a copy of the can to the left QGLSceneNode *node = new QGLSceneNode(canScene); node->addNode(can); { QMatrix4x4 mat; mat.translate(-2.0f, 0.0f, -2.0f); node->setLocalTransform(mat); } // display a copy of the can to the right node = new QGLSceneNode(canScene); node->addNode(can); { QMatrix4x4 mat; mat.translate(2.0f, 0.0f, -2.0f); node->setLocalTransform(mat); } QGLSceneNodes can be used after the builder is created to cheaply copy and redisplay the whole scene. Or to reference parts of the geometry use the functions newNode() or pushNode() and popNode() to manage QGLSceneNode generation while building geometry. To draw the resulting built geometry simply call the draw method of the build geometry. void BuilderView::paintGL(QGLPainter *painter) { canScene->draw(painter); } Call the palette() function on the sceneNode() to get the QGLMaterialCollection for the node, and place textures and materials into it. Built geometry will typically share the one palette. Either create a palette, and pass it to the constructor; or pass no arguments to the constructor and the QGLBuilder will create a palette: QGLBuilder builder; QGLSceneNode *root = builder.sceneNode(); QGLMaterial *mat = new QGLMaterial; mat->setAmbientColor(Qt::lightGray); mat->setDiffuseColor(Qt::lightGray); QUrl url; url.setPath(QLatin1String(":/images/qt-soup.png")); url.setScheme(QLatin1String("file")); mat->setTextureUrl(url); texture = mat->texture(); int canMat = root->palette()->addMaterial(mat); root->setMaterialIndex(canMat); root->setEffect(QGL::LitMaterial); These may then be applied as needed throughout the building of the geometry using the integer reference, canMat in the above code. See the QGLSceneNode documentation for more. Using SectionsDuring initialization of the QGLBuilder, while accumulating geometry, the geometry data in a QGLBuilder is placed into sections - there must be at least one section. Call the newSection() function to create a new section: // create the flat top lid of the can builder.newSection(); builder.currentNode()->setObjectName(QLatin1String("CanTop")); QGeometryData top; top.appendVertex(canRim.center()); top.appendVertexArray(canRim.vertices()); builder.addTriangulatedFace(top); // create the sides of the can builder.newSection(); builder.currentNode()->setObjectName(QLatin1String("CanSides")); builder.currentNode()->setMaterialIndex(canMat); builder.currentNode()->setEffect(QGL::LitModulateTexture2D); QGeometryData canTop = canRim; canTop.detach(); canTop.appendVertex(canTop.vertex(0)); // doubled vert for texture seam canTop.generateTextureCoordinates(); // generate x texture coords QGeometryData canBase = canTop.translated(canExtrudeVec); // base has tex.y == 0 for (int i = 0; i < canTop.count(); ++i) canTop.texCoord(i).setY(1.0); // top has tex.y == 1 builder.addQuadsInterleaved(canTop, canBase); // create the flat bottom lid of the can builder.newSection(); builder.currentNode()->setObjectName(QLatin1String("CanBottom")); builder.currentNode()->setEffect(QGL::LitMaterial); QGeometryData rimReversed = canRim.translated(canExtrudeVec).reversed(); QGeometryData canBottom; canBottom.appendVertex(rimReversed.center()); canBottom.appendVertexArray(rimReversed.vertices()); builder.addTriangulatedFace(canBottom); return builder.finalizedSceneNode(); Here separate sections for the rounded outside cylinder and flat top and bottom of the soup can model makes for the appearance of a sharp edge between them. If the sides and top and bottom were in the same section QGLBuilder would attempt to average the normals around the edge resulting in an unrealistic effect. In 3D applications this concept is referred to as smoothing groups. Within a section (smoothing group) all normals are averaged making it appear as one smoothly shaded surface. The can has 3 smoothing groups - bottom, top and sides. This mesh of a Q is a faceted model - it has 0 smoothing groups: To create geometry with a faceted appearance call newSection() with an argument of QGL::Faceted thus newSection(QGL::Faceted). Faceted geometry is suitable for small models, where hard edges are desired between every face - a dice, gem or geometric solid for example. If no section has been created when geometry is added a new section is created automatically. This section will have its smoothing set to QGL::Smooth. To create a faceted appearance rather than accepting the automatically created section the << operator can also be used: QGLBuilder builder; QGeometryData triangles; triangles.appendVertices(a, b, c); builder << QGL::Faceted << triangles; Geometry Data in a SectionManagement of normals and vertices for smoothing, and other data is handled automatically by the QGLBuilder instance. Within a section, incoming geometry data will be coalesced and indices created to reference the fewest possible copies of the vertex data. For example, in smooth geometry all copies of a vertex are coalesced into one, and referenced by indices. One of the few exceptions to this is the case where texture data forms a seam and a copy of a vertex must be created to carry the two texture coordinates either side of the seam. Coalescing has the effect of packing geometry data into the smallest space possible thus improving cache coherence and performance. Again all this is managed automatically by QGLBuilder and all that is required is to create smooth or faceted sections, and add geometry to them. Each QGLSection references a contiguous range of vertices in a QGLBuilder. Finalizing and Retrieving the SceneOnce the geometry has been accumulated in the QGLBuilder instance, the finalizedSceneNode() method must be called to retrieve the optimized scene. This function serves to normalize the geometry and optimize it for display. While it may be convenient to get pointers to sub nodes in the scene during construction, it is important to retrieve the root of the scene so that the memory consumed by the scene can be recovered. The builder will create a QGLMaterialCollection; and there may be geometry, materials and other resources: these are all parented onto the root scene node. These can easily be recovered by deleting the root scene node: MyView::MyView() : QGLView() { // in the constructor construct a builder on the stack QGLBuilder builder; // add geometry as shown above builder << triangles; // obtain the scene from the builder & take ownership m_scene = builder.finalizedSceneNode(); } MyView::~MyView() { // recover all scene resources delete m_scene; } Alternatively set the scene's parent to ensure resource recovery m_scene->setParent(this). Member Function Documentation
|
Constant | Value | Description |
---|---|---|
QGL::NoSmoothing | 0 | No smoothing processing is performed. |
QGL::Smooth | 1 | Lighting normals averaged for each face for a smooth appearance. |
QGL::Faceted | 2 | Lighting normals separate for each face for a faceted appearance. |
This enum was introduced or modified in Qt 4.8.