Qt開發技術:Q3D圖表開發筆記(三):Q3DSurface三維曲面圖介紹、Demo以及程式碼詳解

2023-04-20 15:00:50

前言

  qt提供了q3d進行三維開發,雖然這個框架沒有得到大量運用也不是那麼成功,效能上也有很大的欠缺,但是普通的點到為止的應用展示還是可以的。
  其中就包括華麗絢爛的三維圖表,資料量不大的時候是可以使用的。
  前面介紹了基礎的q3d散點圖、柱狀圖,本篇介紹基礎的三維曲面圖。

 

Demo:Q3DSurface散點圖演示效果

  在這裡插入圖片描述
  在這裡插入圖片描述
  在這裡插入圖片描述

 

Q3D提供的三維圖表

  依賴QtDataVisualization。在安裝qt的時候要選擇安裝QtDataVisualization模組。

Q3DScatter散點圖

  Q3D的散點圖,效能大約支撐1000個點可以不卡頓,具體依賴pc,1000個點是什麼 概念,可以理解為:10x10x10的區域,每個區域一個資料點。
  在這裡插入圖片描述

Q3DBars柱狀圖

  Q3D的柱狀圖,效能跟散點圖類似。
   在這裡插入圖片描述

Q3DSurface平面凹凸圖,平面紋理圖,平面曲線圖

  Q3D的柱狀圖,效能跟散點圖類似。
  在這裡插入圖片描述

 

Q3DSurface平面曲線圖

簡介

  Q3DSurface類提供了渲染3D曲面圖的方法。該類使開發人員能夠渲染3D表面圖,並通過自由旋轉場景來檢視它們。可以通過QSurface3DSeries控制曲面的視覺財產,例如繪製模式和著色。
  Q3DSurface通過在使用者用滑鼠左鍵點選的資料點上顯示高亮顯示的球(當使用預設輸入處理程式時)或通過QSurface3DSeries進行選擇來支援選擇。選擇指標附帶一個標籤,在預設情況下,該標籤顯示資料點的值和點的座標。
軸上顯示的值範圍和標籤格式可以通過QValue3DAxis進行控制。
  要旋轉圖形,請按住滑鼠右鍵並移動滑鼠。縮放是使用滑鼠滾輪完成的。兩者都假設預設的輸入處理程式正在使用中。
  如果沒有將任何軸明確設定為Q3DSurface,則會建立不帶標籤的臨時預設軸。這些預設軸可以通過軸存取器進行修改,但只要明確設定了方向的任何軸,該方向的預設軸就會被破壞。

構造最小Q3D平面曲線圖

  首先,構造Q3D曲面。由於在本例中,我們將圖形作為頂級視窗執行,因此需要清除Qt::FramelessWindowHint標誌,該標誌在預設情況下設定:

Q3DSurface surface; 
surface.setFlags(surface.flags() ^ Qt::FramelessWindowHint);

  現在Q3DSurface已準備好接收要渲染的資料。建立資料元素以接收值:

QSurfaceDataArray *data = new QSurfaceDataArray;
QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;

  首先將資料餵給行元素,然後將它們的指標新增到資料元素:

*dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
*dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
*data << dataRow1 << dataRow2;、

  建立新系列併為其設定資料:

QSurface3DSeries *series = new QSurface3DSeries;
series->dataProxy()->resetArray(data);   
surface.addSeries(series);

  最後,設定為可見:

surface.show();

  建立和顯示此圖所需的完整程式碼為:

#include <QtDataVisualization>
using namespace QtDataVisualization;
int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

    Q3DSurface surface;
    surface.setFlags(surface.flags() ^ Qt::FramelessWindowHint);
    QSurfaceDataArray *data = new QSurfaceDataArray;
    QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
    QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;

    *dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
    *dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
    *data << dataRow1 << dataRow2;

    QSurface3DSeries *series = new QSurface3DSeries;
    series->dataProxy()->resetArray(data);
    surface.addSeries(series);
    surface.show();

    return app.exec();
}

  執行效果:
  在這裡插入圖片描述

  場景可以被旋轉、放大,並且可以選擇一個專案來檢視其位置,但在這個最小的程式碼範例中不包括其他互動。

 

