QT+QSurface实现曲面的绘制

1 数据准备

1.1 数据需求

数据在仓库中有: https://gitee.com/strivezhangp/3d-surface-demo.git

绘制曲面需要将无数个顶点坐标,在三维坐标系中的基本格式为(x,y,z),然后将这无数个点一个一个小方格,最后拼接实现曲面的绘制。

说明:时间比较紧迫,没有直接使用最底层的OpenGl绘制,而是调用了集成OpenGL接口的*Qt Data Visualization* 模块。

img

img

1.2 数据处理

数据已提供txt格式的数据文档,大概以矩阵的方式,展示了每个点的坐标,数据的处理我使用了GO语言对文本文档中的数据进行读取,并按照自己的需求对文本进行了重新的拼接,最后转换为一个float 类型的数组

处理后结果的输出:(优点:可以直接将转好格式的数据粘贴到C++中构建数组)
在这里插入图片描述

相关代码:

package main
import (
    "fmt"
    "github.com/xuri/excelize/v2"
    "strconv"
)
func main() {
    // 打开数据文件
    f, err := excelize.OpenFile("data/地形数据.xlsx")
    if err != nil {
        fmt.Println(err)
        return
    }
    // 延迟关闭单元格
    defer func() {
        if err := f.Close(); err != nil {
            fmt.Println(err)
        }
    }()
    // 获取 Sheet1 上所有单元格
    rows, err := f.GetRows("Sheet1")
    if err != nil {
        fmt.Println(err)
        return
    }
    // 数据存放集合
    var points [441][3]float64
    n := 0
    var num_x, num_y, num_z float64
    // 转换为三维坐标点 x, y, z
    for i := 1; i < 22; i++ {
        x := rows[0][i]
        for j := 1; j < 22; j++ {
            y := rows[j][0]
            z := rows[i][j]
            // 转换为浮点型
            num_z, _ = strconv.ParseFloat(z, 64)
            num_x, _ = strconv.ParseFloat(x, 64)
            num_y, _ = strconv.ParseFloat(y, 64)
            // 添加到数组中
            points[n][0] = num_x / 10
            points[n][1] = num_y / 10
            points[n][2] = num_z / 10
            n++
        }
    }
    fmt.Printf("{")
    for _, point := range points {
        fmt.Printf("{")
        fmt.Printf("%f ,", point[0])
        fmt.Printf("%f ,", point[1])
        fmt.Printf("%f", point[2])
        fmt.Printf("},\n ")
    }
    fmt.Printf("}\n")
}

2 理论知识

2.1 QSurface简介

QSurface 是 Qt 中用于显示三维曲面的一个基础类,包含在Qt Data Visualization 模块中,其绘图原理是通过将三维数据点转换为平面上的二维数据点来实现的,QSurface 会将三维曲面分割成一个个小块,每个小块都由四个顶点构成,然后将每个小块拼接实现曲面的绘制。在绘制完成后,QSurface 还可以通过设置一些属性来进行渲染效果的调整,例如设置颜色、光照、材质、渐变等,使得三维曲面的显示更加真实和生动。

在底层实现上,QSurface 使用了 OpenGL 来进行曲面的绘制。具体来说,QSurface 使用了 Qt 中的 OpenGL 模块来实现 OpenGL 相关的操作,例如创建 OpenGL 上下文、初始化 OpenGL 相关参数、绘制三维图形等。

OpenGL 是一个跨平台的图形库,它提供了一套接口,可以用来进行图形绘制和计算。使用 OpenGL 可以在不同的平台上实现相同的图形效果,因为 OpenGL 并不依赖于任何特定的操作系统或者硬件。同时,OpenGL 还可以利用 GPU 进行并行计算,从而实现更高效的图形渲染。

2.2 绘制的基本流程

img

  • 数据源和数据代理

在使用 QSurface 绘制三维曲面时,需要提供一个数据源(QSurfaceDataArrayQSurfaceDataRow),用于存储曲面上的各个点的坐标值,再通过使用 QSurfaceDataProxy 对象将数据源中的数据传递给 QSurface3DSeries 对象进行绘制。

  • 曲面绘制

在绘制过程中,QSurface3DSeries 对象会使用 OpenGL 来将数据源中的点连接起来,形成一个完整的三维曲面。为了让曲面显示得更加平滑,*QSurface3DSeries 对象*会对数据源中的点进行插值计算,生成更多的点,从而使得曲面更加平滑。

  • 渲染效果

在绘制完成后,可以通过设置一些属性来进行渲染效果的调整,例如设置颜色、光照、材质、渐变等。这些属性可以帮助开发者实现更加复杂的渲染效果,使得三维曲面的显示更加真实和生动。

总之,QSurface 通过将三维数据点转换为平面上的二维数据点来实现绘图,通过插值计算和属性设置来实现更加平滑和生动的渲染效果。

