百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Qt编程进阶(99):使用OpenGL绘制三维图形

haoteby 2025-06-15 16:22 16 浏览

一、Qt中的OpenGL支持

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

从Qt 4开始,可以在QGLWidget上使用QPainter,尽管它还只是一个普通的窗口部件。在QGLWidget上使用QPainter的好处是可以使用OpenGL高效地绘图,例如进行坐标变换以及像素映射绘制。使用QPainter的另一个好处是可以使用其二维的高级API,并结合OpenGL调用来绘制三维图形。

使用QGLWidget可以在场景中使用OpenGL作为后端绘制三维场景。为了在硬件加速的离屏表面上绘制,可以使用pbuffer和framebuffer对象进行扩展,它们分别在QGLPixelBuffer和QGLFramebufferObject类中。

在应用程序中绘制OpenGL是很简单的:派生QGLWidget,实现几个虚函数,连接QtOpenGL和OpenGL库。由于QGLWidget派生自QWidget,许多已知的技术仍然适用。最大的区别是要使用标准的OpenGL函数替代QPainter来实现绘制。

为了使应用程序正确连接QtOpenGL模块和系统的OpenGL库,需要在.pro文件中添加这两项:

QT += opengl
LIBS += -lopengl32 -lglu32

本文介绍一个简单的应用程序,该程序使用这一技术绘制一个三角锥,用户可以使用鼠标与其交互。

二、Tetrahedron类

1.Tetrahedron类的定义

为了介绍QGLWidget的用法,我们编写一个Tetrahedron应用程序。该应用程序可以显示一个三维的三角锥(tetrahedron),或者也可以称为四面体,它的每个面都具有不同的颜色。用户可以通过单击并拖动鼠标来旋转它。用户也可以通过双击某个面,并从弹出的QColorDialog中选择一种颜色来设置这个面的颜色。

class Tetrahedron : public QGLWidget
{
	Q_OBJECT
public:
  Tetrahedron(QWidget *parent = nullptr);
  ~Tetrahedron();
protected:
  void initializeGL();
  void resizeGL(int w, int h);
  void paintGL();
  void mousePressEvent(QMouseEvent *event);
  void mouseMoveEvent(QMouseEvent *event);
  void mouseDoubleClickEvent(QMouseEvent *event);
private:
  void draw();
  int faceAtPosition(const QPoint &pos);
  GLfloat rotationX;
  GLfloat rotationY;
  GLfloat rotationZ;
  QColor faceColors[4];
  QPoint lastPos;
};

Tetrahedron类从QGLWidget中派生而来。initializeGL ()、resizeGL()和paintGL()这三个函数是在QGLWidget中实现的。鼠标事件处理函数是在QWidget中实现。

2.OpenGL设置

首先是构造函数:

Tetrahedron::Tetrahedron(QWidget *parent)
: QGLWidget(parent)
{
  setFormat(QGLFormat(QGL::DoubleBuffer | QGL::DepthBuffer));
  rotationX = -21.0;
  rotationY = -57.0;
  rotationZ = 0.0;
  faceColors[0]=Qt::red;
  faceColors[1] = Qt::green;
  faceColors[2] = Qt::blue;
  faceColors[3] = Qt::yellow;
}

在构造函数中,调用QGLWidget::setFormat()指定OpenGL的显示描述表,并且初始化私有变量。

void Tetrahedron::initializeGL()
{
  qglClearColor(Qt::black);
  glShadeModel(GL_FLAT);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
}

initializeGL()函数在调用paintGL()之前只被调用一次。可以在这里设置OpenGL的绘图描述表,定义显示列表,以及执行其他的初始化。

所有的代码都是标准的OpenGL,除了对QGLWidget的qglClearColor()函数的调用。如果想坚持使用标准的OpenGL,则可以在RGBA模式下调用glClearColor(),而在颜色索引模式下调用glClearlndex()。

void Tetrahedron::resizeGL(int w, int h)
{
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  GLfloat x=GLfloat(w)/h;
  glFrustum(-x,+x,-1.0,+1.0,4.0,15.0);
  glMatrixMode(GL_MODELVIEW);
}

应在第一次调用paintGL()之前,但在initializeGL()之后调用resizeGL()函数。在窗口部件改变大小时也将调用resizeGL()函数。在这里可以设值OpenGL视口、投影以及其他窗口部件尺寸相关的设置。

void Tetrahedron::paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  draw();
}

在窗口部件需要重绘时调用paintGL()函数。它与QWidget::PaintEvent()类似,使用OpenGL函数代替QPainter函数。实际的绘制由私有函数draw()实现。

void Tetrahedron::draw()
{
  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(rotationX,1.0,0.0,0.0);
  glRotatef(rotationY,0.0,1.0,0.0);
  glRotatef(rotationZ,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();
  }
}

在draw()中,我们绘制三角锥,应用x、y和z轴的旋转以及保存在数组中的颜色。每个调用都是标准的OpenGL,除了qglColor()函数。可以根据颜色模式使用OpenGL函数glColor3d()或者glIndex()。

3.事件处理

void Tetrahedron::mouseMoveEvent(QMouseEvent *event)
{
  GLfloat dx = GLfloat(event->x() - lastPos.x()) / width();
  GLfloat dy = GLfloat(event->y() - lastPos.y()) / height();
  if (event->buttons() & Qt::LeftButton) {
    rotationX += 180 * dy;
    rotationY += 180 * dx;
    updateGL();
  }else if(event->buttons() & Qt::RightButton) {
    rotationX += 180*dy;
    rotationZ += 180*dx;
    updateGL();
  }
  lastPos = event->pos();
}

mousePressEvent()和mouseMoveEvent()函数在QWidget中实现,用户可以通过单击并拖动鼠标旋转视图。鼠标左键可以沿着x和y轴旋转,右键可以沿着x和z轴旋转。

当rotationX,rotationY和rotationZ其中一个变量的值改变时,我们调用updateGL()重绘场景。

void Tetrahedron::mouseDoubleClickEvent(QMouseEvent *event)
{
  int face = faceAtPosition(event->pos());
  if(face != -1) {
  	QColor color = QColorDialog::getColor(faceColors[face], this);
    if(color.isValid()) {
      faceColors[face] = color;
      updateGL();
    }
  }
}

mouseDoubleClickEvent()在QWidget中实现,用户可以双击三角锥的面设置其颜色。调用私有函数faceAtPosition()确定光标下的面。如果用户双击了某个面,就调用QColorDialog::getColor()为该面获取一种新的颜色。然后,使用新颜色更新faceColor数组,并且调用updateGL()重绘场景。

int Tetrahedron::faceAtPosition(const QPoint &pos)
{
  const int MaxSize = 512;
  GLuint buffer[MaxSize];
  GLint viewport[4];
  makeCurrent();
  glGetIntegerv(GL_VIEWPORT, viewport);
  glSelectBuffer(MaxSize, buffer);
  glRenderMode(GL_SELECT);
  glInitNames();
  glPushName(0);
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluPickMatrix(GLdouble(pos.x()), GLdouble(viewport[3]-pos.y()),
  	5.0, 5.0, viewport);
  GLfloat x = GLfloat(width()/height());
  glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0);
  draw();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  if(!glRenderMode(GL_RENDER))
  	return -1;
  return buffer[3];
}

faceAtPosition()函数返回窗口部件某位置所处的面的编号,如果没有面就返回-1。确定被选面的代码在OpenGL中有点复杂。事实上,我们以GL_SELECT模式绘制场景,使用OpenGL的选择功能,从OpenGL的命中记录中读取面编号(它的“名字”)。代码是标准的代码,除了起初的QGLWidget::makeCurrent()调用,它可以确保我们正确地使用OpenGL描述表。[QGLWidget在调用initializeGL()、resizeGL()或paintGL()之前会自动调用它,在Tetrahedron实现的其他地方则不需要调用它。]

三、tetrahedron的调用

以下是该应用程序的main()函数:

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  if(!QGLFormat::hasOpenGL()){
    std::cerr<<"This system has no OpenGL support"<<std::endl;
    return 1;
  }
  Tetrahedron tetrahedron;
  tetrahedron.setWindowTitle("Tetrahedron");
  tetrahedron.resize(300,300);
  tetrahedron.show();
  return a.exec();
}

如果用户的系统不支持OpenGL,则会在控制台打印错误信息并立即返回。

这就完成了Tetrahedron应用程序。

——————————————————

对于本文实例完整代码有需要的朋友,可关注并在评论区留言!

相关推荐

谷歌开源大模型评测框架正式发布,AI模型评测难题迎刃而解

近日科技巨头谷歌正式推出其开源大模型评测框架LMEval,这一创新工具为全球AI开发者和企业提供了标准化的模型评估解决方案。LMEval的发布不仅标志着AI模型评测迈入透明化时代,更通过多项核心技术...

