Qt的Graphics-View框架和OpenGL结合详解

QtGraphics-View框架和OpenGL结合详解并发

演示程序下载地址:这里框架

程序源代码下载地址:这里函数


这是一篇纯技术文,介绍了这一个月来我抽时间研究的成果。学习

Qt中有一个很是炫的例子:Boxes,它展现了Qt可以让其Graphics–View框架和QtOpenGL模块结合起来,渲染出很是出色的效果。其实我私自认为凭这个程序,已经有不少游戏开发者关注Qt了,由于游戏开发一个很是常见的模块就是UI,通常状况下游戏引擎提供的UI模块比较弱,基本上都是游戏引擎+第三方GUI库进行结合的。可是Qt以其Graphics–View框架可以很是轻松地将UI控件嵌入场景中,并且可以和OpenGL底层共存,更重要的是,凭借着QtqssQt能够定制许多GUI元素,这是很是具备吸引力的。因此说,若是你们对游戏开发感兴趣,那么不妨看一下Qt测试

好了,下面介绍一下前几天我制做并发布的一个demo。这个demo是对Boxes这个例子进行模仿,结合学习《OpenGL超级宝典》,制做而成的,因为最近比较忙,因此总共花了将近一个月才完成,不得不说效率有点儿低。this

首先从MainWindow.cpp这个文件提及吧,一开始是要初始化MainWindow类的,这个类是继承QMainWindow的,这里重点说说它的构造函数:spa

MainWindow::MainWindow( QWidget* pParent ):
    QMainWindow( pParent )
{
    QGLWidget* pWidget = new QGLWidget( QGLFormat( QGL::SampleBuffers ), this );
    pWidget->makeCurrent( );
    // scene的内容
    GraphicsScene* pScene = new GraphicsScene( this );
    OpenGLView* pView = new OpenGLView( this );
    pView->setViewport( pWidget );
    pView->setViewportUpdateMode( QGraphicsView::FullViewportUpdate );
    pView->setScene( pScene );
    // 选择不一样的着色器的时候进行着色器链接
    connect( pScene, SIGNAL( SwitchShader( const QString& ) ),
             pView, SLOT( SwitchShader( const QString& ) ) );
    connect( pScene, SIGNAL( SetLightPos( const QVector3D& ) ),
             pView, SLOT( SetLightPos( const QVector3D& ) ) );
    setCentralWidget( pView );
    setWindowTitle( tr( "Light for shader" ) );
    resize( 640, 360 );
}

首先在咱们建立了一个QWidget,而后调用makeCurrent()成员函数,其实意思是让它的rendercontext设为当前的rendercontext。随后创建的是OpenGLView,这个OpenGLView是来自于QGraphicsView的,它的初始化和其祖先的并没有二致,随后一句很是重要:setViewport(),它的做用是将QGLWidget设置为OpenGLViewviewport,这样的话背景的rendercontext再也不是rastercontext而是OpenGLcontext了,不然场景的背景仍是须要用CPU渲染的,效率低下。接着是两段创建链接的代码。最后设置的是窗口大小和标题什么的,一开始仍是很是简单的。.net


接下来咱们看看OpenGLView是怎么定义的:3d

class OpenGLView: public QGraphicsView,
        protected QOpenGLFunctions
{
    Q_OBJECT
public:
    OpenGLView( QWidget* pParent = 0 );
    virtual ~OpenGLView( void );
    void setScene( GraphicsScene* pScene );
public slots:
    bool SwitchShader( const QString& shaderFileName );
    void SetLightPos( const QVector3D& lightPos = QVector3D( ) );
protected:
    void resizeEvent( QResizeEvent* pEvent );
    void mousePressEvent( QMouseEvent* pEvent );
    void mouseReleaseEvent( QMouseEvent* pEvent );
    void mouseMoveEvent( QMouseEvent* pEvent );
    void wheelEvent( QWheelEvent* pEvent );
    void drawBackground( QPainter* pPainter, const QRectF& rect );
private:
    void InitGL( void );
    void ResizeGL( int width, int height );
    void PaintGL( void );
    void DrawAxis( void );
    bool SetupShaders( void );
    Camera m_Camera;
    Format3DS m_3DS;
    // 着色器
    QOpenGLShader* m_pVertexShader;
    QOpenGLShaderProgram* m_pShaderProgram;
};

这里咱们在它的成员中添加了一个摄像机,一个3ds模型实例,还有一个顶点着色器和着色器程序类。在上次的博客中讲到了在这种状况最好使用类指针而不是类成员做为数据成员,这里我索性把着色器程序类也作成了指针成员了。代理

因为OpenGLView类实现比较长,这里我着重说一下其中的几个函数。下面是drawBackground()函数的实现:

void OpenGLView::drawBackground( QPainter* pPainter,
                                 const QRectF& )
{
    pPainter->beginNativePainting( );
    glPushAttrib( GL_ALL_ATTRIB_BITS );
    InitGL( );
    ResizeGL( pPainter->device( )->width( ),
              pPainter->device( )->height( ) );
    PaintGL( );
    glPopAttrib( );
    pPainter->endNativePainting( );
}

为何选择drawBackground()?由于咱们想要获得一种效果,OpenGL在底层绘制,上面绘制控件,其实自从QPainter有了beginNativePainting()endNativePainting()这两个函数,咱们就能够进行纯OpenGL的绘制了。这里还要注意的是,由于绘制控件也是使用OpenGLcontext,这样简单调用会让OpenGL的状态混乱,因此须要将各类状态经过glPushAttrib(GL_ALL_ATTRIB_BITS);保存起来,而后初始化咱们的OpenGL状态,以及绘图,最后记得glPopAttrib();还原全部状态供2D绘制。这里就不像以往的GLWidget套路了,由于GLWidget里面的initializeGL()函数只调用一次,paintGL()函数调用屡次,可是在这里,只要有刷新的消息(经过update()repaint()触发),就必须调用InitGL()函数来进行OpenGL状态的设置,不然先前设置的全部状态都消失了。

接下来看看ResizeEvent()函数:

void OpenGLView::resizeEvent( QResizeEvent* pEvent )
{
    scene( )->setSceneRect( 0.0,
                            0.0,
                            pEvent->size( ).width( ),
                            pEvent->size( ).height( ) );
    pEvent->accept( );
}

这里因为咱们已经设置了sceneGraphicsScene类的实例指针,所以scene()是非空的,咱们将场景限制为view的大小,这样能够避免一些刷新的问题。

paintGL()函数也是至关的简单。

void OpenGLView::PaintGL( void )
{
    glClear( GL_COLOR_BUFFER_BIT |
             GL_DEPTH_BUFFER_BIT );
    glPushMatrix( );
    m_Camera.Apply( );
    m_3DS.RenderGL( );
    if ( g_ShowAxis ) DrawAxis( );
    glPopMatrix( );
}

接着我向你们介绍一下GraphicsScene类:

class GraphicsScene: public QGraphicsScene
{
    Q_OBJECT
public:
    GraphicsScene( QObject* pParent = 0 );
    void SetCamera( Camera* pCamera );
signals:
    void SwitchShader( const QString& shaderFileName );
    void SetLightPos( const QVector3D& pos );
protected:
    void mousePressEvent( QGraphicsSceneMouseEvent* pEvent );
    void mouseMoveEvent( QGraphicsSceneMouseEvent* pEvent );
    void mouseReleaseEvent( QGraphicsSceneMouseEvent* pEvent );
    void wheelEvent( QGraphicsSceneWheelEvent* pEvent );
private slots:
    void Feedback( void );
private:
    // 鼠标事件须要
    QPointF m_LastPos;
    Camera* m_pCamera;
};

这里的GraphicsScene类保存的是来自viewCamera和一些信号以及事件的处理。在实现上也说一下它的构造函数吧。

GraphicsScene::GraphicsScene( QObject* pParent ):
    QGraphicsScene( pParent ), m_pCamera( Q_NULLPTR )
{
    ClickableTextItem* pTextItem = new ClickableTextItem( Q_NULLPTR );
    pTextItem->setPos( 10.0, 10.0 );
    pTextItem->setHtml( tr( "<font color=white>"
                            "Made By Jiangcaiyang<br>"
                            "Created in September<br>"
                            "Click for feedback."
                            "</font>" ) );
    connect( pTextItem, SIGNAL( Clicked( ) ),
             this, SLOT( Feedback( ) ) );
    addItem( pTextItem );
    ShaderOptionDialog* pDialog = new ShaderOptionDialog;
    connect( pDialog, SIGNAL( SwitchShader( const QString& ) ),
             this, SIGNAL( SwitchShader( const QString& ) ) );
    connect( pDialog, SIGNAL( SetLightPos( const QVector3D& ) ),
             this, SIGNAL( SetLightPos( const QVector3D& ) ) );
    QGraphicsProxyWidget* pProxy = addWidget( pDialog, Qt::Window | Qt::WindowTitleHint );
    pProxy->setPos( 100, 200 );
}

咱们建立了一个ClickableTextItem类,它继承于QGraphicsTextItem,它被摆在左上角,显示三排能够点击的文字。随后添加了一个对话框,设置信号和槽完毕后就用代理放入场景中了。整个过程也是很是简单的。

我测试了下,整个程序在个人Ubuntu13.04下可以正常运行。只是因为显卡不一样(Ubuntu下较难支持nvidia显卡,使用的是intel集显),specularOpt效果出不来。