news 2026/9/5 17:00:48

31个QT上位机实战源码解析:串口通讯、运动控制与工业HMI开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
31个QT上位机实战源码解析:串口通讯、运动控制与工业HMI开发

简介:本资源是一套面向Qt初学者与工业上位机开发者的实战型源码合集,聚焦嵌入式与工控场景下的GUI应用开发,涵盖步进电机控制、温湿度监测、触摸屏交互、串口/CAN通信、汽车仪表盘模拟及多轴运动控制等核心方向。压缩包共77个文件,包含17个ZIP、11个RAR、7个C++源文件(.cpp)、7个头文件(.h)、2个UI界面文件(.ui)及多个可执行程序(.exe)、动态库(.dll)和项目配置文件(.pro),总大小108.43MB,结构清晰、模块独立,便于按需学习与工程复用。已有1671人下载学习,适合希望快速掌握Qt跨平台GUI开发、硬件通信协议集成及实时数据可视化能力的开发者。源码均基于Qt5实现,含完整串口调试工具、温度曲线绘图、QML交通灯模拟、CAN总线上位机、四轴运动参数配置界面等典型实例,提供从通信驱动到UI渲染的全链路参考,是理解工控上位机软件架构的优质实践素材。

1. 项目概述:一份来自实战的QT上位机源码合集

在工业自动化、嵌入式设备控制和物联网应用开发领域,上位机软件是连接物理世界与数字世界的桥梁。它负责与下位机(如PLC、单片机、传感器模块)通信,进行数据采集、解析、显示和控制指令下发。而QT,凭借其卓越的跨平台能力、丰富的C++类库和强大的图形界面开发框架,早已成为开发这类专业级上位机软件的首选工具之一。今天要分享的,不是某个单一功能的演示,而是一个汇集了31个实战项目的QT上位机源码合集。这个合集覆盖了步进电机控制、温湿度监控、触摸屏交互、串口通讯、汽车仪表界面仿真、多轴运动控制等多个核心工业场景。

对于刚入行的工程师或有一定基础但想拓宽视野的开发者来说,直接阅读和调试成熟的、解决实际问题的源码,其价值远大于翻阅教科书或官方文档。这份合集就像一本“实战案例集”,每个项目都瞄准了一个具体的工程需求。你可以从中学习到QT框架如何与硬件打交道,如何设计稳定可靠的数据通信协议,如何构建直观易用的工业人机界面(HMI),以及如何处理多线程、定时器、事件循环等并发问题。无论你是想快速搭建一个温湿度数据记录仪,还是开发一套复杂的多轴运动控制系统,这里面的代码都能提供直接的参考和扎实的起点。

2. 源码合集核心模块深度解析

这31个源码项目并非随意堆砌,它们系统地覆盖了上位机开发中的几个关键且高频的技术模块。理解这些模块,就等于掌握了QT上位机开发的骨架。

2.1 硬件交互基石:串口通讯模块

串口(RS232/RS485)是上位机与下位机通讯最传统、最可靠的方式之一,尤其在工业现场。QT提供了QSerialPortQSerialPortInfo类来封装串口操作,但用好它们需要不少技巧。

核心实现要点:

  1. 端口枚举与参数配置:使用QSerialPortInfo::availablePorts()动态获取系统可用串口。配置参数(波特率、数据位、停止位、校验位)必须与下位机严格匹配。一个常见的实践是提供配置文件或界面让用户灵活设置。
  2. 数据收发机制:读写串口数据通常采用异步方式。通过连接QSerialPort::readyRead()信号到一个槽函数,实现数据的实时读取。发送数据则直接调用write()方法。这里的关键是处理数据的“粘包”和“断包”问题。工业协议(如Modbus RTU)通常有严格的帧结构,需要在readyRead槽函数中实现一个状态机或缓冲区,来完整地拼装一帧数据。
  3. 错误处理与超时控制:必须处理QSerialPort::errorOccurred信号,对拔线、奇偶校验错误等情况做出响应(如提示用户、尝试重连)。对于请求-应答式通讯,需要实现超时机制,防止因下位机无响应导致界面卡死。

