sRenderer 是一个纯软件渲染器——每一个像素都完全在 CPU 上计算完成,不依赖任何 GPU。没有 OpenGL,没有 Vulkan,没有 DirectX。唯一的外部依赖是 SFML,它仅用于创建窗口和显示画面。从向量运算到透视投影再到纹理映射,其他所有功能都是从零开始构建的。
项目地址: https://github.com/Oh-noodles/sRenderer
![]() |
![]() |
![]() |
| African Head 模型 | 鲨鱼 | 树木 |
技术栈 #
| 类别 | 技术 | 说明 |
|---|---|---|
| 编程语言 | C++11 | 不依赖任何外部数学库(Eigen、glm 等) |
| 构建系统 | CMake 3.11+ | 使用 FetchContent 自动获取 SFML |
| 界面 / 窗口 | SFML 3.0.0 | 仅用于创建窗口和纹理显示 |
| 模型格式 | Wavefront .obj |
解析顶点、纹理坐标、法线和面 |
| 纹理格式 | TGA (Targa) | 手写的 TGA 加载器,支持 RGBA |
| 编译器 | g++ / clang++ | 在 Linux 和 macOS 上测试 |
特性 #
核心渲染 #
- 纯软件光栅化器 —— 每个像素在 CPU 上着色,无需 GPU
- 透视投影(Perspective Projection) —— 完整的 4x4 矩阵管线:视图矩阵(View)→ 投影矩阵(Project)→ 视口矩阵(Viewport)
- Z 缓冲深度测试(Z-Buffer) —— 逐像素深度比较以处理遮挡关系
- 透视正确插值(Perspective-Correct Interpolation) —— 使用
1/z插值技术实现正确的纹理映射 - 漫反射光照(Diffuse / Lambertian Lighting) —— 表面法线与光线方向的点积,附带环境光项
- 纹理映射(Texture Mapping) —— 带透视校正的 UV 坐标插值
- 线框渲染(Wireframe) —— 类似 Bresenham 的三角线绘制算法
- 着色器抽象(Shader Abstraction) ——
IShader接口,包含顶点阶段和片段阶段(类似 GPU 着色器模型)
数学库(从零实现) #
Vec<T, DIM>—— 泛型 N 维向量,支持运算符(+、-、*、点积、叉积、归一化)Matrix44f—— 完整 4x4 浮点矩阵,支持乘法、逆矩阵和转置lookAt()—— 相机变换矩阵edgeFunction()—— 三角形光栅化辅助函数embed<>()—— 齐次坐标的维度嵌入
模型与图像加载 #
- Wavefront
.obj解析器(顶点、纹理坐标、法线、三角形/四边形面) - 四边形到三角形的扇形转换(支持任意多边形面)
- TGA 图像加载器,支持 RGBA
- 内置模型切换器(运行时可选择 african_head、shark 或 tree)
实时渲染循环 #
- 自动旋转轨道相机(基于时间的动画)
- 使用 SFML 的 60 FPS 渲染循环
- 通过 SFML
RenderTexture+Sprite管线实时显示
渲染管线 #
渲染管线遵循经典 GPU 管线的简化版。以下是每个三角形的处理流程:
OBJ 模型
│
▼
顶点处理(Vertex Processing)
│ 将顶点嵌入为 Vec4f(齐次坐标)
│ 依次应用 View × Project × ViewPort 矩阵变换
│ 执行透视除法(w-divide)
│
▼
光栅化(Rasterization)
│ 计算三角形包围盒
│ 逐像素进行点在三角形内测试(边函数)
│ 透视正确插值(1/z 方法)
│
▼
片段着色(Fragment Shading)
│ 在插值后的 UV 坐标处采样纹理
│ 计算漫反射光照(法线 · 光线方向 + 环境光)
│ 将光照应用到纹理颜色
│
▼
深度测试与写入(Depth Test & Write)
│ 将插值后的 z 值与 Z 缓冲进行比较
│ 将像素写入帧缓冲
│
▼
SFML 显示
将帧缓冲上传为 GPU 纹理(通过 SFML)
渲染精灵到窗口矩阵变换 #
三个主要矩阵的构建方式如下:
- 视图矩阵(View Matrix) ——
lookAt(eye, target, up)的逆矩阵- 将世界空间坐标转换为相机空间
- 投影矩阵(Projection Matrix) —— 对称透视投影
- 将相机空间映射到裁剪空间:near=1、far=30、视场角约 45°
- 视口矩阵(Viewport Matrix) —— 将归一化设备坐标映射到屏幕像素
- 按帧缓冲维度进行缩放和平移
组合变换为:ViewPort × Project × View × vertex
透视正确插值 #
这是避免纹理映射中常见"扭曲"伪影的关键技术。算法不是直接线性插值 UV 坐标,而是先对 UV/z 值进行插值,然后除以插值后的 1/z 来获得透视正确的结果:
1/z = w0/z0 + w1/z1 + w2/z2
uv_corrected = z * (w0*uv0/z0 + w1*uv1/z1 + w2*uv2/z2)项目结构 #
sRenderer/
├── inc/ # 头文件
│ ├── geometry.hpp # Vec<T,DIM>、Matrix44f、数学运算符
│ ├── model.hpp # OBJ 模型加载器(顶点、纹理、法线)
│ ├── renderer.hpp # 主 Renderer 类(光栅化循环)
│ ├── shader.hpp # IShader 接口 + SimpleShader 实现
│ ├── tgaImage.hpp # TGA 图像加载器 + TGAColor + TGAImage
│ └── SFML/ # SFML 头文件(vendored)
├── src/ # 源文件
│ ├── main.cpp # 入口点、模型选择、渲染循环
│ ├── geometry.cpp # 矩阵实现、lookAt、直线数学
│ ├── model.cpp # OBJ 文件解析器
│ ├── renderer.cpp # Renderer::render() —— 完整光栅化管线
│ ├── shader.cpp # 增量式直线绘制
│ └── tgaImage.cpp # TGA 读写(含 RLE 支持)
├── obj/ # Wavefront 模型文件
│ ├── african_head.obj
│ ├── shark.obj
│ ├── tree.obj
│ ├── blender_cube.obj
│ ├── cube.obj
│ ├── frustum.obj
│ └── teapot.obj
├── texture/ # TGA 纹理贴图
│ ├── african_head_diffuse.tga
│ ├── shark_diffuse.tga
│ ├── tree_diffuse.tga
│ └── blender_cube_diffuse.tga
├── doc/assets/ # 演示 GIF
│ ├── head.gif
│ ├── shark.gif
│ └── tree.gif
├── CMakeLists.txt # CMake 构建配置
├── Makefile # 使用 make 的备选构建方式
├── compile_flags.txt # 用于 clangd 的编译选项
├── README.md # 本文件(英文版)
└── README_zh.md # 中文版编译与运行 #
环境要求 #
- Linux 或 macOS(Windows 可能可以工作但未经测试)
g++或clang++,需支持 C++11cmake(版本 3.11 或更高)
编译步骤 #
# 1. 克隆仓库(或进入项目目录)
cd sRenderer
# 2. 配置构建(CMake 会自动获取 SFML 3.0.0)
cmake -B build
# 3. 编译
cmake --build build
# 4. 运行程序
./build/sRenderer使用说明 #
启动后,程序会提示选择一个模型:
Choose a model below:
0. head
1. shark
2. tree
Your choice [0-2]:选择后,模型将以自动旋转的相机轨道进行渲染。


