1

我正在使用 C++ 开发路径/光线追踪器。我现在正在加载 obj 文件。但是有些对象在加载后会翻转法线。我无法弄清楚这种行为来自何处或如何解决它。

请参阅图片以更好地理解该问题。

显示当前行为的图像:

图像

链接到完整的 GitHub 页面

首先,我认为这是表面后面的法线的问题。但是在基于表面法线渲染颜色之后。很明显,法线在某些情况下是翻转的。

这是我加载模型的非常基本的代码。

    //OBJ Loader object.
    bool OBJLoader::loadMesh (std::string filePath){
        // If the file is not an .obj file return false
        if (filePath.substr(filePath.size() - 4, 4) != ".obj"){
            std::cout << "No .obj file found at given file location: "<<filePath << std::endl;

        }
        //Open file stream
        std::ifstream file(filePath);

        //check if file is open.
        if (!file.is_open()){
            std::cout << "File was not opened!" << std::endl;
            return false;
        }

        //Do file loading.
        std::cout << "Parsing obj-file: "<<filePath << std::endl;

        //constuct mesh data.
        bool smoothShading = false;
        std::string obj_name;
        std::vector<Vertex> vertices;
        std::vector<Vect3> Positions;
        std::vector<Vect3> Normals;
        std::vector<Vect2> UVs;

        std::vector<unsigned int> V_indices;

        //the current line
        std::string currentLine;
        //loop over each line and parse the needed data.
        while(std::getline(file, currentLine)){

            //for now we just print the line
            //std::cout << currentLine << std::endl;

            if(algorithm::startsWith(currentLine, "s ")){
                std::vector<std::string> line_split = algorithm::split(currentLine,' ');
                if( line_split[1] == std::string("off")){
                    smoothShading = false;
                }else if(line_split[1] == std::string("1")){
                    //enalbe smooth shading;
                    smoothShading = true;
                }
            }

            //check if the line starts with v -> vertex.
            if(algorithm::startsWith(currentLine, "o ")){
                //construct new vertex position.
                std::vector<std::string> line_split = algorithm::split(currentLine,' ');

                obj_name = line_split[1];
            }

            //check if the line starts with v -> vertex.
            if(algorithm::startsWith(currentLine, "v ")){
                //construct new vertex position.
                std::vector<std::string> line_split = algorithm::split(currentLine,' ');

                float x = std::stof(line_split[1]);
                float y = std::stof(line_split[2]);
                float z = std::stof(line_split[3]);
                Vect3 pos = Vect3(x,y,z);
                Positions.push_back(pos);
            }

            //check if the line starts with vt -> vertex uv.
            if(algorithm::startsWith(currentLine, "vt ")){
                //construct new vertex uv.
                std::vector<std::string> line_split = algorithm::split(currentLine,' ');

                float u = std::stof(line_split[1]);
                float v = std::stof(line_split[2]);
                Vect2 uv = Vect2(u,v);
                UVs.push_back(uv);
            }

            //check if the line starts with vn -> vertex normals.
            if(algorithm::startsWith(currentLine, "vn ")){
                //construct new vertex normal.
                std::vector<std::string> line_split = algorithm::split(currentLine,' ');

                float x = std::stof(line_split[1]);
                float y = std::stof(line_split[2]);
                float z = std::stof(line_split[3]);
                Vect3 normal = Vect3(x,y,z);
                Normals.push_back(normal);
            }

            //check if the line starts with f -> constuct faces.
            if(algorithm::startsWith(currentLine, "f ")){
                //construct new vertex position.

                std::vector<std::string> line_split = algorithm::split(currentLine,' ');

                //@NOTE: this only works when mesh is already triangulated.
                //Parse all vertices.
                std::vector<std::string> vertex1 = algorithm::split(line_split[1],'/');
                std::vector<std::string> vertex2 = algorithm::split(line_split[2],'/');
                std::vector<std::string> vertex3 = algorithm::split(line_split[3],'/');

                if(vertex1.size() <= 1){
                    //VERTEX 1
                    Vect3 position = Positions[std::stoi(vertex1[0])-1];
                    Vertex v1(position);
                    vertices.push_back(v1);

                    //VERTEX 2
                    position = Positions[std::stoi(vertex2[0])-1];
                    Vertex v2(position);
                    vertices.push_back(v2);

                    //VERTEX 3
                    position = Positions[std::stoi(vertex3[0])-1];
                    Vertex v3(position);
                    vertices.push_back(v3);

                    //Add to Indices array.
                    //calculate the index number
                    //The 3 comes from 3 vertices per face.
                    unsigned int index = vertices.size() - 3;
                    V_indices.push_back(index);
                    V_indices.push_back(index+1);
                    V_indices.push_back(index+2);
                }

                //check if T exist.
                else if(vertex1[1] == ""){
                    //NO Uv
                    //V -> index in the positions array.
                    //N -> index in the normals array.

                    //VERTEX 1
                    Vect3 position = Positions[std::stoi(vertex1[0])-1];
                    Vect3 normal = Normals[std::stoi(vertex1[2])-1];

                    Vertex v1(position,normal);
                    vertices.push_back(v1);

                    //VERTEX 2
                    position = Positions[std::stoi(vertex2[0])-1];
                    normal = Normals[std::stoi(vertex2[2])-1];

                    Vertex v2(position,normal);
                    vertices.push_back(v2);

                    //VERTEX 3
                    position = Positions[std::stoi(vertex3[0])-1];
                    normal = Normals[std::stoi(vertex3[2])-1];

                    Vertex v3(position,normal);
                    vertices.push_back(v3);

                    //Add to Indices array.
                    //calculate the index number
                    //The 3 comes from 3 vertices per face.
                    unsigned int index = vertices.size() - 3;
                    V_indices.push_back(index);
                    V_indices.push_back(index+1);
                    V_indices.push_back(index+2);

                }else if (vertex1[1] != ""){
                    //We have UV
                    //V -> index in the positions array.
                    //T -> index of UV
                    //N -> index in the normals array.

                    //VERTEX 1
                    Vect3 position = Positions[std::stoi(vertex1[0])-1];
                    Vect2 uv = UVs[std::stoi(vertex1[1])-1];
                    Vect3 normal = Normals[std::stoi(vertex1[2])-1];

                    Vertex v1(position,normal,uv);
                    vertices.push_back(v1);

                    //VERTEX 2
                    position = Positions[std::stoi(vertex2[0])-1];
                    uv = UVs[std::stoi(vertex2[1])-1];
                    normal = Normals[std::stoi(vertex2[2])-1];

                    Vertex v2(position,normal,uv);
                    vertices.push_back(v2);

                    //VERTEX 3
                    position = Positions[std::stoi(vertex3[0])-1];
                    uv = UVs[std::stoi(vertex3[1])-1];
                    normal = Normals[std::stoi(vertex3[2])-1];

                    Vertex v3(position,normal,uv);
                    vertices.push_back(v3);

                    //Add to Indices array.
                    //calculate the index number
                    //The 3 comes from 3 vertices per face.
                    unsigned int index = vertices.size() - 3;
                    V_indices.push_back(index);
                    V_indices.push_back(index+1);
                    V_indices.push_back(index+2);
                }

                //We can check here in which format. V/T/N, V//N, V//, ...
                //For now we ignore this and use V//N.
            }
        }

        //close stream
        file.close();

        Positions.clear();
        Normals.clear();
        UVs.clear();

        //reorder the arrays so the coresponding index match the position,uv and normal.
        for (Vertex v: vertices) {
            Positions.push_back(v.getPosition());
            Normals.push_back(v.getNormal());
            UVs.push_back(v.getUV());
        }

        //Load mesh data.
        _mesh = Mesh(smoothShading,obj_name, Positions, Normals, UVs, V_indices);

        //return true, succes.
        return true;

在此之后,模型被插入到网格结构中以进行更快的交叉测试。

    for(int i= 0;i<mesh._indices.size();i=i+3){
        Triangle* tri;
        if(mesh.smoothShading){
            tri = new SmoothTriangle(Point3(mesh._positions[mesh._indices[i]]),
                                     Point3(mesh._positions[mesh._indices[i+1]]),
                                     Point3(mesh._positions[mesh._indices[i+2]]),
                                     Normal(mesh._normals[mesh._indices[i]]),
                                     Normal(mesh._normals[mesh._indices[i+1]]),
                                     Normal(mesh._normals[mesh._indices[i+2]]),material);
        }else{
            tri = new Triangle(Point3(mesh._positions[mesh._indices[i]]),
                               Point3(mesh._positions[mesh._indices[i+1]]),Point3(mesh._positions[mesh._indices[i+2]]),Normal(mesh._normals[mesh._indices[i]]),material);
        }

        add_object(tri);
    }
    constructCells();

可能需要添加的是用于插入法线的代码

Normal SmoothTriangle::calculate_normal(double gamma, double beta){
    return (Normal((1 - beta - gamma) * n0 + beta * n1 + gamma * n2)).normalize();
}

固定的

我修复了这个问题。它不在我的 OBJ 加载器中。该模型是从搅拌机中导出的,并且在导出时应用了所有修改器,但是在导出到 .obj 文件后,Solidify 导致一些背面与正面剪辑。删除此修饰符后,一切都恢复了“正常”(只是一个有趣的双关语来完成这个答案)

4

1 回答 1

1

您的代码可能没有错我假设 obj 已损坏,因为某些 obj 模型已翻转法线...

Wavefront obj格式根本没有指定法线方向 我看到的模型甚至没有一致性,所以一些法线指出了其他的。你甚至不能确定面有单一的缠绕规则。所以使用双向法线更安全(你知道使用

|dot(normal,light)|

代替

dot(normal,light)

加载后无需自行剔除面部或重新计算法线甚至缠绕规则。

双向法线/照明有时由面部每一侧的不同材质设置FRONTBACK/FRONT_AND_BACKDOUBLE_SIDED等或其配置设置......只需在您的 gfx API 中查找此类内容。关闭面部剔除寻找类似的东西CULL_FACE

于 2019-06-11T08:55:46.313 回答