Android 开发中文引导-动画和图形概述

安卓系统提供了各种强大的API,用来将动画应用于界面元素和自定义2D和3D图形的绘制当中。下面的小节大概的描述了可用的API和系统功能并帮助你决定那个方案最适合你的需要。动画安卓框架提供了两种动画系统...

Qt5 C++入门教程-第12章 绘图(QPainter)

QPainter类在Qt5中进行绘图时起着重要作用。绘图操作是通过QPainter类在响应paintEvent方法时完成的。线条在第一个示例中,我们在窗口的客户区绘制了一些线条。line...

文创测评︱《如意琳琅图籍》:本土原创解谜书的胜利?

设想这样一个场景,你打开一本书,就化身为乾隆三十六年紫禁城中的画画人周本,有一天你在故纸堆中找到一本神秘的《如意琳琅图籍》,踏上寻宝旅程,历经各种离奇复杂的故事……这是故宫与奥秘之家联手打造的创意解谜...

gif动图制作攻略!快快收藏(求gif制作的动图)

有事没事斗图玩是当下人们乐此不疲的事情,手里的gif动图也渐渐成为了人们抬杠互怼的一大资本。好有趣,好炫酷,gif是怎么做出来的?我也想做。什么?你不会?没关系,我来教你!首先介绍一下制作gif动图需...

eduis未能初始化界面 无法启动 问题解决办法

1.如果edius安装后启动后出现failedtoinitializeskin中文提示无法初始化界面的错误。这说明你的电脑安装了双显卡,而edius所使用的是图形显卡。可以选择edius图标右键...

写真一周:水岛美结水着解禁、长滨祢瑠婚纱写真等

...

Flash Player模拟器更新:Rufffle(flash模拟器安卓下载高版本)

Ruffle是一个适用于WindowsPC的FlashPlayer模拟器,用Rust编写。Ruffle作为一个独立的应用程序在所有现代操作系统上原生运行,并通过使用WebAssembly在所有现代...

支持终身免费4G流量,星星充电7kW星际智能交流充电桩拆解

前言近期星星充电推出了一款星际智能交流充电桩,在正面设有灯条,可根据灯条颜色和显示直观了解充电状态,并设有屏幕显示充电状态和ui表情。充电桩支持220V/7kW充电功率,适配主流新能源车型。并支持终身...

乐动随心之fancy pop(乐动随心壶多少钱一个)

跳动飞扬的音符像是连通人与人之间心电感应的通关密码,融化陌生,拉近彼此。此次我们邀请到宅男女神江语晨,化身音乐精灵。在歌手、演员身份间游刃自如的她,为我们生动诠释了三种不同的音乐时尚风格,娴静可爱,灵...

Asus Zenflash 手机也能玩引闪,从此相机是路人

在讲解Zenflash之前,不得不提索爱的K750c,这个机器采用了氙气闪光灯,让手机的拍摄上了档次,可玩性更高,不过,说实话,当时手机的摄像头像素低,成像一般,没有掀起太大的波澜,可现在,手机的Cm...

Axure有哪些鲜为人知的使用技巧?(axure的使用教程)

阿拓带你飞:不管是想入门产品经理还是已经是PM的人对AXURE都很关注,它是制作产品原型的重要工具,但是有多少人了解AXURE的使用技巧?本文是来自“知乎问答”整理的回答,一起来看看那些不常用的使用技...

挑战黑夜 华硕ZenFlash氙气闪光灯评测

【机锋配件】说到摄影,相信许多朋友都非常喜欢,不管是外出游玩拍拍风景,还是和朋友之间聚会,都会掏出手机拍两张,在餐前拍照晒朋友圈更是成为了许多用户的日常爱好,就算不是专业的摄影爱好者,大家也都有一颗热...

WPS 演示倒计时 3 步设置!从数字动画到进度条全场景教程

做PPT时想添加倒计时却找不到入口?WPS演示自带的"动画+计时"功能就能轻松实现——无论是课堂互动的30秒答题倒计时、商务汇报的5分钟限时讲解,还是活动暖场的动...

flash动画an制作MG动画元素如何调节透明度,小白...

如何在flash动画软件里面调节mg动画元素的透明?因为flash动画软件现在已经升级为flash动画软件,所以直接用新版flash动画软件开工,基本功能都差不多,只是flash增加很多智能化、人性...