需要补充的函数
rasterize_triangle(): 执行三角形栅格化算法
static bool insideTriangle(): 测试点是否在三角形内。你可以修改此函 数的定义,这意味着,你可以按照自己的方式更新返回类型或函数参数。
判断点是否在三角形内
若三角形ABC中有一点p,则AP x AB, BP x BC,CP x CA三个向量的叉乘方向相同。
若在平面上,则两向量叉乘方向平行z轴,为正为负取决于屏幕坐标向内为正为负。
代码
Eigen::Vector2f p;
p << x, y;
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坐标是否统一,因为在这个着色器中z轴正向指向显示屏内部,所以内部点p叉乘值大于0.
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;
三角形栅格化算法
算法不难,理解了整个程序就好写了。
至于下面的函数
auto p = computeBarycentric2D(x, y, t.v);
可查看我的另一篇博客 games101 Lecture 9 线性插值(对三角形内部的线性插值)
auto v = t.toVector4();
// TODO : Find out the bounding box of current triangle.
float l, r, top, bom;
//+0.5 是为了四舍五入
l = min(v.at(0).x(), v.at(1).x(), v.at(2).x())+0.5f;
r = max(v.at(0).x(), v.at(1).x(), v.at(2).x()) + 0.5f;
bom = min(v.at(0).y(), v.at(1).y(), v.at(2).y()) + 0.5f;
top = max(v.at(0).y(), v.at(1).y(), v.at(2).y()) + 0.5f;
// iterate through the pixel and find if the current pixel is inside the triangle
for (int x = l;x <= r;x++) {
for (int y = bom;y <= top;y++) {
Eigen::Vector3f res[3];
std::transform(std::begin(v), std::end(v), res, [](auto& vec) { return Eigen::Vector3f(vec.x()+0.5, vec.y()+0.5, vec.z()); });
if (insideTriangle(x, y, res)) {
//std::cout << x<<","<<y << std::endl;
// If so, use the following code to get the interpolated z value.
auto p = computeBarycentric2D(x, y, t.v);//计算x,y点线性插值系数
float alpha = std::get<0>(p);
float beta = std::get<1>(p);
float gamma = std::get<2>(p);
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.
if (z_interpolated < depth_buf[get_index(x, y)]) {
set_pixel(Eigen::Vector3f(x, y, z_interpolated), t.getColor());
}
}
}
}
MASS(super-sampling Anti-aliasing)
超采样:一个像素分隔为多个像素点采样,然后求多个像素点采样的平均值。
出现黑边
先说结论:
黑边恰恰说明我们对图像做了mass采样,但这种效果并不好!
黑边出现的原因:
在非三角形内的像素点,由于增加了偏移量,使得非三角形内像素点在判断时有一个或两个偏移位置在三角形内,使得该像素点有count/4的颜色,颜色较暗但并非黑色。
消除黑边的办法
- 先判断该像素点是否在三角形内,如果在三角形内再进行MASS采样,即只会模糊化三角形内像素点,不会改变三角形外像素点的颜色和深度。
- 换一种思考方式,如果像素点在图像内部,则count值一般情况下都>=3(但这也会有bug情况出现)。
测试案例
出现黑边
增大采样偏移数
std::vector<Eigen::Vector2f> offset{
{ 1.5f, 1.5f},
{ 1.5f,-1.5f},
{-1.5f, 1.5f},
{-1.5f,-1.5f}
};
考虑出现黑边的原因是采样范围过大,修改偏移量为
std::vector<Eigen::Vector2f> offset{
{ 0.2f, 0.2f},
{ 0.2f,-0.2f},
{-0.2f, 0.2f},
{-0.2f,-0.2f}
};
得到
可以看出只有一小部分任由黑边。
考虑黑边像素点采样时有一部分(count == 1或2)采样会采到三角形内.
故修改像素填充条件:
if (count>2) {
......
}
可以看到,虽然我们消除了黑边,但是右边三角形的锯齿状却变得明显。
使用内部MASS抗锯齿:
if (!insideTriangle(x, y, res)) continue;
效果对比
内部MASS抗锯齿
无抗锯齿
MASS抗锯齿(有黑边)
代码
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();
// TODO : Find out the bounding box of current triangle.
float l, r, top, bom;
//Triangle& t中的x,y为(700,700)内的小数
l = min(v.at(0).x(), v.at(1).x(), v.at(2).x())-1;
r = max(v.at(0).x(), v.at(1).x(), v.at(2).x())+1;
bom = min(v.at(0).y(), v.at(1).y(), v.at(2).y())-1;
top = max(v.at(0).y(), v.at(1).y(), v.at(2).y())+1;
// iterate through the pixel and find if the current pixel is inside the triangle
bool Mass = true;
if (Mass) {
std::vector<Eigen::Vector2f> offset{
{ 0.1f, 0.2f},
{ 0.2f,-0.1f},
{-0.2f, 0.1f},
{-0.1f,-0.2f}
};
//得到三角形三个顶点,记录在res【3】中
Eigen::Vector3f res[3];
std::transform(std::begin(v), std::end(v), res,
[](auto& vec) {
return Eigen::Vector3f(vec.x(), vec.y(), vec.z());
});
for (int x = l;x <= r;x++) {
for (int y = bom;y <= top;y++) {
//if (!insideTriangle(x, y, res)) continue;
int count = 0;//记录细分像素覆盖数量
int maxcount = offset.size();
for (int k = 0;k < maxcount;k++) {
if (insideTriangle(offset.at(k).x() + x, offset.at(k).y() + y , res)) {
++count;
}
}
if (count) {
//计算(x,y)的深度
auto p = computeBarycentric2D(x, y, t.v);
float alpha = std::get<0>(p);
float beta = std::get<1>(p);
float gamma = std::get<2>(p);
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 (z_interpolated < depth_buf[get_index(x, y)]) {//如果深度小,则MASS
Eigen::Vector3f color = t.getColor()* count / maxcount + frame_buf[get_index(x,y)]*(4-count)/4;
set_pixel(Eigen::Vector3f(x, y, z_interpolated), color );
depth_buf[get_index(x, y)] = z_interpolated;
}
}
}
}
}
else {
for (int x = l;x <= r;x++) {
for (int y = bom;y <= top;y++) {
Eigen::Vector3f res[3];
std::transform(std::begin(v), std::end(v), res, [](auto& vec) { return Eigen::Vector3f(vec.x() + 0.5f, vec.y() + 0.5f, vec.z()); });
if (insideTriangle(x, y, res)) {
//std::cout << x<<","<<y << std::endl;
// If so, use the following code to get the interpolated z value.
auto p = computeBarycentric2D(x, y, t.v);//我也不知道这个函数是在干嘛,反正最后用它得到了一个深度值
float alpha = std::get<0>(p);
float beta = std::get<1>(p);
float gamma = std::get<2>(p);
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.
if (z_interpolated < depth_buf[get_index(x, y)]) {
set_pixel(Eigen::Vector3f(x, y, z_interpolated), t.getColor());
depth_buf[get_index(x, y)] = z_interpolated;
}
}
}
}
}
}
FXAA(Fast Approximate AA)
在图像层面做的抗锯齿。快速的对有锯齿的图像做处理使得锯齿变的平滑。
TAA(Temporal AA)
将MSAA复用的像素分布在时间上(前几帧的像素上),用于动态模糊。