2.3 基本类的介绍

2.3.1 QSurfaceDataRow类

QSurfaceDataRowQt Data Visualization 模块中用于存储三维曲面数据的一个类。它可以存储多个 QVector3D 对象,每个 QVector3D 对象代表三维坐标系中的一个点。因此,可以将 QSurfaceDataRow 理解为二维矩阵中的一行数据,其中每个元素代表一个点在三维坐标系中的位置和属性。

先通过 QVector<QVector3D> 对象存储二维矩阵的一行数据,然后将这些一行行的数据存储到 QSurfaceDataRow 中,QSurfaceDataRow 是绘制三维曲面所必须的数据结构之一。

QSurfaceDataRow 还提供了多种方法来操作和管理三维曲面数据,例如添加、删除、修改、排序、搜索等等。同时,它也支持序列化和反序列化操作,可以将数据保存到本地文件中或从文件中读取数据,方便数据的长期存储和共享。

总之,QSurfaceDataRow 为开发人员提供了一种方便、高效、可扩展的数据存储和处理机制。

2.3.2 QSurfaceDataArray类

QSurfaceDataArray Qt Data Visualization 模块中用于存储三维曲面数据的一个类。它可以存储多个 QSurfaceDataRow

在使用 QSurfaceDataArray 绘制三维曲面时,可以先通过 QVector<QSurfaceDataRow> 对象存储二维矩阵的每一行数据,然后将这些二维矩阵数据存储到 QSurfaceDataArray 中。在绘制过程中,QSurface3DSeries 对象会从 QSurfaceDataArray 中读取数据,并将其转化为三维曲面的平面数据。

QSurfaceDataArray 还提供了多种方法来操作和管理三维曲面数据,例如添加、删除、修改、排序、搜索等等。同时,它也支持序列化和反序列化操作,可以将数据保存到本地文件中或从文件中读取数据,方便数据的长期存储和共享。

2.3.4 QSurface3DSeries类

QSurface3DSeries Qt Data Visualization 模块中用于绘制三维曲面的类,它可以利用 OpenGL 实现曲面的绘制。

在使用 QSurface3DSeries 绘制曲面时,需要先创建一个 Q3DSurface 对象,并将 QSurface3DSeries 添加到该对象中。然后,通过调用 QSurface3DSeries 对象的 setMesh() 函数,将三维曲面的网格数据传递给 QSurface3DSeries。在 QSurface3DSeries 中,通过调用 OpenGL 的接口来进行曲面的绘制,从而实现高效的渲染效果。QSurface3DSeries 会在内部创建一个 OpenGL 上下文,并利用该上下文调用 OpenGL 的接口进行曲面的绘制。

总的来说,QSurface3DSeries 利用 OpenGL 实现曲面的绘制,可以充分利用 GPU 的并行计算能力,实现高效的渲染效果。同时,由于 QSurface3DSeries 封装了底层的 OpenGL 接口,因此可以更加方便地实现三维曲面的绘制,而无需处理 OpenGL 的底层细节。

QSurface3DSeries 提供了多种方法来设置曲面的外观和属性,包括坐标轴范围、渐变色、光照效果等等。此外,还可以通过 QSurface3DSeries 的信号和槽机制实现与用户交互,如选中某个点或面片时触发特定的事件处理函数。

2.3.5 QSurfaceDataProxy

具体来说,QSurfaceDataProxy 提供了以下几个主要作用:

  • 存储和管理数据QSurfaceDataProxy 可以通过 setArray() 方法将 QSurfaceDataArray 对象设置为其数据源,从而实现对三维曲面数据的存储和管理。
  • 前向迭代器QSurfaceDataProxy 支持前向迭代器,可以在迭代过程中依次访问 QSurfaceDataArray 中存储的数据,并进行相应的处理和分析。
  • 数据更新通知:当 QSurfaceDataArray 中的数据发生变化时,QSurfaceDataProxy 会自动发送数据更新信号,通知 QSurface3DSeries 对象进行更新绘制。

3 设计实现

说明:此处不粘贴大量代码,具体的代码可以到Gitee仓库获取。

仓库地址: https://gitee.com/strivezhangp/3d-surface-demo.git

