【计算机图形学】
本篇是GAMES101作业的解析:
本人参考了
https://www.cnblogs.com/lawliet12/p/17719365.htm
link
link
https://blog.csdn.net/weixin_44491423/article/details/127226267
这两位的文章对我有极大的帮助,在此基础上,我编写了自己的代码。然而,他们的方法和代码也存在一些问题,因此我将自己的代码贴出供各位参考,并希望能得到大佬们的指正。
该代码最终生成的图像颜色变深了一些,但基本原理已经实现,我便没有继续探究,若有了解的,欢迎教导。
我跳过了详细原理的讲解,直接展示了包含进阶内容的 MSAA 代码。
首先,我们需要了解显示器的坐标系是以左上角为原点,每个像素大小为1。像素的坐标表示为其左上角的坐标,但我们需要使用像素的中心来判断该点是否在三角形内。
为了实现抗锯齿效果,我们将一个像素划分为2×2的小像素,这涉及到两个步骤,每一步都必不可少,也不能重复。具体步骤如下:
从大像素的左上角转移到小像素的左上角。
从小像素的左上角转移到小像素的中心(这一步在 insideTriangle 函数中实现)。
接下来,判断小像素是否在三角形内部。如果在内部,我们计算其深度,并与其他像素点的深度进行比较。如果有其他像素点深度较浅,该小像素点被遮盖;如果没有,它将遮盖其他像素。
如果没有被遮盖,我们通过计算四个小像素颜色的平均值,得到最终像素显示的颜色。这就是整体流程的概述。
static const int sampleTimes = 2;
static const int totalSamplePoint = sampleTimes * sampleTimes;
static bool insideTriangle(int x, int y, const Vector3f* _v)
{
float step = 1.0f /sampleTimes;
Vector3f p = Eigen::Vector3f(x,y,1.0f);
float midX = p.x() + step /2.0f;
float midY = p.y() + step /2.0f; // first sampling point center
Vector3f AP = p - _v[0];
Vector3f AB = _v[1] - _v[0];
auto cross1 = AB.cross(AP);
Vector3f BP = p - _v[1];
Vector3f BC = _v[2] - _v[1];
auto cross2 = BC.cross(BP);
Vector3f CP = p - _v[2];
Vector3f CA = _v[0] - _v[2];
auto cross3 = CA.cross(CP);
if((cross1.z() >0 && cross2.z()>0 && cross3.z()>0) || (cross1.z() < 0 && cross2.z() < 0 && cross3.z() < 0)){
return true;
}
return false;
}
这部分是我们判断像素点是否在三角形内的方法,我们可以直接利用z轴分量是否方向一致可以判断。也就是老师说的利用右手判断都在同一侧的问题。
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
//array<Eigen::Vector4f, 3> represent the triangle three vertex
auto v = t.toVector4();
// TODO : Find out the bounding box of current triangle.
float minx = FLT_MAX, maxx = FLT_MIN, miny = FLT_MAX, maxy = FLT_MIN;
for(auto p :v){
minx = p.x() < minx ? p.x():minx;
miny = p.y() < miny ? p.y():miny;
maxx = p.x() > maxx ? p.x():maxx;
maxy = p.y() > maxy ? p.y():maxy;
}
std::cout << "Bounding box: minx=" <<minx << " miny=" << miny << " maxx=" << maxx <<" maxy="<<maxy<<std::endl;
// iterate through the pixel and find if the current pixel is inside the triangle
// If so, use the following code to get the interpolated z value.
for(int y = floor(miny);y<ceil(maxy);y++){
for(int x = floor(minx);x<ceil(maxx);x++){
float step = 1.0f /sampleTimes;
//从第一个像素的中间开始算
for(int i=0;i<sampleTimes;i++){
for(int j=0;j<sampleTimes;j++){
//这一步是将一个大像素的左上角,转移到小像素的左上角
float samplex = x + i*step;
float sampley = y + j*step;
if(insideTriangle(samplex,sampley,t.v)){
auto[alpha, beta, gamma] = computeBarycentric2D(samplex + step/2.0f , y + step/2.0f, 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;
std::cout << "Sample point (" << samplex << ", " << sampley << ") inside triangle, z_interpolated: " << z_interpolated << std::endl;
int sample_ind = get_index(x,y)* totalSamplePoint + i*sampleTimes + j;
std::cout<<"sample_ind值为"<<sample_ind<< std::endl;
float sample_z = sample_depth_buf[sample_ind];
std::cout<<"sample_z值为"<<sample_z<< std::endl;
std::cout<<"z_interpolated的值为"<<z_interpolated<< std::endl;
if(sample_z > z_interpolated){
sample_depth_buf[sample_ind] = z_interpolated;
sample_frame_buf[sample_ind] = t.getColor()/totalSamplePoint; // 不除以 totalSamplePoint
std::cout << "Set sample_frame_buf[" << sample_ind << "] with color (" << t.getColor().x() << ", " << t.getColor().y() << ", " << t.getColor().z() << ")" << std::endl;
}
}
}
}
int ind = get_index(x,y) * totalSamplePoint;
Vector3f final_color = {0,0,0};
for(int i = ind;i < ind + totalSamplePoint;i++){
final_color += sample_frame_buf[i];
}
final_color /= totalSamplePoint; // 在这里做平均
Vector3f point = Eigen::Vector3f(x,y,1.0f);
set_pixel(point,final_color);
std::cout << "Set pixel at (" << x << ", " << y << ") with color (" << final_color.x() << ", " << final_color.y() << ", " << final_color.z() << ")" << std::endl;
}
}
}
接下来是
float minx = FLT_MAX, maxx = FLT_MIN, miny = FLT_MAX, maxy = FLT_MIN;
for(auto p :v){
minx = p.x() < minx ? p.x():minx;
miny = p.y() < miny ? p.y():miny;
maxx = p.x() > maxx ? p.x():maxx;
maxy = p.y() > maxy ? p.y():maxy;
}
```
判断bounding box范围的代码,找出三个点x,y的极值,确定一个矩阵。
float samplex = x + i*step;
float sampley = y + j*step;
这部分就是由大像素左上角到小像素左上角的代码
然后计算重心坐标先不谈,是提供的源码,但我们需要用他最后提供给我们的 z_interpolated去计算深度值,也就是
if(sample_z > z_interpolated){
sample_depth_buf[sample_ind] = z_interpolated;
sample_frame_buf[sample_ind] = t.getColor()/totalSamplePoint; // 不除以 totalSamplePoint
这部分。只有深度浅的,才会被显示。
sample_depth_buf[sample_ind],sample_frame_buf[sample_ind]都是我们自己声明的变量,他去存储小像素的颜色和深度,去判断是否被遮挡。
这部分要注意一下,有个小细节
enum class Buffers
{
Color = 1,
Depth = 2,
SampleColor = 4,
SampleDepth = 8,
};
这里我们要去设置这个枚举类型,这个很重要,它关系到我们的初始化
void rst::rasterizer::clear(rst::Buffers buff)
{
if ((buff & rst::Buffers::Color) == rst::Buffers::Color)
{
std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0});
}
if ((buff & rst::Buffers::SampleColor) == rst::Buffers::SampleColor)
{
std::fill(sample_frame_buf.begin(), sample_frame_buf.end(), Eigen::Vector3f{0, 0, 0});
}
if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
{
std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
}
if ((buff & rst::Buffers::SampleDepth) == rst::Buffers::SampleDepth)
{
std::fill(sample_depth_buf.begin(), sample_depth_buf.end(), std::numeric_limits<float>::infinity());
std::cout<<"i have already inital"<<std::endl;
}
}
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
frame_buf.resize(w * h);
depth_buf.resize(w * h);
sample_frame_buf.resize(w*h*totalSamplePoint);
sample_depth_buf.resize(w*h*totalSamplePoint);
}
我们在源码中关于大像素初始化的位置填上我们声明的变量初始化,注意头文件记得去声明变量。
其次在main函数中,需要做对应的修改,否则不会初始化我们的变量
r.clear(rst::Buffers::Color | rst::Buffers::Depth | rst::Buffers::SampleColor |rst::Buffers::SampleDepth);
在实现该方法的过程中,使用了以下一组二进制掩码进行异或运算,使各位互不干扰:
1 -> 0001
2 -> 0010
4 -> 0100
8 -> 1000
假设我们输入的缓冲区(buffer)的值为 0011,表示同时启用了 Color 和 Depth 缓冲区。在这种情况下,通过位运算与以下掩码进行比较,可以确定具体启用了哪些缓冲区:
buff & rst::Buffers::Color (0001)
buff & rst::Buffers::Depth (0010)
由于这些掩码是互不干扰的,因此 buff & rst::Buffers::Color 的结果仍然是 0001,而 buff & rst::Buffers::Depth 的结果仍然是 0010。
进一步,通过比较运算:
(buff & rst::Buffers::Color) == rst::Buffers::Color
可以验证上述结果成立。这表明,buff 中对应的位被设置,且相应的缓冲区被启用。
这种方法利用了位运算的特性,使得不同的缓冲区标志可以在一个整数中独立存在,并且可以通过简单的位运算进行有效的检测和处理。