Q3Ddemo構建流程解析

步驟一:確認安裝QtDataVisualization模組

  如何確認,則是在幫助檔案中檢視是否有Q3dscatter類。一般是安裝了模組才會有對應的幫助檔案。沒有則重新安裝qt或者單獨安裝該模組。
  在這裡插入圖片描述

步驟二:工程組態檔中加入模組

  Q3d是在資料視覺化模組中,需要在pro或者pri組態檔中新增。

QT += datavisualization

  在這裡插入圖片描述

步驟三:新增使用到的標頭檔案

  使用到Q3DBar相關類中新增標頭檔案,主要使用到Q3DBar、QBar3DSeries、QBarDataRow等等。

#include <Q3DBars>
#include <Q3DTheme>
#include <QBar3DSeries>
#include <QVector3D>

  在這裡插入圖片描述

步驟四:新增名稱空間

  這時候還是無法使用對應的類,需要新增名稱空間才行:

using namespace QtDataVisualization;

  在這裡插入圖片描述

步驟五:Q3D的圖示基礎構建框架

  下面是包含註釋的Q3DSurface基礎構建流程(注意軸的顯示,檢視末尾「入坑一」,注意資料的成面規則,檢視「入坑二」

_pQ3DSurface = new Q3DSurface();
_pContainer = QWidget::createWindowContainer(_pQ3DSurface, this);
// 設定軸文字
{
    // 注意笛卡爾座標
    _pQ3DSurface->axisX()->setTitle("經度(°)");
    _pQ3DSurface->axisX()->setTitleVisible(true);
    _pQ3DSurface->axisY()->setTitle("高度(m)");
    _pQ3DSurface->axisY()->setTitleVisible(true);
    _pQ3DSurface->axisZ()->setTitle("緯度(°)");
    _pQ3DSurface->axisZ()->setTitleVisible(true);
}
// 設定軸範圍
{
    // 注意笛卡爾座標
    _pQ3DSurface->axisX()->setRange(0, 359);
    _pQ3DSurface->axisY()->setRange(0, 100);
    _pQ3DSurface->axisZ()->setRange(0, 359);
}

// 生成一個曲線
_pSurface3DSeries = new QSurface3DSeries(_pQ3DSurface);
// 設定渲染平滑
_pSurface3DSeries->setMeshSmooth(true);
// 設定渲染模式
//   DrawWireframe           : 繪製柵格
//   DrawSurface             : 繪製表面
//   DrawSurfaceAndWireframe : 繪製柵格和圖表面
_pSurface3DSeries->setDrawMode(QSurface3DSeries::DrawSurface);

// 檢視新增該曲線
_pQ3DSurface->addSeries(_pSurface3DSeries);
// 設定陰影質量
_pQ3DSurface->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
// 設定視角
_pQ3DSurface->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeft);
// 設定子網格
_pQ3DSurface->activeTheme()->setGridEnabled(true);

#if 1
// 新增模擬資料
QSurfaceDataArray *pSurfaceDataArray = new QSurfaceDataArray;
#if 1

