GAMES101——作业2 Triangles and Z-buffering(含提高)

任务

填写并调用函数 rasterize_triangle(const Triangle& t)
该函数的内部工作流程如下:
1. 创建三角形的 2 bounding box
2. 遍历此 bounding box 内的所有像素(使用其 整数 索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。
3. 如果在内部,则将其位置处的 插值深度值 (interpolated depth value) 与深度缓冲区 (depth buffer) 中的相应值进行比较。
4. 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)
提高:使用SSAA进行抗锯齿

图形管线

        

                作业框架代码中的draw函数实现了顶点加工和三角形组装的功能,而下面三步需要自己代码实现。

        Vertex Processing

        对顶点进行加工,使其变换到屏幕空间坐标。

        Triangle Processing

        将加工后的顶点组装成三角形,用于下一步的光栅化。

void rst::rasterizer::draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type)
{   
    //从缓冲区中读取顶点坐标,索引,颜色信息
    auto& buf = pos_buf[pos_buffer.pos_id];
    auto& ind = ind_buf[ind_buffer.ind_id];
    auto& col = col_buf[col_buffer.col_id];
    
    float f1 = (50 - 0.1) / 2.0;
    float f2 = (50 + 0.1) / 2.0;

    Eigen::Matrix4f mvp = projection * view * model;    //计算得到mvp矩阵
    for (auto& i : ind)     //遍历索引数组,对顶点进行加工
    {
        Triangle t;
        //这里对顶点进行mvp变换,并将结果存入v
        Eigen::Vector4f v[] = {
                mvp * to_vec4(buf[i[0]], 1.0f),
                mvp * to_vec4(buf[i[1]], 1.0f),
                mvp * to_vec4(buf[i[2]], 1.0f)
        };
        //因为得到的结果w值是原本的z值,所以除以w才是变换后在[-1,1]^3的空间中的坐标
        for (auto& vec : v) {
            vec /= vec.w();
        }
        //将归一化设备坐标的点转换到屏幕空间坐标
        for (auto & vert : v)
        {
            vert.x() = 0.5*width*(vert.x()+1.0);
            vert.y() = 0.5*height*(vert.y()+1.0);
            vert.z() = vert.z() * f1 + f2;  
        }
        //将屏幕空间的点组装成三角形
        for (int i = 0; i < 3; ++i)
        {
            t.setVertex(i, v[i].head<3>());
            t.setVertex(i, v[i].head<3>());
            t.setVertex(i, v[i].head<3>());
        }
        //从颜色缓冲区中读取颜色
        auto col_x = col[i[0]];
        auto col_y = col[i[1]];
        auto col_z = col[i[2]];
        //为三角形的每个顶点附上颜色数据
        t.setColor(0, col_x[0], col_x[1], col_x[2]);
        t.setColor(1, col_y[0], col_y[1], col_y[2]);
        t.setColor(2, col_z[0], col_z[1], col_z[2]);
        //光栅化三角形
        rasterize_triangle(t);
    }
}
        Rasterization

        将三角形光栅化得到片元,这些片元最终会被渲染成屏幕上的像素。

       FargmentProcessing

        紧随光栅化(Rasterization)之后。在Fragment Processing阶段,主要对光栅化生成的每个片元(Fragment)进行进一步的处理,以确定其最终的颜色、深度和其他属性。而这里的各种属性可以根据线性插值和透视矫正插值得出。作业里要实现的是透视矫正插值。透视矫正插值需要用到重心坐标。重心坐标的计算作业框架中已经直接给出函数,直接调用即可。

        经过透视变换后,三角形内所有的点分布都会发生变化,因此采用线性插值得到的结果是错误的,采用透视矫正插值才能得到正确的结果。

        z坐标的透视矫正插值:

        任意属性的透视矫正插值:

        

        插值完成后利用Z-buffer判断该点是否能够看见。

        该部分代码在下面的SSAA里。

        Framebuffer Opeations

        main.cpp这里通过框架的cv库,将frame_buffer的内容写入图片,从而得到最终的图片。

 cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);

        cv::imwrite(filename, image);

