Drawing into framebuffer objectsThe Nesting example shows how Qt3D can be used to draw into a framebuffer object and then use the associated texture in subsequent drawing operations. It is assumed that the reader is already familar with the following examples:
In this example we are going to draw two rotating and transparent cubes. One cube will display a simple texture as in the Cube example, and the other cube will display a nested scene containing a rotating teapot. As with the other examples, we start by creating the geometric objects we need in the constructor (we will share the cube geometry between the two cubes we will be drawing): CubeView::CubeView(QWindow *parent) : QGLView(parent) , fbo(0) , tangle(0.0f) , cangle(0.0f) , oangle(0.0f) { QGLBuilder builder; builder.newSection(QGL::Faceted); builder << QGLCube(1.5f); cube = builder.currentNode(); cube->setObjectName(QLatin1String("Cube")); builder.newSection(); builder << QGLTeapot(); teapot = builder.currentNode(); teapot->setObjectName(QLatin1String("Teapot")); scene = builder.finalizedSceneNode(); scene->setParent(this); We also need three QPropertyAnimation objects to drive our animations; rotation angle for the teapot, rotation angle for the cube; orbit angle of the two cubes around each other: QPropertyAnimation *animation; animation = new QPropertyAnimation(this, "teapotAngle", this); animation->setStartValue(0.0f); animation->setEndValue(360.0f); animation->setDuration(1000); animation->setLoopCount(-1); animation->start(); animation = new QPropertyAnimation(this, "cubeAngle", this); animation->setStartValue(0.0f); animation->setEndValue(360.0f); animation->setDuration(5000); animation->setLoopCount(-1); animation->start(); animation = new QPropertyAnimation(this, "orbitAngle", this); animation->setStartValue(0.0f); animation->setEndValue(360.0f); animation->setDuration(5000); animation->setLoopCount(-1); animation->start(); The final step in the constructor is to create a camera for the nested scene we will be drawing into the framebuffer object. This is in addition to the default QGLView::camera() object that provides the camera for the main scene. innerCamera = new QGLCamera(this); } To draw the nested scene, we of course need a framebuffer object, which we create in the initializeGL() method: void CubeView::initializeGL(QGLPainter *) { fbo = new QOpenGLFramebufferObject(512, 512, QOpenGLFramebufferObject::Depth); fboSurface.setFramebufferObject(fbo); Note that we also set the framebuffer object on an instance of QGLFramebufferObjectSurface. We will use this fact later when we render the nested scene. For the other cube, we need a regular texture. And we'll also turn on blending to make our cubes transparent: QImage textureImage(QLatin1String(":/qtlogo.png")); qtlogo.setImage(textureImage); glEnable(GL_BLEND); } Now it is time to paint the scene in paintGL(). The first thing we want to do is draw the teapot into the framebuffer object. To do that, we need to establish a nested drawing state: void CubeView::paintGL(QGLPainter *painter) { painter->modelViewMatrix().push(); painter->projectionMatrix().push(); painter->pushSurface(&fboSurface); In the code above, we first push the main scene's modelview and projection matrices. And then we push a new drawing surface onto the painter's surface stack. Everything we draw from now on will be written to fboSurface and thus the framebuffer object. To do that, we set the inner camera position, adjust the painter state, clear the framebuffer object, and draw the teapot: painter->setCamera(innerCamera); painter->modelViewMatrix().rotate(tangle, 0.0f, 1.0f, 0.0f); painter->setFaceColor(QGL::AllFaces, QColor(170, 202, 0)); painter->setStandardEffect(QGL::LitMaterial); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); teapot->draw(painter); Now that the nested scene has been drawn to the framebuffer object, we pop the surface and matrix stacks to return to the main scene: painter->popSurface(); painter->projectionMatrix().pop(); painter->modelViewMatrix().pop(); Because our cubes are transparent, we need to make sure we draw the objects in the scene from back to front. Otherwise the result will not look correct. To do this, we check which of the cube mid-points is furtherest away from the camera and draw that cube first (the more negative the z value, the further away it is): painter->modelViewMatrix().rotate(oangle, 0.0f, 1.0f, 0.0f); QMatrix4x4 m = painter->modelViewMatrix(); QVector3D cube1pos(-1.5f, 0.0f, 0.0f); QVector3D cube2pos(1.5f, 0.0f, 0.0f); if (m.map(cube1pos).z() < m.map(cube2pos).z()) { drawCube1(painter, cube1pos); drawCube2(painter, cube2pos); } else { drawCube2(painter, cube2pos); drawCube1(painter, cube1pos); } } Drawing the first cube with the simple texture is very similar to the Cube example: void CubeView::drawCube1(QGLPainter *painter, const QVector3D &posn) { painter->modelViewMatrix().push(); qtlogo.bind(); painter->setFaceColor(QGL::AllFaces, QColor(202, 100, 0, 150)); painter->setStandardEffect(QGL::LitDecalTexture2D); painter->modelViewMatrix().translate(posn); painter->modelViewMatrix().rotate(cangle, 1.0f, -1.0f, 1.0f); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); cube->draw(painter); glCullFace(GL_BACK); cube->draw(painter); glDisable(GL_CULL_FACE); painter->modelViewMatrix().pop(); } The main interesting wrinkle is that we draw the cube twice, once with front faces culled, and the second time with back faces culled. This effectively causes the cube to be drawn back to front for proper blending. The second cube is drawn in a similar way except that we bind the framebuffer object's texture to the state instead of a static texture: void CubeView::drawCube2(QGLPainter *painter, const QVector3D &posn) { painter->modelViewMatrix().push(); painter->setFaceColor(QGL::AllFaces, QColor(0, 160, 202, 125)); painter->setStandardEffect(QGL::LitDecalTexture2D); glBindTexture(GL_TEXTURE_2D, fbo->texture()); glEnable(GL_TEXTURE_2D); painter->modelViewMatrix().translate(posn); painter->modelViewMatrix().rotate(cangle, 1.0f, 1.0f, 1.0f); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); cube->draw(painter); glCullFace(GL_BACK); cube->draw(painter); glDisable(GL_CULL_FACE); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); painter->modelViewMatrix().pop(); } Face culling is a simple way to draw transparent objects, but it really only works on non-intersecting convex objects like our cubes. For concave objects (like the teapot) or objects that intersect, we would need to break the objects up into smaller convex pieces and then order the pieces from back to front. Files: |