来源 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];
}