参考:https://blog.csdn.net/qq_36242312/article/details/105758619
思路
①三角形栅格算法
作业要求已经给出了*rasterize_triangle()*函数的工作流程:
创建三角形的 2 维 bounding box。
遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。
如果在内部,则将其位置处的插值深度值 (interpolated depth value) 与深度 缓冲区 (depth buffer) 中的相应值进行比较。
如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)。
老师这里要求实现的是Barycentric Algorithm(三角形栅格化),下面几个点可能需要注意一下:
①寻找三角形的bounding box:根据三角形的三个坐标,找出最大x,最小x,最大y,最小y坐标即可,注意最小x,y坐标需要取比原数小,最大x,y坐标需要取比原数大
②点是否在三角形内:只需要连接点和三角形个点,形成一系列向量,然后判断它们的叉乘结果的z值是否统一即可
③MSAA 4X 深度:把一个点看成一个格子,判断里面4个小点是否落在三角形内,然后找到其中插值的最小z值,和帧缓冲中的z值进行比较替换即可。
③MSAA 4X 颜色:
判断有4个小点中有几个小点落入三角形,然后按比例对颜色进行采样即可。
光栅化函数:
//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
//v进行转换后是std::array,不能直接&来获取,作为数组的指针
auto v = t.toVector4();
// bounding box
float min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
//hero:将bounding box扩展一点,将坐标取整为索引(每个像素都是一个有面积的矩形)
min_x = (int)std::floor(min_x);
max_x = (int)std::ceil(max_x);
min_y = (int)std::floor(min_y);
max_y = (int)std::ceil(max_y);
bool MSAA = false;
//MSAA 4X
if (MSAA) {
// 格子里的细分四个小点坐标
std::vector<Eigen::Vector2f> pos
{
{0.25,0.25},
{0.75,0.25},
{0.25,0.75},
{0.75,0.75},
};
//hero:以下遍历screen的像素,x,y这里相当于像素的索引(而不是坐标,从0,0到width-1,height-1)
for (int x = min_x; x <= max_x; x++) {
for (int y = min_y; y <= max_y; y++) {
// 记录最小深度
float minDepth = FLT_MAX;
// 四个小点中落入三角形中的点的个数
int count = 0;
// 对四个小点坐标进行判断
for (int i = 0; i < 4; i++) {
// hero:小点是否在三角形内
/*hero:因为x,y是像素索引,也是该像素的左下角点坐标,所以可以加上pos[i][0基础偏移量,得到4个细分位置*/
if (insideTriangle((float)x + pos[i][0], (float)y + pos[i][1], t.v)) {
/* 如果在,对深度z进行插值,computeBarycentric2D计算该采样点的重心坐标*/
auto tup = computeBarycentric2D((float)x + pos[i][0], (float)y + pos[i][1], t.v);
/*hero:以下就是该采样点相对三角形3个顶点的权重值,而且获得权重后,color、normal、texture、所有顶点信息都可以根据权重被采样*/
float alpha;
float beta;
float gamma;
std::tie(alpha, beta, gamma) = tup;
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;
//获得这4个采样点中最小的minDepth
minDepth = std::min(minDepth, z_interpolated);
count++;
}
}
if (count != 0) {
//hero:如果有值,就有采样点落入三角形,判断是否小于该索引处的像素深度,小于则更新深度,并绘制
if (depth_buf[get_index(x, y)] > minDepth) {
//hero:下面应该采用双线性采样,来计算权重后的color
Vector3f color = t.getColor() * count / 4.0;
Vector3f point(3);
point << (float)x, (float)y, minDepth;
// 替换深度
depth_buf[get_index(x, y)] = minDepth;
// 修改颜色
set_pixel(point, color);
}
}
}
}
}
//hero:以下是没有采用MSAA的方式
else {
for (int x = min_x; x <= max_x; x++) {
for (int y = min_y; y <= max_y; y++) {
if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)) {
auto tup = computeBarycentric2D((float)x + 0.5, (float)y + 0.5, t.v);
float alpha;
float beta;
float gamma;
std::tie(alpha, beta, gamma) = tup;
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;
if (depth_buf[get_index(x, y)] > z_interpolated) {
Vector3f color = t.getColor();
Vector3f point(3);
point << (float)x, (float)y, z_interpolated;
depth_buf[get_index(x, y)] = z_interpolated;
set_pixel(point, color);
}
}
}
}
}
}
判断点是否位于三角形内函数
static bool insideTriangle(double x, double 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]
//hero:对应Vector2的二维向量不能计算Cross叉乘,因为叉乘只出现在3D矢量中
Eigen::Vector2f p;
p << x, y;
/*hero:此时,三角形是具有3D信息的,Sceen2D“图形”(被正交投影过,并适配到视口screen),所以去深度值,只进行平面计算*/
Eigen::Vector2f AB = _v[1].head(2) - _v[0].head(2);
Eigen::Vector2f BC = _v[2].head(2) - _v[1].head(2);
Eigen::Vector2f CA = _v[0].head(2) - _v[2].head(2);
Eigen::Vector2f AP = p - _v[0].head(2);
Eigen::Vector2f BP = p - _v[1].head(2);
Eigen::Vector2f CP = p - _v[2].head(2);
// 判断每个z坐标是否统一
return AB[0] * AP[1] - AB[1] * AP[0] > 0
&& BC[0] * BP[1] - BC[1] * BP[0] > 0
&& CA[0] * CP[1] - CA[1] * CP[0] > 0;
}
结果
以下是结果:
以下依次是没有做MSAA 4X的结果和做了MSAA 4X的结果: