diff --git a/AUTO_PANEL_SIZING.md b/AUTO_PANEL_SIZING.md deleted file mode 100644 index d2ed5d9..0000000 --- a/AUTO_PANEL_SIZING.md +++ /dev/null @@ -1,151 +0,0 @@ -# 自动适配:点阵不越界 + 面板尺寸跟随点阵 - -这份文档说明本项目里为了实现: - -1. **圆点(点阵)始终落在面板顶面上,不越界** -2. **面板的宽/深(W/D)根据点阵规格自动缩放** -3. **面板厚度(H)保持独立可控(不随点阵改变)** - -所做的代码改动和使用方式。 - ---- - -## 现象 & 根因 - -当前点阵的摆放逻辑(`src/glwidget.cpp` 的 `GLWidget::updateInstanceBufferIfNeeded_()`)是: - -- 点阵中心围绕原点居中摆放 -- 每个点的中心坐标: - - `x = c * pitch - (cols-1)*pitch/2` - - `z = r * pitch - (rows-1)*pitch/2` -- 所以 **点中心** 的范围一定在: - - `x ∈ [-gridW/2, +gridW/2]`,其中 `gridW = (cols-1)*pitch` - - `z ∈ [-gridD/2, +gridD/2]`,其中 `gridD = (rows-1)*pitch` - -但每个点是“有半径”的(`dotRadius`),在 shader 里实际画出来的圆会占据: - -- `x` 方向还会向外多出 `dotRadius` -- `z` 方向还会向外多出 `dotRadius` - -如果面板的 `W/D` 没有随点阵变化而调整,就会出现你截图里那种:**外圈点的圆形被画到了面板外**(看起来像“越界”)。 - ---- - -## 解决思路(数学上怎么保证不越界) - -要让“圆点的边缘”也不越界,需要满足: - -- 面板半宽 `panelW/2 >= gridW/2 + dotRadius` -- 面板半深 `panelD/2 >= gridD/2 + dotRadius` - -等价于: - -- `panelW >= gridW + 2*dotRadius` -- `panelD >= gridD + 2*dotRadius` - -本项目采用的就是这条公式。 - -> 如果你希望四周再留白一点,只要把公式改成:`+ 2*(dotRadius + margin)` 即可。 - ---- - -## 代码改动点(做了什么) - -### 1) `setSpec()` 里自动计算面板 W/D - -文件:`src/glwidget.cpp`,函数:`GLWidget::setSpec(...)` - -做的事: - -- 对 `rows/cols/pitch/dotRadius` 做了下限保护(避免负数) -- 根据点阵规格计算面板顶面 `m_panelW/m_panelD`: - - `gridW = (cols-1) * pitch` - - `gridD = (rows-1) * pitch` - - `m_panelW = gridW + 2*dotRadius` - - `m_panelD = gridD + 2*dotRadius` -- 标记面板几何和 dots 几何需要重建(dirty flag) - -这样一旦你改点阵规格,面板会自动缩放到刚好包住点阵。 - ---- - -### 2) 厚度单独控制:`setPanelThickness()` - -文件:`src/glwidget.h` / `src/glwidget.cpp` - -新增接口: - -- `void setPanelThickness(float h);` - -它只修改 `m_panelH`(厚度),并标记面板几何需要重建。 - ---- - -### 3) 由于 W/D 会变:需要“重建几何体 buffer” - -原因:面板顶面矩形的顶点坐标依赖 `m_panelW/m_panelD`,改变后必须重建 VBO/IBO/VAO 才能反映到 GPU。 - -做法: - -- 新增两个 dirty flag: - - `m_panelGeometryDirty` - - `m_dotsGeometryDirty` -- 在 `GLWidget::paintGL()` 的开头判断 dirty: - - dirty 就调用 `initPanelGeometry_()` / `initDotGeometry_()` 重建 - - dots 重建后会重新分配 instanceVBO,所以把 `m_valuesDirty = true`,确保下一帧重新上传 instance 数据 - ---- - -### 4) `initPanelGeometry_()` / `initDotGeometry_()` 现在会先删旧 buffer - -因为会重复调用重建,所以这两个函数里加入了: - -- 先 `glDeleteBuffers` / `glDeleteVertexArrays` -- 再重新 `glGen*` 创建新资源 - -保证不会重复堆积资源(泄漏)或引用旧 VAO。 - ---- - -### 5) `main.cpp` 不再手动写死面板 W/D - -文件:`main.cpp` - -把原来的: - -- `glw->setPanelSize(1.2f, 0.08f, 1.2f);` - -改为: - -- `glw->setPanelThickness(0.08f);` - -原因:如果继续调用 `setPanelSize()`,你会把 `setSpec()` 自动算出来的 W/D 覆盖掉,越界问题就会回来。 - ---- - -## 使用方式(你应该怎么调用) - -推荐顺序: - -1. `glw->setSpec(rows, cols, pitch, dotRadius);` - - 会自动算出 `panelW/panelD`,保证点不越界 -2. `glw->setPanelThickness(panelH);` - - 只改厚度,不影响自动宽深 -3. `glw->setRange(minV, maxV);` -4. `glw->submitValues(values);` - -如果你确实想手动指定面板大小(不走自动适配),可以再调用: - -- `glw->setPanelSize(w, h, d);` - -但要理解:这会覆盖自动计算的 `W/D`,点可能再次越界(除非你按本文公式算好)。 - ---- - -## 可选改进(按你需求继续拓展) - -- **加边距(不要贴边)**:在 `setSpec()` 里引入 `margin`,把公式改成: - `m_panelW = gridW + 2*(m_dotRadius + margin)` - `m_panelD = gridD + 2*(m_dotRadius + margin)` -- **让点阵填满面板但不改变 pitch**:目前面板跟着 pitch 走;如果你希望面板固定而 pitch 自适配,需要反过来解 pitch。 - diff --git a/BACKGROUND_GRID.md b/BACKGROUND_GRID.md deleted file mode 100644 index 206a422..0000000 --- a/BACKGROUND_GRID.md +++ /dev/null @@ -1,134 +0,0 @@ -# 屏幕空间背景(白底 + 灰色网格线)说明 - -这份文档解释项目里新增的“3D 软件常见的白色背景 + 灰色分割线网格”是怎么实现的,以及如何调整效果。 - -需求点: - -- 背景看起来更“科技感”(轻微渐变 + 网格线 + vignette) -- **背景不随相机旋转**(不受 yaw/pitch/zoom 影响) - ---- - -## 实现方式概览(为什么它不会旋转) - -我们采用“屏幕空间(screen-space)”绘制方式: - -1. **画一个全屏 quad**(两个三角形),顶点坐标直接写在裁剪空间 NDC([-1,1]) -2. fragment shader 使用 `gl_FragCoord`(屏幕像素坐标)生成网格线 - -由于整个背景是直接画在屏幕坐标系里,不使用 `uMVP`,所以它不会跟着相机旋转或移动。 - ---- - -## 关键文件 - -- 资源注册:`resources.qrc` - - 新增:`shaders/bg.vert`、`shaders/bg.frag` -- Shader: - - `shaders/bg.vert`:全屏 quad 的顶点 shader(裁剪空间直出) - - `shaders/bg.frag`:背景颜色 + 抗锯齿网格线(基于 `gl_FragCoord`) -- C++(OpenGLWidget): - - `src/glwidget.h`:新增 `m_bgProg`、`m_bgVao/m_bgVbo`、`initBackgroundGeometry_()` - - `src/glwidget.cpp` - - `GLWidget::initPrograms_()`:编译/链接背景 program - - `GLWidget::initBackgroundGeometry_()`:创建全屏 quad VAO/VBO - - `GLWidget::paintGL()`:先画背景(关闭深度写入),再画 3D 内容 - ---- - -## 渲染顺序(为什么不会影响 3D 深度) - -背景绘制发生在 `GLWidget::paintGL()` 的最前面: - -1. `glClear(...)` 清空颜色/深度 -2. **绘制背景**(屏幕空间) - - `glDisable(GL_DEPTH_TEST);` - - `glDepthMask(GL_FALSE);`(不写深度) -3. 恢复深度状态 - - `glDepthMask(GL_TRUE);` - - `glEnable(GL_DEPTH_TEST);` -4. 再绘制 panel / dots(正常 3D 深度测试) - -因此:背景永远在最底层,而且不会把深度缓冲弄脏。 - ---- - -## 全屏 quad(背景几何) - -`GLWidget::initBackgroundGeometry_()` 创建一个覆盖整个屏幕的矩形(两个三角形): - -- 顶点坐标是 NDC(裁剪空间): - - (-1,-1) 到 (1,1) -- 顶点 shader(`shaders/bg.vert`)仅仅把它输出到 `gl_Position` - -这样不需要任何相机矩阵,也不会“跟着相机”动。 - ---- - -## 网格线怎么画出来的(bg.frag) - -`shaders/bg.frag` 主要做了三件事: - -### 1) 背景渐变底色 - -- 使用 `uv = gl_FragCoord.xy / uViewport` 得到 0..1 的屏幕坐标 -- 在 y 方向做一个轻微渐变(上更亮、下稍灰) - -### 2) 细网格 + 粗网格(分割线) - -用 `uMinorStep` / `uMajorStep`(单位:像素)控制网格间距: - -- `uMinorStep`:细分格子(更淡) -- `uMajorStep`:粗分格子(更明显) - -网格线本质是对 `fract(coord / step)` 做“距离最近线”的计算,然后用 `fwidth` 做抗锯齿: - -- `fwidth` 会随着屏幕像素密度和视角变化自动调整边缘过渡,避免锯齿 - -### 3) 轻微 vignette - -四角略暗、中心略亮,让画面更聚焦、更像 3D 软件视口。 - ---- - -## HiDPI 注意点(为什么要乘 devicePixelRatio) - -Qt 的 `width()/height()` 是“逻辑像素”;而 `gl_FragCoord` 是“物理像素”。 - -所以背景在 C++ 里传入: - -- `uViewport = (width * dpr, height * dpr)` -- 网格 step 也乘 `dpr` - -否则在高 DPI 屏幕上网格会显得“变密/变粗”。 - ---- - -## 如何调整外观(常用参数) - -### 调整网格密度 - -在 `src/glwidget.cpp` 里设置了: - -- `uMinorStep = 24 px`(细线间距) -- `uMajorStep = 120 px`(粗线间距) - -改这两个值就能让网格更密/更稀。 - -### 调整颜色/强度/科技感 - -在 `shaders/bg.frag` 里可以改: - -- `topCol/botCol`:背景渐变颜色 -- `minorCol/majorCol`:网格线颜色 -- `mix(...)` 的系数:线条深浅 -- vignette 强度:`dot(p,p)` 前面的系数 - ---- - -## 可选增强(如果你想更像 Blender/Unity) - -- 加“坐标轴线”(X 红、Z 蓝或灰色加深)并在中心画十字 -- 增加 UI 开关:显示/隐藏网格、调 step、调强度 -- 增加“地平线”或 “ground plane” 的淡淡雾化效果 -