提高项 SSAA

        易错点:在光栅化的时候直接将像素分成四份并计算均值,这样子会导致每个三角形都独立计算,不考虑其他三角形,从而产生黑边。

        解决方法:在Rasterizer.h里根据原先定义好的frame_buffer和depth_buffer,自己定义一个四倍大小的SSAA_frame_buffer和SSAA_depth_buffer,对三角形光栅化的时候不对frame_buffer,全都采用SSA_frame_buffer和SSAA_depth_buffer,等到要马上要输出图像的时候,再将SSAA_frame_buffer里的像素分组求平均再赋给frame_buffer输出。

        

        std::vector<Eigen::Vector3f> frame_buf;
        std::vector<float> depth_buf;

        std::vector<Eigen::Vector3f> SSAA_frame_buf;
        std::vector<float> SSAA_depth_buf;
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
    auto v = t.toVector4();
    //确定三角形的包围盒
    int maxX,minX,maxY,minY;
    maxX = (int)std::ceil(std::max(v[0].x(),std::max(v[1].x(),v[2].x())));
    minX = (int)std::floor(std::min(v[0].x(),std::min(v[1].x(),v[2].x())));
    maxY = (int)std::ceil(std::max(v[0].y(),std::max(v[1].y(),v[2].y())));
    minY = (int)std::floor(std::min(v[0].y(),std::min(v[1].y(),v[2].y())));

    //遍历三角形包围盒中的每个像素
    for(int x =minX;x<maxX;x++){
        for(int y=minY;y<maxY;y++){
            int cnt = 0;                                  
            for(float i = 0.25;i<=0.75001;i+=0.5){
                for(float j = 0.25;j<=0.75001;j+=0.5){
                    if(insideTriangle((float)x+i,(float)y+j,t.v)==true){
                        //计算重心坐标
                        auto temp = computeBarycentric2D((float)x+i,(float)y+j,t.v);
                        const float alpha = std::get<0>(temp);
                        const float beta = std::get<1>(temp);
                        const float gamma = std::get<2>(temp);

                        const float Za = v[0].z();
                        const float Zb = v[1].z();
                        const float Zc = v[2].z();

                        float finalZ = 1.0/(alpha/Za+beta/Zb+gamma/Zc); //这里采用了仅针对z轴的插值形式,任意属性插值在下个作业中

                        //判断是否离相机最近,最近则更新片元
                        if(SSAA_depth_buf[4 * get_index(x,y) + cnt]>finalZ ){
                            Eigen::Vector3f color = t.getColor();
                            Eigen::Vector3f pixelPoint((float)x,(float)y,finalZ);
                            SSAA_depth_buf[4 * get_index(x,y) + cnt]=finalZ;
                            SSAA_frame_buf[4 * get_index(x,y) + cnt]=color;
                        }
                        
                    }
                    cnt++;
                }
            }
        }
    }
    //将SSAA采样后的结果求平均赋给frame_buf
    for(int x=minX;x<maxX;x++){
        for(int y=minY;y<maxY;y++){
            Eigen::Vector3f finalColor =( SSAA_frame_buf[4 * get_index(x,y) + 0] + 
                                         SSAA_frame_buf[4 * get_index(x,y) + 1] +
                                         SSAA_frame_buf[4 * get_index(x,y) + 2] +
                                         SSAA_frame_buf[4 * get_index(x,y) + 3] ) / 4.0;
            set_pixel( {(float)x,(float)y,0.0} , finalColor );
        }
    }
}

结果

采用SSAA的结果

不考虑其他三角形时产生的黑边效果

        

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用PyOpenGL的三角形网格绘制技术(triangulated mesh),通过离散化曲面上的点来近似表达该曲面。 具体步骤如下: 1. 定义曲面上的离散点集合,可以使用numpy的meshgrid函数生成一个二维网格点集合。 ```python import numpy as np x = np.linspace(-10, 10, 100) y = np.linspace(-10, 10, 100) X, Y = np.meshgrid(x, y) ``` 2. 计算每个离散点的z值。根据曲面公式计算每个点的z值。 ```python Z = -0.000*X**2 + 2.148*X*Y - 3.279*Y**2 + 0.262*X - 0.023*Y + 617.0 ``` 3. 定义三角形网格索引。使用numpy的reshape和concatenate函数将二维网格点集合转换为一维点集合,并定义三角形网格索引。 ```python indices = np.arange(0, X.size).reshape((X.shape[0]-1, X.shape[1]-1, 2, 3)) indices = np.concatenate((indices[:, :-1, :, :], indices[:, 1:, :, :]), axis=2) indices = np.concatenate((indices[:, :, 0, :], indices[:, :, 1, :]), axis=2) indices = indices.reshape((-1, 3)) ``` 4. 使用OpenGL绘制三角形网格。使用PyOpenGL的glBegin和glEnd函数对每个三角形进行绘制。 ```python from OpenGL.GL import * from OpenGL.GLUT import * def display(): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glBegin(GL_TRIANGLES) for i in range(indices.shape[0]): glVertex3f(X.flatten()[indices[i, 0]], Y.flatten()[indices[i, 0]], Z.flatten()[indices[i, 0]]) glVertex3f(X.flatten()[indices[i, 1]], Y.flatten()[indices[i, 1]], Z.flatten()[indices[i, 1]]) glVertex3f(X.flatten()[indices[i, 2]], Y.flatten()[indices[i, 2]], Z.flatten()[indices[i, 2]]) glEnd() glutSwapBuffers() glutInit() glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH) glutInitWindowSize(800, 800) glutCreateWindow("PyOpenGL Demo") glutDisplayFunc(display) glutMainLoop() ``` 运行代码后,就可以看到绘制出的曲面。可以使用鼠标拖动来旋转视角。 注意,由于这个曲面是开口向下的,因此需要开启深度测试(glEnable(GL_DEPTH_TEST))来正确显示。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值