|
| 1 | +# Software Renderer |
| 2 | + |
| 3 | +**Runs entirely on the CPU.** |
| 4 | + |
| 5 | +[](https://github.com/Speed126/Software-Renderer/actions/workflows/ci.yml) |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +A C++20 rasterizer that loads an OBJ, projects its triangles, shades the |
| 14 | +visible pixels, and writes a TGA image. Includes edge tests, depth comparisons, matrix math, clipping, and attribute interpolation. |
| 15 | + |
| 16 | +## How to Use |
| 17 | + |
| 18 | +Requires **CMake 3.20+** and a C++20 compiler. No runtime libraries beyond the |
| 19 | +C++ standard library. The framebuffer is saved by a small uncompressed TGA writer. |
| 20 | + |
| 21 | +```sh |
| 22 | +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release |
| 23 | +cmake --build build --config Release --parallel |
| 24 | +ctest --test-dir build -C Release --output-on-failure |
| 25 | +``` |
| 26 | + |
| 27 | +From the repository root, with a single-configuration generator (Linux, macOS, |
| 28 | +or MinGW): |
| 29 | + |
| 30 | +```sh |
| 31 | +./build/renderer assets/orbital.obj --output output.tga --width 1400 --height 1000 |
| 32 | +``` |
| 33 | + |
| 34 | +With the Visual Studio generator, in PowerShell: |
| 35 | + |
| 36 | +```powershell |
| 37 | +.\build\Release\renderer.exe assets/orbital.obj --output output.tga --width 1400 --height 1000 |
| 38 | +``` |
| 39 | + |
| 40 | +Running with no arguments uses `assets/orbital.obj` and writes an 800×800 |
| 41 | +`output.tga`. Paths are relative to your working directory. CMake also copies |
| 42 | +the demo into `build/assets`, so running from the build directory works. |
| 43 | + |
| 44 | +## What It Does |
| 45 | + |
| 46 | +- Bounding-box traversal with pixel-center barycentrics and shared-edge rules. |
| 47 | +- Per-pixel float depth buffer and optional counterclockwise back-face culling. |
| 48 | +- Model fitting/rotation, look-at camera, perspective and six-plane clipping. |
| 49 | +- Triangulated OBJ meshes with positions, UVs, normals, and positive or negative indices. |
| 50 | +- Perspective-correct UV/normal interpolation and directional Lambert lighting. |
| 51 | +- Procedural checker or a supplied P3 PPM texture; nearest sampling with repeat. |
| 52 | +- Tests run through CTest, with CI on Linux and Windows. |
| 53 | + |
| 54 | +```mermaid |
| 55 | +flowchart TD |
| 56 | + A[OBJ] --> B[Model loader] |
| 57 | + B --> C[Model · view · projection] |
| 58 | + C --> D[Homogeneous clipping] |
| 59 | + D --> E[Perspective divide · viewport] |
| 60 | + E --> F[Triangle setup · barycentric coverage] |
| 61 | + F --> G[Interpolate depth · visibility test] |
| 62 | + G --> H[Perspective-correct attributes] |
| 63 | + H --> I[Texture sample · Lambert lighting] |
| 64 | + I --> J[Color + depth framebuffer] |
| 65 | + J --> K[TGA image] |
| 66 | +``` |
| 67 | + |
| 68 | +## Useful Arguments |
| 69 | + |
| 70 | +```sh |
| 71 | +./build/renderer path/to/model.obj --texture path/to/texture.ppm --output render.tga |
| 72 | +./build/renderer --rotate 30 --eye 0.5 0.25 3.6 |
| 73 | +./build/renderer --solid --light -0.4 0.7 1 |
| 74 | +./build/renderer --no-cull |
| 75 | +``` |
| 76 | + |
| 77 | +| Option | Meaning | |
| 78 | +|---|---| |
| 79 | +| `--output path` | TGA destination; the parent directory must exist | |
| 80 | +| `--width N`, `--height N` | Integer dimensions from 1 to 4096; default 800 | |
| 81 | +| `--eye X Y Z` | Camera position in fitted model coordinates; looks at the origin | |
| 82 | +| `--rotate degrees` | Rotate the fitted model around Y | |
| 83 | +| `--light X Y Z` | World-space direction **toward** the light; normalized internally | |
| 84 | +| `--texture path.ppm` | Use an ASCII P3 PPM image instead of the checker | |
| 85 | +| `--solid` | Use a neutral solid surface instead of a texture | |
| 86 | +| `--no-cull` | Draw both windings; useful for inspecting open meshes | |
| 87 | +| `--help` | Show usage | |
| 88 | + |
| 89 | +Invalid arguments and failed reads/writes exit with code 1. |
| 90 | + |
| 91 | +## Inside a Triangle |
| 92 | + |
| 93 | +[`rasterizer.cpp`](src/rasterizer.cpp) evaluates signed edge functions inside a |
| 94 | +triangle's screen-space bounding box. Their ratios to the total area give the |
| 95 | +three barycentric weights. Those weights locate the sample within the triangle |
| 96 | +and interpolate its depth and surface attributes. Pixel centers avoid a corner |
| 97 | +sampling bias; a half-open edge convention gives adjacent triangles consistent |
| 98 | +ownership of their shared boundary. Degenerate and nonfinite triangles are |
| 99 | +skipped, and traversal is clamped before converting bounds to integers. |
| 100 | + |
| 101 | +The depth buffer starts at infinity. A fragment updates the color and depth only |
| 102 | +when its depth is smaller than the stored value. Hidden surfaces therefore stay |
| 103 | +hidden when triangle order changes. Exactly equal depths keep the first sample. |
| 104 | + |
| 105 | +## Camera and Surface Detail |
| 106 | + |
| 107 | +[`pipeline.cpp`](src/pipeline.cpp) centers the model bounds and uniformly scales |
| 108 | +the longest extent to two units. Models can arrive with different sizes and |
| 109 | +origins. Matrices use row-major storage with column vectors: |
| 110 | +`projection * view * model * position`. The camera is right-handed, looks down |
| 111 | +view-space −Z, and uses a 45° vertical field of view. Near/far planes are 0.1/100. |
| 112 | +Clipping happens before division by `w`, including triangles crossing the camera |
| 113 | +or near plane. Screen coordinates use a bottom-left origin; TGA rows are written |
| 114 | +with a top-left origin. |
| 115 | + |
| 116 | +For an attribute `a`, interpolation uses |
| 117 | +`sum(barycentric[i] * a[i] / w[i]) / sum(barycentric[i] / w[i])`. |
| 118 | +This keeps textures attached to surfaces as they recede into the |
| 119 | +image. NDC depth stays affine in screen space. Interpolated normals are |
| 120 | +renormalized for Lambert shading, with a small ambient floor so unlit surfaces |
| 121 | +remain readable. The model transform uses uniform scale, so rotating normals |
| 122 | +is sufficient. |
| 123 | + |
| 124 | +| Solid Surface | Textured + Alternate Light | |
| 125 | +|---|---| |
| 126 | +|  |  | |
| 127 | + |
| 128 | +These images are direct renderer output converted to PNG. |
| 129 | +To reproduce the documentation images with an existing Python/Pillow setup: |
| 130 | + |
| 131 | +Linux/macOS/MinGW: |
| 132 | + |
| 133 | +```sh |
| 134 | +python tools/render_docs.py build/renderer |
| 135 | +``` |
| 136 | + |
| 137 | +Visual Studio: |
| 138 | + |
| 139 | +```powershell |
| 140 | +python tools/render_docs.py build/Release/renderer.exe |
| 141 | +``` |
| 142 | + |
| 143 | +Pillow is only a documentation convenience; it is not a build or renderer dependency. |
| 144 | + |
| 145 | +## Tests and Codebase |
| 146 | + |
| 147 | +```sh |
| 148 | +cmake -S . -B build -DRENDERER_STRICT=ON |
| 149 | +cmake --build build --config Release --parallel |
| 150 | +ctest --test-dir build -C Release --output-on-failure |
| 151 | +``` |
| 152 | + |
| 153 | +The 15 CTest entries cover math, OBJ failures, shared edges, clipping, |
| 154 | +perspective-correct texture interpolation, depth/order invariance, fitted transforms, complete |
| 155 | +TGA output and CLI diagnostics. Checks run in Release builds too. The |
| 156 | +[workflow](.github/workflows/ci.yml) builds and tests on Ubuntu and Windows, |
| 157 | +with a separate Ubuntu Clang ASan/UBSan job. All three jobs currently pass. |
| 158 | + |
| 159 | +| Location | Responsibility | |
| 160 | +|---|---| |
| 161 | +| `include/renderer/`, `src/` | Math, loading, pipeline, rasterizer, texture and framebuffer | |
| 162 | +| `src/main.cpp` | Argument handling and the offline render operation | |
| 163 | +| `tests/` | Small C++ test executables and CMake CLI checks | |
| 164 | +| `assets/`, `tools/` | Original demo and optional reproduction scripts | |
| 165 | +| `src/tga_writer.cpp` | Uncompressed 24-bit TGA output using the standard library | |
| 166 | + |
| 167 | +## License |
| 168 | + |
| 169 | +Project code, the procedural model and documentation are [MIT licensed](LICENSE). |
0 commit comments