Games101:作业3(管线分析、深度插值、libpng warning、双线性插值等)

本文详细分析了一个图形学作业,涵盖了图形管线、顶点和三角形处理、光栅化、深度插值、着色模型(normal、phong、texture、bump和displacement)。作者在实现过程中遇到的问题、解决方案以及对代码的解释贯穿全文,揭示了从理论到实践的转换过程。此外,还讨论了双线性插值在提升纹理效果中的应用。

目录

0.作业介绍:

1.0.0 管线分析:

1.1.0 main函数

1.2.0 draw函数

1.3.0 rasterizer_triangle函数

1.3.1 重心坐标 computeBarycentric2D

1.3.2 深度插值

2.0.0 着色模型介绍

2.1 normal着色模型

2.2 phong模型

2.3 texture模型

2.3.1 Segmentation fault 

2.3.2 libpng warning:iCCP:known incorrect sRGB profile

2.4 bump和displacement模型

3.0 提高部分:双线性插值:

4.0 结语

参考链接


0.作业介绍:

         如上图所示为本次作业需要完成的任务,在介绍我对于本次作业的心得体会之前,我想先主观的感叹一句,我真的觉得这次作业很难。虽然上课时理论的学习是可以接受并理解的,但实操问题真的很多,我感觉我本次的作业基本上是“临摹”出来的。理论与实践脱轨的一个关键原因,我认为是有很多变量、对象没有弄清,没有理解它们对应的物理意义,所以在编写代码有些无从下手,不知道怎么把公式“变现”,所以我决定很有必要先介绍一下整个代码的实现流程,其中部分流程其实是在前两个作业已经出现的,但因为当时代码相对好写,所以没有彻底弄清楚,哎~

1.0.0 管线分析:

        在Shading中,闫老师讲了图形管线的一系列操作[1],概括下来分别是[2]:

  1. Vertex Processing 点操作 :将三维空间上的点投影到二维
  2. Triangle Processing 三角形操作 :将三个点连成一个三角形/线,构造出一个平面/线,使得原三维空间物体变成由若干三角形组成的物体
  3. Raserization 光栅化:进行离散化的采样,用像素表示采样结果
  4. Fragment Processing 片元处理 :着色
  5. Framebuffer Operations片元缓冲操作:根据深度信息,确定遮挡和可视情况

        下面我就按照管线的顺序,理一下整个流程,记录在注释里

1.1.0 main函数

int main(int argc, const char** argv)
{
    //记录组成三维图形的所有小三角形
    std::vector<Triangle*> TriangleList;

    float angle = 140.0;
    bool command_line = false;

    std::string filename = "output.png";
    objl::Loader Loader;
    std::string obj_path = "./models/spot/";

    // Load .obj File,加载模型
    bool loadout = Loader.LoadFile("./models/spot/spot_triangulated_good.obj");
    for(auto mesh:Loader.LoadedMeshes)
    {
        //对于图形中的每个面(即一个小三角形)的三个点记录在一起
        for(int i=0;i<mesh.Vertices.size();i+=3)
        {
            Triangle* t = new Triangle();
            //用Triangle类,记录一个小三角形的三个顶点的信息:
            //setVertex顶点位置,setNormal顶点的法线,setTexCoord顶点对应的纹理
            for(int j=0;j<3;j++)
            {
                t->setVertex(j,Vector4f(mesh.Vertices[i+j].Position.X,mesh.Vertices[i+j].Position.Y,mesh.Vertices[i+j].Position.Z,1.0));
                t->setNormal(j,Vector3f(mesh.Vertices[i+j].Normal.X,mesh.Vertices[i+j].Normal.Y,mesh.Vertices[i+j].Normal.Z));
                t->setTexCoord(j,Vector2f(mesh.Vertices[i+j].TextureCoordinate.X, mesh.Vertices[i+j].TextureCoordinate.Y));
            }
            //每三个顶点构成的一个小三角形放入TriangleList
            TriangleList.push_back(t);
        }
    }
    
    //初始化光栅化对象,定义屏幕长宽
    rst::rasterizer r(700, 700);
 
    //记录纹理到对象,注意rasterizer.hpp类有属性 optional<Texture> texture
    auto texture_path = "hmap.jpg";
    r.set_texture(Texture(obj_path + texture_path));
    
    //记录片元处理方式,类似于“赋值函数”,现默认方式是phong
    std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = phong_fragment_shader;

    //处理传入的参数,注意在此处根据调用方式不同,修改了rasterizer对象的片元处理方式!
    if (argc >= 2)
    {
        command_line = true;
        filename = std::string(argv[1]);

        if (argc == 3 && std::string(argv[2]) == "texture")
        {
            std::cout << "Rasterizing using the texture shader\n";
            active_shader = texture_fragment_shader;
            texture_path = "spot_texture.png";
            r.set_texture(Texture(obj_path + texture_path));
        }
        else if (argc == 3 && std::string(argv[2]) == "normal")
        {
            std::cout << "Rasterizing using the normal shader\n";
            active_shader = normal_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "phong")
        {
            std::cout << "Rasterizing using the phong shader\n";
            active_shader = phong_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "bump")
        {
            std::cout << "Rasterizing using the bump shader\n";
            active_shader = bump_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "displacement")
        {
            std::cout << "Rasterizing using the bump shader\n";
            active_shader = displacement_fragment_shader;
        }
    }
    //人眼所在位置
    Eigen::Vector3f eye_pos = {0,0,10};
    
    //设置顶点着色方式,获取顶点位置
    r.set_vertex_shader(vertex_shader);
    //设置片元着色方式,根据调用方式不同已赋值到active_shader
    r.set_fragment_shader(active_shader);

    int key = 0;//修改这个值,可以选择输出图片或动态旋转渲染的模型
    int frame_count = 0;

    if (command_line)
    {
        //清空两个缓冲区,在最后处理遮挡显示情况时发挥作用
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);
        //分别设置MVP矩阵,用于对点操作,实现将三维的点映射到平面
        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));
        
        //光栅化、片元处理
        r.draw(TriangleList);
        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);

        return 0;
    }

    while(key != 'q')
    {
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);

        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));

        //r.draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
        r.draw(TriangleList);
        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::imshow("image", image);
        cv::imwrite(filename, image);
        key = cv::waitKey(0);

        if (key == 'a' )
        {
            angle -= 0.1;
        }
        else if (key == 'd')
        {
            angle += 0.1;
        }

    }
    return 0;
}

         简而言之,主函数实现了:

将顶点以三角形的方式每三个记录在一起,下图是Triangle类的一些属性,注意留意各个变量的名称和存储的内容,可以发现这一个类里装了三个顶点的顶点坐标、颜色、对应纹理坐标、法线。 

设置光栅化器的MVP模型(用于将三维的点变换到屏幕空间)

根据调用命令的不同设置光栅化器的着色方式,之后便进入了rasterizer的draw函数。

1.2.0 draw函数

void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {
    //zfar和znear之间的距离的一半
    float f1 = (50 - 0.1) / 2.0;
    //zfar个znear的中心z坐标
    float f2 = (50 + 0.1) / 2.0;

    //MVP变换矩阵
    Eigen::Matrix4f mvp = projection * view * model;
    
    //对每个小三角形进行操作,注意这个for循环一直到draw函数的结尾,
    //故管线从此处开始其实是对每一个小三角形进行的(着色、深度处理等)
    for (const auto& t:TriangleList)
    {
        Triangle newtri = *t;
        
        //这里记录了只进行了MV变换的三角形的三个顶点,在本次作业中将此作为了视图空间viewspace
        //的坐标,用于后续确定光源与物体表面的作用
        //详见[3]https://games-cn.org/forums/topic/zuoye3-interpolated_shadingcoords/
        std::array<Eigen::Vector4f, 3> mm {
                (view * model * t->v[0]),
                (view * model * t->v[1]),
                (view * model * t->v[2])
        };

        //记录viewspace的点坐标
        std::array<Eigen::Vector3f, 3> viewspace_pos;
        std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
            return v.template head<3>();
        });

        //经过MVP后的点坐标,投影到屏幕
        Eigen::Vector4f v[] = {
                mvp * t->v[0],
                mvp * t->v[1],
                mvp * t->v[2]
        };
        
        //x,y,z同除w,得到齐次坐标
        //Homogeneous division
        for (auto& vec : v) {
            vec.x()/=vec.w();
            vec.y()/=vec.w();
            vec.z()/=vec.w();
        }
        
        //这里是为了计算仅进行了MV操作,即在viewspace下各顶点的法向量,具体解释见下文
        Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();
        Eigen::Vector4f n[] = {
                inv_trans * to_vec4(t->normal[0], 0.0f),
                inv_trans * to_vec4(t->normal[1], 0.0f),
                inv_trans * to_vec4(t->normal[2], 0.0f)
        };
        
        //视图变换,直接对X,Y,Z进行改变,而未使用变换矩阵,效果是相同的
        //Viewport transformation
        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;
        }
        
        //记录下经过MVP、齐次坐标变换和视图变换的顶点坐标
        //完成顶点变换,变换到屏幕空间
        for (int i = 0; i < 3; ++i)
        {
            //screen space coordinates
            newtri.setVertex(i, v[i]);
        }

        //存储法向量n
        for (int i = 0; i < 3; ++i)
        {
            //view space normal
            newtri.setNormal(i, n[i].head<3>());
        }
        
        //设置颜色
        newtri.setColor(0, 148,121.0,92.0);
        newtri.setColor(1, 148,121.0,92.0);
        newtri.setColor(2, 148,121.0,92.0);

        //调用rasterizer_triangle,实现光栅化
        //注意此时仍在循环中,故实际是对每个小三角形分别进行光栅化
        //此时也传入了viewspace的顶点坐标,用于判断真实的光线作用
        // Also pass view space vertice position
        rasterize_triangle(newtri, viewspace_pos);
    }
}

        概括说,draw函数的作用在于对于每一个小三角形,分别计算了:

