22. Triangle Rasterization
Dated: 14-05-2025
Solid Fill Triangle Rendering
Sort the vertices
of a triangle
based on the \(y\) value.
- Top
vertex
- \((x_0, y_0)\) - Middle
vertex
- \((x_1, y_1)\) - Bottom
vertex
- \((x_2, y_2)\)
Now consider the filling process to be done in 2 parts.
- from \(y_0\) to \(y_1\)
- from \(y_1\) to \(y_2\)
Both routines
will use DDA
algorithm to find \(x\) values across the pixel span
of each scanline
.
Sub Pixel Accuracy
void SubPixDDA(float x_a, float y_a, float x_b, float y_b) {
int y_ai, y_bi; // Scanline range
int y; // iterator for scanline
float xp; // pre-step in x
float x; // current x position
float yf; // fractional distance in y
float dxdy = (x_b - x_a) / (y_b - y_a); // 1 / slope
y_ai = ceil(y_a);
y_bi = ceil(y_b);
yf = y_ai - y_a;
xp = dxdy * yf;
x = x_a + xp;
for (y = y_ai; y < y_bi; y++) {
x += dxdy;
}
}
Smooth Shaded Triangle Rasterization
This is used to approximate
- Light fall off
- curved surface
This technique linearly interpolates the RGB colors.
To avoid artifacts, use sub texel
accuracy technique.
Texture Mapped Triangle Rasterization
This is the process of interpolating an image over the triangle.
The image itself is called the texture map
and a pixel
on it is called a texel
.
Color BilinearSampling(float u, float v, Color texMap[256][256]) {
Color c00, c01, c10, c11;
int u0, u1, v0, v1;
float u_frac, v_frac;
u0 = floor(u);
u1 = ceil(u);
v0 = floor(v);
v1 = ceil(v);
u_frac = u - u0;
v_frac = v - v0;
c00 = texMap[u0][v0];
c01 = texMap[u0][v1];
c10 = texMap[u1][v0];
c11 = texMap[u1][v1];
return (
v_frac * (u_frac * c11 + (1 - u_frac) * c01) +
(1 - v_frac) * (u_frac * c10 + (1 - u_frac) * c00);
);
}
Sub Texel Accuracy
Any value that is interpolated over the triangle
such as \(r, g\) and \(b\) for smooth shading and \(u\) and \(v\) for texture mapping must take into account the fractional component of the vertex
information.
Taking these fractional quantities into account is called sub texel
accuracy.
To formalize this, it is identical to the sub pixel DDA.
All that need to be done is to substitute \(u\) or \(v\) for \(x\) in the original, and substitute \(x\) for \(y\) in the original.
Flat Filling Triangles
The general function
1 for linear interpolation
is
Pseudo Code
Imagine a triangle
with 3 vertices
\(A, B\) and \(C\).
First, sort them such that
Now, each scanline
will have a left end point i.e. \(S.x\) and a right end point i.e. \(E.x\).
if (B.y - A.y > 0) dx1 = (B.x - A.x) / (B.y - A.y);
else dx1 = 0;
if (C.y - A.y > 0) dx2 = (C.x - A.x) / (C.y - A.y);
else dx2 = 0;
if (C.y - B.y > 0) dx3 = (C.x - B.x) / (C.y - B.y);
else dx3 = 0;
S = E = A;
if(dx1 > dx2) {
for (; S.y <= B.y; S.y++, E.y++, S.x += dx2, E.x += dx1)
horizontal_line(S.x, E.x, S.y, E.x, color);
E = B;
for (; S.y <= C.y; S.y++, E.y++, S.x += dx2, E.x += dx3)
horizontal_line(S.x, E.x, S.y, E.x, color);
}
else {
for (; S.y <= B.y; S.y++, E.y++, S.x += dx1, E.x += dx2)
horizontal_line(S.x, E.x, S.y, E.x, color);
S=B;
for (; S.y <= C.y; S.y++, E.y++, S.x += dx3, E.x += dx2)
horizontal_line(S.x, E.x, S.y, E.x, color);
}
Gouraud Shading
The idea of gouraud
and flat triangle is nearly the same.
Gouraud
takes 3 additional parameters, which are the colors
for all 3 vertices
, which are linearly interpolated across the triangle
.
The 256 color
version needs
- \(x\) related to \(y\)
color
related to \(x\)color
related to \(y\)
The hi color
version needs
- \(x\) related to \(y\)
color.red
related to \(x\)color.green
related to \(x\)color.blue
related to \(x\)color.red
related to \(y\)color.green
related to \(y\)color.blue
related to \(y\)
struct Point {
float x, y;
float r, g, b;
};
void putpixel(const Point& p);
void drawTriangle(Point A, Point B, Point C) {
float dx1, dr1, dg1, db1;
float dx2, dr2, dg2, db2;
float dx3, dr3, dg3, db3;
float dr, dg, db;
if (B.y - A.y > 0) {
dx1 = (B.x - A.x) / (B.y - A.y);
dr1 = (B.r - A.r) / (B.y - A.y);
dg1 = (B.g - A.g) / (B.y - A.y);
db1 = (B.b - A.b) / (B.y - A.y);
} else {
dx1 = dr1 = dg1 = db1 = 0;
}
if (C.y - A.y > 0) {
dx2 = (C.x - A.x) / (C.y - A.y);
dr2 = (C.r - A.r) / (C.y - A.y);
dg2 = (C.g - A.g) / (C.y - A.y);
db2 = (C.b - A.b) / (C.y - A.y);
} else {
dx2 = dr2 = dg2 = db2 = 0;
}
if (C.y - B.y > 0) {
dx3 = (C.x - B.x) / (C.y - B.y);
dr3 = (C.r - B.r) / (C.y - B.y);
dg3 = (C.g - B.g) / (C.y - B.y);
db3 = (C.b - B.b) / (C.y - B.y);
} else {
dx3 = dr3 = dg3 = db3 = 0;
}
Point S = A, E = A, P;
if (dx1 > dx2) {
for (; S.y <= B.y; S.y++, E.y++) {
if (E.x - S.x > 0) {
dr = (E.r - S.r) / (E.x - S.x);
dg = (E.g - S.g) / (E.x - S.x);
db = (E.b - S.b) / (E.x - S.x);
} else {
dr = dg = db = 0;
}
P = S;
for (; P.x < E.x; P.x++) {
putpixel(P);
P.r += dr;
P.g += dg;
P.b += db;
}
S.x += dx2; S.r += dr2; S.g += dg2; S.b += db2;
E.x += dx1; E.r += dr1; E.g += dg1; E.b += db1;
}
E = B;
for (; S.y <= C.y; S.y++, E.y++) {
if (E.x - S.x > 0) {
dr = (E.r - S.r) / (E.x - S.x);
dg = (E.g - S.g) / (E.x - S.x);
db = (E.b - S.b) / (E.x - S.x);
} else {
dr = dg = db = 0;
}
P = S;
for (; P.x < E.x; P.x++) {
putpixel(P);
P.r += dr;
P.g += dg;
P.b += db;
}
S.x += dx2; S.r += dr2; S.g += dg2; S.b += db2;
E.x += dx3; E.r += dr3; E.g += dg3; E.b += db3;
}
} else {
for (; S.y <= B.y; S.y++, E.y++) {
if (E.x - S.x > 0) {
dr = (E.r - S.r) / (E.x - S.x);
dg = (E.g - S.g) / (E.x - S.x);
db = (E.b - S.b) / (E.x - S.x);
} else {
dr = dg = db = 0;
}
P = S;
for (; P.x < E.x; P.x++) {
putpixel(P);
P.r += dr;
P.g += dg;
P.b += db;
}
S.x += dx1; S.r += dr1; S.g += dg1; S.b += db1;
E.x += dx2; E.r += dr2; E.g += dg2; E.b += db2;
}
S = B;
for (; S.y <= C.y; S.y++, E.y++) {
if (E.x - S.x > 0) {
dr = (E.r - S.r) / (E.x - S.x);
dg = (E.g - S.g) / (E.x - S.x);
db = (E.b - S.b) / (E.x - S.x);
} else {
dr = dg = db = 0;
}
P = S;
for (; P.x < E.x; P.x++) {
putpixel(P);
P.r += dr;
P.g += dg;
P.b += db;
}
S.x += dx3; S.r += dr3; S.g += dg3; S.b += db3;
E.x += dx2; E.r += dr2; E.g += dg2; E.b += db2;
}
}
}
Textured Triangles
Environmental Mapping
The pseudo normal vectors
(perpendicular to the vertices
) can be used to index the texture map
to do environmental mapping, i.e. shading
.
These height
and width
are of the texture map
.