#if 1
// 這是 z 緯度
for(int n = 0; n < 360; n++)
{
    QSurfaceDataRow *pSurfaceDataRow  = new QSurfaceDataRow;
    // 這是 x 經度
    for(int m = 0; m < 360; m++)
    {
       // 注意與笛卡爾座標進行對映
       *pSurfaceDataRow << QVector3D(m, n / 7 + m / 7, n);
    }
    *pSurfaceDataArray << pSurfaceDataRow;
}
#else
for(int n = 0; n < 360; n++)
{
    QSurfaceDataRow *pSurfaceDataRow  = new QSurfaceDataRow;
    // 這是 x 經度
    for(int m = 0; m < 360; m++)
    {
       // 注意與笛卡爾座標進行對映
       *pSurfaceDataRow << QVector3D(m, qrand() % 100, n);
       LOG << n << m;
    }
    *pSurfaceDataArray << pSurfaceDataRow;
}
#endif
#else
QSurfaceDataRow *pSurfaceDataRow1  = new QSurfaceDataRow;
QSurfaceDataRow *pSurfaceDataRow2  = new QSurfaceDataRow;
QSurfaceDataRow *pSurfaceDataRow3  = new QSurfaceDataRow;
// 行與行之間,要形成一個四點成面
*pSurfaceDataRow1 << QVector3D(0, 0, 0)  << QVector3D(359, 20, 0);
*pSurfaceDataRow2 << QVector3D(50, 20, 179)  << QVector3D(359, 40, 179);
*pSurfaceDataRow3 << QVector3D(100, 80, 359)  << QVector3D(359, 100, 359);
*pSurfaceDataArray << pSurfaceDataRow1 << pSurfaceDataRow2 << pSurfaceDataRow3;
#endif
// 新增資料(自動沖掉之前的資料)
_pSurface3DSeries->dataProxy()->resetArray(pSurfaceDataArray);
#endif
_pQ3DSurface->addSeries(_pSurface3DSeries);
_pQ3DSurface->show();
 

Demo原始碼

Q3dSurfaceWidget.h

#ifndef Q3DSURFACEWIDGET_H
#define Q3DSURFACEWIDGET_H

#include <QWidget>
#include <Q3DSurface>
#include <Q3DTheme>
#include <QSurface3DSeries>
#include <QVector3D>


using namespace QtDataVisualization;

namespace Ui {
class Q3dSurfaceWidget;
}

class Q3dSurfaceWidget : public QWidget
{
    Q_OBJECT

public:
    explicit Q3dSurfaceWidget(QWidget *parent = 0);
    ~Q3dSurfaceWidget();

protected:
    void initControl();


protected:
    void resizeEvent(QResizeEvent *event);

private:
    Ui::Q3dSurfaceWidget *ui;

private:
    Q3DSurface *_pQ3DSurface;          // q3d平面曲線圖
    QWidget *_pContainer;           // q3d視窗容器
    QSurface3DSeries  *_pSurface3DSeries ;    // q3d柱狀圖資料
};

#endif // Q3DSURFACEWIDGET_H

Q3dSurfaceWidget.cpp

#include "Q3dSurfaceWidget.h"
#include "ui_Q3dSurfaceWidget.h"
#include <Q3DTheme>


#include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")

Q3dSurfaceWidget::Q3dSurfaceWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Q3dSurfaceWidget),
    _pQ3DSurface(0),
    _pContainer(0),
    _pSurface3DSeries(0)
{
    ui->setupUi(this);

    QString version = "v1.0.0";

    initControl();
}

Q3dSurfaceWidget::~Q3dSurfaceWidget()
{
    delete ui;
}


void Q3dSurfaceWidget::initControl()
{
    _pQ3DSurface = new Q3DSurface();
    _pContainer = QWidget::createWindowContainer(_pQ3DSurface, this);

    // 設定軸文字
    {
        // 注意笛卡爾座標
        _pQ3DSurface->axisX()->setTitle("經度(°)");
        _pQ3DSurface->axisX()->setTitleVisible(true);
        _pQ3DSurface->axisY()->setTitle("高度(m)");
        _pQ3DSurface->axisY()->setTitleVisible(true);
        _pQ3DSurface->axisZ()->setTitle("緯度(°)");
        _pQ3DSurface->axisZ()->setTitleVisible(true);
    }
    // 設定軸範圍
    {
        // 注意笛卡爾座標
        _pQ3DSurface->axisX()->setRange(0, 359);
        _pQ3DSurface->axisY()->setRange(0, 100);
        _pQ3DSurface->axisZ()->setRange(0, 359);
    }

    // 生成一個曲線
    _pSurface3DSeries = new QSurface3DSeries(_pQ3DSurface);
    // 設定渲染平滑
    _pSurface3DSeries->setMeshSmooth(true);
    // 設定渲染模式
    //   DrawWireframe           : 繪製柵格
    //   DrawSurface             : 繪製表面
    //   DrawSurfaceAndWireframe : 繪製柵格和圖表面
    _pSurface3DSeries->setDrawMode(QSurface3DSeries::DrawSurface);

    // 檢視新增該曲線
    _pQ3DSurface->addSeries(_pSurface3DSeries);
    // 設定陰影質量
    _pQ3DSurface->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
    // 設定視角
    _pQ3DSurface->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeft);
    // 設定子網格
    _pQ3DSurface->activeTheme()->setGridEnabled(true);