实操心得:在长时间、高频率的串口通讯中,直接在主线程(GUI线程)中进行大量数据读写和解析,极易导致界面卡顿。务必QSerialPort对象及其数据处理逻辑移到一个独立的QThread线程中。通过信号槽与主线程通信(例如,发送解析后的数据到界面显示,或接收界面下发的控制指令)。这是保证上位机响应流畅性的黄金法则。

2.2 运动控制核心:步进与多轴控制模块

控制步进电机或伺服电机是自动化设备(如3D打印机、CNC机床、点胶机)的核心功能。上位机需要实现复杂的轨迹规划、速度控制和实时状态监控。

核心实现要点:

  1. 指令协议封装:常见的开源运动控制固件如GRBL、Marlin都有其特定的G代码或自定义指令集。上位机需要将这些高级运动指令(如直线插补、圆弧插补)翻译成下位机可识别的字符串指令,并通过串口或网络发送。源码中会展示如何构建一个CommandSender类来管理指令队列和发送。
  2. 状态监控与反馈:上位机需要持续解析下位机返回的状态信息(如当前位置X:100.0 Y:50.0、限位状态Pn:XYZ)。这通常通过定时查询或下位机主动上报实现。解析后的数据需要实时更新到UI的坐标显示控件和虚拟坐标轴上。
  3. 手动控制与参数设置:提供Jog(点动)、回零(Homing)、设置脉冲当量、加速度、最大速度等参数的界面。这部分UI逻辑需要与下位机的参数存储/调用指令紧密结合。
  4. 多轴协同与界面同步:对于三轴、四轴系统,界面往往需要一个3D预览视图(可使用Qt3D或集成OpenGL)来模拟运动轨迹。同时,各轴的坐标显示、速度滑块、使能按钮需要独立控制又相互关联。

避坑指南:运动控制指令的发送间隔和时机非常重要。不要在一个循环中密集发送大量指令,这会导致下位机缓冲区溢出。应该实现一个指令队列,并监听下位机返回的“ok”或“空闲”信号,再发送下一条指令。此外,所有涉及位置、速度的参数在发送前,都要在软件侧进行二次限幅和校验,防止因误操作产生危险运动。

2.3 人机交互界面:触摸屏与汽车仪表UI

现代上位机越来越注重操作体验,尤其是在触摸屏设备上运行。同时,像汽车仪表盘这类高实时性、高美观度的界面也是QT的用武之地。

核心实现要点:

  1. 触摸屏适配:QT默认支持触摸事件。但针对工业触摸屏,可能需要全屏显示、隐藏系统光标、自定义虚拟键盘。使用QWidget::setWindowState(Qt::WindowFullScreen)实现全屏,并通过QApplication::setOverrideCursor(Qt::BlankCursor)隐藏光标。对于复杂的HMI,可以考虑使用QML(Qt Quick)来构建,它更擅长处理流畅的动画和触摸手势。
  2. 仪表控件自定义:汽车仪表盘上的速度表、转速表、油量表等都是自定义控件的典型场景。通过继承QWidgetQFrame,在paintEvent中使用QPainter进行绘制。需要计算指针角度、绘制刻度线和数字标签。关键是将一个物理量(如速度0-240 km/h)映射到角度范围(如-135度到135度)。
  3. 数据绑定与刷新:仪表数据需要高速刷新(如每秒10-60次)。切忌在每次刷新时重新创建控件或进行复杂布局计算。正确的做法是:将数据模型(如车速、转速值)与控件的绘制逻辑解耦。当新数据到来时,只更新模型数据,然后调用控件的update()方法触发重绘。在控件的paintEvent中,从模型读取当前值进行绘制。
  4. 界面风格与主题:使用Qt Style Sheets(QSS)可以方便地统一更换界面风格,适应不同的工业环境或客户需求。对于仪表类控件,其背景、指针颜色、刻度样式都可以通过QSS进行部分定义,再结合自定义绘制实现最终效果。

2.4 数据感知与监控:温湿度等传感器模块

温湿度监控是环境监测、仓储管理、农业大棚等场景的常见需求。上位机需要实现数据的采集、显示、记录、报警。

