概要
本示例分别使用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();
}