← back    main
한국어    ☾

Building a Graphics Engine
created: 2021.12.28. 18:19 · modified: 2021.12.30. 10:07 · license


Around the start of the fall semester, I suddenly became interested in computer graphics. I had a vague idea that objects were represented as triangular surfaces and that each triangle was projected onto a plane to determine which pixels to display. But I knew nothing about the geometry required for projection, or about textures and lighting. While looking for a resource that would help me learn these things as I built a graphics engine myself, I came across a YouTube tutorial that seemed perfect. It used no graphics libraries such as OpenGL; its only interface to the computer screen was a function that printed a character corresponding to an individual pixel at a specified position. The tutorial had four parts, covering everything from basic projective geometry to textures. It was exactly the guide I wanted, with one problem: the pixel-printing function belonged to the Windows console API and worked only on Windows. Since I use a MacBook, I needed to find a replacement. Xcode might have let me develop a console application with similar functionality, but with the semester about to begin, I wasn't up for digging into a new development tool. So I put the project on hold.

This week, after the semester ended, I returned to the project. Sitting down to work on it again gave me a new goal: this time, I would build a graphics engine using only what I already knew, without consulting any outside resources. Understanding the principles mattered more to me than optimizing performance, so I chose Python, which would let me turn ideas into code quickly. To draw pixels, I could simply create and manipulate a two-dimensional array of characters large enough to fill the console, then print it. I got straight to coding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from time import sleep, time
 
class XY:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
WIDTH, HEIGHT = 100, 30
 
SCREEN_BASE = [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)]
 
SCREEN = SCREEN_BASE.copy()
 
def clear():
    SCREEN = SCREEN_BASE.copy()
 
def fill(xy):
    SCREEN[HEIGHT-1-xy.y][xy.x] = '▇'
 
def render(pixels):
    clear()
    for pixel in pixels: fill(pixel)
    output = ' '.join(''.join(col for col in row) for row in SCREEN)
    print(output)
 
for i in range(30):
    render([XY(3*i, i)])
    sleep(0.1)
    
cs

I had made a basic coordinate class and a drawing function. I tried updating the display about once every 0.1 seconds to draw a line with a slope of 1/3.

Drawing a line in the console

Okay. There were a couple of minor issues: even though I used the Unicode full block character, the pixels had different widths and heights, and there was spacing between the lines. I decided to leave that for later.

Now it was time to define a triangle and calculate the pixel positions for its vertices. First, I needed to work out the geometry of the projection.

Attempt to work out the projection geometry

I had a feeling there must be an elegant way to project things using transformation matrices, but I had long forgotten the introductory linear algebra I took at the end of my first year (not that I'd understood it back then, either), and I couldn't work it out from scratch. So I started by defining the coordinates of the camera and the point, the distance between the camera and the screen, and two vectors specifying the camera's viewing direction and tilt. I could find where the line through c and d intersected the screen plane, then calculate the pixel's distance from the center of the screen. From there, the vector from the center to the pixel and w would let me determine the pixel's angle relative to the screen's rightward direction. The calculations looked as though they would get messy, but I didn't mind as long as I got the result I needed.

Wait—how did the equation of a line in three dimensions go again? My original resolve not to look anything up vanished, and I quickly grew tired of trying to remember, so I checked Wikipedia. The moment I saw the equation, it all came back to me. Still, it was a bit of a shock that I couldn't immediately recall something so basic. Knowledge you don't use really does fade frighteningly fast. Just minutes after resuming the project, I realized where I stood and decided to study the geometry I needed, one step at a time.

Reference material.

Notes, page 1
Notes, page 2
Notes
Notes
Notes
Notes

Quaternions, Interpolation and Animation

To be continued.


#CS #CG 


Subscribe to my newsletter to receive an email when a new post is published.


Next: January 2022

Previous: Rome, My Dream


Back to list

© 2026 Gunhee Yi