来源 http://hi.baidu.com/dem_simulation/item/a66e688c81b172da5e0ec1cd
-
OpenGL 是渲染 3D 图形的标准 API 函数库。
-
在 Qt 应用程序中,可以使用 QtOpenGL 这个模块来实现 3D 图像的绘制。
-
QtOpenGL 模块对系统上的 OpenGL 库进行了封装,这个模块中提供一个 QGLWidget 类,任何由这个类派生的 widget 都能使用 OpenGL 命令绘制自己。对于许多的 3D 应用程序,这个类提供的功能已经相当足够了。
-
同时,在 QGLWidget 中使用 QPainter,这样做很方便,一方面可以发挥 OpenGL 在三维渲染方面的强大性能,同时还可以具备 QPainter 提供的高效的 2D 图形 API。
1. OpenGL 绘图
(1)将 3D 应用程序链接到 QtOpenGL 模块上,并加入 OpenGL 库,所以需要在 .pro 文件中加入:
QT += opengl
(2)由 QGLWidget 派生一个类;
(3)重载并实现 QGLWidget 中的 initializeGL( ) ,resizeGL( ),paintGL( ) 这三个虚函数;
2. 函数实现
(1)构造函数
Tetrahedron::Tetrahedron(QWidget *parent)
: QGLWidget(parent)
{
setFormat(QGLFormat(QGL::DoubleBuffer | QGL::DepthBuffer));
……
}
-
在 3D 应用程序的构造函数中,使用 QGLWidget : : setFormat( ) 来指定 OpenGL 的 DC(display context)。其中,通常需要设置画面的双缓冲和深度缓存:
-
setFormat( QGLFormat( QGL : : DoubleBuffer | QGL : : DepthBuffer ) ) ;
(2)重载 initializeGL 函数
-
函数的调用过程是:
-
initializeGL; // 只被调用一次
-
resizeGL;
-
paintGL;
-
任何引起窗口重绘的地方:paintGL;
-
任何引起尺寸变化的地方:resizeGL;
void Tetrahedron::initializeGL()
{
qglClearColor(Qt::black);
glShadeModel(GL_FLAT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
-
在 initializeGL 中,OpenGL 中的 glClearColor 函数被封装到:
-
qglClearColor( Color )
-
如果使用这个函数,就可以调用 Qt 中预定义的颜色标识如: Qt : : yellow 等等;
-
如果使用 glClearColor( ) 只只能使用 RGB 值的方式。
-
initializeGL 这个函数在调用 paintGL 之前首先被调用,并且只调用一次,这是初始化 OpenGL 的 RC (rendering context)、显示列表和其它初始化的地方,
(3)重载 resizeGL 函数
void Tetrahedron::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLfloat x = GLfloat(width) / height;
glFrustum(-x, +x, -1.0, +1.0, 4.0, 15.0);
glMatrixMode(GL_MODELVIEW);
}
-
在 paintGL 被调用之前,在 initializeGL 被调用之后,resizeGL 也会被调用一次;同时在 widget 被 resized 之后,这个函数也会被调用。
-
在这个函数中主要设置 OpenGL 的视口,视景体等物件。
(4)重载 paintGL 函数
void Tetrahedron::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw();
}
-
paintGL 则是在 widget 需要重绘时被调用,这个有点像是 QWidget : : paintEvent( ) 函数;
-
QGLWidget : : updateGL( ) 是虚 slot,调用时将直接引发 glDraw( ) 函数来更新 widget;
-
QGLWidget : : glDraw( ) 函数执行虚函数 paintGL( ) ;
-
QGLWidget : : paintGL( ) 函数是虚函数,如果 widget 需要重绘时,这个虚函数就会被调用;
-
在绘制 opengl 三维图形时,使用 opengl 命令前不必使用 makeCurrent( ) 函数,因为这个函数已加入到了 paintGL( ) 函数中去了。
-
其中,使用 glEnable( GL_MULTISAMPLE) 是允许开启“反走样”。
3. 三维图形与鼠标交互
一般的三维图形与鼠标的交互方法包括:
-
左(右)键点击并移动鼠标时,三维图形绕轴旋转;
-
中间滑轮滚动时,三维图形缩放。
这里主要需要重载三个事件:
(1)重载 void mousePressEvent( QMouseEvent* ) 事件
void Tetrahedron::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
-
通常用成员变量 QPoint,保存左键按下的那一点。
(2)重载 void mouseMoveEvent( QMouseEvent* ) 事件
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( ) ; // 如果是重载 paintEvent,则使用 update( ) ;
}
else if ( event->buttons( ) & Qt : : RightButton )
{
rotationX+ = 180 * dy ;
rotationZ+ = 180 * dx ;
updateGL( ) ; // 如果是重载 paintEvent,则使用 update( ) ;
}
lastPos = event->pos( ) ;
}
-
主要是将鼠标的移动距离转化成角度变化(成员变量保存),同时更新 lastPos;
-
这里的 rotationX,rotationY,rotationZ 分别将在 MODELVIEW 的 glRotatef() 中使用。
(3)重载 void wheelEvent( QWheelEvent * ) 事件
double numDegrees = -event->delta( ) / 8.0 ;
double numSteps = numDegrees / 15.0 ;
scaling *= std : : pow( 1.125 , numSteps ) ;
updateGL( ) ; // 如果是重载 paintEvent,则使用 update( ) ;
-
将滑轮滚动转换成放大倍数;
-
event( )->delta 返回滑轮滚动的距离,正值表示向前滑动,负值表示向后滑动;
-
大多数的鼠标是以滑动 15 度为一步,通常这一步就是 1/8 的 delta( );
-
这里的 scaling 是一个浮点数,MODELVIEW矩阵中,画图之前进行的矩阵变换中使用:glScalef( scaling, scaling, scaling )。
4. 屏幕拾取
-
首先需要鼠标点击的点:event->pos( ) 。然后在 GL_SELECT 模式下渲染 OpenGL 场景,这样就能利用 OpenGL 提供的拾取的能力。
-
关于 OpenGL 中的拾取模式,详细内容参见:
http://blog.csdn.net/zsc09_leaf/article/details/6785472
示例代码如下:
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];
}