架构
本指南跟随一帧画面穿过 geometry3d 的各个包,说明每项职责位于何处以及原因。细节和推导见各包的页面。
层次
Luna-Flow/linear-algebra (Vector[Double], Matrix[Double])
│
core meshes, transforms, normals, visibility, Lambert
│
view camera, projection, perspective-correct depth, lens model
│
frontend scene → DrawList; shadow map; LumaBuffer; exposure; flow; timelines
│
┌────┼──────────────┐
backend/tui backend/canvas backend/gsap
│ │ │
demo demo_canvas demo_gsap
每个包只导入位于其上方的包。core、view 和 frontend 对字符、颜色、DOM 和操作系统一无所知;各后端互不知晓;只有演示程序会接触时钟、环境变量、文件或页面元素。
一帧的旅程
- 模型。
frontend把每个SceneObject的Transform3应用到其网格(core)上,得到世界空间中的顶点。 - 阴影。 它从方向光的视角把世界空间场景渲染到 128 × 128 的正交深度图中。
- 视图。 它用
Camera3的视图变换(view)把顶点映射到相机空间: 向右, 向上, 向前。 - 投影。 它用
PerspectiveProjection把顶点投影到视口中:,,并保留 作为深度。 - 剔除与着色。 对每个朝向眼睛的面,它计算朗伯项,按阴影贴图判定为受光的面的比例对其衰减,并以该亮度输出两个
DrawTriangle。 - 后端。 后端把
DrawList转换为输出:backend/tui按terminal_y_scale压缩 ,为每个三角形选取一个字符表字符,并借助深度缓冲光栅化到FrameBuffer中;backend/canvas光栅化到前端的LumaBuffer中,并在画布上绘制明暗相同的像素段;backend/gsap把三角形由远到近排序并写出 SVG 多边形。
- 呈现。 演示程序打印这一帧、在浏览器中绘制它,或把它存入
.tui3d序列。
为何以绘制列表为边界
三个后端需要的东西完全相同:每个三角形在屏幕上的位置、它有多远、它有多亮。此前的一切都是与设备无关的几何,在前端只计算一次;此后的一切都是设备层面的决定。frontend 设计讨论了其他方案。
遮挡是各后端唯一不同的决定。TUI 和 Canvas 后端使用相同的深度缓冲规则,结果精确。SVG 没有深度缓冲,因此 GSAP 后端使用画家排序,它对单个凸物体是精确的,其他情况下是近似的(GSAP 设计)。
跨包约定
- 坐标。 世界空间和相机空间遵循 Direct3D 的左手约定:使用默认相机时, 向右, 向上, 远离观察者。屏幕 向下增大。面的环绕方向使 指向外部。
- 光照方向。
Light存储从场景指向光源的单位向量。 - 深度。 投影之后,深度处处都是相机空间的 ;越小越近。空像素的深度为 。
- 容差。
@core.DEPTH_EPSILON= 在每个包中决定退化法向量、齐次除法、零面积三角形以及深度测试的平局。 - 宽松的构造函数。 构造函数用默认值替换无效的尺寸和计数,而不是失败;库中没有任何函数返回
Result或抛出错误。 - 只读记录。 公开结构体是
pub struct:调用者可以读取每个字段,但只能通过构造函数创建值。
不在本仓库中的内容
这里没有针对近平面的裁剪、没有场景图、没有材质或纹理、没有平滑着色、没有资源加载、没有物理,也没有空间索引。仓库约定要求文档在这些功能实现之前不要描述它们。