核心实现要点:

  1. 传感器协议解析:无论是通过串口连接的Modbus温湿度变送器,还是通过USB/网络连接的集成模块,亦或是像ESP32这类单片机上传的定制数据包,都需要编写对应的协议解析器。解析器应从原始字节流中提取出有效的温度、湿度数值,并进行单位换算和有效性校验(如CRC校验)。
  2. 实时曲线展示:使用QChartQCustomPlot(第三方库,更强大)来绘制温湿度随时间变化的曲线。需要管理一个数据缓冲区,实现滚动显示或历史回顾。注意图表刷新频率,过高会导致CPU占用率上升,过低则曲线不连贯。
  3. 数据存储与导出:简单的记录可以使用QSettings或文本文件(如CSV)。对于长期、大量的数据记录,建议集成轻量级数据库如SQLite。QT提供了QSqlDatabase模块来方便地操作SQLite。同时,应提供数据导出功能(如导出为Excel文件),方便用户进行离线分析。
  4. 阈值报警与通知:允许用户设置温度、湿度的上下限阈值。当数据超限时,通过改变UI颜色(如数值变红)、弹出提示框、播放声音,甚至发送邮件/短信(需网络支持)的方式进行报警。报警逻辑应放在独立的线程或定时器中检查,避免阻塞主界面。

3. 关键技术与实战代码剖析

掌握了模块概念后,我们深入到几个关键技术点的代码级实现,这是从“看懂”到“会用”的关键一步。

3.1 基于QSerialPort的稳健串口通讯框架

一个健壮的串口通讯类,远不止是打开、关闭、读写那么简单。下面是一个简化但核心的框架设计:

// SerialManager.h #pragma once #include <QObject> #include <QSerialPort> #include <QThread> class SerialWorker : public QObject { Q_OBJECT public: explicit SerialWorker(QObject *parent = nullptr); ~SerialWorker(); public slots: void openPort(const QString &portName, qint32 baudRate); void closePort(); void writeData(const QByteArray &data); signals: void dataReceived(const QByteArray &data); void portOpened(bool success, const QString &message); void errorOccurred(const QString &errorString); private slots: void handleReadyRead(); void handleError(QSerialPort::SerialPortError error); private: QSerialPort *m_serialPort; QByteArray m_buffer; // 用于处理粘包断包的缓冲区 // 可以添加协议解析状态机成员变量 }; class SerialManager : public QObject { Q_OBJECT public: static SerialManager& instance(); void init(); void sendCommand(const QByteArray &cmd); signals: void rawDataReceived(const QByteArray &data); void sensorDataParsed(double temperature, double humidity); // 示例:解析后的数据 void requestOpenPort(QString, qint32); void requestClosePort(); void requestWriteData(QByteArray); private: SerialManager(QObject *parent = nullptr); QThread m_workerThread; SerialWorker *m_worker; };

实现解析:

  1. 线程分离SerialManager在主线程,SerialWorkerm_workerThread中运行。所有耗时的串口操作都在工作线程完成。
  2. 信号槽通信:通过信号槽跨线程通信。界面点击“打开串口”触发requestOpenPort信号,工作线程的openPort槽函数执行实际操作,完成后发出portOpened信号通知界面结果。
  3. 缓冲区与协议解析:在handleReadyRead中,将读到的数据追加到m_buffer,然后调用一个tryParseFrame()函数。这个函数根据协议(例如,判断是否收到帧头、帧尾,长度是否正确,CRC是否匹配)尝试从缓冲区中提取一帧完整数据。提取成功后,发射dataReceived信号(携带完整帧数据),并从缓冲区中移除该帧数据。对于更复杂的协议,这里可以是一个状态机。
  4. 错误统一处理:所有串口错误在handleError中捕获,并统一通过errorOccurred信号上报给管理器,再由管理器决定是记录日志还是通知用户。

3.2 步进电机控制指令队列与状态管理

运动控制中,指令的可靠有序发送至关重要。下面是一个指令队列管理器的核心思路:

// MotionController.h class MotionController : public QObject { Q_OBJECT public: enum ControllerState { Idle, Sending, Error, Homing }; Q_ENUM(ControllerState) void enqueueCommand(const QString &cmd); void startSending(); void pauseSending(); void emergencyStop(); signals: void stateChanged(ControllerState newState); void currentPositionUpdated(const QVector3D &pos); void commandSent(const QString &cmd); void commandResponseReceived(const QString &response); private slots: void onSerialDataReceived(const QByteArray &data); void onSendTimeout(); private: void processNextCommand(); void parseStatusLine(const QString &line); // 解析如“<Idle|MPos:10.0,20.0,0.0>”的状态行 QQueue<QString> m_commandQueue; ControllerState m_currentState; QTimer *m_waitResponseTimer; QString m_lastSentCommand; QVector3D m_machinePosition; // ... 其他状态变量 };

