Accelerate your Widgets with OpenGL

10 篇文章 0 订阅

by Samuel Rødal

Qt 4.4 introduced a powerful feature to allow any QWidget subclass to be put into QGraphicsView. It is now possible to embed ordinary widgets in a QGLWidget, a feature that has been missing in Qt's OpenGL support for some time. In this article, we will show how to use QGraphicsView to place widgets in a scene rendered with standard OpenGL commands.

In some applications, such as those used in Computer Aided Design (CAD), or in applications that use OpenGL rendering to improve performance, being able to render widgets inside a QGLWidget is a desirable feature. For example, using semi-transparent widgets on top of an OpenGL-rendered scene can better utilize screen real estate. Also, widgets can be used as tooltips or labels to annotate objects in a 3D scene.

One way to achieve this is to perform OpenGL drawing in a QGraphicsView , as opposed to the traditional way of subclassing QGLWidget and overriding its paintGL() function.

To demonstrate this technique, we will use a model viewer which loads a 3D model stored in the .obj file format and renders it using OpenGL. On top of this model, we will draw a set of widgets to control the rendering parameters and display various bits of information about the model. This is done simply by adding the widgets to the scene.

Openglwidgets2

Turbo Charging Graphics View

In our example, the actual OpenGL scene rendering and widget controls will be handled in a QGraphicsScene subclass, but first we need to correctly set up a QGraphicsView with a QGLWidget viewport.

Since we want our widgets to be positioned relative to the view coordinates, we need to keep the scene rectangle synchronized with the size of the QGraphicsView . To achieve this, we create a simple subclass of QGraphicsView which updates the scene's rectangle whenever the view itself is resized.

The custom GraphicsView class looks like this:

    class GraphicsView : public QGraphicsView

    {
    public:
        GraphicsView()
        {
            setWindowTitle(tr("3D Model Viewer"));
        }
    
    protected:
        void resizeEvent(QResizeEvent
 *event) {
            if (scene())
                scene()->setSceneRect(
                    QRect
(QPoint
(0, 0), event->size()));
            QGraphicsView
::resizeEvent(event);
        }
    };

In our main function we instantiate this class and set the QGraphicsView parameters that are needed in our case. First of all the viewport needs to be a QGLWidget in order to do OpenGL rendering in our graphics scene. We use the SampleBuffers format specifier to enable multisample anti-aliasing in our rendering code.

    int main(int argc, char **argv)
    {
        QApplication
 app(argc, argv);
    
        GraphicsView view;
        view.setViewport(new QGLWidget
(
            QGLFormat
(QGL
::SampleBuffers)));
        view.setViewportUpdateMode(
            QGraphicsView
::FullViewportUpdate);
        view.setScene(new OpenGLScene);
        view.show();
    
        view.resize(1024, 768);
        return app.exec();
    }

Next, we set the viewport update mode of the QGraphicsView to FullViewportUpdate as a QGLWidget cannot perform partial updates. Thus, we need to redraw everything whenever a part of the scene changes. We set as the scene an instance of our OpenGLScene class, a subclass of QGraphicsScene , and resize the view to a decent size.

Rendering the OpenGL Scene

The actual OpenGL rendering is done by reimplementing QGraphicsScene 's drawBackground() function. By rendering the OpenGL scene in drawBackground() , all widgets and other graphics items are drawn on top. It is possible to reimplement drawForeground() to do OpenGL rendering on top of the graphics scene, if that is required.

Unlike when subclassing QGLWidget , it is not sufficient to set up common state in initializeGL() or resizeGL() . A painter is already active on the GL context when drawBackground() is called, and QPainter changes the GL state when begin() is called. For example, the model view and projection matrices will be changed so that we have a coordinate system that maps to the QWidget coordinate system.

