您的位置:首页 > 编程语言 > Qt开发

三维绘图之OpenGL和Qt的结合

2017-03-13 15:47 288 查看
 OpenGL是绘制三维图形的标准API。Qt应用程序可以使用QtOpenGL模块绘制三维图形,该模块依赖于系统的OpenGL库。Qt OpenGL模块提供QGLWidget类,可以通过对它子类化,并使用OpenGL命令开发出自己的窗口部件。对许多三维应用程序来说,这就足够了。 

        这节假设大家都已经学过OpenGL,这样我们就无后顾之忧了。 

        在Qt中绘制OpenGL通常需要做以下工作:1)、必须子类化QGLWidget;2)、实现几个虚函数:void initiallizeGL() 

void resizeGL(), void paintGL(), 这些都是在QGLWidget中实现的,还有一些和用户交互的虚函数,诸如void mouseMoveEvent()之类的,想必大家都比较熟了,这些虚函数是在Widget中实现的。  

         下面我们介绍一个例子。先给出该程序的效果:



 

        菜单栏里的第一项可以完成一个自定义大小的抓图,即由用户自己决定抓图的大小,抓图会显示在右侧的方框里,注意这里只能设置图形的大小小雨当前图形的尺寸, 如果大于当前图形尺寸,则钳位到当前图形尺寸。效果 看起来应该是这样:



 

        菜单栏第二项也是一个抓图功能,它返回一个当前图形尺寸的图形,并填充到右侧。 

        第三项即清除右侧图形。  

        这个代码由以下部件构成: 

        一个QMainWindow,我们通过子类化这个类来完成自己想要的一些功能。 

        一个QWidget,我们把它作为中央窗口,在其上添加自己想要的一些子部件。 

        两个QScrollBar,用来盛载一个QGLWidget和一个QLabel。 

        一个QGLWidget,我们通过子类化它并把它加进一个QScrollBar来实现三维绘图,即上图所示的左边窗口。 

        一个QLabel,同样,我们把这个QLabel加进一个QScrollBar来接收抓图后的显示效果。 

        三个QSlider,我们通过这三个滑动条控制所绘制的四面体沿x,y,z轴转动,同样鼠标拖动这个四面体也可以改变滑动条的值。 

        以上是整个程序的框架。 

        以下是代码的实现部分。 

        MainWindow 类定义了我们整个程序的框架:
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class QAction;
class QLabel;
class QMenu;
class QSlider;
class QScrollArea;

class GLWidget;

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = 0);
~MainWindow();

private slots:
void renderIntoPixmap();
void grabFrameBuffer();
void clearPixmap();
void about();

private:

void createMenus();
void createActions();

QSlider *createSlider(const char *changedSignal, const char *setterSlot);
void setPixmap(const QPixmap &pixmap);
QSize getSize();

QWidget *centralWidget;
QScrollArea *glWidgetArea;
QScrollArea *pixmapLabelArea;
GLWidget *glWidget;
QLabel *pixmapLabel;
QSlider *xSlider;
QSlider *ySlider;
QSlider *zSlider;

QMenu *fileMenu;
QMenu *helpMenu;
QAction *renderIntoPixmapAction;
QAction *grabFrameBufferAction;
QAction *clearPixmapAction;
QAction *exitAction;
QAction *aboutAction;
QAction *aboutQtAction;

};

#endif // MAINWINDOW_H


 

        以下是程序的实现部分:
//mainwindow.cpp
#include <QtOpenGL>
#include <QAction>
#include <QLabel>
#include <QMenu>
#include <QSlider>
#include <QScrollArea>
#include <QMenuBar>
#include <QApplication>

#include "mainwindow.h"
#include "glwidget.h"

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
centralWidget = new QWidget;
setCentralWidget(centralWidget);

glWidget = new GLWidget;
pixmapLabel = new QLabel;

glWidgetArea = new QScrollArea;
glWidgetArea->setWidget(glWidget);
//glWidgetArea->viewport()->setBackgroundRole(QPalette::Dark);
glWidgetArea->setWidgetResizable(true);
glWidgetArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
glWidgetArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
glWidgetArea->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
glWidgetArea->setMinimumSize(50, 50);

pixmapLabelArea = new QScrollArea;
pixmapLabelArea->setWidget(pixmapLabel);
pixmapLabelArea->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
pixmapLabelArea->setMinimumSize(50, 50);

//在构造一个QSlider时将QGLWidget的信号和槽传给这个函数的形参,这样就可以在QMainWindow中
//控制OpenGL的动作了,而让GLWidget类只完成绘图工作。
xSlider = createSlider(SIGNAL(xRotationChanged(int)),
SLOT(setXRotation(int)));
ySlider = createSlider(SIGNAL(yRotationChanged(int)),
SLOT(setYRotation(int)));
zSlider = createSlider(SIGNAL(zRotationChanged(int)),
SLOT(setZRotation(int)));