工作流程:

  1. 指令入队:界面上的任何运动操作(如点动、执行G代码文件)都转化为指令字符串(如G1 X10 Y20 F500)并调用enqueueCommand入队。
  2. 状态机驱动:控制器有一个状态机。默认Idle状态时,如果队列非空,则切换到Sending状态,并调用processNextCommand发送队首指令,同时启动一个超时定时器m_waitResponseTimer(例如设置2秒)。
  3. 等待与响应:串口线程收到数据并解析出一行后,通过信号触发onSerialDataReceived。控制器检查该行数据。如果是“ok”,则确认上一条指令执行完毕,停止超时定时器,从队列移除该指令,并尝试发送下一条。如果是状态行(如<Idle,MPos:...>),则调用parseStatusLine更新内部坐标m_machinePosition并发出currentPositionUpdated信号刷新UI。
  4. 超时与异常处理:如果超时定时器触发onSendTimeout,说明下位机未在规定时间内响应“ok”。这可能意味着指令错误、下位机死机或通讯中断。此时应进入Error状态,停止发送,清空队列,并发出错误警报。

3.3 使用QCustomPlot实现高性能实时曲线

对于需要高速刷新(如每秒数十个数据点)的曲线图,QT自带的QChart在大量数据时性能可能不足。QCustomPlot是一个更好的选择。

// 初始化图表 m_plot = new QCustomPlot(this); m_plot->addGraph(); // 温度曲线 m_plot->graph(0)->setPen(QPen(Qt::red)); m_plot->addGraph(); // 湿度曲线 m_plot->graph(1)->setPen(QPen(Qt::blue)); m_plot->xAxis->setLabel("时间"); m_plot->yAxis->setLabel("数值"); m_plot->xAxis->setTickLabelType(QCPAxis::ltDateTime); m_plot->xAxis->setDateTimeFormat("hh:mm:ss"); m_plot->xAxis->setAutoTickStep(false); m_plot->xAxis->setTickStep(2); // 每2秒一个主刻度 m_plot->setupFullAxesBox(); m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); // 定时更新数据槽函数 void DataMonitor::onNewData(double temp, double humi, qint64 timestamp) { static QTime startTime = QTime::currentTime(); // 使用从启动开始的秒数作为x轴,或直接使用timestamp double key = startTime.msecsTo(QTime::currentTime()) / 1000.0; m_plot->graph(0)->addData(key, temp); m_plot->graph(1)->addData(key, humi); // 自动调整X轴范围,实现滚动效果(保留最近60秒数据) m_plot->xAxis->setRange(key, 60, Qt::AlignRight); // 自动调整Y轴范围以适应数据 m_plot->yAxis->rescale(); // 高性能重绘,只重绘图表本身,不重绘整个widget m_plot->replot(QCustomPlot::rpQueuedReplot); }

性能优化点:

  • rpQueuedReplot:这是关键。它不会立即重绘,而是将重绘请求加入事件队列,避免在高速数据到来时频繁阻塞GUI线程。
  • 数据缓冲区管理QCPGraph内部会存储所有添加的数据点。对于长时间运行的监控,需要定期清理旧数据,防止内存无限增长。可以使用m_plot->graph(0)->data()->removeBefore(key - 3600)来移除一小时前的数据。
  • 减少不必要的重绘:如果同时更新多条曲线,应在所有addData操作完成后,调用一次replot

4. 项目集成与工程化实践

有了各个模块的代码,如何将它们整合成一个稳定、可维护的上位机软件,是更大的挑战。

4.1 模块化设计与依赖管理

不要将所有代码都堆在MainWindow里。合理的目录结构如下:

