概要
本示例分别使用native(CPU)方式和OpenGL(GPU)方式繪制同樣一個動畫。
QPainter類用于将二維圖原繪制到QPaintDevice子類(如QWidget和QImage)提供的繪制裝置上。由于QGLWidget是QWidget的一個子類,是以可以重新實作其paintEvent()并使用QPainter在裝置上繪制,就像使用QWidget一樣。唯一的差別是,如果系統的OpenGL驅動程式支援,則繪制操作将在硬體中加速。在本例中,我們對QWidget和QGLWidget執行相同的繪制操作。QWidget啟用了抗鋸齒,如果系統的OpenGL驅動程式支援所需的擴充,QGLWidget也将使用抗鋸齒。我們派生了QWidget和QGLWidget的子類,使用一個單獨的Helper類為它們執行相同的繪制操作,并将它們放置在一個頂級widget中,它本身提供了一個視窗類。
OpenGL方式中,展示了QPainter和QGLWidget如何一起使用,在支援的硬體上顯示加速的2D圖形。

從上圖看不出太大的差別,對于簡單的二維圖像來說,使用Native方式更加清晰。實際上兩種方式,背後的實作方式有着巨大的差異。
下面我們将原執行個體分成兩個獨立的程式。我們把需要繪制的圓改為1000個,對兩個應用分别進行測試,觀察下面兩張圖,會發現巨大的性能差異:
實作步驟
建立工程
Helper
類定義
在本例中,繪制操作由Helper類執行。我們這樣做是因為我們希望對QWidget子類和QGLWidget子類執行相同的繪制操作。
#ifndef HELPER_H
#define HELPER_H
#include <QBrush>
#include <QFont>
#include <QPen>
#include <QWidget>
class Helper
{
public:
Helper();
public:
//除了構造函數之外,它隻提供了一個paint()函數
//使用QWidget子類提供的painter來進行繪制。
void paint(QPainter *painter, QPaintEvent *event, int elapsed);
private:
QBrush background;
QBrush circleBrush;
QFont textFont;
QPen circlePen;
QPen textPen;
};
#endif
類實作
#include "helper.h"
#include <QPainter>
#include <QPaintEvent>
#include <QWidget>
Helper::Helper()
{
//線性漸變的一種顔色
//僅僅了解為一種顔色就好
QLinearGradient gradient(QPointF(50, -20), QPointF(80, 20));
gradient.setColorAt(0.0, Qt::white);
gradient.setColorAt(1.0, QColor(0xa6, 0xce, 0x39));
background = QBrush(QColor(64, 32, 64));
circleBrush = QBrush(gradient);
circlePen = QPen(Qt::black);
circlePen.setWidth(1);
textPen = QPen(Qt::white);
textFont.setPixelSize(50);
}
//實際的繪制是在paint函數中執行的。
//painter:已經設定好繪制裝置(QWidget或QGLWidget)的QPainter
//event:提供有關要繪制區域的資訊的QPaintEvent
//elapsed:自上次更新繪圖裝置以來,經過的時間(毫秒)
//繪制圓形的螺旋圖案,使用指定的經過時間設定動畫,使它們圍繞坐标系的原向外移動:
void Helper::paint(QPainter *painter, QPaintEvent *event, int elapsed)
{
//在轉換坐标系原點之前,我們先填充“event”事件中包含的區域
painter->fillRect(event->rect(), background);
//将原點移到正中心
painter->translate(100, 100);
//坐标系被多次旋轉,我們預先儲存QPainter的狀态,然後再還原它。
painter->save();//儲存狀态
painter->setBrush(circleBrush);
painter->setPen(circlePen);
//![動畫]
//這兩條語句,通過elapsed産生動畫效果
painter->rotate(elapsed * 0.030);
qreal r = elapsed / 1000.0;
//![動畫]
int n = 30;
for (int i = 0; i < n; ++i) {
//在循環内每次旋轉30度,使得○有合适的間隔
painter->rotate(30);
qreal factor = (i + r) / n;
//radiu在循環内,每次增大20%,得到○裡原點原來越遠
qreal radius = 0 + 120.0 * factor;
qreal circleRadius = 1 + factor * 20;
painter->drawEllipse(QRectF(radius, -circleRadius,
circleRadius * 2, circleRadius * 2));
}
painter->restore();//恢複狀态
painter->setPen(textPen);
painter->setFont(textFont);
painter->drawText(QRect(-50, -50, 100, 100), Qt::AlignCenter, QStringLiteral("Qt"));
}
在構造函數中完成了一些初始值的設定。去除構造函數的内容,觀察預設值的效果如下:
Widget類
類定義
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Helper;
//提供一個自定義QWidget,顯示由Helper類繪制的簡單動畫
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(Helper *helper, QWidget *parent);
public slots:
//設定動畫
void animate();
protected:
//繪制自定義内容
void paintEvent(QPaintEvent *event) override;
private:
Helper *helper;
int elapsed;
};
#endif
類實作
#include "widget.h"
#include "helper.h"
#include <QPainter>
#include <QTimer>
Widget::Widget(Helper *helper, QWidget *parent)
: QWidget(parent), helper(helper)
{
elapsed = 0;
setFixedSize(200, 200);
}
//當我們稍後定義的計時器逾時時調用
void Widget::animate()
{
//更新elapse的值,加上自計時器上次逾時以來經過的時間間隔
//每秒循環一次
elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
//update會觸發paintEvent
update();
}
//由于Helper類完成了所有實際的繪制,我們隻需實作一個繪制事件,
//該事件為widget設定一個QPainter,,并調用Helper的paint()函數:
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter;
// 設定painter,QPainter::begin(QPaintDevice *device)
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
helper->paint(&painter, event, elapsed);
painter.end();
}
GLWidget類
類定義
GLWidget類定義基本上與Widget類相同,隻是它派生自QGLWidget。
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
class Helper;
class GLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
GLWidget(Helper *helper, QWidget *parent);
public slots:
void animate();
protected:
void paintEvent(QPaintEvent *event) override;
private:
Helper *helper;
int elapsed;
};
#endif
類實作
#include "glwidget.h"
#include "helper.h"
#include <QPainter>
#include <QTimer>
GLWidget::GLWidget(Helper *helper, QWidget *parent)
: QOpenGLWidget(parent), helper(helper)
{
elapsed = 0;
setFixedSize(200, 200);
setAutoFillBackground(false);
}
void GLWidget::animate()
{
elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
update();
}
void GLWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
helper->paint(&painter, event, elapsed);
painter.end();
}
Window類
類定義
#ifndef WINDOW_H
#define WINDOW_H
#include "helper.h"
#include <QWidget>
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private:
//它包含一個将在所有widget之間共享的Helper對象。
//widget中包含的是指針
Helper helper;
};
#endif
類實作
#include "glwidget.h"
#include "widget.h"
#include "window.h"
#include <QGridLayout>
#include <QLabel>
#include <QTimer>
Window::Window()
{
setWindowTitle(tr("2D Painting on Native and OpenGL Widgets"));
//建立每種類型的widget,并将它們與标簽一起插入到布局中
Widget *native = new Widget(&helper, this);
GLWidget *openGL = new GLWidget(&helper, this);
QLabel *nativeLabel = new QLabel(tr("Native"));
nativeLabel->setAlignment(Qt::AlignHCenter);
QLabel *openGLLabel = new QLabel(tr("OpenGL"));
openGLLabel->setAlignment(Qt::AlignHCenter);
QGridLayout *layout = new QGridLayout;
layout->addWidget(native, 0, 0);
layout->addWidget(openGL, 0, 1);
layout->addWidget(nativeLabel, 1, 0);
layout->addWidget(openGLLabel, 1, 1);
setLayout(layout);
//設定一個周期為50毫秒的定時器,出發widget的動畫
//animate裡改變畫面的參數,調用update出發widget的重新繪制
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, native, &Widget::animate);
connect(timer, &QTimer::timeout, openGL, &GLWidget::animate);
timer->start(50);
}
main函數
#include "window.h"
#include <QApplication>
#include <QSurfaceFormat>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//OpenGL設定
//格式包括顔色緩沖區、alpha緩沖區、深度和模闆緩沖區的大小;
//以及多個采樣設定中每像素的采樣數。這裡是為了設定采樣數。
QSurfaceFormat fmt;
//為了抗鋸齒,需要采用對每個像素進行多重采樣,這裡采樣數設定為4
fmt.setSamples(64);
QSurfaceFormat::setDefaultFormat(fmt);
Window window;
window.show();
return app.exec();
}