①仅进行MV操作、在viewspace空间中的顶点坐标,保存在mm,后传给了viewspace_pos,并传递到rasterizer_tirangle函数中,该坐标的作用在于后续计算光线作用,详见[3]:

经过MVP变换的顶点坐标,保存在v,后进行了齐次坐标化,注意此时并未处理齐次坐标的w维,因为经过了MVP变换后,w坐标记录了原本的z值,这对于后续进行插值十分有用,等下文插值求深度时再展开说。

viewspace下的顶点的法向量,保存在n,目的是在判断光线作用时,需要知道没有变形的三维物体的形状、位置信息,该法向量n和上方说的viewspace_pos皆是如此。

注意代码最初令newtri == *t,即利用newtri记录小三角形三个顶点的位置、法线、纹理、颜色,但后续修改了①位置,将其改成了经过MVP变换后的,②法线,将其改成了viewspace空间的。同时,viewspace的位置坐标虽然没有记录在newtri中,但是以参数的形式传递给了后续的rasterizer_t

### C语言中libpng警告`iCCP: known incorrect sRGB profile`的原因 此警告的根本原因是某些PNG图像文件中的`sRGB`配置文件存在错误或不符合标准。具体来说,`libpng`库在解析这些PNG文件时发现其内部嵌入的国际色彩联盟(ICC)配置文件存在问题[^1]。这种问题可能源于图像编辑软件生成的PNG文件未严格遵循`sRGB`规范。 当使用C语言调用`libpng`库来读取此类PNG文件时,如果启用了警告功能,则会出现上述消息。尽管这只是一个警告而非致命错误,但它可能会干扰程序的日志记录或其他正常操作流程[^3]。 ### 解决方法概述 #### 方法一:修正原始图片的颜色空间 最根本的方法是对有问题的PNG文件进行预处理,使其符合标准。可以通过专门的图形处理工具调整图像的颜色配置文件: - **ImageMagick**: 使用命令行工具`convert`将图片重新编码并移除潜在的问题配置文件。 ```bash convert input.png -strip output.png ``` - **pngcrush**: 此工具能够优化PNG文件的同时修复常见的元数据问题。 ```bash pngcrush -rem alla -reduce input.png output.png ``` 这两种方式都可以有效去除可能导致警告的信息,从而避免运行期间触发警告[^4]。 #### 方法二:升级libpng版本 确保使用的`libpng`库是最新的稳定版。较新的版本往往已经修补了许多已知缺陷,并改进了对非标准输入的兼容性和健壮性。对于开发者而言,在构建阶段指定更高版本的依赖项是一个可行的选择: ```bash pip install --upgrade libpng ``` 虽然这条指令适用于Python环境下的包管理器pip,但在C/C++环境下同样建议手动下载最新源码编译安装[^3]。 #### 方法三:忽略特定警告 (仅限必要情况) 如果不希望更改现有资源又无法更新第三方库,还可以考虑抑制这类特定类型的警告输出。不过这种方法治标不治本,应谨慎采用。例如设置自定义回调函数过滤掉不需要的消息: ```c #include <stdio.h> #include <stdlib.h> #include <png.h> void custom_warning_fn(png_structp png_ptr, png_const_charp warning_msg){ /* Ignore the specific ICC Profile related warnings */ if(strstr(warning_msg,"known incorrect sRGB profile")==NULL){ fprintf(stderr,"%s\n",warning_msg); } } int main(){ png_structp png_ptr; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,(png_error_ptr)NULL,&custom_warning_fn); // Continue with normal PNG reading operations... return EXIT_SUCCESS; } ``` 以上代码片段展示了如何创建一个定制化的警告处理器,它会选择性跳过关于“incorrect sRGB”的通知[^5]。 ### 总结 综上所述,针对`libpng warning: iCCP: known incorrect sRGB profile`这一现象,推荐优先采取措施纠正源头素材的质量;其次才是探索其他变通手段如屏蔽冗余日志或是替换基础组件至更先进的实现形式。 问题
评论 25
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值