Render 3D shapes

We have learned how to draw simple 2D shapes onscreen in the previous section. However, to fully utilize the OpenGL API, we also need to learn how to use it to render 3D images. In a nutshell, 3D images are simply an illusion created using 2D shapes stacked in a way that makes them look like 3D.

The main ingredient here is the depth value, which determines which shapes should appear in front or at the back of the other shapes. The primitive shape that is positioned behind another surface (with a shallower depth than another shape) will not be rendered (or partially rendered). OpenGL provides a simple way to achieve this, without too much technical hassle.

How to do it…

  1. First, add the QTimer header to your mainwindow.h:
    #include <QTimer>
  2. Then, add a private variable to your MainWindow class:
    private:
      QOpenGLContext* context;
      QOpenGLFunctions* openGLFunctions;
      float rotation;
    
  3. We also add a public slot to mainwindow.h for later use:
    public slots:
      void updateAnimation();
  4. After that, enable depth testing by adding glEnable(GL_DEPTH_TEST) to the initializeGL() function in mainwindow.cpp:
    void MainWindow::initializeGL()
    {
      //  Enable Z-buffer depth test
      glEnable(GL_DEPTH_TEST);
      resizeGL(this->width(), this->height());
    }
  5. Next, we will alter the resizeGL() function so that it uses the perspective view instead of the orthogonal view:
    void MainWindow::resizeGL(int w, int h)
    {
      // Set the viewport
      glViewport(0, 0, w, h);
      qreal aspectRatio = qreal(w) / qreal(h);
    
      // Initialize Projection Matrix
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
    
      glOrtho(-1 * aspectRatio, 1 * aspectRatio, -1, 1, 1, -1);
      gluPerspective(75, aspectRatio, 0.1, 400000000);
    
      // Initialize Modelview Matrix
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }
  6. After that, we need to alter the paintGL() function as well. First, add GL_DEPTH_BUFFER_BIT to the glClear() function, because we also need to clear the depth information for the previous frame before we proceed to render the next frame. Then, remove the code we used in the previous example, which rendered a quad and a triangle on the screen:
    void MainWindow::paintGL()
    {
      // Initialize clear color (cornflower blue)
      glClearColor(0.39f, 0.58f, 0.93f, 1.f);
    
      // Clear color buffer
      glClear(GL_COLOR_BUFFER_BIT);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
      glBegin(GL_QUADS);
      glColor3f(1.f, 0.f, 0.f); glVertex2f(-0.8f, -0.8f);
      glColor3f(1.f, 1.f, 0.f); glVertex2f(0.3f, -0.8f);
      glColor3f(0.f, 1.f, 0.f); glVertex2f(0.3f, 0.3f);
      glColor3f(0.f, 0.f, 1.f); glVertex2f(-0.8f, 0.3f);
      glEnd();
    
      glBegin(GL_TRIANGLES);
      glColor3f(1.f, 0.f, 0.f); glVertex2f(-0.4f, -0.4f);
      glColor3f(0.f, 1.f, 0.f); glVertex2f(0.8f, -0.1f);
      glColor3f(0.f, 0.f, 1.f); glVertex2f(-0.1f, 0.8f);
      glEnd();
    
      glFlush();
    }
  7. Then, before calling glFlush(), we will add the following code to draw a 3D cube:
    // Reset modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    // Transformations
    glTranslatef(0.0, 0.0, -3.0);
    glRotatef(rotation, 1.0, 1.0, 1.0);
    
    // FRONT
    glBegin(GL_POLYGON);
      glColor3f(0.0, 0.0, 0.0);
      glVertex3f(0.5, -0.5, -0.5); glVertex3f(0.5, 0.5, -0.5);
      glVertex3f(-0.5, 0.5, -0.5); glVertex3f(-0.5, -0.5, -0.5);
    glEnd();
    
    // BACK
    glBegin(GL_POLYGON);
      glColor3f(0.0, 1.0, 0.0);
      glVertex3f(0.5, -0.5, 0.5); glVertex3f(0.5, 0.5, 0.5);
      glVertex3f(-0.5, 0.5, 0.5); glVertex3f(-0.5, -0.5, 0.5);
    glEnd();
    
    // RIGHT
    glBegin(GL_POLYGON);
      glColor3f(1.0, 0.0, 1.0);
      glVertex3f(0.5, -0.5, -0.5); glVertex3f(0.5, 0.5, -0.5);
      glVertex3f(0.5, 0.5, 0.5); glVertex3f(0.5, -0.5, 0.5);
    glEnd();
    
    // LEFT
    glBegin(GL_POLYGON);
      glColor3f(1.0, 1.0, 0.0);
      glVertex3f(-0.5, -0.5, 0.5); glVertex3f(-0.5, 0.5, 0.5);
      glVertex3f(-0.5, 0.5, -0.5); glVertex3f(-0.5, -0.5, -0.5);
    glEnd();
    
    // TOP
    glBegin(GL_POLYGON);
      glColor3f(0.0, 0.0, 1.0);
      glVertex3f(0.5, 0.5, 0.5); glVertex3f(0.5, 0.5, -0.5);
      glVertex3f(-0.5, 0.5, -0.5); glVertex3f(-0.5, 0.5, 0.5);
    glEnd();
    
    // BOTTOM
    glBegin(GL_POLYGON);
      glColor3f(1.0, 0.0, 0.0);
      glVertex3f(0.5, -0.5, -0.5); glVertex3f(0.5, -0.5, 0.5);
      glVertex3f(-0.5, -0.5, 0.5); glVertex3f(-0.5, -0.5, -0.5);
    glEnd();
  8. Once you are done with that, add a timer to the construction of the MainWindow class, like so:
    MainWindow::MainWindow(QWidget *parent)
    {
      setSurfaceType(QWindow::OpenGLSurface);
      QSurfaceFormat format;
      format.setProfile(QSurfaceFormat::CompatibilityProfile);
      format.setVersion(2, 1); // OpenGL 2.1
      setFormat(format);
    
      context = new QOpenGLContext;
      context->setFormat(format);
      context->create();
      context->makeCurrent(this);
    
      openGLFunctions = context->functions();
    
      QTimer *timer = new QTimer(this);
      connect(timer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
      timer->start(100);
    
      rotation = 0;
    }
  9. Lastly, we increase the rotation variable by 10 every time the updateAnimation() slot is called by the timer. We also manually call the update() function to update the screen:
    void MainWindow::updateAnimation()
    {
      rotation += 10;
      this->update();
    }
  10. If you compile and run the program now, you should see a spinning cube in your main window!
    How to do it…

How it works...

In any 3D rendering, depth is very important and thus we need to enable the depth testing feature in OpenGL by calling glEnable(GL_DEPTH_TEST). When we clear the buffer, we also must specify GL_DEPH_BUFFER_BIT so that the depth information is also being cleared, in order for the next image to be rendered correctly.

We use gluPerspective() to set up a perspective projection matrix so that the graphics appear to have depth and distance. The opposite to the perspective view is the orthographic view, which is the default view in OpenGL, and we have used it in our previous example. Orthographic projection is a form of parallel projection where objects appear to be flat and do not suggest depth and distance:

How it works...

In this example, we used a timer to increase the rotation value by 10 every 100 milliseconds (0.1 second). The rotation value is then applied to the cube by calling glRotatef() before supplying the vertex data to OpenGL. We also called glTranslatef() to move the cube slightly to the back so that it's not too close to the camera view.

Remember to call update() manually so that the screen gets refreshed, otherwise the cube will not be animated.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.83.151