MySupervisor/ ├── CMakeLists.txt / .pro file (Qt项目文件) ├── src/ │ ├── core/ # 核心业务逻辑,与UI无关 │ │ ├── SerialManager/ │ │ ├── MotionController/ │ │ ├── SensorDataParser/ │ │ └── DatabaseLogger/ │ ├── widgets/ # 自定义UI控件 │ │ ├── DialGauge.cpp │ │ ├── LedIndicator.cpp │ │ └── VideoWidget.cpp (如果集成摄像头) │ ├── ui/ # 自动生成的UI头文件(如果使用Qt Designer) │ └── main.cpp ├── include/ # 公共头文件 ├── resources/ # 图片、QSS样式表、翻译文件 └── tests/ # 单元测试

依赖管理:在CMakeLists.txt.pro文件中清晰定义模块依赖。例如,MotionController依赖SerialManager,那么就在MotionController的编译选项中链接SerialManager的库。对于第三方库如QCustomPlot,可以将其源码放入3rdparty目录,或使用CMake的FetchContent/find_package管理。

4.2 配置管理与持久化

工业软件需要保存用户的各类配置:串口参数、电机参数、报警阈值、界面主题等。

  1. 简单配置:使用QSettings,它支持注册表和INI文件。适合键值对形式的配置。
QSettings settings("MyCompany", "MySupervisor"); settings.setValue("serial/portName", "COM3"); settings.setValue("motion/maxSpeedX", 1000.0); QString theme = settings.value("ui/theme", "default").toString(); // 第二个参数是默认值
  1. 复杂配置:对于结构化的配置(如多个电机的参数列表),建议使用JSON或XML格式。QT提供了QJsonDocumentQXmlStreamReader/Writer。将配置读写封装成一个单独的ConfigManager类。
  2. 数据库存储:对于历史数据,使用SQLite。创建一个DataLogger类,负责初始化数据库、创建表、插入记录和查询数据。

4.3 多语言国际化支持

如果软件可能面向海外用户,从一开始就考虑国际化(i18n)。

  1. 在所有需要翻译的字符串上使用tr()宏,例如QPushButton *btn = new QPushButton(tr("Start"));
  2. 在项目文件(.pro)中添加TRANSLATIONS = myapp_zh_CN.ts myapp_en.ts
  3. 使用Qt Linguist工具(lupdate,lrelease)生成和编译翻译文件。
  4. 在软件启动时,根据系统语言或用户选择加载对应的.qm文件。

4.4 日志系统与调试

一个健壮的日志系统对现场调试至关重要。不要依赖qDebug(),因为它在生产环境中可能被关闭。

  1. 使用像spdlog这样的第三方日志库,或者自己封装一个简单的日志类。
  2. 日志应支持不同级别(Info, Warning, Error, Debug),并输出到文件和控制台。
  3. 在关键业务流程(如发送指令、收到数据、状态改变)处记录日志。
  4. 软件应提供日志文件查看界面或导出功能。

5. 常见问题排查与性能优化

在实际开发和部署中,你会遇到各种各样的问题。这里记录了一些典型问题的排查思路。

5.1 串口通讯不稳定或数据丢失

现象可能原因排查步骤与解决方案
偶尔收不到数据1. 硬件连接松动
2. 波特率不匹配
3. 缓冲区溢出
1. 检查物理连接,更换线缆或串口转换器测试。
2. 用示波器或逻辑分析仪测量实际波特率,确保上下位机一致。
3. 在下位机程序或上位机中增加流控(RTS/CTS),或降低发送数据频率。
数据乱码1. 数据位、停止位、校验位设置错误
2. 电磁干扰
1. 核对上下位机串口参数,必须完全一致。
2. 使用屏蔽线缆,确保设备良好接地。尝试在软件端对收到数据进行校验(如和校验、CRC),丢弃无效帧。
长时间运行后卡死1. 内存泄漏
2. 线程死锁
3. 信号槽连接未断开
1. 使用Valgrind或Qt Creator内置分析工具检查内存。
2. 检查多线程间共享数据的锁(QMutex)使用是否正确,避免循环等待。
3. 确保在对象销毁前,断开其相关的所有信号槽连接,特别是跨线程的连接。

