【Qwt 7.0 系列】3D 数据可视化 —— OpenGL 高性能三维绘图

【Qwt 7.0 系列】3D 数据可视化 ------ OpenGL 高性能三维绘图

本文是 Qwt 7.0 系列介绍和教程,如果你正在寻找一个高性能、协议友好、同时支持 2D 和 3D 绘图的 Qt 数据可视化库,那么这篇文章就是为你准备的。

系列总述文章:Qwt 7.0 ------ 基于 Qt 的高性能 2D/3D 绘图库

概述 | 高性能曲线绘制 | 常用图表类型 | 高级科学图表 | 多坐标轴与布局 | 交互功能 | 3D 数据可视化 | 坐标轴与刻度 | 控件与辅助元素 | 总体架构解析 | matplotlib 风格绘图

项目地址:GitHub | Gitee | 在线文档


一、引言:为什么要在 Qwt 里做 3D?

科学计算和工程分析中,三维数据可视化几乎是绕不开的需求------有限元分析的热力分布、地形高程模型、电磁场强度曲面,都需要一张能旋转、能缩放的三维图来把数据看清楚。

在过去,如果用 Qt 做 2D 绘图你会选 Qwt,但要做 3D 又得去引入另一个独立的 QwtPlot3D 库。两个库版本不同步、API 风格不一致、构建配置各搞各的,维护成本很高。

Qwt 7.0 改变了这一点。 这个基于原版 Qwt 6.2.0 的现代化维护分支,从 7.1 起将原 QwtPlot3D 库完整整合进来,实现了 2D/3D 一体化 :一个库、一套构建系统、统一的 CMake target,同时支持二维曲线图和三维表面图。不仅如此,7.0 还新增了 3D 主题系统22 种科学 colormap 预设,让三维图表的视觉风格可以一键切换。

本篇就带你从零上手 Qwt 7.0 的 3D 绘图模块。


二、3D 绘图模块概述

2.1 三库结构中的 plot3d

回顾系列的架构介绍,Qwt 7.0 由三个共享库组成:

复制代码
    ┌──────────────────────────┐
    │        qwt::core         │  ← 基础工具库(颜色、数学、数据类型、变换)
    └──────────────────────────┘
           ↗              ↖
  ┌──────────────┐  ┌───────────────┐
  │   qwt::plot   │  │  qwt::plot3d  │
  │     (2D)      │  │     (3D)      │
  └──────────────┘  └───────────────┘

qwt::plot3d 就是 3D 绘图模块,它链接 qwt::core(复用 colormap、数学工具等),但与 qwt::plot(2D)互不依赖。这意味着你只想用 3D 功能时,不必把 2D 也链接进来。

构建时通过 CMake 选项 QWT_CONFIG_QWTPLOT_3D(默认 ON)控制是否编译该模块。

2.2 核心类一览

3D 模块的所有类都位于 Qwt3D 命名空间下,使用时记得加 Qwt3D:: 前缀,或在源文件中写 using namespace Qwt3D;

类名 说明
Qwt3D::Plot3D 3D 绘图基类,提供基本框架和交互能力
Qwt3D::SurfacePlot 3D 表面图,显示连续曲面,同时支持网格和单元数据
Qwt3D::Function 3D 函数绘图,根据数学函数 z = f(x, y) 生成曲面
Qwt3D::GraphPlot 图形类 3D 绘图的中间基类
Qwt3D::Axis 3D 坐标轴配置
Qwt3D::ColorLegend 3D 颜色条(图例)
Qwt3D::Qwt3DTheme 3D 主题系统,封装背景、网格、colormap、坐标轴、光照等全部视觉属性

类的继承关系很清晰:SurfacePlot 继承自 Plot3DFunction 则是一个被 Plot3D 使用的辅助类,负责把数学函数转换成曲面数据。

新手避坑 :类名是 Plot3DSurfacePlot,不要写成 Qwt3DPlot3DQwt3DSurfacePlot。正确的完整写法是 Qwt3D::Plot3DQwt3D::SurfacePlot。命名空间 Qwt3D 和类名之间用 :: 分隔,不要把 Qwt3D 当成类名前缀拼进去。

