# 3D-objects **Repository Path**: L-XiaoHui/3D-objects ## Basic Information - **Project Name**: 3D-objects - **Description**: Qt6 Widgets + OpenGL 4.5 3D 模型查看器(基于 8Altair/3D-objects,含本地构建适配) - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-31 - **Last Updated**: 2026-11-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 3D Objects > 基于 [8Altair/3D-objects](https://github.com/8Altair/3D-objects) 的学习用私有分支, > 新增了 `src`/`doc` 目录结构、Windows(Qt 6.8 MinGW)与 Linux(系统 Qt 6)双平台构建适配、学习笔记。 > 原项目为 MIT 协议,感谢原作者。 ## 简介 **3D Objects** 是一个 **Qt 6** 桌面应用,把 **OpenGL 4.5 Core Profile** 渲染嵌入 Qt Widgets 界面。 场景由一块地面网格与任意数量的用户导入模型组成;模型在运行时经 **Assimp** 从 **OBJ 文件**加载,无需重新编译即可随时放入网格。每个导入的网格拥有独立的 VAO/VBO,可单独选中、变换和着色。 ## 功能 - 现代 **OpenGL 4.5 Core Profile**(不用任何废弃函数) - **Assimp** 动态加载 OBJ 模型 - **地面网格**作为所有模型的基准面 - **相机控制**:轨道旋转、平移、推拉(鼠标 + 键盘) - **单对象操作**:选中、拖动平移、滚轮缩放 - 每对象**独立 VAO/VBO** - 基于顶点属性的**着色模式**:Uniform / Position / Normal / UV / Position+Normal - **GLSL 4.50** 着色器(in/out varyings + uniforms) - **命令控制台**:底部面板可键入命令驱动场景(`help` 查看全部),同一套命令将来直接供脚本调用 - 技术栈:**CMake + GLM + Qt 6 + Assimp** ## 目录结构 ``` 3D-objects/ ├─ CMakeLists.txt # 构建入口(源文件位于 src/ 下) ├─ CMakePresets.json # 工具链预设(Windows: default/release/python;Linux: linux/linux-release/linux-python) ├─ src/ # 源码(按 main / gui / 3DEngine 三层拆分) │ ├─ main/ # 程序入口 │ │ └─ main.cpp │ ├─ gui/ # Widgets 界面层 │ │ ├─ MainWindow.(cpp|h|ui) # 主窗口(PIMPL:头文件只有一个 d_ptr) │ │ └─ ConsolePanel.(cpp|h) # 底部命令控制台(只搬文本,命令逻辑在 command 层) │ ├─ 3DEngine/ # 渲染核心层(按纯净度分目录,依赖单向向下) │ │ ├─ Viewport.(cpp|h) # QOpenGLWidget 子类(事件转发 + 每帧调度) │ │ ├─ core/ # 纯数学 / 纯 CPU(零 Qt-GL 依赖,可整体搬走) │ │ │ ├─ Camera.(cpp|h) # 相机状态与矩阵数学(纯 glm) │ │ │ ├─ Picking.h # 拾取射线几何(header-only,std::optional) │ │ │ └─ MeshImporter.(cpp|h) # OBJ 导入(Assimp,纯 CPU) │ │ ├─ gl/ # GL 资源(需 GL 上下文,create(this) 注入) │ │ │ ├─ ColorMode.h # 着色模式枚举(Viewport 与命令层共用) │ │ │ ├─ ShaderProgram.(cpp|h) # GLSL 编译链接 + uniform 缓存上传 │ │ │ ├─ CubeGeometry.(cpp|h) # 地面几何与线框绘制(立方体网格资源) │ │ │ └─ Scene.(cpp|h) / SceneObject.h # 对象集合:上传/摆放/增删/拾取 │ │ ├─ interaction/ # 交互(不碰 GL、不持有 widget) │ │ │ ├─ InteractionRouter.(cpp|h) # Qt 事件 → 相机手势 / 对象编辑(唯一碰 Qt 事件的地方) │ │ │ ├─ CameraNavigator.(cpp|h) # 相机手势状态机(视图侧,零 Qt) │ │ │ ├─ SelectionModel.(cpp|h) # 选中集合 + 变更广播(文档侧,零 Qt 零 GL) │ │ │ ├─ SceneEditor.(cpp|h) # 拖拽 / 缩放 / 删除(文档侧,回调注入) │ │ │ └─ EditHistory.(cpp|h) # 撤销/重做栈(文档侧,零 Qt 零 GL) │ │ └─ command/ # 命令层("名字 + 参数 → 一次编辑",脚本化的接入点) │ │ ├─ CommandValue.h # Value / Args / CommandResult / ErrorCode │ │ ├─ CommandParser.(cpp|h) # 分词 + 按 ParamSpec 绑定并转类型 │ │ ├─ CommandRegistry.(cpp|h) # 注册 / 分发 / 生成 help │ │ ├─ CommandContext.h # 命令能看到的一切(6 个指针 + 7 个回调) │ │ └─ BuiltinCommands.(cpp|h) # 27 条内置命令 │ └─ resources/ # 应用资源 │ ├─ resources.qrc # Qt 资源(图标) │ ├─ appicon.rc # Windows 应用图标(仅 Windows 参与编译) │ └─ Icon/ # 图标文件 ├─ models/ # 测试模型(sphere / torus / cone,供手工验证用) ├─ doc/ # 学习资料(按用途分为 arc / plan / manual / notes / assets) │ ├─ arc/ # 架构:代码怎么组织 │ │ ├─ architecture-notes.md # 架构精读笔记(模块分层与实现要点) │ │ ├─ dependency-map.md # 模块依赖关系图(分层与 include 边) │ │ └─ uml-class-model.md # UML 类图与逐类职责 │ ├─ notes/ # 原理:单个技术点讲透(摘录 + 出处) │ │ ├─ 01-QOpenGLFunctions_4_5_Core.md │ │ ├─ 02-Viewport双继承设计解析.md │ │ ├─ 03-Qt-PIMPL与MainWindow重构.md │ │ ├─ 04-3D图形学术语表.md # 查词:几何/坐标/材质/光照/EDA 双语对照 │ │ └─ 05-交互层拆分与Document-View分界.md # 为什么把交互拆成四件套 │ ├─ plan/ # 规划:怎么学、往哪走 │ │ ├─ learning-plan.md # 学习计划(面向有 Qt 经验的开发者) │ │ ├─ learnopengl-reading-map.md # 外部教材 LearnOpenGL 章节对照(只索引,不含正文) │ │ ├─ eda-roadmap.md # 进阶路线:迈向工程级 EDA 3D 软件 │ │ └─ p0-command-layer.md # P0 命令层设计(脚本化 / Undo 的共同地基) │ ├─ manual/ # 操作:怎么跑、怎么验 │ │ ├─ build-and-debug-guide.md # 本地构建与调试指南 │ │ ├─ unit-tests.md # 单元测试指南(gtest + CTest) │ │ ├─ verification-guide.md # 手工功能验证手册 │ │ └─ gen_models.py # 测试模型生成脚本(备用) │ └─ assets/ # 文档配图(SVG,随仓库渲染) ├─ tests/ # 单元测试(gtest,链接 objects_engine 静态库) ├─ third_party/ # 第三方依赖本地源码(不入库,见构建说明) ├─ build/ # Windows 构建产物(不入库) └─ build-linux*/ # Linux 构建产物(不入库) ``` ## 依赖 | 依赖 | 最低版本 | 用途 | |---|---|---| | **Qt 6** | 6.2+(推荐 6.5+) | Core / Gui / Widgets / OpenGL / OpenGLWidgets | | **CMake** | 3.21+ | 构建系统 | | **Ninja**(可选) | — | 构建后端 | | **GLM** | 最新 | 矩阵/向量数学 | | **Assimp** | 5.3+ | 导入 OBJ 模型 | | **GoogleTest** | 1.17 | 单元测试(`FetchContent` 自动获取,仅 `BUILD_TESTING=ON` 时需要) | | **C++ 编译器** | C++23 | MSVC、MinGW、GCC ≥ 12、Clang ≥ 15 均可 | Linux 上大部分依赖直接用发行版包(Debian / Ubuntu 示例,完整清单见 [构建与调试指南](doc/manual/build-and-debug-guide.md) 第七节): ```bash sudo apt install build-essential cmake ninja-build git pkg-config \ qt6-base-dev libqt6opengl6-dev libgl1-mesa-dev libglu1-mesa-dev \ libxkbcommon-dev libxcb-cursor0 mesa-utils zlib1g-dev \ python3-dev # 仅 OBJECTS_WITH_PYTHON=ON 时需要 ``` > `CMakePresets.json` 是 v6 格式,用 `cmake --preset` 需要 CMake ≥ 3.25; > 更老的发行版用下文的手动 `cmake -S . -B ...` 命令,CMake ≥ 3.21 即可。 ## 构建 GLM、Assimp 与 GoogleTest 由 CMake 的 `FetchContent` 自动获取;网络受限时可手动克隆到 `third_party/`(存在即优先使用): ```bash mkdir -p third_party git clone --depth 1 https://gitee.com/mirrors/glm.git third_party/glm git clone --depth 1 https://gitee.com/mirrors/assimp.git third_party/assimp git clone --depth 1 -b v1.17.0 https://gitee.com/mirrors/googletest.git third_party/googletest # Linux 上 GitHub 可达时也可换成 github.com 的上游仓库 ``` ### Windows(Qt 6.8 + MinGW + Ninja) 先设好两个用户环境变量(`QTROOT=D:\Qt`、`QTDIR=%QTROOT%\6.8.0\mingw_64`,细节见 [构建与调试指南](doc/manual/build-and-debug-guide.md)),然后: ```bash cmake --preset default # 配置(generator / 编译器 / Qt 前缀都在 CMakePresets.json) cmake --build --preset default # 构建 cmake --preset release && cmake --build --preset release # Release 版 → build-release/ ``` 产物为 `build/3D-objects.exe`,Qt DLL 与 platforms 插件由 POST_BUILD 自动拷贝,双击即用。 VSCode(cpptools + CMake Tools)用户直接按 **F5** 即可编译并进入 GDB 调试, 详见 `doc/manual/build-and-debug-guide.md`。 ### Linux(系统 Qt 6 + GCC/Clang + Ninja) ```bash cmake --preset linux # → build-linux/(Debug) cmake --build --preset linux # 产物:build-linux/3D-objects ./build-linux/3D-objects cmake --preset linux-release && cmake --build --preset linux-release # → build-linux-release/ ``` Linux 预设不设 `CMAKE_PREFIX_PATH`:系统包的 `Qt6Config.cmake` 在 CMake 默认搜索路径里, `QTDIR` 只在 Windows 上是必需的。用官方 Qt 安装器时手动指定套件目录: ```bash cmake --preset linux -DCMAKE_PREFIX_PATH="$HOME/Qt/6.8.0/gcc_64" ``` CMake < 3.25(读不了 v6 预设)或想换生成器时走普通命令,最低 3.21: ```bash cmake -S . -B build-linux -G Ninja -DCMAKE_BUILD_TYPE=Debug cmake --build build-linux -j "$(nproc)" ``` 运行要求 **OpenGL 4.5 Core**(着色器是 `#version 450 core`,入口按 4.5 请求上下文, 以便 Mesa 软件渲染也可用):`glxinfo -B` 里 `OpenGL core profile version` ≥ 4.5 即可。 虚拟机 / 无独显可用软件渲染;Wayland 下若视口黑屏,强制走 X11/XWayland: ```bash LIBGL_ALWAYS_SOFTWARE=1 ./build-linux/3D-objects QT_QPA_PLATFORM=xcb ./build-linux/3D-objects ``` VSCode 在 Linux 上选「调试 3D-objects (Linux GDB)」即可 F5(对应 `linux` 预设与 `build-linux/3D-objects`)。完整依赖清单、无头测试与排错见 [构建与调试指南](doc/manual/build-and-debug-guide.md) 第七节。 ### 可选:内嵌 Python 脚本层 默认关闭——不开时工程不引用任何 Python 头文件与库,不装 Python 也能一把过。 打开后需要 CPython 3.9+ 的开发文件(`Python.h` + 导入库 / `libpython`);pybind11 由 `FetchContent` 获取(也可手动放进 `third_party/pybind11`): ```bash # Windows cmake --preset python -DPython3_ROOT_DIR= # → build-python/ cmake --build --preset python # Linux(需 python3-dev) cmake --preset linux-python # → build-linux-python/ cmake --build --preset linux-python ``` Windows 上 MinGW 可以直接链官方的 `python313.lib`(COFF 导入库,`ld` 认), `python313.dll` 与 vcruntime 由 POST_BUILD 拷到产物目录;Linux 链系统 `libpython`, `PYTHONHOME` 默认留空让 CPython 用编译时的 prefix,无需拷贝任何运行时。 conda / pyenv 这类自定义安装可显式指定: ```bash cmake --preset linux-python -DOBJECTS_PYTHON_HOME="$(python3 -c 'import sys; print(sys.prefix)')" ``` ## 测试 `src/3DEngine/` 被编成静态库 `objects_engine`,主程序与测试目标 `objects_tests` 链接同一份源码: ```bash cmake --build --preset default # 顺带编译 objects_tests ctest --preset default # 290 条 gtest 用例,十几秒(每个用例一个进程) ctest --preset default -R Camera # 按名称过滤 ``` Linux 把 `default` 换成 `linux`(产物 `build-linux/tests/objects_tests`,没有 `.exe` 后缀): ```bash ctest --preset linux ctest --preset linux -R Camera xvfb-run -a ctest --preset linux # 无图形环境时连 GL 用例一起跑 ``` 无显示(SSH / CI)且没用 xvfb 时,12 条离屏 GL 用例会**自动跳过**:Qt 的 xcb 插件在 `DISPLAY` / `WAYLAND_DISPLAY` 都缺失时会直接 abort 进程,`TestGlContext` 在构造 `QGuiApplication` 之前就把它转成一次普通 skip,其余用例照常通过。 覆盖 `core`(相机矩阵、拾取几何、OBJ 归一化)、`gl`(离屏上下文里跑真实的 VAO/VBO 上传、 GLSL 编译与 FBO 绘制)、`interaction` 的整套鼠标键盘状态机,以及 `command` 层的值类型、 解析器、注册表与 27 条内置命令;`pyengine/PythonBindings`(命令表 → 纯数据描述)也在其中, 不需要装 Python。用例清单、 可测边界与扩展方式见 [单元测试指南](doc/manual/unit-tests.md)。不想要测试时配置加 `-DBUILD_TESTING=OFF`。 ## 操作方式 ### 鼠标 | 动作 | 效果 | |---|---| | 左键拖动 | 轨道旋转(或取消选中) | | 中键拖动 | 轨道旋转(绕焦点) | | 右键拖动 | 平移相机 / 拖动选中对象 | | Shift + 右键拖动 | 垂直移动 | | 滚轮 | 推拉相机 / 缩放选中对象 | | 双击 | 选中对象(拾取点成为旋转焦点) | | Backspace | 删除选中对象 | ### 键盘(相机) | 按键 | 动作 | |---|---| | W / S | 前进 / 后退 | | A / D | 左移 / 右移 | | R / F | 上升 / 下降 | | I / K | 俯仰 | | J / L | 偏航 | | U / O | 翻滚 | ### 命令控制台 窗口底部的「命令控制台」用文本驱动同一份状态,`Ctrl+`` 收起或展开。 ``` >>> help 列出全部命令(按域分组) >>> help camera.dolly 单条命令的完整用法 >>> scene.list 逐行列出对象:[索引, x, y, z, 缩放, 半径] >>> object.move 0 1 0 0 位置参数按声明顺序绑定 >>> object.move index=0 to=[0, 2, 0] 键值参数;to 是绝对位置 >>> selection.set [0, 2] 批量选中(框选的接口也是它) >>> camera.dolly 2 推拉相机(正数靠近场景) >>> io.import "D:\models\cube.obj" 含空格的路径用双引号;Linux 用 /home/me/models/cube.obj >>> edit.undo 撤销上一步(拖拽、滚轮、move、scale、remove 都算) >>> edit.redo 重做 >>> edit.history 看历史栈:undo 行在上,redo 行在下 ``` `↑`/`↓` 翻历史,`Tab` 补全命令名,`#` 开头的行是注释。 **撤销有三条入口,落的是同一个栈**:菜单「编辑 → 撤销 / 重做」、视图里的 `Ctrl+Z` / `Ctrl+Y`(`Ctrl+Shift+Z` 也行)、控制台的 `edit.undo` / `edit.redo`。 菜单项会带上动作名("撤销 move #2"),空栈时置灰,所以不用点开就知道下一步会撤销什么。 一次拖拽 = 一条历史;一串滚轮缩放 = 一条历史(每次按下鼠标都会切断合并链), 否则按住滚轮转十圈,撤销就得按十次。删除对象只是从场景里摘下来,显存要等这条 历史被丢弃(撤销栈溢出、`edit.clear`、redo 分支被新编辑冲掉)才释放—— 所以撤销回来的模型不可能"变黑"。 控制台**不含任何命令逻辑**:它把一行文本交给 `Viewport::runCommand()`, 拿回结果再打印,所以同一套命令将来可以原样接给 Python 或脚本文件。 ## 着色管线(GLSL 4.50) 顶点数据为 interleaved 布局:`[position3 | normal3 | uv2]`,每顶点 8 个 float。 5 种着色模式共用一个着色程序,通过 `color_mode` uniform 切换。 ```glsl // 顶点着色器(节选) gl_Position = mvp * vec4(position, 1.0); vWorldPosition = vec3(model * vec4(position, 1.0)); vNormal = normalize(normal_matrix * normal); // 片段着色器(节选) if (color_mode == 1) final_color.rgb = normalize(vWorldPosition) * 0.5 + 0.5; else if (color_mode == 2) final_color.rgb = normalize(vNormal) * 0.5 + 0.5; else if (color_mode == 3) final_color.rgb = vec3(fract(vTexCoord), 0.5); FragColor = mix(final_color, color, 0.35); ``` 渲染流程、各模块接口、拾取射线的数学细节见 **`doc/arc/architecture-notes.md`**; 模块间的分层与依赖边见 **`doc/arc/dependency-map.md`**; 类关系与每个类的职责见 **`doc/arc/uml-class-model.md`**; 底层原理(GL 函数加载、Viewport 双继承、Qt PIMPL)与图形学术语对照见 **`doc/notes/`**; 新人学习路线见 **`doc/plan/learning-plan.md`**; 操作与验证见 **`doc/manual/`**。 ## 说明 - 代码风格遵循 Rocket GUI 规范:文件名 = 类名(`Viewport` → `Viewport.cpp`)、方法小驼峰 (`loadObject`)、成员 `m` 前缀大驼峰(`mNavigator`)、可交互控件均设 `objectName` - **Debug 构建自带 GL 诊断**(`src/3DEngine/gl/GlDebug.h`):启动打印显卡/驱动版本, 驱动报的 GL 错误转成 `GL debug [high] …`,着色器编译/链接失败会打印 info log 和带行号的 GLSL 源码。Windows 上没有控制台,日志看 VSCode/Qt Creator 的"输出"面板或 DebugView; 想直接重定向到 stderr 可设 `QT_ASSUME_STDERR_HAS_CONSOLE=1` - 对象与地面之间保留了微小高度偏移,避免 z-fighting - 右键点击空白处可取消选中 - 本地适配改动:Windows 下 `CMAKE_PREFIX_PATH` 缺省取环境变量 `QTDIR`(未设置则报错并提示); Linux 下不要求任何 Qt 环境变量,直接 `find_package(Qt6)`。`appicon.rc` 与 WIN32 子系统 只在 Windows 参与构建;`third_party/` 存在时优先本地依赖源码