In 1995, 3D software rendering was the “black magic” of game development, reserved for hand-optimized Assembly and C. Python, with its reputation for being “slow”, is the last language you’d choose for a pixel-level graphics engine… or is it?
In the era before dedicated GPUs (Voodoo, RIVA TNT), the CPU was responsible for everything: vertex transformation, clipping, and the dreaded “inner loop”: calculating the color of every single pixel on the screen. To make games like Quake or Descent possible, developers used every trick in the book, from fixed-point math to self-modifying Assembly code.
A naive pixel-by-pixel loop in Python is orders of magnitude too slow for interactive 3D rendering. However, Python has a secret weapon: NumPy. While NumPy is usually associated with Data Science, its ability to perform “Single Instruction, Multiple Data” (SIMD)-style operations on arrays makes it a perfect, albeit unconventional, candidate for a software rasterizer.
This talk will walk through the architecture of a Python-based software renderer.
1) Vectorized Rasterization: Moving away from the traditional scanline approach, we use simple linear equations to test entire blocks of pixels simultaneously. We will look at how to “mask” arrays to find which pixels fall inside a triangle and calculate their UV texture coordinates without a single Python for loop in the render path.
2) Texture Mapping & Shading: How to map a 2D image onto a 3D surface and apply basic lighting using array broadcasting.
3) From Desktop to Web: How the same code runs in a PyGame window and a browser via Pyodide, proving that Python’s portability extends to high-performance graphical experiments.