2.3 主要特性

  • 多种绘图类型:表面图、网格图、参数曲面等
  • OpenGL 渲染:利用 GPU 实现高性能三维渲染
  • 交互操作:鼠标旋转视角、平移、滚轮缩放
  • 光照和材质:支持光照效果和材质参数配置
  • 主题系统:一键切换视觉风格,10 种预设主题 + 22 种科学色彩映射

三、基本使用:画出你的第一个 3D 曲面

我们从最经典的例子开始------根据一个数学函数 z = sin(x) * cos(y) 生成三维曲面。对应的示例代码位于 examples/3D/simpleplot3D,效果如下图:

3.1 完整代码示例

cpp 复制代码
#include <qwt3d_surfaceplot.h>
#include <qwt3d_function.h>

using namespace Qwt3D;

// 1. 定义数学函数:继承 Function,重载 operator()
class MyFunction : public Function
{
public:
    // z = f(x, y)
    double operator()(double x, double y) override
    {
        return std::sin(x) * std::cos(y);
    }
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    // 2. 创建表面图控件
    SurfacePlot* plot = new SurfacePlot();

    // 3. 创建函数对象并绑定到绘图
    MyFunction* func = new MyFunction(*plot);

    // 4. 设置数据范围(x 和 y 的区间)和网格分辨率
    func->setDomain(-5, 5, -5, 5);  // x ∈ [-5, 5], y ∈ [-5, 5]
    func->setMesh(50, 50);           // 50 x 50 网格

    // 5. 生成曲面数据
    func->create();

    // 6. 设置视角:X、Y、Z 轴旋转角度(单位:度)
    plot->setRotation(30, 0, 45);

    // 7. 启用鼠标交互
    plot->enableMouse(true);

    // 8. 显示
    plot->show();

    return app.exec();
}

3.2 代码解读

这段代码的逻辑分为四步:

第一步:定义函数。 继承 Qwt3D::Function,重载 operator()(double x, double y),返回值就是曲面在该点的 Z 坐标。想画什么样的曲面,改这一个函数就行。

第二步:创建绘图控件。 SurfacePlotPlot3D 的子类,专门用于显示连续曲面。它同时支持网格数据和单元数据两种模式。

第三步:配置数据范围和分辨率。 setDomain() 决定函数在 X、Y 方向的取值区间;setMesh() 决定采样网格的密度(这里是 50×50,共 2500 个采样点)。网格越密,曲面越平滑,但渲染开销也越大。create() 负责真正执行采样并把数据附加到绘图上。

第四步:设置视角并显示。 setRotation(30, 0, 45) 表示绕 X 轴旋转 30°、Y 轴 0°、Z 轴 45°。enableMouse(true) 开启鼠标交互后,你可以用左键拖动旋转、中键拖动平移、滚轮缩放。

3.3 从数据数组加载

除了用数学函数生成,你也可以直接喂一组已有的 Z 值数据:

cpp 复制代码
#include <qwt3d_surfaceplot.h>

using namespace Qwt3D;

SurfacePlot* plot = new SurfacePlot();

// 分配 100x100 的 Z 值数组
double* zData[100];
for (int i = 0; i < 100; ++i)
    zData[i] = new double[100];
// ... 在此处填充你的实际数据 ...

// 加载 Z 值数据,需显式指定 X/Y 范围
plot->loadFromData(zData, 100, 100, 0.0, 100.0, 0.0, 100.0);

// setResolution 控制下采样:1 表示用全部数据,值越大下采样越强
plot->setResolution(1);

setResolution() 是个性能调节开关:当数据量很大时,调大这个值会让绘图只用部分数据来渲染,牺牲一点精度换取流畅度。

3.4 交互与视角控制

cpp 复制代码
// 启用鼠标交互
plot->enableMouse(true);
// 鼠标操作说明:
//   左键拖动 → 旋转视角
//   中键拖动 → 平移
//   滚轮     → 缩放

// 用代码设置缩放比例(X、Y、Z 三个方向)
plot->setScale(1.0, 1.0, 1.0);

// 用代码设置旋转角度
plot->setRotation(45, 30, 60);

四、3D 主题系统:一键切换视觉风格

如果你用过 matplotlib 的样式表(plt.style.use('dark')),那对 Qwt 7.0 的 3D 主题系统会感到很亲切。Qwt3D::Qwt3DTheme 把 3D 绘图的全部视觉属性------背景色、网格色与线宽、数据色彩映射、坐标轴颜色、标题样式、光照预设、着色模式、材质参数------打包成一个对象,一行代码就能整体切换。

这是 Qwt 7.0 新增的能力,原版 Qwt 6.x 并没有这套主题系统。

4.1 十种内置预设主题

预设名称 说明
Default 白底 + jet 色彩映射 + 无光照
Dark 深灰底 + viridis + 柔和光照
Scientific 白底 + jet + 工作室光照
Warm 暖色底 + hot 色彩映射
Cool 冷色底 + cool 色彩映射
Matplotlib matplotlib 风格(viridis + 柔和光照)
EarthTones 大地色调 + autumn 色彩映射
Ocean 海洋色调 + winter 色彩映射
HighContrast 黑底白线,高对比度
Presentation 大字体 + 粗线条,适合演示投屏

4.2 三种使用方式

cpp 复制代码
#include <qwt3d_theme.h>

// 方式 1:用枚举值应用预设主题(推荐)
plot->applyTheme(Qwt3D::Qwt3DTheme::Dark);

// 方式 2:用字符串名称应用主题
plot->applyTheme("Scientific");

// 方式 3:基于预设做自定义修改后再应用
Qwt3D::Qwt3DTheme theme(Qwt3D::Qwt3DTheme::Scientific);
theme.setDataColorPreset("plasma");  // 换成 plasma 色彩映射
theme.setShininess(20.0);            // 调整材质光泽度
theme.setLightingPreset(Qwt3D::Qwt3DTheme::Studio);  // 换光照
theme.apply(plot);  // plot 是 Qwt3D::Plot3D* 指针

方式 3 最灵活:以一个预设为起点,只改你关心的那几个属性,其余沿用预设值。

4.3 二十二种科学 colormap 预设

主题系统里的数据色彩映射,底层复用的是 core 模块的 QwtColorMapPreset,提供 22 种科学可视化标准色彩映射。这是 Qwt 7.0 的 colormap 预设系统,2D 和 3D 共用同一套:

类别 预设名称
感知均匀(推荐用于顺序型数据) viridisplasmainfernomagmacividis
经典 jethotcoolspringsummerautumnwinter
灰度 graybonecopper
彩虹 rainbowhsvturbo
发散(适合有正负的数据) coolwarmrdylburdylgnspectral
cpp 复制代码
// 切换色彩映射
theme.setDataColorPreset("viridis");

// 查询所有可用预设
QStringList presets = QwtColorMapPreset::availablePresets();

选色建议viridisplasma 这类感知均匀的色彩映射对色盲友好,且在打印成灰度时仍能保持信息层次,是科研论文的首选。jet 虽然经典,但在某些区段会有"假边界"现象,慎用于精确读数场景。

4.4 五种光照预设

预设 说明
NoLighting 无光照,纯色平面渲染
FlatLight 均匀环境光,无强阴影
Studio 经典三点照明(主光 + 辅光 + 轮廓光)
Outdoor 强方向光 + 环境光,阴影明显
Soft 柔和漫射光,过渡自然

光照让曲面有了立体感。NoLighting 适合需要精确辨识颜色的场景(比如热力图),StudioSoft 适合展示和汇报。

4.5 直接使用 colormap(不通过主题)

如果你不想动整套主题,只想给数据上个色:

cpp 复制代码
#include <qwt3d_colormap_color.h>

using namespace Qwt3D;

// 显示颜色条(图例)
plot->showColorLegend(true);

// 用 core 模块的 colormap 预设,按 Z 值映射颜色
plot->setDataColor(new ColorMapColor(plot, "viridis"));

ColorMapColor 是一个适配器,把 core 模块的 QwtColorMap 桥接到 3D 模块的 Qwt3D::Color 接口,所以那 22 种预设可以直接用在 3D 表面图上。


五、3D 绘图增强:坐标轴、颜色条与标注

光有曲面还不够,一张合格的工程图表需要清晰的坐标轴、图例和标注。Qwt 7.0 的 3D 模块提供了这些增强能力。

5.1 坐标轴配置

3D 绘图有三条坐标轴,通过 Qwt3D::Axis 可以配置刻度、标签、标题等。对应示例位于 examples/3D/axes

cpp 复制代码
using namespace Qwt3D;

// 获取某条坐标轴(以底面 X 轴为例)
Axis* xAxis = plot->axis(Plot3D::AxisX1);

