docs: rewrite README, DOCUMENTATION, and CHANGELOG — too robotic, rewrote from scratch
This commit is contained in:
148
DOCUMENTATION.md
148
DOCUMENTATION.md
@@ -1,32 +1,144 @@
|
||||
# ASCII 3D Renderer - Engineering Documentation
|
||||
# ASCII 3D Renderer — Technical Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
ASCII 3D Renderer is a sophisticated terminal-based rendering engine implemented entirely in C11. Its goal is to bring modern 3D rendering methodologies—such as Physical-Based Lighting approximations and Depth Buffering—into a constrained ASCII viewport without utilizing external graphical libraries.
|
||||
This is a software 3D renderer that outputs to a terminal. It takes text input, turns each character into a 3D shape, lights it with a multi-light Blinn-Phong model, and draws the result using ASCII characters (or colored output via ANSI escape sequences). Everything runs in a single-threaded loop at 60 FPS.
|
||||
|
||||
## Technical Architecture
|
||||
There are no GPU calls, no windowing system, and no third-party rendering libraries. The only external dependency beyond `libc` is `libm` for math functions (`powf`, `fmaxf`, `sqrtf`, etc.).
|
||||
|
||||
The codebase adheres strictly to functional abstractions and strict modular boundaries to enhance performance within deep rendering loops.
|
||||
## How the rendering works
|
||||
|
||||
### Directory Structure
|
||||
The pipeline, roughly, goes like this:
|
||||
|
||||
- `include/`: Enforces API boundaries and struct definitions across all C modules.
|
||||
- `src/`: Houses the isolated functional algorithms driving vectors, physics, and input multiplexing.
|
||||
1. Look up the glyph bitmap for each character (from `font.c`)
|
||||
2. Extrude the 2D bitmap into a 3D volume by adding depth along the Z-axis
|
||||
3. Step through the volume at regular intervals (`VOXEL_STEP = 0.15`) and, at each point, check whether we're "inside" geometry
|
||||
4. Calculate surface normals by looking at which neighboring cells are empty — a filled voxel next to an empty one means there's a surface edge there, and the normal points toward the gap
|
||||
5. Apply rotation matrices (one per axis) based on the current angle
|
||||
6. Project the 3D points into 2D screen coordinates using perspective projection (field of view, near/far planes)
|
||||
7. For each projected point, run it through the lighting model to get a brightness value
|
||||
8. Map that brightness to a character from the current shade palette
|
||||
9. Write it to the framebuffer (a 2D array of chars), respecting the Z-buffer so closer surfaces win
|
||||
10. Dump the entire framebuffer to stdout in one pass
|
||||
|
||||
### Subsystem Design
|
||||
When anti-aliasing is enabled, steps 3–8 are repeated with jittered sample offsets (2×2 pattern by default), and the results are averaged.
|
||||
|
||||
1. **Input multiplexing (`tui.c`)**
|
||||
Toggling standard IO into non-blocking, non-canonical states permits smooth real-time TTY interactions. Signal states are captured instantaneously and interpreted within the main event-loop per tick, altering global behavior objects without disrupting the core voxel algorithms.
|
||||
## Project structure
|
||||
|
||||
2. **Core 3D Engine (`renderer.c`)**
|
||||
Calculates pseudo-normals via 3D vector traversal. Text characters initially manifest as two-dimensional `5x7` bitmaps encoded as raw bitflags (`font.c`). `renderer.c` mathematically extrudes these glyphs along a virtual Z-axis. Multi-sampled pixel rays compute intersection boundaries and calculate normal approximations by reading empty adjacencies along geometry ridges.
|
||||
```bash
|
||||
src/
|
||||
main.c — CLI parsing, signal handling, main loop
|
||||
renderer.c — the core rendering engine: projection, voxel traversal,
|
||||
framebuffer management, screen output
|
||||
lighting.c — Blinn-Phong lighting: diffuse, specular, multi-light
|
||||
accumulation, color math
|
||||
font.c — 5×7 bitmap font data for A-Z and 0-9, stored as
|
||||
bitflag arrays
|
||||
tui.c — terminal input handling: puts the TTY into raw mode
|
||||
for non-blocking reads, processes keypresses
|
||||
timing.c — frame rate limiter using CLOCK_MONOTONIC + nanosleep
|
||||
vec3.c — basic 3D vector math (dot, cross, normalize, rotate)
|
||||
|
||||
3. **Lighting Approximations (`lighting.c`)**
|
||||
Standard Blinn-Phong equations exist within an ambient, diffuse, and specular composition loop. These color values are derived utilizing an arbitrary virtual camera and bounded to integer offsets matching one of our standard luminance dictionaries (e.g., `.:-=+*#%@`).
|
||||
include/
|
||||
config.h — all the tuneable constants in one place: viewport size,
|
||||
camera FOV, lighting params, shade palettes, etc.
|
||||
(+ headers for each .c file)
|
||||
```
|
||||
|
||||
4. **Temporal Control (`timing.c`)**
|
||||
Forces a consistent execution boundary using positive UNIX epoch resolution (`CLOCK_MONOTONIC`) mapped directly via `nanosleep`. Predictable delta steps stabilize the rotation matrix speed regardless of hardware IO constraints.
|
||||
## Module details
|
||||
|
||||
## Compilation and Dependencies
|
||||
### `font.c` — Bitmap font
|
||||
|
||||
The system is pure logic—`libc` and `-lm` (for `libmath`) constitute the full scope of integration dependencies. The custom `Makefile` orchestrates compilation utilizing Standard `-O3 -flto` link time passes, alongside memory sanitizer profiles natively integrated within Clang for debugging cycles.
|
||||
Each character is a 5-wide, 7-tall bitmap stored as 7 bytes. Each byte represents one row — bits 4 down to 0 tell you which pixels are filled. For example, the letter "A" is:
|
||||
|
||||
```bash
|
||||
0x0E → .###.
|
||||
0x11 → #...#
|
||||
0x11 → #...#
|
||||
0x1F → #####
|
||||
0x11 → #...#
|
||||
0x11 → #...#
|
||||
0x11 → #...#
|
||||
```
|
||||
|
||||
Only A–Z (case-insensitive) and 0–9 are supported. Anything else gets skipped.
|
||||
|
||||
### `renderer.c` — 3D engine
|
||||
|
||||
This is the biggest file and does most of the heavy lifting.
|
||||
|
||||
**Glyph extrusion:** Each 5×7 bitmap gets depth. The renderer walks through the glyph volume at small Z increments. At each (x, y, z) point, if the corresponding 2D pixel is "on" in the bitmap, that point is considered solid geometry.
|
||||
|
||||
**Normal estimation:** For shading to work, you need surface normals. Since we're working with blocky voxel-like shapes, the normals are estimated by checking adjacent cells. If a filled cell has an empty neighbor to the left, the normal has a leftward component. Same for all six directions. The result is normalized to get a unit vector. This gives you smooth-ish shading on what are otherwise cube-shaped surfaces.
|
||||
|
||||
**Projection:** Standard perspective projection. A 3D point gets divided by its Z distance (scaled by field-of-view), then offset to the center of the screen. Points behind the near plane or past the far plane are clipped.
|
||||
|
||||
**Z-buffering:** The renderer keeps a depth buffer (initialized to a large value). When drawing a point, it only updates the framebuffer if the new point is closer than what was already there. This keeps front surfaces in front.
|
||||
|
||||
**Render modes:**
|
||||
|
||||
- *Shaded* — full lighting + ASCII shade mapping (the default)
|
||||
- *Solid* — everything gets drawn with `@`, no lighting
|
||||
- *Wireframe* — only draw points that sit on the boundary between filled and empty cells
|
||||
- *Points* — draw all filled voxels with `.`
|
||||
|
||||
### `lighting.c` — Blinn-Phong model
|
||||
|
||||
The lighting system supports up to 3 lights (configurable via `MAX_LIGHTS`). By default it sets up a classic 3-point rig:
|
||||
|
||||
- **Key light** — the main light, slightly above and to the right, warm white
|
||||
- **Fill light** — dimmer, coming from the left, slightly blue-tinted, softens shadows
|
||||
- **Rim light** — behind and below the subject, adds edge definition
|
||||
|
||||
Each light can be directional (parallel rays, like the sun) or point (with inverse-square falloff).
|
||||
|
||||
For each surface point, the shader calculates:
|
||||
|
||||
- **Diffuse** — Lambert's cosine law: brightness depends on the angle between the surface normal and the light direction
|
||||
- **Specular** — Blinn-Phong half-vector method: creates shiny highlights where the surface reflects light toward the camera
|
||||
|
||||
The final brightness is the sum of ambient + all light contributions, clamped to [0, 1]. In monochrome mode this maps to an index into the shade character palette. In color mode, the full RGB result is computed per-channel and output using ANSI escape sequences.
|
||||
|
||||
Material properties (ambient/diffuse/specular colors, shininess) are set to sensible defaults — there's no way to change them at runtime yet.
|
||||
|
||||
### `tui.c` — Terminal input
|
||||
|
||||
On startup, the terminal gets switched to non-canonical mode with echo disabled (via `termios`). This means keypresses are available immediately without waiting for Enter, and typed characters don't show up on screen.
|
||||
|
||||
The original terminal settings are saved and restored on exit — this is important because if the program crashes without restoring them, you'd end up with a terminal that doesn't echo your typing or respond to Ctrl+C properly. Signal handlers for SIGINT and SIGTERM are set up specifically to make sure cleanup happens even on interrupt.
|
||||
|
||||
### `timing.c` — Frame limiter
|
||||
|
||||
Pretty straightforward. After each frame, it checks how much time has passed since the frame started using `clock_gettime(CLOCK_MONOTONIC)`. If there's time remaining in the frame budget (1/60th of a second), it sleeps for the difference with `nanosleep`. This keeps the animation speed consistent regardless of how fast the machine is.
|
||||
|
||||
## Build system
|
||||
|
||||
The Makefile has three build profiles:
|
||||
|
||||
| Target | Flags | Purpose |
|
||||
|--------|-------|---------|
|
||||
| `release` | `-O3 -march=native -flto -DNDEBUG` | Production — fast as possible |
|
||||
| `debug` | `-O0 -g3 -fsanitize=address,undefined` | Development — catches memory bugs and UB |
|
||||
| `profile` | `-O2 -g -pg` | Profiling with gprof |
|
||||
|
||||
Dependency tracking is handled with `-MMD -MP`, so incremental builds work correctly.
|
||||
|
||||
The `install` target copies the binary to `/usr/local/bin` (or wherever `PREFIX` points).
|
||||
|
||||
## Configuration
|
||||
|
||||
Everything tuneable lives in `include/config.h`. Some notable values:
|
||||
|
||||
| Constant | Default | What it controls |
|
||||
|----------|---------|-----------------|
|
||||
| `SCREEN_WIDTH` / `SCREEN_HEIGHT` | 120 × 45 | Framebuffer size in characters |
|
||||
| `CAMERA_DISTANCE` | 30.0 | How far back the virtual camera sits |
|
||||
| `FIELD_OF_VIEW` | 50.0 | Perspective FOV in degrees |
|
||||
| `EXTRUSION_DEPTH` | 4.0 | How thick the 3D letters are |
|
||||
| `VOXEL_STEP` | 0.15 | Sampling resolution along Z when building geometry |
|
||||
| `AA_SAMPLES` | 2 | Anti-aliasing grid (2 = 2×2 = 4 samples per pixel) |
|
||||
| `AMBIENT_INTENSITY` | 0.15 | Base light level in shadows |
|
||||
| `DIFFUSE_INTENSITY` | 0.70 | Strength of the diffuse lighting term |
|
||||
| `SPECULAR_INTENSITY` | 0.40 | Strength of specular highlights |
|
||||
| `SPECULAR_POWER` | 32.0 | Shininess exponent (higher = tighter highlights) |
|
||||
| `TARGET_FPS` | 60 | Frame rate cap |
|
||||
|
||||
Reference in New Issue
Block a user