IMX6U-Game/docs/CONVENTIONS.md

159 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CPU 软件渲染器项目约定
本文档用于记录当前项目已经采用的坐标系、矩阵、相机和屏幕空间约定,避免后续开发时在符号、方向和乘法顺序上产生混乱。
> 文档分工坐标、矩阵、深度等数学语义记录在本文档IMX6U 运行时性能红线记录在 `../docs/DEVELOPMENT_GUIDELINES.md`;两个游戏、启动器和底层图形库的分层边界记录在 `../docs/APP_AND_CORE_ARCHITECTURE.md`。新增或修改热路径、应用层边界、显示后端代码时,应同步参考对应文档。
## 1. 通用约定
- 项目使用右手坐标系。
- `Vector3::cross(a, b)` 遵循右手定则。
- 除非特别说明,向量按列向量理解。
- 变换写法按 `M * v` 解释。
## 2. 世界空间与局部空间
世界空间与物体局部空间目前使用同一套方向命名:
- `+X`:右
- `+Y`:上
- `+Z`:前
`Scene::Transform` 也遵循这套约定:
- `get_right()`:旋转后的局部右方向
- `get_up()`:旋转后的局部上方向
- `get_forward()`:旋转后的局部前方向
- `get_left()`、`get_down()`、`get_back()`:分别为对应反方向
也就是说,`Transform` 中的 `forward` 明确定义为 `+Z`
## 3. 旋转约定
- 欧拉角使用弧度制。
- `rotation.x`:绕 `X` 轴旋转
- `rotation.y`:绕 `Y` 轴旋转
- `rotation.z`:绕 `Z` 轴旋转
- 组合旋转顺序为 `Rz * Ry * Rx`
当前项目里,方向向量的旋转方式是先构造旋转矩阵,再去变换基础方向轴。
## 4. 矩阵约定
`Math::Matrix4x4` 当前采用以下规则:
- 语义上按列向量使用,写法为 `M * v`
- 元素访问方式为 `matrix[row][col]`
- `data()` 暴露的是连续的 row-major 内存
- 平移分量存放在最后一列
因此,常见的组合变换阅读顺序是从右往左:
- `worldPosition = Translation * Rotation * Scale * localPosition`
## 5. 相机与视图空间
相机目前由 `Scene::Camera::transform` 驱动。
相机自身局部方向定义为:
- 相机右方向:`transform.get_right()`
- 相机上方向:`transform.get_up()`
- 相机前方向:`transform.get_forward()`
但进入视图空间后,当前项目采用的是常见的相机空间约定:
- 位于相机前方的点,其 view-space `z` 为负值
- 视图矩阵第三行存的是相机 backward而不是 forward
这和当前透视投影矩阵实现是一致的,因为那里对应的是 `clip.w = -viewZ` 这套约定。
## 6. 三角形绕序与正面约定
当前项目将三角形正面统一约定为顺时针 `CW` 绕序。
这里的“顺时针”按当前渲染流程解释为:
- 三角形经过 view / projection / viewport 变换后
- 从屏幕上观察其顶点顺序时,正面三角形按顺时针排列
与这套约定配套的实现规则是:
- 背面剔除当前在 view space 中完成
- 法线方向使用 `faceNormal = (v1 - v0).cross(v2 - v0)` 计算
- 当前 demo 中,`faceNormal.dot(faceCenter) > 0` 被视为正面
这意味着:
- 所有手写或导入的三角形索引都必须保持一致绕序
- 如果未来改成逆时针 `CCW` 为正面,那么剔除判定符号也必须同步调整
- `main.cpp` 里的 `cubeTriangles``cubeFaces` 当前应继续保持与这套约定一致,不要单独翻转其中一部分
## 7. 投影与 NDC
当前透视投影相关约定如下:
- 规范化设备坐标NDC可见范围为 `[-1, 1]`
- `x`、`y`、`z` 都会在映射到 viewport 之前做范围检查
- 当前测试代码里,如果点超出 NDC可能会直接判为不可见而不是继续做线段裁剪
这意味着:
- 当前 demo 还没有实现完整的视锥裁剪
- **2D 屏幕空间线段裁剪已实现**`Rasterizer::DrawLine` 使用 Cohen-Sutherland 算法对屏幕边界做裁剪,部分在屏幕内的线段可以正确绘制,不再整条丢弃
- 3D 视锥裁剪(齐次裁剪空间)仍未实现
## 8. 屏幕与像素坐标
屏幕/像素坐标使用左上角为原点的约定:
- 原点在左上角
- `x` 向右增大
- `y` 向下增大
`Core::FrameBuffer` 当前行为如下:
- 有效像素范围:`x in [0, width)`
- 有效像素范围:`y in [0, height)`
- 越界写入会被直接忽略
- 像素缓冲按 row-major 排列
- 内存中的第一行对应 `y = 0`
`Camera::get_viewport_matrix()` 里也对 `Y` 做了翻转,因此 NDC 的“向上”为正,最终会映射成屏幕坐标“向下”为正。
## 9. 深度缓冲约定
当前项目已经接入 `Core::DepthBuffer`,并采用以下规则:
- `DepthBuffer` 存储类型为 `float``TriangleRasterizer` 内部使用定点深度插值(`depth_fp += depth_fp_per_pixel`,提取时 `>> 16`),内层循环无 float 运算
- 每帧开始时必须调用 `depthBuffer->clear()`,默认清为 `INFINITY`
- 当前约定为“深度值越小,离相机越近”
- 深度测试通过后,必须同时更新 `DepthBuffer``FrameBuffer`
- `DepthBuffer` 只负责存储和读取深度,不负责决定颜色写入逻辑;是否写颜色由光栅化阶段决定
当前三角形光栅化里的深度流程为:
- 使用增量式整数边缘函数替代每像素 float 重心计算和除法
- 深度插值使用定点算术:`depth_fp += depth_fp_per_pixel`,提取时 `>> 16`
- 若新深度更近,则写入 `DepthBuffer``FrameBuffer`
也就是说:
- 当前实现已定点化,内层循环无 float 运算,符合 `DEVELOPMENT_GUIDELINES.md` 性能红线
- 后续如果引入纹理、法线或更严格的属性插值,需要进一步考虑透视校正插值
## 10. Demo 中的可见性规则
`main.cpp` 里的旋转立方体示例,目前采用以下可见性与遮挡规则:
- 三角形是否参与光栅化,仍由投影合法性检查和背面剔除决定
- 三角形之间的遮挡,不再依赖按平均深度排序,而是由 `DepthBuffer` 逐像素决定
- 当前 demo 仍保留按面筛选后的轮廓线绘制,用于显示黑色边框
这意味着:
- `DepthBuffer` 不能替代投影合法性检查或背面剔除
- `DepthBuffer` 负责的是像素级遮挡,而不是顶点级或三角形级是否进入渲染流程
后续如果项目要加入更严格的裁剪、剔除、透视校正插值或隐藏线规则,应当以代码实现为准,并同步更新本文档。