// 设置轴标题
xAxis->setLabelString("X Axis (mm)");

// 设置刻度数和数值格式
xAxis->setMajors(5);   // 主刻度数
xAxis->setMinors(3);   // 次刻度数

5.2 颜色条与注释标注

颜色条(color legend)告诉读者"这个颜色对应什么数值",是热力图和表面图的标配。examples/3D/enrichments 演示了更丰富的增强效果,包括自定义标注:

cpp 复制代码
// 显示颜色条
plot->showColorLegend(true);

5.3 自动切换与动态曲面

examples/3D/autoswitch 展示了在不同绘图风格间自动切换的效果:

examples/3D/figureSurface3D 则展示了 3D 曲面与 QwtFigure(类似 matplotlib Figure 的多绘图布局容器)的集成,可以在一个窗口里管理多个 3D 子图并支持拖动、缩放:


六、OpenGL 渲染:为什么 3D 需要 GPU 加速

3D 绘图天然依赖 OpenGL------旋转一个有 2500 个顶点的曲面,每秒要重绘几十帧,CPU 软件渲染很难扛住。Qwt 7.0 的 3D 模块直接基于 OpenGL 和 GLU 实现,GPU 渲染是它的底座。

有意思的是,Qwt 的 OpenGL 加速能力并不局限于 3D。即使是 2D 绘图,也提供了 OpenGL 画布选项来应对大数据量场景。

6.1 CMake 配置

使用 OpenGL 需要在 CMake 中引入 Qt 的 OpenGL 模块:

cmake 复制代码
find_package(Qt${QT_VERSION_MAJOR} ${QWT_MIN_QT_VERSION} COMPONENTS
    OpenGL
    REQUIRED
)
target_link_libraries(${QWT_APP_NAME} PRIVATE
    Qt${QT_VERSION_MAJOR}::OpenGL
)

# Qt6 还需要 OpenGLWidgets 模块
if(${QT_VERSION_MAJOR} EQUAL 6)
    find_package(Qt${QT_VERSION_MAJOR} ${QWT_MIN_QT_VERSION} COMPONENTS
        OpenGLWidgets
        REQUIRED
    )
    target_link_libraries(${QWT_APP_NAME} PRIVATE
        Qt${QT_VERSION_MAJOR}::OpenGLWidgets
    )
endif()

6.2 QwtPlotOpenGLCanvas(2D 的 OpenGL 加速)

QwtPlotOpenGLCanvas 继承自 QOpenGLWidget,直接在 GPU 上渲染 2D 绘图内容。相比默认的 QwtPlotCanvas,在处理大数据集和实时更新时性能显著提升:

cpp 复制代码
#include <qwt_plot.h>
#include <qwt_plot_opengl_canvas.h>
#include <qwt_plot_curve.h>

auto* plot = new QwtPlot("OpenGL Plot");

// 用 OpenGL 画布替换默认画布
auto* canvas = new QwtPlotOpenGLCanvas(plot);
canvas->setFrameStyle(QFrame::Box | QFrame::Plain);
canvas->setLineWidth(1);
plot->setCanvas(canvas);

// 照常添加曲线
auto* curve = new QwtPlotCurve("Data");
// ... 设置数据 ...
curve->attach(plot);

还可以传入自定义 QSurfaceFormat 做高级配置,比如开启 MSAA 抗锯齿:

cpp 复制代码
QSurfaceFormat format;
format.setSamples(4);        // 4 倍 MSAA 抗锯齿
format.setDepthBufferSize(24);

auto* canvas = new QwtPlotOpenGLCanvas(format, plot);
plot->setCanvas(canvas);

6.3 两种加速方案怎么选

除了 QwtPlotOpenGLCanvas,还有一种是给标准 QwtPlotCanvas 开启 OpenGLBuffer 属性:

cpp 复制代码
auto* canvas = new QwtPlotCanvas(plot);
canvas->setPaintAttribute(QwtPlotCanvas::OpenGLBuffer, true);
plot->setCanvas(canvas);

两者各有适用场景:

场景 推荐方案
静态或小数据集(<1 万点) 默认 QwtPlotCanvas 即可
大数据集(>10 万点) QwtPlotOpenGLCanvas,交互更流畅
实时流式数据 QwtPlotOpenGLCanvas,帧率更稳定
复杂布局的混合控件 UI QwtPlotCanvas + OpenGLBuffer,集成更友好