5.2 界面卡顿或响应迟缓

  1. 耗时操作阻塞主线程:这是最常见的原因。任何超过几十毫秒的操作(如大量文件IO、复杂计算、网络请求)都必须移到工作线程。使用QThread配合QObjectmoveToThread方法,或直接使用QtConcurrent::run
  2. UI过度重绘:检查是否在paintEvent中进行了复杂计算或创建了大量临时对象。确保paintEvent只做绘制工作。对于频繁更新的数据,考虑使用双缓冲技术(QPixmap缓存)。
  3. 信号槽过度触发:例如,一个每秒触发100次的定时器,其连接的槽函数如果执行较慢,会导致事件队列堆积。要么降低定时器频率,要么优化槽函数效率,要么确保信号槽连接为Qt::QueuedConnection(如果是跨线程)以避免隐性阻塞。
  4. 样式表(QSS)滥用:过于复杂或全局应用的QSS选择器会影响界面初始化速度和重绘性能。尽量将样式表的应用范围限制在必要的控件上。

5.3 在多版本QT或不同平台上的兼容性

  1. 动态链接库问题:在Windows上发布,需要将Qt5Core.dll,Qt5Widgets.dll等依赖的DLL和platforms/qwindows.dll文件夹一起打包。使用windeployqt工具可以自动完成。在Linux上,通常通过包管理解决依赖,或提供AppImage等打包格式。
  2. C++标准与编译器差异:在项目文件(.pro或CMake)中明确指定C++标准(如CONFIG += c++17)。避免使用特定编译器扩展。
  3. 路径与文件系统:使用QCoreApplication::applicationDirPath()来获取可执行文件所在目录,以此为基础构造资源文件路径,避免使用绝对路径。使用QDir::separator()来处理路径分隔符,保证跨平台。
  4. 高分屏支持:在main函数开头,调用QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);以启用高DPI缩放,使界面在不同分辨率的屏幕上显示正常。

这份31个QT上位机源码合集的价值,在于它提供了一个从点到面、从模块到系统的全景式学习路径。它不仅仅是代码的堆砌,更是工程思维和实战经验的凝结。建议你在阅读时,不要只看一个项目,而是横向对比。比如,看不同的项目是如何处理串口通讯的,哪种设计更优雅、更健壮;看不同的UI项目是如何布局和实现数据绑定的。然后,尝试以其中一个项目为骨架,融入其他项目的优秀模块,打造一个属于你自己的、功能更强大的上位机软件。编程之路,始于模仿,成于思考与创造。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 16:58:15

树莓派5与深度相机构建无接触3D腰臀围测量仪

这次我们来看一个把树莓派5当嵌入式主机、把深度相机当测量探头、把点云算法当量尺的完整方案&#xff1a;无接触 3D 智能腰臀围测量仪。它的核心不是“拉软尺”&#xff0c;也不是让测量员贴身去量&#xff0c;而是让被测者站到设备前面&#xff0c;3D 相机采集人体表面点云&a…

作者头像 李华
网站建设 2026/9/5 16:57:39

基于Vue+SpringBoot+MySQL的超市商品管理系统全栈开发实战

简介&#xff1a;这是一套面向计算机专业本科生的高分毕业设计级超市商品管理系统&#xff0c;采用VueSpringBootMySQL技术栈实现前后端分离架构&#xff0c;适用于毕设开发、课程设计及Java全栈实战学习。资源包共包含源码、MySQL数据库脚本、详细功能文档、开题报告与外文文献…

作者头像 李华
网站建设 2026/9/5 16:57:15

基于uni-app与Serverless的英语学习小程序全栈开发实践

简介&#xff1a;这是一套面向英语学习者与小程序开发者的微信小程序源码项目&#xff0c;聚焦考研英语备考、日常词汇积累与口语能力提升三大核心场景。项目基于uniapp前端框架与uniCloud Serverless云服务架构实现&#xff0c;具备生词本管理、拍照识别单词、离线词典查询、A…

作者头像 李华
网站建设 2026/9/5 16:57:06

技术博客选题落地:从影视特效到可复现的工程实践

没法把“蜘蛛侠vs超人”写成技术博客。这个选题面向的是影视角色比较&#xff0c;不属于可落地、可复现的工程主题&#xff0c;和本博客定位不一致。 如果想在技术社区写作&#xff0c;围绕这个方向可以换成这些可展开的选题&#xff1a; 从 OpenGL / Vulkan 渲染管线理解超人…

作者头像 李华