踩坑:实现了数据的传输,但是没有按照行列进行传输,而且没有把坐标系统的轴分清楚,绘制出来如下效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRNqREOE-1680084283889)(null#pic_center)]

3.1 创建QT项目

具体操作可以见之前的QT笔记,详细介绍。

3.2 构建数据坐标

img

坐标的结构为一个 422 * 3 的三维float矩阵。包含每个点的 x, y, z坐标。

3.3 创建三维坐标系

坐标系说明:这个坐标系的基本结构如下

img

QT Data模块可以快速构建三维坐标系,具体的实现细节参照代码仓库,关键代码如下:

groupBox->axisX()->setTitle("X轴");
groupBox->axisY()->setTitle("Y轴");
groupBox->axisZ()->setTitle("Z轴");

// 设置轴范围
groupBox->axisY()->setRange(-8, 8);
groupBox->axisX()->setRange(-3.5, 3.5);
groupBox->axisZ()->setRange(-3.5, 3.5);

效果展示:

img

3.4 数据存储对象定义绑定

  • 提前定义数据的存储区,以便于数据的传入修改。
  • 将画布与窗口绑定,以便于效果的显示。
	// 将三维图与数据进行绑定
    QSurfaceDataProxy* proxy = new QSurfaceDataProxy; //数据代理
    series = new QSurface3DSeries(proxy);
    groupBox->addSeries(series);

    // 三维图画布添加到界面 == 布局
    QVBoxLayout* vLayout = new QVBoxLayout();
    vLayout->addWidget(createWindowContainer(groupBox));
    ui.widget->setLayout(vLayout);

	// 创建数据存放区
    dataArray = new QSurfaceDataArray;
    dataArray->clear();

3.5 数据的传入

数据的传入是关键部分,需要有一定的行列排列实现数据的传输,这样便于实现小方格的绘制。代码注释中有详细介绍。

采用两个for循环,实现数据按行按列传输。

    // 数据点,分为22x20,就22行,20列,
    // 一个点一个点的连接,需要按顺序连接好节点,即一行一列连接,否则曲面就会看不出形状。
    int NN = 21;//设置行
    dataArray->reserve(NN);
    for (int i = 0; i <= NN; i++) { // 数据行传输
        QSurfaceDataRow* newRow = new QSurfaceDataRow(20);
        int index = 0;
        for (int j = 0; j <= 19; j++) { // 数据列传输
            // 设置点坐标
            (*newRow)[index++].setPosition(QVector3D(points[j + i * NN][0], points[j + i * NN][2], points[j + i * NN][1]));
            *dataArray << newRow;
        }
        // 传入数据行
        *dataArray << newRow;
    }
	// 数据上传实现绘制
    series->dataProxy()->resetArray(dataArray);

数据传入的第二种方式:

 QSurfaceDataArray *dataArray = new QSurfaceDataArray();
    dataArray->reserve(21);
    for (int i = 0 ; i < 21 ; i++) {
        QSurfaceDataRow *newRow = new QSurfaceDataRow(21);
        int index = 0;
        for (int j = 0; j < 21; j++){
            float x=0, z=0, y=0;
            //此处省略数据
            (*newRow)[index++].setPosition(QVector3D(x, y, z));
    	}
    	*dataArray << newRow;
	}
    m_sqrtSinProxy->resetArray(dataArray);

3.6 渐变色的添加

渐变色的设计是使用线性渐变的原则,以Y轴为主线,设置起点和终点,一次展示不同的颜色,最后实现渐变。

     // 添加颜色渐变
    QPointF startPoint(0, 0);
    QPointF endPoint(0, 1);
    // 创建线性渐变对象
    QLinearGradient gr(startPoint, endPoint);
    gr.setColorAt(0.0, Qt::darkGreen);
    gr.setColorAt(0.5, Qt::yellow);
    gr.setColorAt(0.8, Qt::red);
    gr.setColorAt(1.0, Qt::darkRed);
    groupBox->seriesList().at(0)->setBaseGradient(gr);
    groupBox->seriesList().at(0)->setColorStyle(Q3DTheme::ColorStyleRangeGradient);

3.7 交互事件的添加

说明:在此设计中我们使用了 QSurface3DSeries 类,此类自动封装了鼠标滚轮放大效果,以及 鼠标右键长按后角度旋转效果。在此程序中,没有进行二次开发,就直接使用了封装的效果。

3.8 效果展示

image-20230329161653118

4 总结

4.1 实现过程分享

在整个设计中遇到了很多问题,也了解到了很多绘制的方法:

  • OpenGl结合bezier曲线方式绘制 效果视频:

https://player.bilibili.com/player.html?bvid=BV1fd4y1y7TZ

  • 直接使用OpenGL+sahder进行绘制(我掌握的知识不够多,短时间能实现不了)
  • 使用QT自带的Qt Data Visualization 模块(搜索资料都是直接的源码,关于具体的细节,没人详细介绍,只能参考官方文档,很幸运遇到了CSDN上一个博主,进行了交流)
  • 官方文档:https://doc.qt.io/

找到了一个大佬,实现了绘制,同时将自己的这个案例也做成了他的博客中的一个实例,参考他的博客地址:https://blog.csdn.net/qq_45179361/article/details/128278267?spm=1001.2014.3001.5501

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值