简单说:QwtPlotOpenGLCanvas 直接渲染到 QOpenGLWidget,性能更好;OpenGLBuffer 方案在包含复杂混合排布的桌面应用中集成更顺畅。

3D 模块的 OpenGL 依赖 :3D 绘图模块(qwt::plot3d)本身就依赖 OpenGL 和 GLU 库,使用前请确保系统已安装 OpenGL 驱动和 GLU。这一点和 2D 可选 OpenGL 不同------3D 是必须的。


七、与旧版本的区别

如果你之前用过原版 Qwt 6.x,这里有几个关键变化值得注意:

对比项 原版 Qwt 6.x Qwt 7.0
3D 绘图 需单独引入独立的 QwtPlot3D 库,版本不同步、构建配置独立 从 7.1 起整合为 qwt::plot3d,与 2D 同库同构建系统
主题系统 无,视觉属性需逐项手动设置 新增 Qwt3D::Qwt3DTheme,10 种预设一键切换
色彩映射 3D 自有色彩逻辑,与 2D 不互通 新增 QwtColorMapPreset,22 种科学 colormap,2D/3D 共用
colormap 适配 需自行桥接 提供 ColorMapColor 适配器,自动桥接 core 模块
光照预设 无系统化方案 5 种光照预设(NoLighting/FlatLight/Studio/Outdoor/Soft)
CMake target 无统一 target qwt::plot3dtarget_link_libraries 一行搞定

对老项目迁移来说,最大的好处是不用再维护一个游离的 QwtPlot3D 依赖了------升级到 Qwt 7.0 后,2D 和 3D 用同一个库版本、同一套构建配置,省心很多。


八、核心方法速查表

最后整理一张常用方法速查表,方便查阅:

方法 所属类 说明
setDomain() Function / GridMapping 设置 X/Y 数据范围
setMesh() Function / GridMapping 设置网格分辨率(列、行)
setResolution() SurfacePlot 设置数据分辨率(1 = 用全部数据,值越大下采样越强)
loadFromData() SurfacePlot 加载数据数组到绘图
create() Function 生成并附加曲面数据
setRotation() Plot3D 设置 X/Y/Z 旋转角度(度)
setScale() Plot3D 设置 X/Y/Z 缩放比例
enableMouse() Plot3D 启用/禁用鼠标交互
showColorLegend() Plot3D 显示/隐藏颜色条
setDataColor() Plot3D 设置数据颜色映射函数
updateData() Plot3D 重新计算并更新数据
applyTheme() Plot3D 应用主题(枚举或名称)

九、实践建议与总结

写到这里,关于 Qwt 7.0 的 3D 数据可视化就介绍完了。最后给几条实践建议:

  1. 控制数据规模 :3D 渲染对数据量比 2D 敏感,推荐 100×100 网格以下。数据太大时用 setResolution() 降采样。
  2. 善用主题系统 :与其逐项手动设颜色,不如先 applyTheme() 选一个接近的预设,再做微调,代码量少很多。
  3. 色彩映射选 viridis 系:感知均匀、色盲友好、打印安全,是科研场景的稳妥选择。
  4. 2D 大数据也别忘了 OpenGL :即使不做 3D,2D 绘图超过 10 万点时,换 QwtPlotOpenGLCanvas 能明显提升交互流畅度。

系列文章

系列总述:Qwt 7.0 ------ 基于 Qt 的高性能 2D/3D 绘图库


相关链接

相关推荐
Tian_Hang4 小时前
eclipse ditto 学习笔记
运维·服务器·开发语言·javascript·3d
尘中远6 小时前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远7 小时前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia7 小时前
Qt——进程与线程的概念
qt
AI视觉网奇7 小时前
BambuStudio 编译实战 2026
3d
AI前沿资讯7 小时前
AI3D角色生产如何减少返工?用 V2Fun 前移建模与动画流程
人工智能·3d
郝学胜-神的一滴8 小时前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
开发语言·c++·qt·程序人生·软件开发·用户界面
蓝速科技8 小时前
蓝速科技视觉 3D 全息舱 AI 数字人一体机带灯与无灯款深度评测
人工智能·科技·3d
满天星83035779 小时前
【Qt】控件(二) (geometry及与frameGeometry的区别)
开发语言·qt