使用osgQt::QWidgetImage将Qt界面集成到OSG三维场景中

版权声明: https://blog.csdn.net/guimaxingtian/article/details/80936752

写在前面

  最近在做三维gis数据显示相关的内容,开发使用的是OSG和OSGEarth,因为osg本身对界面显示这块比较弱,不能在三维场景中呈现较好的二维界面效果,所以我想的是将Qt下开发的界面集成到OSG中,王锐老师的《OpenSceneGraph3 cookbook》中有一节是专门讲这个的,算是个引子,有兴趣的同学可以先读一读这一节的内容。在实现的时候遇到了很多的问题,网上相关的资料又比较少,所以摸索了一周左右,记录一下解决过程。

正文

先写一个简单的界面类

//InfoWidget.h
#ifndef INFOWIDGET_H
#define INFOWIDGET_H

#include <QWidget>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QLabel>

class InfoWidget: public QWidget
{
    Q_OBJECT
public:
    InfoWidget();
    ~InfoWidget();
    void setNameValue(const QString& labelValue);
    const QString getNameValue() const;
    void setAttribute_1_Value(const QString& labelValue);
    const QString getAttribute_1_Value() const;
protected:
    void paintEvent(QPaintEvent * event);
private:
    QLabel* nameLabel;
    QLabel* nameValue;
    QLabel* attribute_1_Label;
    QLabel* attribute_1_Value;

    QGridLayout* mGridLayout;
    QVBoxLayout* mVBoxLayout;
};
#endif //INFOWIDGET_H
//InfoWidget.cpp
#include "InfoWidget.h"
#include <QPainter>

InfoWidget::InfoWidget()
{
    //文字标签
    nameLabel = new QLabel(QString::fromLocal8Bit ("姓名:"));
    nameValue = new QLabel(QString::fromLocal8Bit ("张三"));
    attribute_1_Label = new QLabel(QString::fromLocal8Bit ("性别:"));
    attribute_1_Value = new QLabel(QString::fromLocal8Bit ("男"));

    //设置标签字体,字号要尽量大一点,以便在缩放时看的清楚一点
    QFont mFont ( QString::fromLocal8Bit ("楷体"),40,75); 
    nameLabel->setFont(mFont);
    nameValue->setFont(mFont);
    attribute_1_Label->setFont(mFont);
    attribute_1_Value->setFont(mFont);
    //设置标签颜色
    QString textStyle = "color: rgb(255, 255, 51);"
    nameLabel->setStyleSheet(textStyle);
    nameValue->setStyleSheet(textStyle);
    attribute_1_Label->setStyleSheet(textStyle);
    attribute_1_Value->setStyleSheet(textStyle);

    mGridLayout =new QGridLayout;   //栅格布局
    mVBoxLayout = new QVBoxLayout;  //整体垂直布局

    //栅格布局添加控件
    mGridLayout->addWidget(nameLabel, 0, 0);
    mGridLayout->addWidget(nameValue, 0, 1);
    mGridLayout->addWidget(attribute_1_Label,1 , 0);
    mGridLayout->addWidget(attribute_1_Value,1, 1);
    //设置列宽比例
    mGridLayout->setColumnStretch(0, 1);
    mGridLayout->setColumnStretch(1, 1);
    //设置边距和行距
    mGridLayout->setContentsMargins(2, 2, 2, 2);
    mGridLayout->setHorizontalSpacing(10);
    //整体布局
    mVBoxLayout->addLayout(mGridLayout);
    mVBoxLayout->setContentsMargins(0, 0, 0 ,0);

    setLayout(mVBoxLayout);
}

InfoWidget::~InfoWidget()
{
    //在这里做关闭界面时的操作,下面的代码可以不写
    delete nameLabel;
    delete nameValue;
    delete attribute_1_Label;
    delete attribute_1_Value;
    delete mGridLayout;
    delete mVBoxLayout; 
}

void InfoWidget::paintEvent(QPaintEvent * event)
{
    //这里是为了给界面设置一个背景图片
    Q_UNUSED(event);
    QPainter painter(this);
    painter.drawPixmap(0, 0, this->width(), this->height(), QPixmap("C:/Users/mark/Desktop/pic/background.png"));
}

