OpenSceneGraph源码分析:osgQt模块

济南友泉软件有限公司

osgQt基于QGLWidget实现了在Qt窗口内OSG渲染操作。Qt以其开源跨平台方便快捷现代化的界面风格等优点,已经成为了目前桌面版CAD/CAE/CAM等软件开发的首选组件。因此,非常有必要在OSG的基础之上,研究Qt桌面系统内集成OSG渲染功能的相关技术。

注1:文章内容会不定期更新。

注2:限于笔者研究水平,难免有表述不当之处,欢迎批评指正。

零、系统配置

操作系统WIndows 10 64bit
编译器Visual Studio 2019 Community Edition
Git2.29.1
CMake3.15.0-rc2
osgQt3.5.7
工作目录D:\YouQuan\CaeFrameworks\OpenSceneGraph

一、编译、安装

在进行osgQt编译安装之前,需要完成OSG的编译安装,可以参照笔者前面的博客。完成编译安装之后,便可编译安装osgQt。

    1.1 下载代码

首先,需要从GitHub上下载osgQt,

git clone https://github.com/openscenegraph/osgQt.git
git checkout -b 3.5.7 3.5.7

    1.2 构建项目

打开CMake,按照下表构建项目配置,

CMAKE_INSTALL_PREFIXD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL
QT_QMAKE_EXECUTABLED:/YouQuan/CaeFrameworks/FreeCAD/FreeCADLibs_12.1.6_x64_VC15/bin/qmake.exe
QT5Widgets_DIRD:/YouQuan/CaeFrameworks/FreeCAD/FreeCADLibs_12.1.6_x64_VC15/lib/cmake/Qt5Widgets
OPENTHREADS_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/include
OPENTHREADS_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/OpenThreadsd.lib
OPENTHREADS_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/OpenThreads.lib
OSG_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSG_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgd.lib
OSG_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osg.lib
OSGDB_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSGDB_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgDBd.lib
OSGDB_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgDB.lib
OSGGA_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSGGA_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgGAd.lib
OSGGA_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgGA.lib
OSGTEXT_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSGTEXT_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgTextd.lib
OSGTEXT_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgText.lib
OSGUTIL_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSGUTIL_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgUtild.lib
OSGUTIL_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgUtil.lib
OSGVIEWER_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSGVIEWER_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgViewerd.lib
OSGVIEWER_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgViewer.lib
OSGWIDGET_INCLUDE_DIRD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/include
OSGWIDGET_LIBRARY_DEBUGD:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgWidgetd.lib
OSGWIDGET_LIBRARY_RELEASED:/YouQuan/CaeFrameworks/OpenSceneGraph/INSTALL/lib/osgWidget.lib

    1.3 编译安装

打开生成的osgQt.sln,构建ALL_BUILD项目完成编译;构建“INSTALL”项目完成安装,

二、代码分析

从整体上来看,osgQt实际上是仿照osgViewer::run()函数,借助于QTimer、QGLWidget完成了在Qt窗口内渲染OSG场景。

    2.1 HeartBeat

HeartBeat是个单例类,借助于QTimer,触发OSG场景渲染。与osgViewer::run()实现相似,HeartBeat::timeEvent(QTimeEvent*)也实现了渲染帧率控制的功能(相当于控制”渲染频率“的节流阀)。

void HeartBeat::timerEvent( QTimerEvent * /*event*/ )
{
    osg::ref_ptr< osgViewer::ViewerBase > viewer;
    if( !_viewer.lock( viewer ) )
    {
        // viewer has been deleted -> stop timer
        stopTimer();
        return;
    }

    // limit the frame rate
    if( viewer->getRunMaxFrameRate() > 0.0)
    {
        double dt = _lastFrameStartTime.time_s();
        double minFrameTime = 1.0 / viewer->getRunMaxFrameRate();
        if (dt < minFrameTime)
            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(minFrameTime-dt)));
    }
    else
    {
        // avoid excessive CPU loading when no frame is required in ON_DEMAND mode
        if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND )
        {
            double dt = _lastFrameStartTime.time_s();
            if (dt < 0.01)
                OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(0.01-dt)));
        }

        // record start frame time
        _lastFrameStartTime.setStartTick();

        // make frame
        if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND )
        {
            if( viewer->checkNeedToDoFrame() )
            {
                viewer->frame();
            }
        }
        else
        {
            viewer->frame();
        }
    }
}

为了触发OSG场景渲染,首先需要调用HeartBeat::init( osgViewer::ViewerBase *viewer ),这样便可将QTimer绑定到osgViewer::ViewerBase,进而不断的触发场景的渲染。

/// Initializes the loop for viewer. Must be called from main thread.
void HeartBeat::init( osgViewer::ViewerBase *viewer )
{
    if( _viewer == viewer )
        return;

    stopTimer();

    _viewer = viewer;

    if( viewer )
    {
        _timerId = startTimer( 0 );
        _lastFrameStartTime.setStartTick( 0 );
    }
}

    2.2 osgQt::GraphicsWindowQt

从运行原理上来看,OSG实际上是对OpenGL状态机采用C++进行了封装,而OpenGL在底层使用图形上下文(Graphics Context)来描述状态机相关信息。使用OpenGL必须要首先指定图形上下文。osgQt::GraphicsWindowQt就是用来提供QGLWidget的图形上下文。

Ref. from OpenGL Wiki =======================================================

An OpenGL context represents many things. A context stores all of the state associated with this instance of OpenGL. It represents the (potentially visible) default framebuffer that rendering commands will draw to when not drawing to a framebuffer object. Think of a context as an object that holds all of OpenGL; when a context is destroyed, OpenGL is destroyed.

