【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::Plot3D3D 绘图基类,提供基本框架和交互能力
Qwt3D::SurfacePlot3D 表面图,显示连续曲面,同时支持网格和单元数据
Qwt3D::Function3D 函数绘图,根据数学函数 z = f(x, y) 生成曲面
Qwt3D::GraphPlot图形类 3D 绘图的中间基类
Qwt3D::Axis3D 坐标轴配置
Qwt3D::ColorLegend3D 颜色条(图例)
Qwt3D::Qwt3DTheme3D 主题系统,封装背景、网格、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,效果如下图:

01-simpleplot3D

3.1 完整代码示例

#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 值数据:

#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 交互与视角控制

// 启用鼠标交互
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 色彩映射
Matplotlibmatplotlib 风格(viridis + 柔和光照)
EarthTones大地色调 + autumn 色彩映射
Ocean海洋色调 + winter 色彩映射
HighContrast黑底白线,高对比度
Presentation大字体 + 粗线条,适合演示投屏

4.2 三种使用方式

#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
// 切换色彩映射
theme.setDataColorPreset("viridis");

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

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

4.4 五种光照预设

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

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

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

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

#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

02-axes

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 演示了更丰富的增强效果,包括自定义标注:

03-enrichments

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

5.3 自动切换与动态曲面

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

04-autoswitch

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

05-figureSurface3D


六、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 模块:

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,在处理大数据集和实时更新时性能显著提升:

#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 抗锯齿:

QSurfaceFormat format;
format.setSamples(4);        // 4 倍 MSAA 抗锯齿
format.setDepthBufferSize(24);

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

6.3 两种加速方案怎么选

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

auto* canvas = new QwtPlotCanvas(plot);
canvas->setPaintAttribute(QwtPlotCanvas::OpenGLBuffer, true);
plot->setCanvas(canvas);

两者各有适用场景:

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

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

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


七、与旧版本的区别

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

对比项原版 Qwt 6.xQwt 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无统一 targetqwt::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 绘图库


相关链接

  • 项目地址:https://github.com/czyt1988/QWT
  • Gitee 镜像:https://gitee.com/czyt1988/QWT
  • 在线文档:https://czyt1988.github.io/QWT/zh/
  • 系列总述:https://blog.csdn.net/czyt1988/article/details/160193393
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尘中远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值