/*
xSlider = new QSlider(Qt::Horizontal);
ySlider = new QSlider(Qt::Horizontal);
zSlider = new QSlider(Qt::Horizontal);
*/

QGridLayout *centralLayout = new QGridLayout;
centralLayout->addWidget(glWidgetArea, 0, 0);
centralLayout->addWidget(pixmapLabelArea, 0, 1);
centralLayout->addWidget(xSlider, 1, 0, 1, 2);
centralLayout->addWidget(ySlider, 2, 0, 1, 2);
centralLayout->addWidget(zSlider, 3, 0, 1, 2);
centralWidget->setLayout(centralLayout);

createActions();
createMenus();

xSlider->setValue(15 * 16);
ySlider->setValue(345 * 16);
zSlider->setValue(0 * 16);

setWindowTitle(tr("Grabeer"));
resize(480, 360);
}

void MainWindow::setPixmap(const QPixmap &pixmap)
{
pixmapLabel->setPixmap(pixmap);
QSize size = pixmap.size();
if (size - QSize(1, 0) == pixmapLabelArea->maximumViewportSize())
size -= QSize(1, 0);
pixmapLabel->resize(size);
}

QSize MainWindow::getSize()
{
bool ok;
QString text = QInputDialog::getText(this, tr("Grabber"),
tr("Enter Pixmap Size:"),
QLineEdit::Normal,
tr("%1 x %2").arg(glWidget->width())
.arg(glWidget->height()),
&ok);
if (!ok)
return QSize();

QRegExp regExp(tr("([0-9]+) *x *([0-9]+)"));
if (regExp.exactMatch(text)) {
int width = regExp.cap(1).toInt();
int height = regExp.cap(2).toInt();
if (width > 0 && width < 2048 && height > 0 && height < 2048)
return QSize(width, height);
}
return glWidget->size();
}

void MainWindow::renderIntoPixmap()
{
QSize size = getSize();
if (size.isValid()) {
QPixmap pixmap = glWidget->renderPixmap(size.width(), size.height());
setPixmap(pixmap);
}
}

void MainWindow::grabFrameBuffer()
{
//QGLWidget有一个返回其帧缓冲区的QImage图片的函数
QImage image = glWidget->grabFrameBuffer();
//QPixmap的fromImage函数把一个QImage转换成QPixmap
setPixmap(QPixmap::fromImage(image));
}

void MainWindow::clearPixmap()
{
setPixmap(QPixmap()); //给它传一个空的对象
}

void MainWindow::about()
{
QMessageBox::about(this, tr("About Grabber"),
tr("The <b>Grabber</b> example demonstrates two approaches for "
"rendering OpenGL into a Qt pixmap."));
}

QSlider *MainWindow::createSlider(const char *changedSignal,
const char *setterSlot)
{
QSlider *slider = new QSlider(Qt::Horizontal);
slider->setRange(0, 16 * 360);
slider->setSingleStep(16);
slider->setPageStep(15 * 16);
slider->setTickInterval(15 * 16);
slider->setTickPosition(QSlider::TicksRight);

//这种经典的用法一定要小心,报错:glWidget的槽函数在传进来的时候已经被强制转换成SLOT了,
//所以setterSlot不用SLOT修饰;同样,changedSignal也不能再拿SIGNAL修饰
connect(slider, SIGNAL(valueChanged(int)), glWidget, setterSlot);
connect(glWidget, changedSignal, slider, SLOT(setValue(int)));

return slider;
}

void MainWindow::createActions()
{
renderIntoPixmapAction = new QAction(tr("&Render into Pixmap..."), this);
renderIntoPixmapAction->setShortcut(tr("Ctrl+R"));
renderIntoPixmapAction->setToolTip(tr("yes, triggerd it"));
connect(renderIntoPixmapAction, SIGNAL(triggered()),
this, SLOT(renderIntoPixmap()));

grabFrameBufferAction = new QAction(tr("&Grab Frame Buffer"), this);
grabFrameBufferAction->setShortcut(tr("Ctrl+G"));
connect(grabFrameBufferAction, SIGNAL(triggered()),
this, SLOT(grabFrameBuffer()));

clearPixmapAction = new QAction(tr("&Clear Pixmap"), this);
clearPixmapAction->setShortcut(tr("Ctrl+L"));
connect(clearPixmapAction, SIGNAL(triggered()), this, SLOT(clearPixmap()));

exitAction = new QAction(tr("E&xit"), this);
exitAction->setShortcuts(QKeySequence::Quit);
connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));

