- 1 Model space
- 2 World space
- 3 Camera space
- 3.5 Backface culling
- 4 Frustum culling/clipping (GPU do it after the projection usually)
- 5 Projection
- 5.5 Perspective divide
- 6 Image space (NDC)
- 7 Screen Space
y+
|
| z+ . (1, 0, 1)
| /
| /
____|/________ x+
/
/|
/ |
Z axis growth + in the screen:
- Left handed coordinate system
- Clockwise winding order for triangle
- A normal would be:
N = (B - A) x (C - A)
Rotation REM: SOH CAH TOA ;)
Rotation is the following
- We have:
x = r cos(a) and y = r sin (a)
- We want
x' = r cos(a+b) and y' = r sin(a+b)
- Then with the angle addition formula for cosine
x' = r cos(a + b)
x' = r (cos(a) cos(b) - sin(a) sin(b))
x' = r cos(a) cos(b) - r sin(a) sin(b)
x' = x cos(b) - y sin(b)
So then the matrix will be:
[cos(a) -sin(a)] [x]
[sin(a) cos(a)] [y]
And extending to the next dimention:
[cos(a) -sin(a) 0 ] [x]
[sin(a) cos(a) 0 ] [y]
[ 0 0 1 ] [z]
3D Mesh
Each triangle has 3 vertex
(corners)
Let's check the code for a cube
#include "vector.h"
// those are points
vec3_t cube_vertices[8] = {
{ .x = -1, .y = -1, .z = -1}, // 1
{ .x = -1, .y = 1, .z = -1}, // 2
{ .x = 1, .y = 1, .z = -1}, // 3
{ .x = 1, .y = -1, .z = -1}, // 4
{ .x = 1, .y = 1, .z = 1}, // 5
{ .x = 1, .y = -1, .z = 1}, // 6
{ .x = -1, .y = 1, .z = 1}, // 7
{ .x = -1, .y = -1, .z = 1}, // 8
};
// for a face (triangle)
typedef struct {
int a, b, c;
} face_t;
// the collection of triangle for the cube
// warning: the order matter because
// we want a front and a back face
//
// here: we do a clock wise orientation
face_t cube_faces[12] = {
// front
{ .a = 1, .b = 2, .c = 3},
{ .a = 1, .b = 3, .c = 4},
// right
{ .a = 4, .b = 3, .c = 5},
{ .a = 4, .b = 5, .c = 6},
// back
{ .a = 6, .b = 5, .c = 7},
{ .a = 6, .b = 7, .c = 8},
// left
{ .a = 8, .b = 7, .c = 2},
{ .a = 8, .b = 5, .c = 1},
// top
{ .a = 2, .b = 7, .c = 5},
{ .a = 2, .b = 5, .c = 3},
// bottom
{ .a = 6, .b = 8, .c = 1},
{ .a = 6, .b = 1, .c = 4},
};
Lines Line drawing algorithms (Rasterize):
- [Old solition] Naive algorithm
- [Old solition] Digital Differential Analyzer (DDA graphics algorithm) — Similar to the naive line-drawing algorithm, with minor variations.
- Warning: a bit slower because of the
/
operation
- Warning: a bit slower because of the
- [Current solution] Bresenham's line algorithm - Optimized to use only additions (i.e. no division Multiplications); it also avoids floating-point computations.
- The Gupta-Sproull algorithm - Based on Bresenham's line algorithm but adds antialiasing.
Triagles Filled triangle algorithms used: simple scanline algorithm, with a flat-bottom and flat-top triangle.
Problem: The order of the face being render is important for the depth.
Old solition: Painters Algorithm, assumption: Z-value is the average of the 3 points.
Current solution: Z-buffering (or depth buffering)
Homogeneous coordinates - 4x4 Matrix
[ m m m m ] [ x ]
[ m m m m ] [ y ]
[ m m m m ] [ z ]
[ m m m m ] [ w ]
- m: extra component to enable matrix transformation, set to 1.
- 4x4: because some transformation required extra information for them to work.
[ sx 0 0 0 ] [ x ]
[ 0 sy 0 0 ] [ y ]
[ 0 0 sz 0 ] [ z ]
[ 0 0 0 1 ] [ w ]
[ 1 0 0 tx ] [ x ]
[ 0 1 0 ty ] [ y ]
[ 0 0 1 tz ] [ z ]
[ 0 0 0 1 ] [ w ]
[ cos(a) -sin(a) 0 0 ] [ x ]
[ sin(a) cos(a) 0 0 ] [ y ]
[ 0 0 1 0 ] [ z ]
[ 0 0 0 1 ] [ w ]
- Aspect ration:
a = width / height
- Field of view:
f = 1 / tan(fov / 2)
[x] [afx]
[y] -> [fy ]
[z] [ z ]
- We have to normalize the z value
--------- z_far ----------
\ /
\ ___ /
\ |_| /
\ /
\ /
\ /
\ /
\--z_near--/ <- Screen
\ /
\ /
\ /
obs
lambda = (z_far / (z_far - z_near)) - (z_far * z_near / (z_far - z_near))
|--------- scaling ---------------| |------- offset / translation ----|
[x] [ afx ]
[y] -> [ fy ]
[z] [ lambda*z - lambda*z_near ]
[ af 0 0 0 ]
[ 0 f 0 0 ]
[ 0 0 lambda -lambda*z_near ]
[ 0 0 1 0 ]
One of the simplest shading techniques, it calculates the color of each polygon, based on the color of the light source and the color of the polygon itself.
- Gouraud shading
- Phong reflexion model
A texture is a bitmap image applied to a 3D model. It can be used to add details to the model that would be difficult to model with geometry.
We can load a png, and "paint" the triangle with the pixel value.
- Each vertex has a coordinate (UV Coordinate) in the texture space, and we interpolate the pixel value between the 3 points.
- Texture: UV coord are between 0 and 1
Representing Texture in memory: "Color information in sequence"
- Will be a buffer in memory,
uint32_t* texture
Finding the mapping: Barycentric coordinates
- alpha, beta, gamma: the 3 weights of the 3 points of the triangle
P = alpha * A + beta * B + gamma * C
alpha + beta + gamma = 1
- To find alpha, beta, gamma:
- using 3 points, we extand to a parallelogram and find the area
alpah = area_triangle(PBC) / area_triangle(ABC)
alpah = area_parallelogram(PBCD) / area_parallelogram(ABCD)
alpah = ||PCxPV|| / ||ACxAB||
(lenght of the cross product)
- Same for beta and gamma
Barycentric coord can also be use to create edge function to have "gradient" color on a triangle
Models:
- Look-at camera model (Always look at a target)
- FPS camera model
We need a look_at
function that will create a matrix to rotate the camera to look at a point.
→ Will convert the vertices to the camera space
We need:
- A
eye
position - A
target
position
The transformation consists of 2 steps:
- Translate the eye to the origin matrix
- Rotate the eye to look at the target (with reverse orientation)
M_view = M_look_at * M_translate_eye
Move the eye to the origin
[ 1 0 0 -eye_x ]
[ 0 1 0 -eye_y ]
[ 0 0 1 -eye_z ]
[ 0 0 0 1 ]
For roation, we must compute forward(z), right(x), and up(y) vectors
[ x_x y_x z_x 0 ] ^-1
[ x_y y_y z_y 0 ]
[ x_z y_z z_z 0 ] = M_look_at
[ 0 0 0 1 ]
But we need the inverse. Here we can transpose the matrix because it's an orthogonal matrix.
Developing, we can find the final matrix:
[ x_x x_y x_z -dot(x, eye) ]
[ y_x y_y y_z -dot(y, eye) ]
[ z_x z_y z_z -dot(z, eye) ]
[ 0 0 0 1 ]
We need:
- position
- direction
- forward velocity
- yaw and pitch (rotation)
We need to clip the triangle that is outside the screen.
- Modern way to do it
We need to clip against six planes:
- Top, Bottom, Left, Right, Near, Far The resulting polygon that is produced is a convex polygon. We can clip it against the other planes. Otherwise, we need to accumulate the clipped polygon
To find the plane (right as the example):
** Near/Far plane **
P = {0, 0, z_near}
P = {0, 0, z_far}
** Side plane **
We have the point P (the camera at {0, 0, 0}) and the normal N from the fov/2 angle.
# Right plane:
P = {0, 0, 0}
n.x = -cos(fov/2)
n.y = 0
n.z = sin(fov/2)
Same for the other planes.
FYI
Two side of a plane (P), on, inside or outside:
- A point (Q) can be inside the plane.
(Q - P) . N > 0
- A point (Q) can be outside the plane.
(Q - P) . N < 0
- A point (Q) can be on the plane.
(Q - P) . N = 0
Goal: determine if a line intersects a plane. From: Kenneth I. Joy | On-Line Computer Graphics Notes | Clipping
Q_1 N
\ ^
\ |
----I------P----- <- Place
\
Q_2
Triangle
I = Q_1 + t(Q_2 - Q_1)
- t is the factor to find the intersection point [0, 1]
To find the intersection point, we can do:
I = Q_1 + t(Q_2 - Q_1)
(I-P) = (Q_1-P) + t((Q_2-P) - (Q_1-P))
n.(I-P) = n.(Q_1-P) + t(n.(Q_2-P) - n.(Q_1-P))
------- --------- --------- ---------
0 Dot_Q_1 Dot_Q_2 Dot_Q_1
0 = Dot_Q_1 + t( Dot_Q_2 - Dot_Q_1 )
t = -Dot_Q_1 / (Dot_Q_2 - Dot_Q_1)
t = Dot_Q_1 / (Dot_Q_1 - Dot_Q_2)
Once we have the intersection point, we can clip the triangle, But we end up with a polygon :( We need to triangulate it!
But before, algo to clip a polygon against a plane:
__Q_3__
__-- --__
^ Q_2 Q_4
| \ /
----P---I_1---------I_2-
\ /
Q_1---Q_5
| Inside | Outside |
+--------+---------+
| I_1 | Q_1 |
| | I_2 |
| Q_2 | |
| Q_3 | |
| Q_4 | |
| I_2 | |
| | I_2 |
| | Q_5 |
We end up with two list, one for points for inside and one for outside.
- In this case, the resulting polygone is the inside one:
{I_1, Q_2, Q_3, Q_4, I_2}
Technique:
- Vertices to non-adjectent vertices
- Point in the middle of the polygon
- All from the same point:
Q_0____Q_3
/ \ /
/ \ /
Q_l---Q_2
So:
for (int i=0; i < (num_vertices-2); i++)
add_triangle(Q_0, Q_i+1, Q_i+2)
- See kristoffer dyrkorn's blog: here
V_0
|\
| \
| \
| \
| . \
| P \
|______\
V_2 V_1
Check if P is to the "right" or the "left" the edges
- Using cross product to find the magnitude of the z component (coming out of the screen)
- `cross2d = ax * by - bx * ay`
- if the arrow point "inside" the triangle, the sign is +
The problem of rendering the overlap occur with this algo too
- Convention (Rasterization Rules): Top-Left rule - A pixel center is defined to lie inside of a triangle if it lies on a flat top edge or a left edge of a triangle