本次实现基于tinyRender项目,笔者进行了自己的思考,而后和书籍进行验证
三角形的填充
线扫描
线扫描是比较古典的方法了,我们确定三角形的边界,就可以自低向上的进行扫描
那么首先,我们需要对三角形顶点的y轴从小到大进行排序,ymin,ymid和ymax
y = ymid 这条横线把三角形分割成上下两个部分,每个部分的x轴的边界由对应的边构成
那么我们只需要从ymin往ymax一行一行扫描就行,代码如下:
void LineScan(TGAImage& image, Vec2i t[3], TGAColor color = red)
{
SortPoint_ByYAxis(t);
float x1 = t[0].x;
float x2 = t[0].x;
float reverseK01 = 1.0 * (t[1].x - t[0].x) / (t[1].y - t[0].y);
float reverseK02 = 1.0 * (t[2].x - t[0].x) / (t[2].y - t[0].y);
float reverseK12 = 1.0 * (t[2].x - t[1].x) / (t[2].y - t[1].y);
int error1 = 0, error2 = 0;
for (int y = t[0].y; y < t[1].y; ++y)
{
line(x1 + offset, y, x2 + offset, y, image, color);
x1 += reverseK01;
x2 += reverseK02;
}
// 这里有一点冗余
error1 = 0, error2 = 0;
for (int y = t[1].y; y <= t[2].y; ++y)
{
line(int(x1 + offset), y, int(x2 + offset), y, image, color);
x1 += reverseK12;
x2 += reverseK02;
}
}
笔者本来想从ymin出发和ymid-ymax所在斜线上的点,利用斜线进行三角形填充。但是这样会出现漏洞,因为像素块是一个一个的,这样会导致一部分的像素块没办法被填充
重心计算法
假设三角形ABC,重心为P,三角形的重心计算 : P = (1-u-v)A + uB + vC
进行展开:P = A + u(B-A) + v(C-A) + A ➡️
转换成线性方程组:
转换成矩阵形式:
那么我们对三角形进行BOX裁剪,然后计算其三角形内的所有重心,进行填充即可,代码实现如下:
void GravityCenterAlgorithmn(TGAImage& image, Vec2i* pts, TGAColor color = red)
{
// 减一 是为了在合理范围内
Vec2i bboxmin(image.get_width() - 1, image.get_height() - 1);
Vec2i bboxmax(0, 0);
Vec2i clamp(image.get_width() - 1, image.get_height() - 1);
// BOX裁剪 ➡️ 因为三角形可能在边界外
for (int i = 0; i < 3; ++i)
{
// 确保边界大于0,为三角形最小坐标的边界
bboxmin.x = std::max(0, std::min(pts[i].x, bboxmin.x));
bboxmin.y = std::max(0, std::min(pts[i].y, bboxmin.y));
// 确保边界为三角形的边界
bboxmax.x = std::min(clamp.x, std::max(pts[i].x, bboxmax.x));
bboxmax.y = std::min(clamp.y, std::max(pts[i].y, bboxmax.y));
}
Vec2i P;
for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++)
{
for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++)
{
Vec3f bc_screen = barycentric(pts, P);
// 其实就是避免点在三角形之外的情况
if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0)
continue;
image.set(P.x, P.y, color);
}
}
}