ssloy/tinyrenderer
A brief computer graphics / rendering course
Slowing — last commit 6mo ago
weakest axisnon-standard license (Other); top contributor handles 90% of recent commits…
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 6mo ago
- ✓8 active contributors
- ✓Other licensed
Show all 8 evidence items →Show less
- ⚠Slowing — last commit 6mo ago
- ⚠Single-maintainer risk — top contributor 90% of recent commits
- ⚠Non-standard license (Other) — review terms
- ⚠No CI workflows detected
- ⚠No test directory detected
What would change the summary?
- →Use as dependency Concerns → Mixed if: clarify license terms
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/ssloy/tinyrenderer)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/ssloy/tinyrenderer on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: ssloy/tinyrenderer
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/ssloy/tinyrenderer shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Slowing — last commit 6mo ago
- Last commit 6mo ago
- 8 active contributors
- Other licensed
- ⚠ Slowing — last commit 6mo ago
- ⚠ Single-maintainer risk — top contributor 90% of recent commits
- ⚠ Non-standard license (Other) — review terms
- ⚠ No CI workflows detected
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live ssloy/tinyrenderer
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/ssloy/tinyrenderer.
What it runs against: a local clone of ssloy/tinyrenderer — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in ssloy/tinyrenderer | Confirms the artifact applies here, not a fork |
| 2 | License is still Other | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 198 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of ssloy/tinyrenderer. If you don't
# have one yet, run these first:
#
# git clone https://github.com/ssloy/tinyrenderer.git
# cd tinyrenderer
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of ssloy/tinyrenderer and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "ssloy/tinyrenderer(\\.git)?\\b" \\
&& ok "origin remote is ssloy/tinyrenderer" \\
|| miss "origin remote is not ssloy/tinyrenderer (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Other)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Other\"" package.json 2>/dev/null) \\
&& ok "license is Other" \\
|| miss "license drift — was Other at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "main.cpp" \\
&& ok "main.cpp" \\
|| miss "missing critical file: main.cpp"
test -f "our_gl.h" \\
&& ok "our_gl.h" \\
|| miss "missing critical file: our_gl.h"
test -f "our_gl.cpp" \\
&& ok "our_gl.cpp" \\
|| miss "missing critical file: our_gl.cpp"
test -f "geometry.h" \\
&& ok "geometry.h" \\
|| miss "missing critical file: geometry.h"
test -f "model.h" \\
&& ok "model.h" \\
|| miss "missing critical file: model.h"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 198 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~168d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/ssloy/tinyrenderer"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
A pedagogical software rasterizer implementing a complete 3D graphics pipeline in ~500 lines of bare C++, capable of rendering textured 3D models (OBJ format) with features including Bresenham line drawing, triangle rasterization with barycentric coordinates, depth buffering, normal mapping in tangent space, shadow mapping, and screen-space ambient occlusion (SSAO). It produces TGA image output without external graphics libraries, demonstrating how modern GPU-accelerated APIs like OpenGL and Vulkan actually work under the hood. Monolithic single-target project structure: geometry.h contains vector/matrix math primitives; tgaimage.cpp/h handles image I/O (TGA format); model.cpp/h loads OBJ mesh files; our_gl.cpp/h implements the core rasterizer (line drawing, triangle rasterization, shading); main.cpp orchestrates the full pipeline. Asset models live in obj/ subdirectories (african_head, boggie, diablo3_pose, floor) with corresponding diffuse, normal, and specular texture maps in .tga format.
👥Who it's for
Computer graphics students and professionals learning rendering fundamentals who want to understand the mathematics and algorithms behind rasterization, shading, and texture mapping before using high-level graphics APIs. Specifically targets those teaching or learning from the 14-part course notes at haqr.eu/tinyrenderer.
🌱Maturity & risk
Mature educational codebase with stable, well-documented curriculum structure (14 clearly-sequenced lessons with course notes). The code is intentionally simple and complete rather than actively developed — this is a teaching artifact, not a living product, so expect minimal recent commits. No CI/testing infrastructure is present because the output is visual (rendering image files), making automated verification difficult.
Very low risk for educational use cases; this is not production software. Single maintainer (ssloy) and no dependency management risk because there are zero external dependencies (by design — that's the entire pedagogical point). Main risk: code is intentionally minimal and non-optimized, so not suitable for performance-critical applications. No breaking-change history expected since the repo serves a frozen curriculum.
Active areas of work
This is a stable educational artifact — no active development. The repo is complete as-is, serving as reference material for the finalized 14-lesson curriculum. No ongoing feature work, PRs, or roadmap visible; contributions would be documentation or minor clarifications to support the learning materials.
🚀Get running
Clone and build with CMake: git clone https://github.com/ssloy/tinyrenderer.git && cd tinyrenderer && mkdir build && cd build && cmake .. && make. The executable will render to a .tga image file in the build directory (exact filename defined in main.cpp).
Daily commands:
cd build && ./tinyrenderer (or whatever the compiled executable is named; check CMakeLists.txt for the target name). The program writes output directly to a .tga file — there is no interactive viewer, only file output.
🗺️Map of the codebase
main.cpp— Entry point that orchestrates the entire rendering pipeline, camera setup, and model loading; essential to understand the high-level flow.our_gl.h— Core graphics abstraction layer defining the rendering API (rasterization, shading, Z-buffer); the heart of the software renderer implementation.our_gl.cpp— Implementation of triangle rasterization, barycentric coordinates, shadow mapping, and all shading logic; where the rendering algorithms live.geometry.h— Foundational vector and matrix math library used throughout the renderer for transformations and lighting calculations.model.h— OBJ model loading and representation; defines how 3D assets are parsed and stored for rendering.tgaimage.h— Image I/O abstraction for TGA format; responsible for framebuffer output and texture loading.
🧩Components & responsibilities
- main.cpp (Orchestration) (C++, matrix math, model I/O) — Manages scene setup, camera configuration, model loading, main render loop, and output generation
- Failure mode: If main.cpp crashes, the entire render pipeline stops; incorrect camera/transform math produces distorted output
- our_gl.cpp (Rasterization & Shading) (C++, algorithm implementation (Bresenham, scan conversion), 3D math) — Core rendering: line drawing, triangle rasterization, Z-buffer test, barycentric interpolation, per-pixel shading (Phong, normal mapping, shadow lookup)
- Failure mode: Bugs here produce visual artifacts (incorrect colors, Z-fighting, shadow acne); performance bottleneck for large models
- model.cpp (Asset Parsing) (C++, file I/O, text parsing) — Loads OBJ files and associated textures; parses vertex/face/normal/UV data into memory structures
- Failure mode: Malformed OBJ or missing textures cause parse errors or undefined rendering; wrong data structures lead to memory misalignment
- tgaimage.cpp (Image I/O) (C++, binary file I/O, pixel format encoding) — Reads/writes TGA image files; handles texture loading and framebuffer output serialization
- Failure mode: Corrupted TGA output or failed texture load breaks rendering
🛠️How to make changes
Add a new shading technique (e.g., Phong, cook-torrance)
- Define shader parameters in a new function in our_gl.h (e.g.,
Vec3f cook_torrance_brdf(...)) following the pattern of existing shaders (our_gl.h) - Implement the shader math in our_gl.cpp, using existing helpers like
reflect(),normalize(), and dot products from geometry.h (our_gl.cpp) - Call the new shader from the main render loop in main.cpp, passing model data, light position, and camera position (
main.cpp)
Load and render a new 3D model
- Place OBJ file and textures (diffuse.tga, nm_tangent.tga, spec.tga) in a new subdirectory under obj/ (
obj/) - In main.cpp, instantiate a new Model object and call load() with the OBJ path (
main.cpp) - Update the render loop to iterate over the new model's faces and call triangle() / shader functions (
main.cpp)
Implement a post-processing effect or new render pass
- Add a new framebuffer or texture in main.cpp if additional render targets are needed (
main.cpp) - Define a processing kernel or pass function in our_gl.h (e.g.,
void blur_framebuffer(...)orvoid apply_ssao(...)) (our_gl.h) - Implement the effect in our_gl.cpp using pixel/texel iteration and existing math utilities (
our_gl.cpp) - Integrate the pass into the main render loop after all geometry is rendered (
main.cpp)
🔧Why these technologies
- C++ (bare, no external graphics library) — Demonstrates the underlying principles of rasterization without abstraction; educational focus on algorithms rather than API usage
- TGA image format — Lightweight, uncompressed format ideal for educational framebuffer output; easy to parse and write without external libraries
- OBJ model format — Simple, text-based 3D format widely supported; straightforward to parse and demonstrate UV/normal/vertex data loading
- Software rasterization (no GPU) — Teaches the CPU-based rendering pipeline fundamentals: transformation, rasterization, shading, Z-buffering—core concepts behind all graphics APIs
⚖️Trade-offs already made
-
No GPU acceleration; software rasterization only
- Why: Clarity and education: students see every algorithm step; no black-box GPU behavior
- Consequence: Slow performance; cannot handle high polygon counts or complex scenes in real-time; suitable only for small models and still images
-
Minimal external dependencies; custom math library (geometry.h)
- Why: Self-contained, portable codebase; no dependency management overhead
- Consequence: No SIMD optimizations, no advanced matrix library features; modest performance; limited precision handling
-
Single-threaded main loop
- Why: Simplicity; easier to follow the rendering sequence step-by-step
- Consequence: No parallelism; poor CPU utilization; candidate for optimization but not the educational goal
-
Fixed pipeline (no programmable shaders; shaders are C++ functions)
- Why: Avoids shader language complexity; students focus on lighting math and rasterization
- Consequence: Inflexible; shading changes require C++ recompilation; cannot match modern GPU pipeline flexibility
🚫Non-goals (don't propose these)
- Real-time rendering performance (targets still images / educational visualization)
- GPU acceleration or use of OpenGL/Vulkan/DirectX (teaches the principles behind those APIs)
- High-polygon or large-scale scene rendering (demo models are small for clarity)
- Animation or dynamic effects (static geometry and camera only)
- Color grading, post-processing beyond basic effects (focus on core rasterization)
- Indirect lighting beyond SSAO (no full global illumination or ray tracing)
- Multi-threaded or parallel rendering (educational clarity over performance)
🪤Traps & gotchas
No hidden traps — this is intentionally transparent code. TGA texture files must exist in the same directory as the .obj file that references them (the loader uses relative paths). Coordinate system is left-handed in screen space (Y+ is down). Depth buffer values are float and require careful min/max initialization in our_gl.cpp. Normal map tangent space requires precomputed tangent vectors in the OBJ file or derived from texture coordinates — see the _nm_tangent.tga variants for pre-baked versions.
🏗️Architecture
💡Concepts to learn
- Bresenham's Line Algorithm — Foundation for efficient rasterization without floating-point arithmetic; tinyrenderer implements this as the basis for drawing triangle edges and scene visualization
- Barycentric Coordinates — Essential for determining which pixels are inside a triangle and for interpolating vertex attributes (texture coordinates, normals) across the triangle surface in our_gl.cpp
- Tangent Space Normal Mapping — Lesson 10 of the curriculum; allows low-poly meshes to appear high-detail by perturbing surface normals in tangent space — critical for realistic character rendering in the african_head and boggie models
🔗Related repos
opengl-examples/opengl_tutorials— Similar educational trajectory for 3D graphics but using OpenGL instead of software rendering; good for comparing API-based vs. manual implementationraysan5/raylib— Minimalist graphics library that uses hardware rendering but shares the philosophy of simplicity and educational focus — useful after mastering tinyrenderer for learning GPU renderingfogleman/pt— Path tracer written in pure C with no dependencies, similar pedagogical philosophy of understanding rendering without external libraries but focuses on ray tracing rather than rasterizationjdemeyer/tinyshader— Companion micro-project exploring shader compilation and GPU pipeline concepts; complements understanding of software shaders in tinyrendererTheCherno/Hazel— More advanced 3D game engine with similar educational intent but includes both rendering and scene management; natural next step after tinyrenderer
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add CMake build validation and multi-platform CI workflow
The repo has CMakeLists.txt and Dockerfile but no GitHub Actions workflow to validate builds across platforms (Linux, macOS, Windows). This catches breakage early and ensures contributors don't accidentally introduce platform-specific issues. Currently there's no guarantee the CMake setup works on all platforms.
- [ ] Create .github/workflows/build.yml with matrix strategy for ubuntu-latest, macos-latest, and windows-latest
- [ ] Test CMake configuration and compilation on each platform
- [ ] Verify executable runs successfully with a sample model (e.g., obj/floor.obj)
- [ ] Add build status badge to README.md
Create unit tests for geometry.h vector/matrix operations
The geometry.h header implements core vector and matrix types used throughout the renderer (Vec2, Vec3, Vec4, Matrix, etc.) but has no tests. Without tests, refactoring or bug fixes to these fundamental types risk silent correctness issues. This is the mathematical foundation of the entire renderer.
- [ ] Create tests/geometry_test.cpp with test cases for Vec3 operations (dot product, cross product, normalization, scalar ops)
- [ ] Add tests for matrix operations and transformations used in our_gl.cpp
- [ ] Integrate tests into CMakeLists.txt with add_executable for the test binary
- [ ] Document expected behavior in a new TESTING.md file for contributors
Add integration tests for model loading and rendering pipeline
The repo has model.cpp/model.h for OBJ loading and our_gl.cpp for rendering, but no automated tests validating that models load correctly and render without crashes. The obj/ directory contains multiple test models but they're never validated by CI. Add regression tests to catch model loading or rendering pipeline breakage.
- [ ] Create tests/render_test.cpp that loads each model from obj/ (african_head, boggie, diablo3_pose, floor)
- [ ] Verify model vertices/faces parse correctly by checking counts match expected values
- [ ] Perform a minimal render pass and validate output TGA image properties (dimensions, non-zero content)
- [ ] Add CMake test target and document expected test outputs in a new TEST_MODELS.md
🌿Good first issues
- Add documented examples in a new examples/ directory showing how to render each lesson concept separately (e.g., examples/01_bresenham.cpp rendering just lines, examples/03_triangles.cpp rendering a single flat-shaded triangle without texturing) — helps learners isolate individual algorithm implementation
- Create a minimal test suite validating geometry.h math functions (Vec3 cross product, matrix multiplication, barycentric coordinate calculation) with expected numerical results — would catch accidental refactors and help new contributors trust the math primitives
- Write a simplified loader for the OBJ format that can be compiled standalone (separate from model.cpp) with clear comments explaining the file format spec — currently the loader is tightly integrated with main.cpp, making it hard to reuse or teach
⭐Top contributors
Click to expand
Top contributors
- @ssloy — 90 commits
- @paroj — 3 commits
- @scivision — 2 commits
- @NicholasWilsonDEV — 1 commits
- @freddy1020 — 1 commits
📝Recent commits
Click to expand
Recent commits
9517998— update readme (ssloy)87bc715— Update README.md (ssloy)22fdc36— readme update (ssloy)2c9a6be— Merge pull request #159 from ssloy/to-discard (ssloy)d59a98c— linear interpolation perspective correction (ssloy)850c71b— tangent space normal mapping (ssloy)91e9c71— diffuse+specular mapping (ssloy)773f2d1— global space normal mapping (ssloy)c3719a6— normal interpolation (ssloy)6afd8a6— specular highlight (ssloy)
🔒Security observations
This educational graphics rendering project has a relatively good security posture given its nature as a learning resource. The primary concerns are Docker container hardening (unpinned ImageMagick dependency) and potential robustness issues in file parsing operations. The project does not contain typical web security vulnerabilities (SQLi, XSS, auth issues) as it is a standalone graphics application. Recommendations focus on dependency management, input validation, and defensive programming practices for file handling. No hardcoded credentials or sensitive information exposure detected.
- Medium · Unrestricted ImageMagick Installation —
Dockerfile (apt-get install -y imagemagick). The Dockerfile installs ImageMagick without version pinning or security constraints. ImageMagick has a history of security vulnerabilities (CVE-2016-3714, CVE-2018-20225, etc.). Without specific version pinning, the image may inherit unpatched vulnerabilities from the base image. Fix: Pin ImageMagick to a specific tested version (e.g., 'imagemagick=8:6.9.10.23+1-0ubuntu0.20.04.1') and regularly update for security patches. Consider using Alpine Linux base image for smaller attack surface. - Medium · Missing apt-get Cache Cleanup —
Dockerfile (RUN apt-get install command). The Dockerfile does not clean up apt cache after installation, increasing the Docker image size and potentially exposing package metadata. This is a minor security and best-practice issue. Fix: Add 'apt-get clean && rm -rf /var/lib/apt/lists/*' after package installation to reduce image size and metadata exposure. - Low · File Input Handling Without Validation —
model.cpp, tgaimage.cpp, main.cpp (file loading operations). The codebase loads OBJ model files and texture files (TGA images) without explicit validation of file format or content. While this is expected in a graphics educational project, improper error handling could lead to crashes or undefined behavior if malformed files are provided. Fix: Implement robust file format validation, boundary checks on image dimensions, and graceful error handling for corrupted or unexpected file formats. - Low · Potential Buffer Overflow in TGA Image Loading —
tgaimage.cpp (image loading functions). Image loading code (tgaimage.cpp) may be vulnerable to buffer overflows if TGA headers specify dimensions larger than allocated memory, or if file size doesn't match declared dimensions. Fix: Add strict validation of image dimensions against file size, implement maximum dimension limits, and use safe buffer operations with bounds checking. - Low · No Input Sanitization for Model Files —
model.cpp (OBJ file parsing). OBJ and MTL file parsing (model.cpp) does not appear to have robust input validation, which could cause parsing errors or crashes on maliciously crafted or corrupted files. Fix: Implement strict parsing with length limits, null-terminator checks, and validation of numeric ranges for vertex indices and coordinates.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.