计算机图形学 GAMES101 作业2 解析

【计算机图形学】

本篇是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 中对应的位被设置,且相应的缓冲区被启用。

这种方法利用了位运算的特性,使得不同的缓冲区标志可以在一个整数中独立存在,并且可以通过简单的位运算进行有效的检测和处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值