aboutAction = new QAction(tr("&About"), this);
connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));

aboutQtAction = new QAction(tr("About &Qt"), this);
connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}

void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(renderIntoPixmapAction);
fileMenu->addAction(grabFrameBufferAction);
fileMenu->addAction(clearPixmapAction);
fileMenu->addSeparator();
fileMenu->addAction(exitAction);

helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(aboutAction);
helpMenu->addAction(aboutQtAction);
}

MainWindow::~MainWindow()
{
}


        GLWidget类是整个绘图的部分,在这部分虽然用了一点Qt的东西,但我们不是非要这样使用,我们可以用整个纯净的OpengGL库来完成我们的绘制。
//glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>

class GLWidget : public QGLWidget
{
Q_OBJECT

public:
explicit GLWidget(QWidget *parent = 0);

int xRotation() const { return xRot; }
int yRotation() const { return yRot; }
int zRotation() const { return zRot; }

signals:
void xRotationChanged(int angle);
void yRotationChanged(int angle);
void zRotationChanged(int angle);

public slots:
void setXRotation(int angle);
void setYRotation(int angle);
void setZRotation(int angle);

protected:
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);

private slots:
void alwaysRotate();
void drawTriangle();

private:
void normalizeAngle(int *angle);

int xRot;
int yRot;
int zRot;

QColor faceColors[4];
QPoint lastPos;
};

#endif // GLWIDGET_H


        以下是代码实现部分:
//glwidget.cpp
#include <QtGui>
#include <QtOpenGL>

#include <math.h>

#include "glwidget.h"

GLWidget::GLWidget(QWidget *parent) :
QGLWidget(parent)
{
xRot = 0;
yRot = 0;
zRot = 0;

faceColors[0] = Qt::red;
faceColors[1] = Qt::green;
faceColors[2] = Qt::blue;
faceColors[3] = Qt::yellow;

QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(alwaysRotate()));
timer->start(70);
}

void GLWidget::initializeGL()
{
glClearColor(0.0, 0.2, 0.3, 1.0);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH);
}

void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();
drawTriangle();
glPopMatrix();
}

void GLWidget::resizeGL(int w, int h)
{
int side = qMin(w, h);
glViewport((width() - side) / 2, (height() - side) / 2, side, side);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(-1.2, 1.2, -1.2, 1.2, 5.0, 60.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -40.0);
}

void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}

void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
int dx = event->x() - lastPos.x();
int dy = event->y() - lastPos.y();

if (event->buttons() & Qt::LeftButton) {
(xRot + 4 * dx);
setYRotation(yRot + 4 * dy);
} else if (event->buttons() & Qt::RightButton) {
(xRot + 4 * dy);
setZRotation(zRot + 4 * dx);
}

lastPos = event->pos();
}

void GLWidget::drawTriangle()
{
static const GLfloat P1[3] = { 0.0, -1.0, +2.0 };
static const GLfloat P2[3] = { +1.73205081, -1.0, -1.0 };
static const GLfloat P3[3] = { -1.73205081, -1.0, -1.0 };
static const GLfloat P4[3] = { 0.0, +2.0, 0.0 };

static const GLfloat * const coords[4][3] = {
{ P1, P2, P3 }, { P1, P3, P4 }, { P1, P4, P2 }, { P2, P4, P3 }
};

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -10.0);
glRotatef(xRot, 1.0, 0.0, 0.0);
glRotatef(yRot, 0.0, 1.0, 0.0);
glRotatef(zRot, 0.0, 0.0, 1.0);

for (int i = 0; i != 4; ++i) {
//glLoadName(i);
glBegin(GL_TRIANGLES);
qglColor(faceColors[i]);
for (int j = 0; j < 3; ++j) {
glVertex3f(coords[i][j][0], coords[i][j][1],
coords[i][j][2]);
}
glEnd();
}
}

void GLWidget::normalizeAngle(int *angle)
{
while (*angle < 0)
angle += 360 * 16;
while (*angle > 360 *16)
angle -= 360 *16;
}

void GLWidget::setXRotation(int angle)
{
normalizeAngle(&angle);
if ( angle != xRot ) {
xRot = angle;
emit xRotationChanged(angle);
updateGL();
}
}

void GLWidget::setYRotation(int angle)
{
normalizeAngle(&angle);
if ( angle != yRot ) {
yRot = angle;
emit yRotationChanged(angle);
updateGL();
}
}

void GLWidget::setZRotation(int angle)
{
normalizeAngle(&angle);
if ( angle != zRot ) {
zRot = angle;
emit zRotationChanged(angle);
updateGL();
}
}

void GLWidget::alwaysRotate()
{
zRot += 2;
emit zRotationChanged(zRot);
updateGL();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: