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.
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.
This resource, along with Mathematics for Computer Graphics by John Vince [1], was truly indispensable when I wrote my own software renderer [2]. This was long before LLMs, so the whole process took me at least a couple months - most of it trying to wrap my head around math behind computer graphics and tracking down C segmentation faults. Fun times.
I wish we could have just one of these tutorials properly cover the concern of triangle clipping. This is the part that I struggle with the most in a software renderer. If you are going to be building a practical one, this is something you will eventually have to deal with, even for super basic scenes. Any time geometry intersects the view frustum you need to clip those triangles.
Sorta unrelated but I really enjoy (I don't want to use the past tense as I often refer back to it) your article on Emulator Backed Remakes (https://gabrielgambetta.com/remakes.html). Thanks!
Thanks for saying that :) Have you built something based on that idea? I still haven't, but I'm thinking on throwing some Claude at it for the most mechanical parts.
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.
I'm clearly super old-school when it comes to rasterisation, and a lot of this has flown above my head. I have a ton of questions; I hope you can answer them.
> (1) discard (fast but not a great user experience)
What are we discarding here, and why is it fast but not a great user experience?
> (2) primitive synthesis
I assume this is retriangulating clipped triangles that are now no longer triangles?
> reverse transformed clipping rectangle
Which spaces does this reverse transformation map from and to? I assume the clipping rectangle here is the triangle's AABB in raster space (or as you say, barycentric space).
> integer fixed point, but that is historical — modern fixed point can be handled with careful control of the fp unit in the mantissa
So we are no longer doing 16.16 fixed point, but tweaking the FP representation itself?
> The major issue is regenerating the Z and the 1/Z values for the new vertices
Why is this a major issue?
> deferred attribute synthesis rasterizer
I assume this means attributes are perspective-correct interpolated in raster space.
My own knowledge of GPU rasterization may be dated, but IIRC GPUs tend to rely on guard band clipping up to a guard band threshold before using geometric clipping. The guard band clipping involves rejecting 2D coarse rasterization blocks that are fully outside of the scissor rect. This is just a quick rectangle check, but the tradeoff is that a larger guard band means more GPU time lost in over-rasterization and potential higher precision requirements for rasterization values (which could be fixed point). Beyond the guard band, the triangles are clipped in floating point against the frustum clip planes.
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.
[1]: https://www.amazon.co.uk/Mathematics-Computer-Graphics-John-...
[2]: https://github.com/nkanaev/tipsy
There are examples in the open source version of my rasterizer: OpenSWR.org.
> (1) discard (fast but not a great user experience)
What are we discarding here, and why is it fast but not a great user experience?
> (2) primitive synthesis
I assume this is retriangulating clipped triangles that are now no longer triangles?
> reverse transformed clipping rectangle
Which spaces does this reverse transformation map from and to? I assume the clipping rectangle here is the triangle's AABB in raster space (or as you say, barycentric space).
> integer fixed point, but that is historical — modern fixed point can be handled with careful control of the fp unit in the mantissa
So we are no longer doing 16.16 fixed point, but tweaking the FP representation itself?
> The major issue is regenerating the Z and the 1/Z values for the new vertices
Why is this a major issue?
> deferred attribute synthesis rasterizer
I assume this means attributes are perspective-correct interpolated in raster space.
Personally I’d have gone with a teapot as my demo but I don’t see anything shocking about the model the author chose.
mbappe'?
An error occurred: API rate limit already exceeded "