• Lesson 6 重构代码


    Lesson 6

    重构源代码,下面将按照光栅器运行的顺序来介绍代码

    读取模型

    读取模型就是读取OBJ文件里的一系列数据,OBJ文件用文本方式打开就可以看见

    image-20220908182119965

    #ifndef __MODEL_H__
    #define __MODEL_H__
    #include 
    #include 
    #include "geometry.h"
    #include "tgaimage.h"
    
    class Model {
    private:
        std::vector verts_; //储存三角形顶点数据,v开头
        std::vector > faces_; //面数据,Vec3i保存的是 v/vt/vn
        std::vector norms_; //储存顶点的法向量 vn开头
        std::vector uv_;    //储存纹理坐标 vt开头
        TGAImage diffusemap_;
        TGAImage normalmap_;
        TGAImage specularmap_;
        //加载纹理
        void load_texture(std::string filename, const char* suffix, TGAImage& img);
    public:
        Model(const char* filename);
        ~Model();
        int nverts(); //返回顶点数量
        int nfaces(); //返回面数量
        Vec3f normal(int iface, int nthvert); //返回顶点法向量序号
        Vec3f normal(Vec2f uv);
        Vec3f vert(int i); //通过序号,返回顶点
        Vec3f vert(int iface, int nthvert);//返回顶点序号
        Vec2f uv(int iface, int nthvert); //返回纹理坐标序号
        TGAColor diffuse(Vec2f uv);
        float specular(Vec2f uv);
        std::vector face(int idx); //返回一个面的三个顶点
    };
    #endif //__MODEL_H__
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    #include 
    #include 
    #include 
    #include "model.h"
    
    Model::Model(const char* filename) : verts_(), faces_(), norms_(), uv_(), diffusemap_(), normalmap_(), specularmap_() {
        std::ifstream in;
        in.open(filename, std::ifstream::in);
        if (in.fail()) return;
        std::string line;
        while (!in.eof()) {
            std::getline(in, line);
            std::istringstream iss(line.c_str());
            char trash;
            if (!line.compare(0, 2, "v ")) { 				//读入顶点数据
                iss >> trash;
                Vec3f v;
                for (int i = 0; i < 3; i++) iss >> v[i];
                verts_.push_back(v);
            }
            else if (!line.compare(0, 3, "vn ")) {			//读入顶点法向量数据
                iss >> trash >> trash;
                Vec3f n;
                for (int i = 0; i < 3; i++) iss >> n[i];
                norms_.push_back(n);
            }
            else if (!line.compare(0, 3, "vt ")) {			//读入纹理坐标数据
                iss >> trash >> trash;
                Vec2f uv;
                for (int i = 0; i < 2; i++) iss >> uv[i];
                uv_.push_back(uv);
            }
            else if (!line.compare(0, 2, "f ")) {			//读入面数据
                std::vector f;
                Vec3i tmp;
                iss >> trash;
                while (iss >> tmp[0] >> trash >> tmp[1] >> trash >> tmp[2]) {
                    for (int i = 0; i < 3; i++) tmp[i]--; // in wavefront obj all indices start at 1, not zero
                    f.push_back(tmp);
                }
                faces_.push_back(f);
            }
        }
        std::cerr << "# v# " << verts_.size() << " f# " << faces_.size() << " vt# " << uv_.size() << " vn# " << norms_.size() << std::endl;
        //加载纹理
        load_texture(filename, "_diffuse.tga", diffusemap_);
        load_texture(filename, "_nm.tga", normalmap_);
        load_texture(filename, "_spec.tga", specularmap_);
    }
    
    Model::~Model() {}
    
    int Model::nverts() {
        return (int)verts_.size();
    }
    
    int Model::nfaces() {
        return (int)faces_.size();
    }
    
    std::vector Model::face(int idx) {
        std::vector face;
        for (int i = 0; i < (int)faces_[idx].size(); i++) face.push_back(faces_[idx][i][0]);
        return face;
    }
    
    Vec3f Model::vert(int i) {
        return verts_[i];
    }
    
    Vec3f Model::vert(int iface, int nthvert) {
        return verts_[faces_[iface][nthvert][0]];
    }
    
    void Model::load_texture(std::string filename, const char* suffix, TGAImage& img) {
        std::string texfile(filename);
        size_t dot = texfile.find_last_of(".");
        if (dot != std::string::npos) {
            texfile = texfile.substr(0, dot) + std::string(suffix);
            std::cerr << "texture file " << texfile << " loading " << (img.read_tga_file(texfile.c_str()) ? "ok" : "failed") << std::endl;
            img.flip_vertically();
        }
    }
    
    TGAColor Model::diffuse(Vec2f uvf) {
        Vec2i uv(uvf[0] * diffusemap_.get_width(), uvf[1] * diffusemap_.get_height());
        return diffusemap_.get(uv[0], uv[1]);
    }
    
    Vec3f Model::normal(Vec2f uvf) {
        Vec2i uv(uvf[0] * normalmap_.get_width(), uvf[1] * normalmap_.get_height());
        TGAColor c = normalmap_.get(uv[0], uv[1]);
        Vec3f res;
        for (int i = 0; i < 3; i++)
            res[2 - i] = (float)c[i] / 255.f * 2.f - 1.f;
        return res;
    }
    
    Vec2f Model::uv(int iface, int nthvert) {
        return uv_[faces_[iface][nthvert][1]];
    }
    
    float Model::specular(Vec2f uvf) {
        Vec2i uv(uvf[0] * specularmap_.get_width(), uvf[1] * specularmap_.get_height());
        return specularmap_.get(uv[0], uv[1])[0] / 1.f;
    }
    
    Vec3f Model::normal(int iface, int nthvert) {
        int idx = faces_[iface][nthvert][2];
        return norms_[idx].normalize();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    这就是导入模型数据的主要代码,其中涉及到TGA格式文件的使用,我也没太弄懂,但这不重要,我们的目的不是为了搞懂文件的具体编码,关注点应该放在渲染上面。

    计算矩阵

    模型导入完成后,现在需要对几个顶点着色器涉及到的矩阵进行计算处理

        lookat(eye, center, up);                                            //视图变换
        projection(-1.f / (eye - center).norm());                           //投影变换
        viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);     //视角矩阵
        light_dir.normalize();                                              //光源
    
    • 1
    • 2
    • 3
    • 4

    视图变换

    视图变换的推导可以查看视图变换,视图变换的目的就是把世界空间变成摄像机空间,以摄像机为中心,这里变换的最终结果就是摄像机的聚焦点center移到了原点,摄像机位于-z方向看向原点,也就是看向center

    void lookat(Vec3f eye, Vec3f center, Vec3f up) {
        Vec3f z = (eye - center).normalize();
        Vec3f x = cross(up, z).normalize();
        Vec3f y = cross(z, x).normalize();
        ModelView = Matrix::identity();
        Matrix translaition = Matrix::identity();
        Matrix rotation = Matrix::identity();
        for (int i = 0; i < 3; i++) {
            translaition[i][3] = -center[i];
        }
        for (int i = 0; i < 3; i++) {
            rotation[0][i] = x[i];
            rotation[1][i] = y[i];
            rotation[2][i] = z[i];
        }
        ModelView = rotation * translaition;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    投影变换

    这里采用的是透视投影,关于透视投影的推导可以查看投影

    void projection(float coeff) {
        Projection = Matrix::identity();
        Projection[3][2] = coeff; //coeff = -1/c
    }
    
    • 1
    • 2
    • 3
    • 4

    视角矩阵

    //视图矩阵,把模型坐标的(-1,1)转换成屏幕坐标的(100,700)
    //zbuffer从(-1,1)转换成0~255
    void viewport(int x, int y, int w, int h) {
        Viewport = Matrix::identity();
        Viewport[0][3] = x + w / 2.f;
        Viewport[1][3] = y + h / 2.f;
        Viewport[2][3] = 255.f / 2.f;
        Viewport[0][0] = w / 2.f;
        Viewport[1][1] = h / 2.f;
        Viewport[2][2] = 255.f / 2.f;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最后得到的 Viewport 矩阵是

    300 0 0 400

    0 300 0 400

    0 0 255/2 255/2

    0 0 0 0

    当一个顶点(x ,y , z, 1)和这个视角矩阵相乘时,得到的结果是

    (300x+400,300y+400,255/2*z+255/2,0)显然可以看出,模型坐标的(-1,1)转换成了屏幕坐标的(100,700),zbuffer从(-1,1)转换成0~255

    初始化image和zbuffer

    TGAImage image(width, height, TGAImage::RGB);
    TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);
    
    • 1
    • 2

    同样,关于TGA格式文件的详细内容不会进行文字说明

    初始化着色器

    //这里采用高洛德着色
    GouraudShader shader;
    
    • 1
    • 2
    struct GouraudShader : public IShader {
        //顶点着色器会将数据写入varying_intensity
        //片元着色器从varying_intensity中读取数据
        Vec3f varying_intensity;
        mat<2, 3, float> varying_uv;
        //接受两个变量,(面序号,顶点序号)
        virtual Vec4f vertex(int iface, int nthvert) {
            //根据面序号和顶点序号读取模型对应顶点,并扩展为4维 
            Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
            varying_uv.set_col(nthvert, model->uv(iface, nthvert));
            //变换顶点坐标到屏幕坐标(视角矩阵*投影矩阵*变换矩阵*v)
            //先进行视图变换,把世界空间转换到摄像机空间,再进行投影变换,把三维空间变成二维空间,即图片
            mat<4, 4, float> uniform_M = Projection * ModelView;
            mat<4, 4, float> uniform_MIT = ModelView.invert_transpose();
            gl_Vertex = Viewport * uniform_M * gl_Vertex;
            //计算光照强度(顶点法向量*光照方向)
            Vec3f normal = proj<3>(embed<4>(model->normal(iface, nthvert))).normalize();
            varying_intensity[nthvert] = std::max(0.f, model->normal(iface, nthvert) * light_dir); // get diffuse lighting intensity
            return gl_Vertex;
        }
        //根据传入的质心坐标,颜色,以及varying_intensity计算出当前像素的颜色
        virtual bool fragment(Vec3f bar, TGAColor& color) {
            Vec2f uv = varying_uv * bar;
            TGAColor c = model->diffuse(uv);
            float intensity = varying_intensity * bar;
            color = c * intensity;
            return false; 
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    绘制

        for (int i = 0; i < model->nfaces(); i++) {
            Vec4f screen_coords[3];
            for (int j = 0; j < 3; j++) {
                screen_coords[j] = shader.vertex(i, j);//为三角形的每个顶点调用顶点着色器
            }
            Vec2i uv[3];
            for (int k = 0; k < 3; k++) {
                uv[k] = model->uv(i, k);
            }
            triangle(screen_coords, shader, image, zbuffer);
        }
        image.flip_vertically();
        image.write_tga_file("output.tga");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    循环遍历每个面,再遍历每个面的三个顶点,为三个顶点调用顶点着色器进行处理,处理结束后,对这个三角形面再进行绘制

    绘制三角形

    void triangle(Vec4f* pts, IShader& shader, TGAImage& image, TGAImage& zbuffer) {
        //初始化三角形边界框
        Vec2f bboxmin(std::numeric_limits::max(), std::numeric_limits::max());
        Vec2f bboxmax(-std::numeric_limits::max(), -std::numeric_limits::max());
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 2; j++) {
                //这里pts除以了最后一个分量,实现了透视中的缩放,所以作为边界框
                bboxmin[j] = std::min(bboxmin[j], pts[i][j] / pts[i][3]);
                bboxmax[j] = std::max(bboxmax[j], pts[i][j] / pts[i][3]);
            }
        }
        //当前像素坐标P,颜色color
        Vec2i P;
        TGAColor color;
        //遍历边界框中的每一个像素
        for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++) {
            for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++) {
                //c为当前P对应的质心坐标
                //这里pts除以了最后一个分量,实现了透视中的缩放,所以用于判断P是否在三角形内
                Vec3f c = barycentric(proj<2>(pts[0] / pts[0][3]), proj<2>(pts[1] / pts[1][3]), proj<2>(pts[2] / pts[2][3]), proj<2>(P));
                //插值计算P的zbuffer
                //pts[i]为三角形的三个顶点
                //pts[i][2]为三角形的z信息(0~255)
                //pts[i][3]为三角形的投影系数(1-z/c)
    
                float z_P = (pts[0][2] / pts[0][3]) * c.x + (pts[0][2] / pts[1][3]) * c.y + (pts[0][2] / pts[2][3]) * c.z;
                int frag_depth = std::max(0, std::min(255, int(z_P + .5)));
                //P的任一质心分量小于0或者zbuffer小于已有zbuffer,不渲染
                if (c.x < 0 || c.y < 0 || c.z<0 || zbuffer.get(P.x, P.y)[0]>frag_depth) continue;
                //调用片元着色器计算当前像素颜色
                bool discard = shader.fragment(c, color);
                //通过判断片元着色器的返回值来丢弃当前像素
                if (!discard) {
                    //zbuffer
                    zbuffer.set(P.x, P.y, TGAColor(frag_depth));
                    //为像素设置颜色
                    image.set(P.x, P.y, color);
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    到这里我们的基本的绘制就结束了,这就是绘制一个三角形面的步骤,对于一个模型,我们这只需要不断地重复这些步骤就行了。

    接下来是一些功能函数

    功能函数

    计算重心坐标

    计算重心坐标的推导可以看重心坐标

    Vec3f barycentric(Vec2f A, Vec2f B, Vec2f C, Vec2f P) {
        Vec3f s[2];
        for (int i = 2; i--; ) {
            s[i][0] = C[i] - A[i];
            s[i][1] = B[i] - A[i];
            s[i][2] = A[i] - P[i];
        }
        Vec3f u = cross(s[0], s[1]);
        if (std::abs(u[2]) > 1e-2)
            return Vec3f(1.f - (u.x + u.y) / u.z, u.y / u.z, u.x / u.z);
        return Vec3f(-1, 1, 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    着色器基类

    struct IShader {
        virtual ~IShader();
        virtual Vec4f vertex(int iface, int nthvert) = 0;
        virtual bool fragment(Vec3f bar, TGAColor& color) = 0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面涉及到TGA文件的代码我没有讲解,因为源码较为繁杂,我也没咋看,并且不是此次课程的重点,只需要知道TGA的函数的功能即可,就略过。不了解TGA文件并不会影响渲染部分的代码的阅读。

    复习一下步骤

    读取模型数据->初始化矩阵->初始化着色器->对顶点进行顶点着色器处理->绘制处理完的顶点形成的面->重复

  • 相关阅读:
    mysql8.0以上的版本忘记密码如何重置
    【毕业设计】基于单片机的智能避障超声波跟随小车 - 物联网 嵌入式
    使用Github Copilot完成代码编写
    iOS APP 转让避坑指南
    Linux常用指令--时间操作指令
    base_network
    图像识别与处理学习笔记(三)形态学和图像分割
    数据库表中创建字段查询出来却为NULL?
    Git,Gitee,GitHub使用总结(内含命令行详细操作)
    已解决ModuleNotFoundError: No module named‘ pip‘(重新安装pip的两种方式)
  • 原文地址:https://blog.csdn.net/qq_51599283/article/details/126772817