A software wireframe renderer in pure Python: hand-written vectors, matrices and perspective projection, plus a free-flying camera.
A small 3D renderer written from scratch on top of Pygame's 2D line drawing. It has its own vector and matrix classes, turns world points into camera space with rotation matrices, and projects them onto the screen with a pinhole-camera model. Pygame is used only for the window, input and drawing lines and dots. The demo is a wireframe cube (cubo) that you fly around with the keyboard and mouse. Two experiments sit in experiments/: a point cloud with bounding boxes, and an unfinished start of a Minecraft-style 3D game.
python -m pip install -r requirements.txt
python cubo.pyThe point-cloud experiment runs the same way with python experiments/point-cloud/cubo.py.
| Input | Action |
|---|---|
| W / S | Fly forward / backward along the view direction |
| A / D | Strafe left / right |
| Left Shift / Left Ctrl | Move up / down |
| Hold the right mouse button and move the mouse | Look around (yaw and pitch) |
| Esc or close the window | Quit |
-
Hand-written linear algebra.
Vector3supports addition, subtraction, scaling and the dot product (written as**).Matrixmultiplies by takingVectorNdot products of rows and columns, and treats aVector3as a 1 × 3 row. NumPy is not used. -
Camera space. For every vertex the renderer subtracts the camera position, then undoes the camera's yaw about the z axis (
CameraZRotation) and its pitch about the y axis (CameraYRotation) with two 3 × 3 rotation matrices. In camera space$x_c$ points forward,$y_c$ to the left and$z_c$ up. -
Perspective projection. The focal length comes from a 100° horizontal field of view across the 1400 × 800 window, about 587 px. A camera-space point is divided by its depth
$x_c$ :f = \frac{W}{2\tan(\mathrm{FOV}/2)},\qquad (u,\,v) = \Bigl(\frac{W}{2} - f\,\frac{y_c}{x_c},\;\; \frac{H}{2} - f\,\frac{z_c}{x_c}\Bigr) -
Lines and the cube.
renderLineprojects both endpoints and joins them withpygame.draw.line, and the root renderer also marks each endpoint with a small dot. The cube is twelve hard-coded edges of the box$[10, 12] \times [0, 2] \times [0, 2]$ . -
Fly camera. Each frame the camera moves 0.01 units (0.1 in the point-cloud experiment) along its own forward and sideways axes, which
transformVectorYZgets by applying the inverse of the view rotation to the unit vectors. Vertical movement is along the world z axis. While the right button is held, each pixel of mouse motion turns the camera by 0.001 rad. -
Point-cloud experiment. It scatters 100 random points in
$[-10, 10]^3$ and draws red coordinate axes. It rotates a copy of the points by 45° about z, x and y in turn, and picks the points with the largest and smallest x, y and z in that rotated frame. It then draws twelve axis-aligned wireframe boxes (dibujarcubo, "draw cube") between pairs of those extreme points, and colours red every point that lies strictly inside one of the boxes. Its renderer adds per-line colours and skips points and lines behind the camera. -
Minecraft-style fork. An earlier copy of the engine adds
renderShape(a closed polygon),renderCube(center, length),fillScreen,flipScreenand a blockinginit()loop.minecraft3D.pywas meant to draw a cube at$(10, 0, 0)$ every frame as the first step of a voxel game.
| Path | Role |
|---|---|
Engine3D.py |
Vector and matrix classes, camera state, renderPoint and renderLine. Opens the window on import. |
cubo.py |
The demo: input handling, camera movement and the wireframe cube |
experiments/point-cloud/ |
A later variant of both files: random point cloud, rotated-frame extremes and bounding boxes |
experiments/minecraft-3d/ |
Earlier engine fork with cube and polygon helpers, the start of a Minecraft-style 3D game |
- There is no real clipping. The root renderer projects points behind the camera through the centre, so they appear mirrored, and a vertex exactly in the camera plane (
$x_c = 0$ ) raisesZeroDivisionError. The point-cloud version hides points behind the camera, and drops any line with an endpoint behind the camera instead of clipping it. - Movement is a fixed step per frame and the loop has no
Clock.tick, so speed depends on the machine and one CPU core stays busy.deltaTimeis computed but never used. - Every vertex builds two new 3 × 3 matrices in pure Python each frame. This is fine for a cube but slow for bigger scenes.
- The window is fixed at 1400 × 800, the scene is hard-coded, and each script defines its own copy of
Vector3, and all butminecraft3D.pyalso ofMatrix. -
experiments/minecraft-3ddoes not run as it is.minecraft3D.pynever importsmath, so creating a vector raisesNameError, andEngine3D.init()blocks until the window is closed once. - In
experiments/point-cloud/cubo.pythe search for the smallest z compares against the point with the smallest x, sominzcan be wrong. The script also prints the rotated points to the console as object addresses.