Contexts are localized within a particular process of execution (an application, more or less) on an operating system. A process can create multiple OpenGL contexts. Each context can represent a separate viewable surface, like a window in an application.

Each context has its own set of OpenGL Objects, which are independent of those from other contexts. A context's objects can be shared with other contexts. Most OpenGL objects are sharable, including Sync Objects and GLSL Objects. Container Objects are not sharable, nor are Query Objects.

Any object sharing must be made explicitly, either as the context is created or before a newly created context creates any objects. However, contexts do not have to share objects; they can remain completely separate from one another.

In order for any OpenGL commands to work, a context must be current; all OpenGL commands affect the state of whichever context is current. The current context is a thread-local variable, so a single process can have several threads, each of which has its own current context. However, a single context cannot be current in multiple threads at the same time.

=======================================================Ref. from OpenGL Wiki


    2.3 osg::GLWidget

osg::GLWidget在QWidget渲染功能基础之上,将Qt键盘、鼠标等事件转化为OSG事件,并触发相关事件函数的调用。

void GLWidget::setKeyboardModifiers( QInputEvent* event )
{
    int modkey = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier);
    unsigned int mask = 0;
    if ( modkey & Qt::ShiftModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_SHIFT;
    if ( modkey & Qt::ControlModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_CTRL;
    if ( modkey & Qt::AltModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_ALT;
    _gw->getEventQueue()->getCurrentEventState()->setModKeyMask( mask );
}

void GLWidget::resizeEvent( QResizeEvent* event )
{
    const QSize& size = event->size();

    int scaled_width = static_cast<int>(size.width()*_devicePixelRatio);
    int scaled_height = static_cast<int>(size.height()*_devicePixelRatio);
    _gw->resized( x(), y(), scaled_width,  scaled_height);
    _gw->getEventQueue()->windowResize( x(), y(), scaled_width, scaled_height );
    _gw->requestRedraw();
}

void GLWidget::moveEvent( QMoveEvent* event )
{
    const QPoint& pos = event->pos();
    int scaled_width = static_cast<int>(width()*_devicePixelRatio);
    int scaled_height = static_cast<int>(height()*_devicePixelRatio);
    _gw->resized( pos.x(), pos.y(), scaled_width,  scaled_height );
    _gw->getEventQueue()->windowResize( pos.x(), pos.y(), scaled_width,  scaled_height );
}

void GLWidget::keyPressEvent( QKeyEvent* event )
{
    setKeyboardModifiers( event );
    int value = s_QtKeyboardMap.remapKey( event );
    _gw->getEventQueue()->keyPress( value );

    // this passes the event to the regular Qt key event processing,
    // among others, it closes popup windows on ESC and forwards the event to the parent widgets
    if( _forwardKeyEvents )
        inherited::keyPressEvent( event );
}

void GLWidget::keyReleaseEvent( QKeyEvent* event )
{
    if( event->isAutoRepeat() )
    {
        event->ignore();
    }
    else
    {
        setKeyboardModifiers( event );
        int value = s_QtKeyboardMap.remapKey( event );
        _gw->getEventQueue()->keyRelease( value );
    }

    // this passes the event to the regular Qt key event processing,
    // among others, it closes popup windows on ESC and forwards the event to the parent widgets
    if( _forwardKeyEvents )
        inherited::keyReleaseEvent( event );
}

void GLWidget::mousePressEvent( QMouseEvent* event )
{
    int button = 0;
    switch ( event->button() )
    {
        case Qt::LeftButton: button = 1; break;
        case Qt::MidButton: button = 2; break;
        case Qt::RightButton: button = 3; break;
        case Qt::NoButton: button = 0; break;
        default: button = 0; break;
    }
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button );
}

void GLWidget::mouseReleaseEvent( QMouseEvent* event )
{
    int button = 0;
    switch ( event->button() )
    {
        case Qt::LeftButton: button = 1; break;
        case Qt::MidButton: button = 2; break;
        case Qt::RightButton: button = 3; break;
        case Qt::NoButton: button = 0; break;
        default: button = 0; break;
    }
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseButtonRelease( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button );
}

void GLWidget::mouseDoubleClickEvent( QMouseEvent* event )
{
    int button = 0;
    switch ( event->button() )
    {
        case Qt::LeftButton: button = 1; break;
        case Qt::MidButton: button = 2; break;
        case Qt::RightButton: button = 3; break;
        case Qt::NoButton: button = 0; break;
        default: button = 0; break;
    }
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseDoubleButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button );
}

void GLWidget::mouseMoveEvent( QMouseEvent* event )
{
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseMotion( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio );
}

void GLWidget::wheelEvent( QWheelEvent* event )
{
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseScroll(
        event->orientation() == Qt::Vertical ?
            (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN) :
            (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_LEFT : osgGA::GUIEventAdapter::SCROLL_RIGHT) );
}

附录A:常见问题

Q1. 在Release配置下,编译osgQt可能会报以下问题,

1>------ Build started: Project: osgQt5, Configuration: Release x64 ------
1>LINK : fatal error LNK1181: cannot open input file 'optimized.lib'
1>Done building project "osgQt5.vcxproj" -- FAILED.

A1. 在osgQt项目属性中,去掉"debug.lib"、“optimized.lib"等库引用,即可顺利完成编译。

网络资料

osgQthttps://github.com/openscenegraph/osgQt

OpenGL Wiki https://www.khronos.org/opengl/wiki/

OpenSceneGraph编译、安装、开发环境https://blog.csdn.net/qq_26221775?spm=1010.2135.3001.5343

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值