CPU-Software-Renderer/TODO.md

8.8 KiB
Raw Blame History

CPU Software Renderer TODO

目标

把当前项目从“能显示线框立方体”的状态,逐步推进到“具备基本 3D 渲染管线能力的 CPU Software Renderer”。

当前已经具备的基础:

  • SDL 窗口、纹理和显示循环
  • FrameBuffer
  • 像素写入
  • 直线光栅化
  • 三角形包围盒遍历和点内测试雏形
  • Model -> View -> Projection -> NDC -> Viewport
  • 简单的线框绘制与面可见性判断

下面的 TODO 按建议实现顺序展开。


Step 1. 稳定 2D 光栅基础

目标:把当前 2D 线段 / 三角形基础打稳,为后续 3D 面渲染做准备。

1.1 线段光栅化收尾

  • 确认 Bresenham 在所有斜率下都正确
  • 验证水平线、垂直线、45 度线、反向输入点
  • 统一线段接口的输入输出约定
  • 为调试准备简单测试图形

完成标准:

  • 任意两个屏幕点都能稳定画线
  • 没有明显断裂、漏点、方向依赖问题

1.2 三角形 2D 填充稳定化

  • 校验 BoundingBox 的边界是否正确
  • 校验点在三角形内判定的顶点顺序依赖
  • 明确像素采样点采用像素中心还是整数坐标
  • 明确边界像素规则,避免共享边重复或漏画

完成标准:

  • 任意顺序输入的三角形都能被正确填充
  • 边界行为稳定,没有明显裂缝

1.3 TriangleRasterizer 接入主流程前的准备

  • TriangleRasterizer 支持直接接收屏幕空间三角形
  • 明确光栅阶段输入结构
  • 为之后接 DepthBuffer 预留每像素深度写入点

Step 2. MVP 与视口变换

目标:把 3D 顶点稳定变换到屏幕空间。

2.1 Model / View / Projection 约定固定

  • 统一矩阵乘法约定
  • 明确当前使用列向量还是行向量
  • 明确当前 view space 是右手系还是左手系
  • 明确相机前方是 -Z
  • 明确 nearPlanefarPlane 使用正距离

完成标准:

  • M * V * P 的方向清晰
  • 相机朝向、投影矩阵、裁剪空间定义互相一致

2.2 NDC 与 Viewport 约定固定

  • 明确 NDC 使用 [-1, 1]
  • 明确屏幕原点为左上角
  • 明确 x 向右、y 向下
  • 明确 viewport 使用 width - 1 / height - 1

完成标准:

  • ProjectToScreen() 或等价流程不再分散写逻辑
  • Camera::get_viewport_matrix() 成为唯一屏幕映射入口

2.3 从 main 中抽离变换逻辑

  • clip -> ndc -> viewportmain.cpp 抽到专门模块
  • 明确输入是 object vertex 还是 assembled vertex
  • 为后续 mesh / triangle pipeline 准备统一入口

建议新增模块:

  • RendererPipeline
  • ProjectVertex
  • AssembleTriangle

Step 3. 三角形装配与实心面渲染

目标:从“线框”进入“实心三角形渲染”。

3.1 三角形装配

  • 从 mesh 顶点和索引装配三角形
  • 不再手写 cube edge 作为主流程
  • 统一输入为 triangle list

完成标准:

  • 主流程以“三角形”为核心,而不是“边”

3.2 背面剔除

  • 统一顶点绕序约定
  • 明确在 view space 还是 screen space 做剔除
  • 验证法线方向与绕序一致

完成标准:

  • 可稳定剔除背面
  • 不会因为模型顶点顺序混乱而随机闪面

3.3 实心立方体跑通

  • 用三角形填充代替线框
  • 先可不加光照,只显示纯色面
  • 验证旋转时每个面都正确显示

完成标准:

  • 可以正确绘制一个实心旋转立方体

Step 4. 深度测试

目标:解决遮挡关系。

4.1 DepthBuffer 基础

  • 初始化深度缓冲
  • 明确默认值是最远深度
  • 每帧清空深度缓冲
  • FrameBuffer 保持相同分辨率

4.2 深度写入与比较

  • 明确深度值使用哪个空间
  • 推荐先使用 NDC z 或映射后的深度值
  • 明确比较方向
  • 在通过测试时更新颜色和深度

完成标准:

  • 前面的三角形能正确遮挡后面的三角形

4.3 深度约定补全

  • 明确 near -> 0 / far -> 1 还是其他映射
  • 明确 viewport 是否处理 z
  • 明确是否保留 w 供后续透视校正使用

这是当前 TODO 中原本写得比较粗的部分,建议补成文档。


Step 5. 重心坐标与属性插值

目标:从“只会画面”走到“会在片元阶段传递数据”。

5.1 重心坐标

  • 在三角形内部计算每个像素的重心坐标
  • 输出 alpha / beta / gamma
  • 先用于深度插值

5.2 线性属性插值

  • 插值颜色
  • 插值深度
  • 插值法线
  • 插值世界坐标
  • 插值 UV

5.3 透视校正插值

这一项建议显式加入 TODO原文没有写清楚。

需要补上的内容:

  • 保存每个顶点的 1 / w
  • 对属性做 attr / w 插值
  • 最后再除以插值后的 1 / w

完成标准:

  • 纹理坐标和法线在透视投影下不发生明显拉伸错误

Step 6. 裁剪

目标:正确处理部分进入视锥的几何体。

这一项当前代码和原 TODO 都不够明确,但它是完整渲染器必须补的。

6.1 近平面裁剪

  • 不要简单按顶点是否在 NDC 内直接丢弃
  • 支持一个三角形部分穿过近平面
  • 处理裁剪后生成 1 个或 2 个新三角形

6.2 视锥裁剪

  • 支持左右上下近平远平面
  • 最开始至少要处理 near plane
  • 后续再扩展到完整 frustum clipping

完成标准:

  • 靠近相机的大三角形不会突然破碎或整块消失

Step 7. Shader 数据流

目标:让渲染流程从“硬编码逻辑”过渡到“清晰的着色阶段”。

这一项 README 提过方向,但 TODO 里不够明确,建议单独成阶段。

7.1 顶点输出结构

  • 定义 vertex shader 输出
  • 至少包含:
    • clip position
    • world position
    • normal
    • color / uv

7.2 片元输入结构

  • 定义 rasterizer 传给 fragment 的输入
  • 区分线性插值字段与透视校正字段

7.3 Shader 接口整理

  • 不要把光照直接写死在 main
  • 把着色逻辑集中到 Shading/
  • 明确 shader 输入输出边界

完成标准:

  • 后续增加 Lambert / Blinn-Phong / 纹理采样时不需要重写主流程

Step 8. Blinn-Phong 光照

目标:实现基本光照模型。

8.1 光照输入准备

  • 法线插值
  • 世界空间或观察空间位置
  • 相机方向
  • 光源方向

8.2 Blinn-Phong 模型

  • ambient
  • diffuse
  • specular
  • half vector H = normalize(L + V)

8.3 法线空间与归一化

  • 明确法线在 world 还是 view space 计算
  • 每像素重新归一化法线

完成标准:

  • 模型出现稳定高光和明暗面变化

Step 9. OBJ 加载与通用模型渲染

目标:从内置立方体过渡到真实模型。

9.1 OBJ 基础加载

  • 读取 v
  • 读取 vn
  • 读取 vt
  • 读取 f

9.2 Mesh 数据整理

  • 统一索引格式
  • 处理 OBJ 中 position / uv / normal 分离索引
  • 生成项目自己的 mesh 数据结构

9.3 模型接入渲染管线

  • 用真实 mesh 走完整 triangle pipeline
  • 不再依赖 demo 专用几何体

完成标准:

  • 能渲染 teapot、bunny 或其他基础模型

Step 10. 相机控制

目标:让 renderer 具备交互能力。

10.1 平移控制

  • WASD
  • 上下移动

10.2 旋转控制

  • 鼠标控制 yaw / pitch
  • 限制 pitch 防止翻转

10.3 运行时参数调整

  • FOV
  • near / far plane
  • 光照参数

完成标准:

  • 能在场景中自由观察模型

Step 11. 结构收口

目标:让代码不再长期堆在 main.cpp

11.1 Renderer 主流程落地

  • 把渲染调度逻辑移入 Renderer
  • main.cpp 只保留:
    • 初始化
    • 场景创建
    • 输入处理
    • 每帧调用 renderer

11.2 数据结构分层

  • Scene: 相机、模型、变换
  • RenderData: 光栅输入输出数据
  • Rasterizer: 三角形覆盖与插值
  • Shading: 顶点 / 片元着色
  • Core: framebuffer / depthbuffer / renderer

11.3 调试与测试工具

  • 增加简单数学单元测试
  • 增加投影和视口断言
  • 增加线框 / 深度 / 法线可视化模式

可选增强

这些不影响基础 renderer 完成,但能显著提升项目质量。

A. Texture Mapping

  • 纹理采样
  • UV 插值
  • 可先做 nearest再做 bilinear

B. MSAA

  • 子像素采样
  • Resolve 到最终 framebuffer

C. 阴影或简单后处理

  • Shadow map
  • Gamma 校正
  • Tone mapping

D. 性能优化

  • 降低无效像素遍历
  • 更高效的 depth / color 写入
  • SIMD 或分块光栅化

里程碑

Milestone 1

  • 线框立方体稳定显示
  • MVP 与 viewport 正确

Milestone 2

  • 实心立方体
  • 背面剔除
  • Z-buffer

Milestone 3

  • 属性插值
  • Blinn-Phong
  • OBJ 模型

Milestone 4

  • 相机交互
  • 结构整理
  • 可选增强项

当前最建议优先做的 5 件事

  1. 把主流程从线框切到实心三角形
  2. 接入 DepthBuffer
  3. 实现重心坐标和深度插值
  4. 明确并实现透视校正插值
  5. main.cpp 的渲染逻辑收口到 Renderer