<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj" -->

---
title: Software rendering in 500 lines of bare C++ | daily.dev
description: A tutorial series that teaches how 3D graphics APIs like OpenGL, Vulkan, Metal, and DirectX work by building a software renderer from scratch in ~500 lines of...
canonical: https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Software rendering in 500 lines of bare C++ | daily.dev
og:description: A tutorial series that teaches how 3D graphics APIs like OpenGL, Vulkan, Metal, and DirectX work by building a software renderer from scratch in ~500 lines of...
og:url: https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj
og:image: https://api.daily.dev/og/posts/8zXoC44yj.png
og:image:alt: Software rendering in 500 lines of bare C++
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Software rendering in 500 lines of bare C++

**[Hacker News](https://daily.dev/sources/hn)** · 2 min read · 0 upvotes · 0 comments

## Summary

A tutorial series that teaches how 3D graphics APIs like OpenGL, Vulkan, Metal, and DirectX work by building a software renderer from scratch in ~500 lines of C++. No third-party graphics libraries are used. Starting from just a TGA file handler and a single pixel-setting function, students implement line drawing, triangle rasterization, and more over 10–20 hours of work. The goal is to build intuition for how GPU rendering pipelines work, not to write GPU applications directly.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://haqr.eu/tinyrenderer>

## Community take

How the wider developer community reacted, aggregated from 3 discussions and 59 comments across hackernews (as of 2026-07-23).

**TL;DR:** The community is broadly positive about this software renderer tutorial (identified as ssloy's tinyrenderer), with the most substantive discussion centering on triangle clipping — a topic commenters feel the tutorial underserves. Several commenters share their own implementations and deep technical knowledge, while a minority note the content is not new.

**Sentiment:** 60% positive · 30% mixed · 10% skeptical

**The case for**

- Commenters find the tutorial genuinely educational and indispensable for learning software rendering from scratch.
- A commenter demonstrated that a single-threaded CPU software renderer can run an interactive 3D game with special effects, validating the practical value of the approach.
- The Sutherland–Hodgman clipping algorithm was independently rediscovered and shared in detail, enriching the discussion beyond the tutorial itself.

**The pushback**

- Some commenters note the tutorial is not new (it's ssloy's tinyrenderer) and lacks a publication date.
- Triangle clipping — a critical practical concern — is not adequately covered in the tutorial according to multiple commenters.
- The tutorial appears broken on mobile, showing only a few lines of code and some photos with no real article content.

**By community**

- hackernews (positive): Broadly positive reception with rich technical side-discussions on clipping, rasterization internals, and framebuffer display, though some criticism about the tutorial's age and missing topics.

**Hottest debate:** Whether triangle clipping is a necessary topic for a practical software renderer tutorial, and how best to handle it (discard vs. primitive synthesis vs. tile-based sampling).

**Open questions**

- Why doesn't the tutorial cover triangle clipping, and where can learners find a good treatment of it?
- Is there a better cross-platform alternative to wgpu/softbuffer for blitting pixels to screen in a software renderer?

**Highlights**

> My renderer attempts always got stuck on the "should implement clipping" phase too, until I finally bit the bullet and managed to write a working one without much effort, independently "rediscovering" the Sutherland–Hodgman algorithm [1] as I found out later (googling it beforehand would've been cheating, of course). The algorithm itself is fairly straightforward and intuitive, I think the biggest mental block is the weirdness of the projective space and working with homogeneous coordinates (actually the only frustum plane that you have to clip against in P₃(ℝ) is the front plane, the rest could be clipped after the perspective division, but no reason not to do it all at the same time while you're at it). The plane equations in the clip space are super simple, basically the six equations of the form ax + by + cz = w simplify to   x = ±w    y = ±w    z = ±w. Meaning, for example, that if the x coordinate of your vertex is greater than the w coordinate, that vertex is outside the right clipping plane. The Sutherland–Hodgman itself goes something like this:   # Returns true if point is inside the half-space defined by plane   def point_inside_plane(point, plane) -> bool:     # single dot product, can be further simplified   # Returns t such that the edge (p1, p2) intersects plane at lerp(t, p1, p2)   def edge_intersect_plane(edge: (Point, Point), plane) -> float:     # single dot product, can be further simplified   # Given the vertices of a simple polygon and a plane,   # returns the part of the polygon fully inside the plane    def clip_against_plane(poly: [Vertex], plane):     let result: [Vertex] = []     let [(v_1, v_2), (v_2, v_3), ..., (v_n, v_1)] = poly.edges()     for each (v_i, v_j) of the edges:       let i_inside = point_inside_plane(v_i, plane)       let j_inside = point_inside_plane(n_j, plane)       if i_inside and j_inside:         # v_j will be pushed on the next iteration!         result.push(v_i)        else if not i_inside and not j_inside:         pass # Nothing to do!       else:         # One is inside, the other is not, we have to clip         let t = edge_intersect_plane((v_i.pos, v_j.pos), plane)         # Synthetize a new vertex straddling the plane         let v_new = Vertex(           pos = lerp(t, v_i.pos, v_j.pos),           # For each vertex attribute           attrib = lerp(t, v_i.attrib, v_j.attrib)         )         if i_inside:           result.push(v_i); result.push(v_new) # discard v_j         else:           result.push(v_new); result.push(v_j) # discard v_i           return result Then you just call this for all the planes so that the output of one call becomes the input for the next call! The end result of this process is a convex polygon (of at most nine vertices for a triangle against six planes), which can be trivially triangulated. You can make the whole process faster by precomputing so-called outcodes which allow you to avoid clipping triangles known to be entirely outside at last one plane, or entirely inside every plane. [1]: I. Sutherland and G. Hodgman. 1974. "Reentrant polygon clipping." Communications of the ACM, Volume 17, Issue. Available: https://dl.acm.org/doi/10.1145/360767.360802
> — [Sharlin on hackernews · 1 comments](https://news.ycombinator.com/item?id=49024254)

> Sorry I was so terse! Let's ignore guard band, for now. Our rasterization surface is a rectangle. We don't need to "geometrically clip" a triangle to the rectangle's surface. Instead, we just walk the surface of the rectangle and ask two questions: (1) does the triangle capture this (sub)sample; and, (2) what's the interpolated value of the attributes at this (sub)sample. In practice, for software rasterizers, we're working on tiny subrectangles (the tile), e.g., 4x4 or 2x8, whatever. So, we can be a little "inefficient" with our walking with respect to the edge testing for interiority. (This is roughly how HW works at the 2x2 level, as well.) Because the (sub)sample is "pulling" the attribute, we don't need to geometrically "clip" the triangle to the tile: the tile's (x,y) subsamples do that "for free". On the flip side, if a triangle is 'really big' we need a guard band to either reject or subdivide triangles. The first one is fairly cheap -- we're throwing away the triangle! -- the second one is a better user experience, but requires synthesizing primitives. (The worst case is that a single triangle becomes 5 triangles, I think.) Each of those triangles needs its Z and 1/Z calculated in fixed precision. The precision of that fixed precision (though) can be clamped to the local tile; so, even though the global precision might need to be 25.25 (or whatever), the tile-local precision is only 4.9 (or whatever), with an intermediate 24.24 that can be handled with a float-float patch-up. The computation should all occur in the triangle's barycentric space: that means you need the inverted barycentric mapping to invert the guard band into the triangle's barycentric space. You do that because it lets you control the fixed point calculations better. (You can leave off the inverted determinant multiplication until the last moment.) When I say "deferred attribute synthesis" I mean that we don't calculate attributes in the vertex shader. Instead, we calculate the barycentric, Z, and 1/Z values and pass those along. When we fire up the tile walker for the triangle (in general we only need 1-3 tiles), we calculate the attributes "on the fly, as they're used" and then let the compiler do CSE to fold down the replicated constructions.
> — [thechao on hackernews](https://news.ycombinator.com/item?id=49023932)

> I went through this a few months ago in Rust. I wrote all the code by hand, no LLMs. Then I went ahead and added a small "game" on top, plus some special effects like pixelization shaders and chromatic aberration at the edge of a flashlight. https://github.com/kshitijl/tinyrenderer-rs if anyone is interested! The repo has lots and lots of in-progress screenshots so you can see the renderer come to life, plus all the hilarious visual bugs along the way. I learned a lot! My biggest lesson, other than the specifics of how rendering works, was that modern CPUs are really fast: a single-threaded CPU renderer can definitely run an interactive 3D game with some fancy special effects.
> — [articulatepang on hackernews · 1 comments](https://news.ycombinator.com/item?id=49022842)

> You only need to clip triangles is you're worried about attribute interpolation for very large triangles. There's two ways to handle this: (1) discard (fast but not a great user experience); or, (2) primitive synthesis. Just frustum clipping is enabled by point picking in the local tile. Primitive synthesis requires some FP kung fu; but, is easiest done in barycentric space against a reverse transformed clipping rectangle. This lets you carefully control clip rounding error using either doubles or (better) fixed point. Abrash likes to use integer fixed point, but that is historical — modern fixed point can be handled with careful control of the fp unit in the mantissa. The major issue is regenerating the Z and the 1/Z values for the new vertices of the synthesized primitives. Everything else should flow down the pipe naturally, assuming a deferred attribute synthesis rasterizer. There are examples in the open source version of my rasterizer: OpenSWR.org.
> — [thechao on hackernews · 1 comments](https://news.ycombinator.com/item?id=49023128)

> I thought this would be something new but it’s just ssloy’s tinyrenderer. Article should have a date since it’s old as dirt
> — [uncivilized on hackernews · 1 comments](https://news.ycombinator.com/item?id=49023976)

**Source threads**

- [hackernews](https://news.ycombinator.com/item?id=45077337) · 20 points · 4 comments
- [hackernews](https://news.ycombinator.com/item?id=46492298) · 10 points · 4 comments
- [hackernews](https://news.ycombinator.com/item?id=49022038) · 66 points · 51 comments

## Similar posts on daily.dev

- [Implementing 3D Graphics Basics](https://daily.dev/posts/implementing-3d-graphics-basics-p1wmhwsdw) · Hackaday · 0 upvotes · 0 comments
- [Master Low-Level Graphics in C: Build a Software Renderer from Scratch](https://daily.dev/posts/master-low-level-graphics-in-c-build-a-software-renderer-from-scratch-ssvj3mayp) · freeCodeCamp · 0 upvotes · 0 comments
- [What To Learn To Be A Graphics Programmer](https://daily.dev/posts/what-to-learn-to-be-a-graphics-programmer-sivi9qcqv) · Bottom of the sea · 1 upvotes · 0 comments

---

Tags: [#3d](https://daily.dev/tags/3d), [#c++](https://daily.dev/tags/c++), [#graphics-programming](https://daily.dev/tags/graphics-programming)

[View this post on daily.dev](https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Software rendering in 500 lines of bare C++","url":"https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj"},"datePublished":"2026-07-23T15:36:55.600Z","dateModified":"2026-07-23T21:30:15.937Z","description":"A tutorial series that teaches how 3D graphics APIs like OpenGL, Vulkan, Metal, and DirectX work by building a software renderer from scratch in ~500 lines of...","image":"https://media.daily.dev/image/upload/s--0_ODbtD2--/f_auto/v1722860399/public/Placeholder%2008","thumbnailUrl":"https://media.daily.dev/image/upload/s--0_ODbtD2--/f_auto/v1722860399/public/Placeholder%2008","isAccessibleForFree":true,"articleSection":"Hacker News","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Hacker News","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/hn","url":"https://daily.dev/sources/hn"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/software-rendering-in-500-lines-of-bare-c--8zxoc44yj","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"3d,c++,graphics-programming","timeRequired":"PT2M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Hacker News","item":"https://daily.dev/sources/hn"},{"@type":"ListItem","position":3,"name":"Software rendering in 500 lines of bare C++"}]}
```