void InfoWidget::setAttribute_1_Value(const QString& labelValue)
{
    attribute_1_Value->setText(labelValue);
}
void InfoWidget::setNameValue(const QString& labelValue)
{
    nameValue->setText(labelValue);
}
const QString InfoWidget::getNameValue() const
{
    return nameValue->text();
}
const QString InfoWidget::getAttribute_1_Value() const
{
    return attribute_1_Value->text();
}

上面的代码只是写一个简单的界面类,事实上只要继承于QWidget的界面类都可以在创建实例后,通过osgQt::QWidgetImage集成到osg的三维场景中。下面给出示例代码:

//test.cpp
#include <osg/ref_ptr>
#include <osg/Texture2D>
#include <osg/Geometry>
#include <osg/LineWidth>
#include <osg/Point>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgGA/TrackballManipulator>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Viewer>
#include <osgQt/qWidgetImage>
#include <osg/Depth> 
#include <osg/BlendFunc>
#include "InfoWidget.h"

void initWidgetImage
{
    osg::ref_ptr<osg::MatrixTransform>  mt;     //将信息UI从本地坐标变换到三维场景中的矩阵
    InfoWidget*             demoWidget; //要在三维场景中显示的信息UI
    osg::ref_ptr<osgQt::QWidgetImage>   widgetImage;    //可以理解为界面转换后,在三维场景中生成的图片,我将其称为界面图
    osg::ref_ptr<osg::Texture2D>        texture;    //纹理
    osg::ref_ptr<osg::Geometry>     quad;       //矩形区域
    osg::ref_ptr<osg::Geode>        geode;      //界面节点
    osg::ref_ptr<osg::Geode>        geode2;     //引线节点

    demoWidget= new InfoWidget();//创建界面实例
    demoWidget->setObjectName("testwidget");
    widgetImage = new osgQt::QWidgetImage(demoWidget);//创建osgQt::QWidgetImage实例
    widgetImage->getQWidget()->setAttribute(Qt::WA_TranslucentBackground); //如果想将界面设置为透明背景的话这一句一定要加上,不然将界面背景设置为透明的话底色总是白色
    widgetImage->getQGraphicsViewAdapter()->setBackgroundColor(QColor(102, 255, 255,100)); //这一句是设置界面的背景颜色
        osg::ref_ptr<osgViewer::InteractiveImageHandler> handler = new osgViewer::InteractiveImageHandler(widgetImage.get());//这一句是用来创建将界面图上的操作比如鼠标、键盘等等的操作传给界面的处理器,这样在界面中定义的一些信号或者槽就可以起作用了

    texture = new osg::Texture2D;//创建纹理贴图
    texture->setImage(widgetImage.get());//将界面图设置为纹理图片
    texture->setResizeNonPowerOfTwoHint(false);  这一句是用来设置图片的尺寸不需要是2的n次方
    texture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR);  //这一句是用来设置图像绘制时小于贴图的原始尺寸时采用
    texture->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE);  
    texture->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE);  //上面两句是设置图片在S T(X Y)轴上的拉伸方式
    //关于纹理映射的相关参数,这篇博客讲的比较清楚:https://blog.csdn.net/xiaoliantongtong/article/details/77675903

    quad = osg::createTexturedQuadGeometry(osg::Vec3(10.0f,0.0f,3.0f), osg::Vec3(20.0f,0.0f,0.0f),osg::Vec3(0.0f,0.0f,20.0f), 1,1);//创建quad,第一个参数是起点位置,第二、三个参数是长宽方向上的方向和长度,具体哪个方向看坐标值
    quad->setEventCallback(handler.get());
    quad->setCullCallback(handler.get());//将处理器附加到quad上

    //界面与原点的连接线
    osg::ref_ptr<osg::Geometry> lineGeom = new osg::Geometry();
    osg::ref_ptr<osg::LineWidth> lineWidth = new osg::LineWidth();//设置连线的线宽对象
    lineWidth->setWidth(3.0f);
    lineGeom->getOrCreateStateSet()->setAttributeAndModes(lineWidth.get(), osg::StateAttribute::ON);

    osg::ref_ptr<osg::Vec3Array> lineVertices = new osg::Vec3Array();//设置连线的三个节点
    lineVertices->push_back(osg::Vec3(0.5f, 0.5f, 0.5f));
    lineVertices->push_back(osg::Vec3(5.0f, 0.0f, 13.0f));
    lineVertices->push_back(osg::Vec3(10.0f, 0.0f, 13.0f));
    osg::ref_ptr<osg::Vec3Array> lineNormal = new osg::Vec3Array();
    lineNormal->push_back(osg::Vec3(0.0f, 1.0f, 0.0f));
    osg::ref_ptr<osg::Vec4Array> lineColor = new osg::Vec4Array();
    lineColor->push_back(osg::Vec4(0.4f, 1.0f, 1.0f, 1.0f));
    lineGeom->setVertexArray(lineVertices.get());
    lineGeom->setNormalArray(lineNormal.get());
    lineGeom->setNormalBinding(osg::Geometry::BIND_OVERALL);
    lineGeom->setColorArray(lineColor.get());
    lineGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
    lineGeom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, 3));
    //连接线上折断处的点
    osg::ref_ptr<osg::Geometry> pointGeom = new osg::Geometry();
    osg::ref_ptr<osg::Point> pointSize = new osg::Point();/绘制点
    pointSize->setSize(12.0);
    pointGeom->getOrCreateStateSet()->setAttributeAndModes(pointSize.get(), osg::StateAttribute::ON);

    osg::ref_ptr<osg::Vec3Array> pointVertices = new osg::Vec3Array();
    pointVertices->push_back(osg::Vec3(5.0f, 0.0f, 13.0f));
    osg::ref_ptr<osg::Vec3Array> pointNormal = new osg::Vec3Array();
    pointNormal->push_back(osg::Vec3(0.0f, 1.0f, 0.0f));
    osg::ref_ptr<osg::Vec4Array> pointColor = new osg::Vec4Array();
    pointColor->push_back(osg::Vec4(0.4f, 1.0f, 1.0f, 1.0f));
    pointGeom->setVertexArray(pointVertices.get());
    pointGeom->setNormalArray(pointNormal.get());
    pointGeom->setNormalBinding(osg::Geometry::BIND_OVERALL);
    pointGeom->setColorArray(pointColor.get());
    pointGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
    pointGeom->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, 1));

    geode = new osg::Geode;//作为界面图的节点
    geode2 = new osg::Geode;//作为引线的节点
    geode->addDrawable(quad.get());
    geode2->addDrawable(lineGeom.get());
    geode2->addDrawable(pointGeom.get());

    osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc;
    blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//用于显示透明效果,不加这句无法实现透明效果

    geode->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
    geode->getOrCreateStateSet()->setTextureAttributeAndModes(0,texture.get());
    geode->getOrCreateStateSet()->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
    geode->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);

    mt =new osg::MatrixTransform;
    mt->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF);//关闭深度测试,不然渲染优先级设置会失效
    mt->getOrCreateStateSet()->setRenderBinDetails(111, "RenderBin");//设置渲染优先级 ,大一点以显示在最前面
    mt->addChild(geode.get());
    mt->addChild(geode2.get());
    m_pRoot->addChild(mt.get());
}

说明

1.程序最后的m_pRoot是我的一个组节点,你可以将mt放入你自己的组节点或者viewer中,这里的mt是因为我是在三维的数字地球中插入的这个界面,为了形成类似billboard的效果,我还需要通过后面的setMatrix()来变换界面图的朝向和位置,关于这方面的内容之后可能会再写一篇内容来讲一下;
2.关于信号和槽的处理,这里可能没体现出来,实际上在界面类里按正常的添加信号和槽的方式就可以了,程序中我们定义的handler会自动处理,我们只需要加进去就可以了;
3.王锐老师的《OpenSceneGraph3 cookbook》中有这一块的基本说明,我这边主要是加入了一些细节方面的代码;
4.以上的代码只是提供一个技术思路和一些相关的设置,没有将项目代码完全贴上来,所以不能直接运行,仅供参考。

猜你喜欢

转载自blog.csdn.net/guimaxingtian/article/details/80936752