GAMES101 作业2
GAMES101 作业2
实现一个三角形栅格化算法, 能够在屏幕上绘制实心三角形, 并正确处理深度缓冲, 最终实现输出预期的图像.
我的所有GAMES101作业的仓库地址: GAMES101-Assignments
判别点是否在三角形内
常用的方法: 计算顶点与三角形的顶点连线向量与三角形的边的叉乘, 如果三个叉乘的方向都一致, 则该点在三角形内.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static bool insideTriangle(int x, int y, const Vector3f* _v)
{
// TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
// 定义三个点ABC以及像素点P
Eigen::Vector2f P(x, y);
Eigen::Vector2f A(_v[0].x(), _v[0].y());
Eigen::Vector2f B(_v[1].x(), _v[1].y());
Eigen::Vector2f C(_v[2].x(), _v[2].y());
float AB_AP = (B.x() - A.x()) * (P.y() - A.y()) - (B.y() - A.y()) * (P.x() - A.x()); // AB × AP
float BC_BP = (C.x() - B.x()) * (P.y() - B.y()) - (C.y() - B.y()) * (P.x() - B.x()); // BC × BP
float CA_CP = (A.x() - C.x()) * (P.y() - C.y()) - (A.y() - C.y()) * (P.x() - C.x()); // CA × CP
return ((AB_AP >= 0 && BC_BP >= 0 && CA_CP >= 0) || (AB_AP <= 0 && BC_BP <= 0 && CA_CP <= 0)); // P在三角形内部时,AB × AP、BC × BP、CA × CP的符号应该一致
}
光栅化
我们先计算三角形的包围盒
1
2
3
4
float x_min = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));
float x_max = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));
float y_min = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));
float y_max = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));
然后根据包围盒的范围, 找到整数索引范围, 用std::floor
和std::ceil
取整
1
2
3
4
int x_min_int = std::floor(x_min);
int x_max_int = std::ceil(x_max);
int y_min_int = std::floor(y_min);
int y_max_int = std::ceil(y_max);
接着遍历包围盒内的像素点, 找到像素中心点, 判断是否在三角形内部:
1
2
3
4
5
6
7
8
9
10
11
for (int x = x_min_int; x <= x_max_int; x++) {
for (int y = y_min_int; y <= y_max_int; y++) {
// 找到像素中心点
float x_center = x + 0.5;
float y_center = y + 0.5;
// 检查是否在三角形内部
if (insideTriangle(x_center, y_center, t.v)) {
// TODO
}
}
}
根据像素点的位置, 计算像素点的深度值(这段代码是给出的, 不需要自己实现):
1
2
3
4
auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal; // 当前像素点的深度值
深度测试并更新深度缓冲区:
1
2
3
4
5
6
7
// 深度测试并更新深度缓冲区
int index = get_index(x, y); // 获取像素点的索引
if (z_interpolated < depth_buf[index]) { // 深度测试: 如果当前像素点的深度值小深度缓冲区中的深度值
depth_buf[index] = z_interpolated; // 更新深度缓冲区
// 设置像素颜色
set_pixel(Eigen::Vector3f(x, y, 0), t.getColor());
}
完整代码:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();
// TODO : Find out the bounding box of current triangle.
// iterate through the pixel and find if the current pixel is inside the triangle
// 计算三角形的包围盒(根据三个顶点的坐标的最大值和最小值)
float x_min = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));
float x_max = std::max(v[0].x(), std::max(v[1].x(), v[2].x()));
float y_min = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));
float y_max = std::max(v[0].y(), std::max(v[1].y(), v[2].y()));
// 根据包围盒的范围, 找到整数索引范围, 用std::floor和std::ceil取整
int x_min_int = std::floor(x_min);
int x_max_int = std::ceil(x_max);
int y_min_int = std::floor(y_min);
int y_max_int = std::ceil(y_max);
// If so, use the following code to get the interpolated z value.
//auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
//float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
//float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
//z_interpolated *= w_reciprocal;
// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
// 遍历包围盒内的像素点, 判断是否在三角形内部
for (int x = x_min_int; x <= x_max_int; x++) {
for (int y = y_min_int; y <= y_max_int; y++) {
// 找到像素中心点
float x_center = x + 0.5;
float y_center = y + 0.5;
// 检查是否在三角形内部
if (insideTriangle(x_center, y_center, t.v)) {
// 如果在三角形内部, 则计算像素点的深度值
auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal; // 当前像素点的深度值
// 深度测试并更新深度缓冲区
int index = get_index(x, y); // 获取像素点的索引
if (z_interpolated < depth_buf[index]) { // 深度测试: 如果当前像素点的深度值小于深度缓冲区中的深度值
depth_buf[index] = z_interpolated; // 更新深度缓冲区
// 设置像素颜色
set_pixel(Eigen::Vector3f(x, y, 0), t.getColor());
}
}
}
}
}
渲染结果
渲染结果是两个三角形的叠加, 一个是浅绿色, 一个是浅蓝色:
当然, 和上次作业也一样, 需要将”opencv world4100d.dll”文件放到项目的Debug目录下, 或者设置它的环境变量, 否则会报错: 将由于找不到 opencv world4100d.dll,无法继续执行代码。重新安装程序可能会解决此问题
本文由作者按照 CC BY 4.0 进行授权