Here is how our reimplemented drawBackground() looks:

    void OpenGLScene::drawBackground(QPainter
 *painter,
                                     const QRectF
 &)
    {
        if (painter->paintEngine()->type()
                != QPaintEngine
::OpenGL) {
            qWarning("OpenGLScene: drawBackground needs a "
                     "QGLWidget
 to be set as viewport on the "
                     "graphics view");
            return;
        }

First, we ensure that we actually have an OpenGL paint engine, otherwise we issue a warning and return immediately.

Since the painter passed to drawBackground() is already active we know that we have an active GL context, and we are ready to issue GL commands. We start by clearing the background and performing any setup that is required, such as initializing the matrices.

      glClearColor(m_backgroundColor.redF(),
                     m_backgroundColor.greenF(),
                     m_backgroundColor.blueF(), 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        if (!m_model)
            return;
    
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        gluPerspective(70, width() / height(), 0.01, 1000);
    
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();

Once the matrices have been set up, we position the light and render the model. Afterwards, we restore the matrices and schedule a repaint.

      const float pos[] = { m_lightItem->x() - width() / 2,
                              height() / 2 - m_lightItem->y(),
                              512, 0 };
        glLightfv(GL_LIGHT0, GL_POSITION, pos);
        glColor4f(m_modelColor.redF(), m_modelColor.greenF(),
                  m_modelColor.blueF(), 1.0f);
    
        const int delta = m_time.elapsed() - m_lastTime;
        m_rotation += m_angularMomentum * (delta / 1000.0);
        m_lastTime += delta;
    
        glTranslatef(0, 0, -m_distance);
        glRotatef(m_rotation.x, 1, 0, 0);
        glRotatef(m_rotation.y, 0, 1, 0);
        glRotatef(m_rotation.z, 0, 0, 1);
    
        glEnable(GL_MULTISAMPLE);
        m_model->render(m_wireframeEnabled, m_normalsEnabled);
        glDisable(GL_MULTISAMPLE);
    
        glPopMatrix();
    
        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
    
        QTimer
::singleShot(20, this, SLOT(update()));
    }

We will not look further into how the actual 3D model is rendered as it is beyond the scope of this article---more details can be found in the source code available on the Qt Quarterly Web site.

Embedding Widgets in the Scene

In our OpenGLScene constructor, which we won't quote here, we add the widgets and graphics items to control rendering parameters. In order to get a window frame for widgets that are embedded in the graphics scene, we construct them using QDialog , with the CustomizeWindowHint and WindowTitleHint flags set, disabling their close buttons.

The helper function used for this is as follows:

    QDialog
 *OpenGLScene::createDialog(
             const QString
 &windowTitle) const
    {
        QDialog
 *dialog = new QDialog
(0,
            Qt::CustomizeWindowHint | Qt::WindowTitleHint);
    
        dialog->setWindowOpacity(0.8);
        dialog->setWindowTitle(windowTitle);
        dialog->setLayout(new QVBoxLayout
);
    
        return dialog;
    }

Setting the window opacity causes the embedded widgets to be slightly transparent, which makes it possible to discern OpenGL scene elements that are partially hidden behind the control widgets. We also use setWindowTitle() to add a title to each embedded widget's window frame.

Subwidgets are added to each embedded widget by creating a layout and adding widgets to it before using QGraphicsScene 's addWidget() function to embed the widget in the scene. Here's how the instructions widget is created and added to the scene:

    QWidget
 *instructions = createDialog(tr("Instructions"));
    instructions->layout()->addWidget(new QLabel
(
        tr("Use mouse wheel to zoom model, and click and "
           "drag to rotate model")));
    instructions->layout()->addWidget(new QLabel
(
        tr("Move the sun around to change the light "
           "position")));
    ...
    addWidget(instructions);

Once we have embedded all the widgets in the graphics scene, we iterate through the resulting QGraphicsItem s and position them beneath each other in the left part of the view. We use the ItemIsMovable flag to allow the user to move the widgets around with the mouse, and we set the cache mode to DeviceCoordinateCache so that widgets are cached in QPixmap s. These pixmaps are then mapped to OpenGL textures, making the widgets very cheap to draw when they are not being updated.

    QPointF
 pos(10, 10);
    foreach (QGraphicsItem
 *item, items()) {
        item->setFlag(QGraphicsItem
::ItemIsMovable);
        item->setCacheMode(
            QGraphicsItem
::DeviceCoordinateCache);
    
        const QRectF
 rect = item->boundingRect();
        item->setPos(pos.x() - rect.x(), pos.y() - rect.y());
        pos += QPointF
(0, 10 + rect.height());
    }

For event handling, we need to reimplement the relevant function in QGraphicsScene . Here is how our mouse move event handling is done:

    void OpenGLScene::mouseMoveEvent(
                      QGraphicsSceneMouseEvent
 *event)
    {
        QGraphicsScene
::mouseMoveEvent(event);
        if (event->isAccepted()) return;
        if (event->buttons() & Qt::LeftButton) {
            const QPointF
 delta =
                event->scenePos() - event->lastScenePos();
            const Point3d angularImpulse =
                Point3d(delta.y(), delta.x(), 0) * 0.1;
    
            m_rotation += angularImpulse;
            m_accumulatedMomentum += angularImpulse;
    
            event->accept();
            update();
        }
    }

Since we are handling events for widgets in a graphics scene, we use QGraphicsSceneMouseEvent instead of QMouseEvent . We begin by calling the base class implementation to give other items on top of the OpenGL scene the chance to handle events first. If the event has already been handled we return, otherwise we handle the event ourselves and call accept() .

Note that calling scenePos() on the QGraphicsSceneMouseEvent will give us the coordinates in the actual view as, earlier, we made sure to set the scene rectangle to match the view rectangle. QGraphicsSceneMouseEvent also provides the convenient lastScenePos() function which simplifies our code a bit. Finally we call update() to redraw the scene and background.

Wrapping Up

With the development of new underlying technologies in Qt, the Graphics View framework is becoming a focal point for new experiments in user interface design and a proving ground for initiatives to improve rendering performance.

The example code outlined in this article is part of a Qt Labs project:

http://labs.trolltech.com/page/Graphics/About/Dojo

A snapshot of the code from this project is available from the Qt Quarterly Web site .

 

转自:http://doc.qt.nokia.com/qq/qq26-openglcanvas.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值