This python project implements a 3D polyhedron wireframe viewer using Pygame, but the main focus is to understant the representation of 3D objects and how it can be viewed as a 2D image using matrix transformations to express parallel and perspective views. We archieved this projecting 3D points in the world space to 2D point in the image space - which is good for producing wireframe renderings (where only the edges of the object are draw).
To run the viewer just execute the main.py
file. Make sure you have PyGame and NumPy installed.
python main.py
To create a 3D wireframe representation of a polyhedron, besides the need of a data structure to represent the object (vertices and edges), we need to map the 3D locations (coordinates x, y and z in the canonical coordinate system) onto the screen (represented in pixels). This can be fully represented as matrix multiplication, thanks to projective geometry and its propeties.
This mapping is known as the viewing transformation and depends on several factors, including the camera's position and orientation, type of projection, field of view, and resolution of the image. We can break this process into a sequence of simpler transformations:
- Camera transformation: A rigid-body transformation that adjusts the view for the camera position (with the default camera located at the origin, looking in the -z direction).
-
Projection transformation: Projects points from camera space so that all visible points are mapped to the unit image rectangle (ranging from -1 to 1 in both
$x$ and$y$ ). - Viewport transformation: Maps this unit rectangle to the desired screen rectangle (e.g. the window's resolution in pixel coordinates).
The entire process can be summarized as follows:
Objects initially exist in object space, where local rigid-body transformations like rotation and scaling are applied. The object is then placed into world space, which represents the scene (in this case, a simple translation from the origin). The camera transformation converts the points into camera space (camera coordinates), after which the projection maps the points to the canonical view volume. Finally, the viewport transformation maps the canonical view to screen space.
We assume that the geometry we want to view is in the canonical view volume, a 3D cube with sides of length 2, centered at the origin (i.e.
Given a screen with dimensions
This transformation maps one axis-aligned rectangle to another, and can be represented as:
This representation ignores the
Note the use of homogeneous coordinates as we are dealing with transformations on the projective space.
It is often convenient to render geometry in some region of space other than the canonical view volume. To generalize the view, we keep the idea of the view direction looking along
The goal of orthographic projection is to arrange points so that when projected onto the screen, parallel lines remain parallel. This type of projection applies to any arbitrary rectangular view volume, defined by the coordinates of its sides, such that the view volume is
-
$x = l$ (left plane), -
$x = r$ (right plane), -
$y = b$ (bottom plane), -
$y = t$ (top plane), -
$z = n$ (near plane), -
$z = f$ (far plane).
The near plane is closer to the camera than the far plane, so it must have the relation
Transforming from the orthographic view volume to the canonical view volume requires a scaling and translation transformation, which can be represented as:
To draw 3D line segments in the orthographic view volume, we project the vertices points to the screen by ignoring the
A simple code to render a polyhedron wireframe can be written as:
construct M_vp
construct M_orth
M = M_vp M_orth
for each edge (a_i, b_i) do
p = M a_i
q = M b_i
drawline(x_p, y_p, x_q, y_q)
In perspective projection, objects that are farther from the camera appear smaller, following an inverse relationship with depth. We assume the camera is positioned at the origin
where
To represent perspective projection as a matrix transformation, we use homogeneous coordinates (where a point can be represented in infinitely many ways). Modifying the
With this approach, we can build the perspective projection matrix in 3D, considering a default camera position. Since we typically define near and far planes along the negative
This perspective projection leaves points on
The praticality of this approach is that once we apply perspective, we can use the same orthographic projection matrix to map the result to the canonical view volume. The final perspective projection matrix is obtained by combining these two transformations:
To set values for
construct M_vp
construct M_per
M = M_vp M_per
for each edge (a_i, b_i) do
p = M a_i
q = M b_i
drawline(x_p/w_p, y_p/w_p, x_q/w_p, y_q/w_p)
An important consideration when constructing projection matrices is how to select appropriate values for
To calculate these values, we can use the screen's aspect ratio (the ratio of width to height) and the angle
From the figure, we can calculate the boundaries of the viewing frustum based on the fovy angle
It would be good to render a 3D scene from different viewpoints, so we need to define the camera's position and orientation in space. We specify the viewer’s position and orientation using the following convention specifying the vectors:
-
Eye position
$e$ : The position of the camera or the center of the viewer’s eye, -
Gaze direction
$g$ : The direction the viewer is looking at, -
View-up vector
$t$ : A vector that defines the "up" direction for the viewer. It points upward, bisecting the viewer’s field of view into right and left halves.
To define the viewing transformation, the user specifies viewing with
With these vectors, we can build a coordinate system with origin
To handle arbitrary viewing directions, we need to change the points for a appropriated coordinated system.
To transform the coordinates between this spaces, we apply a transformation matrix that change it basis to one with
To use this camera transformation in the rendering pipeline (which previously assumed a fixed origin looking down the
construct M_vp
construct M_per
construct M_cam
M = M_vp M_per M_cam
for each edge (a_i, b_i) do
p = M a_i
q = M b_i
drawline(x_p/w_p, y_p/w_p, x_q/w_p, y_q/w_p)
This matrix
- Book - Fundamentals of Computer Graphics - P. Shirley and S. Marschner, Fundamentals of Computer Graphics, 3rd ed. A K Peters, 2009.
- Book - Computer Graphics: Theory and Practice - J. Gomes, L. Velho, and M. Costa Sousa, Computer Graphics: Theory and Practice. CRC Press, 2012.
- Webpages - Learn WebGL:
- "Perspective projections," Learn WebGL. [Online]. Available: http://learnwebgl.brown37.net/08_projections/projections_perspective.html.
- "Orthographic projections," Learn WebGL. [Online]. Available: http://learnwebgl.brown37.net/08_projections/projections_ortho.html.