#if 1
    // 新增模擬資料
    QSurfaceDataArray *pSurfaceDataArray = new QSurfaceDataArray;
#if 1

#if 1
    // 這是 z 緯度
    for(int n = 0; n < 360; n++)
    {
        QSurfaceDataRow *pSurfaceDataRow  = new QSurfaceDataRow;
        // 這是 x 經度
        for(int m = 0; m < 360; m++)
        {
           // 注意與笛卡爾座標進行對映
           *pSurfaceDataRow << QVector3D(m, n / 7 + m / 7, n);
        }
        *pSurfaceDataArray << pSurfaceDataRow;
    }
#else
    for(int n = 0; n < 360; n++)
    {
        QSurfaceDataRow *pSurfaceDataRow  = new QSurfaceDataRow;
        // 這是 x 經度
        for(int m = 0; m < 360; m++)
        {

           // 注意與笛卡爾座標進行對映
           *pSurfaceDataRow << QVector3D(m, qrand() % 100, n);
           LOG << n << m;
        }
        *pSurfaceDataArray << pSurfaceDataRow;
    }
#endif
#else
    QSurfaceDataRow *pSurfaceDataRow1  = new QSurfaceDataRow;
    QSurfaceDataRow *pSurfaceDataRow2  = new QSurfaceDataRow;
    QSurfaceDataRow *pSurfaceDataRow3  = new QSurfaceDataRow;
    // 行與行之間,要形成一個四點成面
    *pSurfaceDataRow1 << QVector3D(0, 0, 0)  << QVector3D(359, 20, 0);
    *pSurfaceDataRow2 << QVector3D(50, 20, 179)  << QVector3D(359, 40, 179);
    *pSurfaceDataRow3 << QVector3D(100, 80, 359)  << QVector3D(359, 100, 359);
    *pSurfaceDataArray << pSurfaceDataRow1 << pSurfaceDataRow2 << pSurfaceDataRow3;
#endif

    // 新增資料(自動沖掉之前的資料)
    _pSurface3DSeries->dataProxy()->resetArray(pSurfaceDataArray);

#endif
    _pQ3DSurface->addSeries(_pSurface3DSeries);
    _pQ3DSurface->show();

}

void Q3dSurfaceWidget::resizeEvent(QResizeEvent *event)
{
    if(_pContainer)
    {
        _pContainer->setGeometry(rect());
    }
}
 

工程模板v1.2.0

  在這裡插入圖片描述

 

入坑

入坑一:xyz座標系不對

問題

  x精度,y維度,z高度(海拔高度)對映錯誤
  在這裡插入圖片描述

原因

  x,y,z實際是遵循笛卡爾座標集

解決

  先理解座標,然後z軸方向,資料也要替換(按照x,y,z來排列,改為x,z,y)
 &emso;在這裡插入圖片描述

入坑二:曲面顯示不對

問題

  資料顯示對映錯誤

原因

  點成面,需要遵循4點成面的規則,和opengl相關3點成面和4點成面的原理類似。
  在這裡插入圖片描述

  在這裡插入圖片描述
  

解決

  相鄰行與行之間,要形成面,修改後展示如下:

  在這裡插入圖片描述
  在這裡插入圖片描述