如何根据Wavefront(.obj)文件中给出的纹理索引对纹理位置进行排序?

Ste*_*ski 5 c++ opengl stdvector wavefront

我正在尝试为OpenGL项目创建一个Wavefront(.obj)文件加载器.我正在使用的方法是逐行进行并分离矢量(std :: vectors)中的顶点位置,纹理位置和正常位置,并且我将它们的索引(顶点,纹理和法线索引)存储在三个单独的中向量(来自文件的'f'行,每个面).

我无法根据纹理索引对完整的纹理坐标进行排序.我能够将顶点渲染到正确的位置,因为我的'loader'类需要索引,但我无法弄清楚如何以任何方式对纹理坐标进行排序,因此纹理在某些三角形上看起来像是一个偏移结果.

偏移纹理的多维数据集的图像:

IMG

纹理(.png)的图像,它应该如何在每张脸上:

IMG

编辑:这是.obj文件和.mtl文件的链接. Google云端硬盘.

这是我的OBJLoader.cpp文件:

    rawObj.open(filePath); // Open file

    while (!rawObj.eof()) {
        getline(rawObj, line); // Read line

        // Read values from each line 
        // starting with a 'v' for 
        // the vertex positions with
        // a custom function (gets the word in a line
        // at position i)

        if (strWord(line, 1) == "v") {   
            for (int i = 2; i <= 4; i++) {
                std::string temp;
                temp = strWord(line, i);
                vertexStrings.push_back(temp);
            }

        // Same for texture positions

        } else if (strWord(line, 1) == "vt") {     
            for (int i = 2; i <= 3; i++) {
                std::string temp;
                temp = strWord(line, i);
                textureStrings.push_back(temp);
            }

        // Same for normal positions

        } else if (strWord(line, 1) == "vn") {     // normals
            for (int i = 2; i <= 4; i++) {
                std::string temp;
                temp = strWord(line, i);
                normalStrings.push_back(temp);
            }

        // Separate each of the three vertices and then separate 
        // each vertex into its vertex index, texture index and
        // normal index

        } else if (strWord(line, 1) == "f") {      // faces (indices)
            std::string temp;

            for (int i = 2; i <= 4; i++) {
                temp = strWord(line, i);
                chunks.push_back(temp);

                k = std::stoi(strFaces(temp, 1));
                vertexIndices.push_back(k-1);

                l = std::stoi(strFaces(temp, 2));
                textureIndices.push_back(l-1);

                m = std::stoi(strFaces(temp, 3));
                normalIndices.push_back(m-1);

            }

        }
    }

    // Convert from string to float

    for (auto &s : vertexStrings) {
        std::stringstream parser(s);
        float x = 0;

        parser >> x;

        vertices.push_back(x);
    }

    for (auto &s : textureStrings) {
        std::stringstream parser(s);
        float x = 0;

        parser >> x;

        texCoords.push_back(x);
    }

    // Y coords are from top left instead of bottom left
    for (int i = 0; i < texCoords.size(); i++) {
        if (i % 2 != 0)
            texCoords[i] = 1 - texCoords[i];
    }

    // Passes vertex positions, vertex indices and texture coordinates 
    // to loader class
    return loader.loadToVao(vertices, vertexIndices, texCoords);
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试在循环中插入texCoords [textureIndices [i]]中的值(vector.insert),但这不起作用并使输出更糟.我试过一个简单的:

tempVec[i] = texCoords[textureIndices[i]] 
Run Code Online (Sandbox Code Playgroud)

在一个for循环但但也没有用.

我已经完成了整个项目,并且我确定排序是问题的原因,因为当我插入立方体的硬编码值时,它完美地工作并且纹理根本没有偏移.(OpenGL命令/图像加载器正在按预期工作.)

最终,还有另一种基于textureIndices对texCoords进行排序的方法吗?

Spe*_*tre 1

我想在我的引擎中实现这个(为 obj 文件添加纹理)很长一段时间,你的问题让我有心情真正做到这一点:)。

您作为纹理提供的图像看起来更像是预览而不是纹理。此外,纹理坐标与它不对应,正如您在预览中看到的那样:

3D预览

如果你看一下纹理坐标:

vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102 
Run Code Online (Sandbox Code Playgroud)

只有 2 个数字:

0.736102
0.263898
Run Code Online (Sandbox Code Playgroud)

这对于纹理中不存在的纹理中轴对齐的四边形或方形子图像有意义。此外,纹理点的数量没有意义,20 它应该只是4。因此你会感到困惑。

无论如何,Rabbid76是对的,你需要重复点......它相对容易,所以:

  1. 提取所有位置、颜色、纹理点和法线

    从您的 obj 文件到单独的表中。因此解析以 开头的行v,vt,vn并从中创建 4 个表。是的 4 因为颜色有时会被编码vv x y z r g b某些 3D 扫描仪的输出。

    所以你应该有这样的东西:

    vt 0.736102 0.263898
    vt 0.263898 0.736102
    vt 0.263898 0.263898
    vt 0.736102 0.263898
    vt 0.263898 0.736102
    vt 0.263898 0.263898
    vt 0.736102 0.263898
    vt 0.263898 0.736102
    vt 0.263898 0.263898
    vt 0.736102 0.263898
    vt 0.263898 0.736102
    vt 0.263898 0.263898
    vt 0.736102 0.263898
    vt 0.263898 0.736102
    vt 0.263898 0.263898
    vt 0.736102 0.736102
    vt 0.736102 0.736102
    vt 0.736102 0.736102
    vt 0.736102 0.736102
    vt 0.736102 0.736102 
    
    Run Code Online (Sandbox Code Playgroud)
  2. 处理面f

    现在您应该将上述表格作为临时数据处理,并从头开始为网格创建实际数据到新结构中(或将其直接加载到 VBO)。因此,您需要的是将所有数据重新索引f为所有现有索引的唯一组合。为此,您需要跟踪您已经拥有的内容。为此,我有趣的是这个结构:

    0.736102
    0.263898
    
    Run Code Online (Sandbox Code Playgroud)

    vertex因此,创建现在处理第一行的空列表f并提取索引

    f 1/1/1 2/2/2 3/3/3
    
    Run Code Online (Sandbox Code Playgroud)

    因此,对于脸上的每个点(一次只处理一个)提取其ppos,ptxr,pnor索引。现在检查它是否已经存在于您的最终网格数据中。如果是,则使用其索引。如果没有将新点添加到网格具有的所有表中 ( pos,col,txr,nor) 并使用新添加点的索引。

    当处理完面的所有点后,将具有重新索引索引的面添加到最终的网格面中并处理下一f行。

可以肯定的是,这是我在引擎中使用的Wavefront OBJ加载器 C++ 类(但它取决于引擎本身,所以你不能直接使用它,它只是为了查看代码的结构以及如何对其进行编码...从头开始可能很困难)。

double ppos[]= // v
    {
    -1.000000, 1.000000, 1.000000,
    -1.000000,-1.000000,-1.000000,
    -1.000000,-1.000000, 1.000000,
    -1.000000, 1.000000,-1.000000,
     1.000000,-1.000000,-1.000000,
     1.000000, 1.000000,-1.000000,
     1.000000,-1.000000, 1.000000,
     1.000000, 1.000000, 1.000000,
     };
double pcol[]= // v
    {
    };
double ptxr[]= // vt
    {
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.736102,
    0.736102,0.736102,
    0.736102,0.736102,
    0.736102,0.736102,
    0.736102,0.736102,
    };
double pnor[]=  // vn
    {
    -0.5774, 0.5774, 0.5774,
    -0.5774,-0.5774,-0.5774,
    -0.5774,-0.5774, 0.5774,
    -0.5774, 0.5774,-0.5774,
     0.5774,-0.5774,-0.5774,
     0.5774, 0.5774,-0.5774,
     0.5774,-0.5774, 0.5774,
     0.5774, 0.5774, 0.5774,
    };
Run Code Online (Sandbox Code Playgroud)

它尚未使用该*.mtl文件(我对预览的纹理进行了硬编码)。

附言。如果我用它作为纹理:

质地

结果如下:

预览

我在这里使用了很多我自己的东西,所以一些解释:


str_load_str(s,i,true)返回表示 string 中索引i中第一个有效单词的字符串s。true 意味着只是i用 中的新位置进行更新s
str_load_lin(s,i,true)返回表示 string 中索引的行 (tillCRLFCRLFor LFCR) 的i字符串s。true 意味着仅i更新该行之后的新位置。
txt_load_...是相同的,但不是从字符串中读取,而是读取表单BYTE*CHAR*如果您愿意的话。

请注意AnsiString索引形式1BYTE*,CHAR*from 0

我还使用我的动态列表模板:


List<double> xxx;double xxx[];
xxx.add(5);与添加5到列表末尾 相同
xxx[7]访问数组元素(安全)
xxx.dat[7]访问数组元素(不安全但快速直接访问)
xxx.num是数组的实际使用大小
xxx.reset()清除数组并为项目设置xxx.num=0
xxx.allocate(100)预分配空间100

这里使用 mtl 文件中的纹理更新了更快的重新索引代码(其他内容被忽略,目前仅支持单个对象/纹理):

//---------------------------------------------------------------------------
//--- Wavefront obj librrary ver: 2.11 --------------------------------------
//---------------------------------------------------------------------------
#ifndef _model_obj_h
#define _model_obj_h
//---------------------------------------------------------------------------
class model_obj
    {
public:

    class vertex
        {
    public:
        int pos,txr,nor;
        vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
        int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
        int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
        int operator <  (vertex &a)
            {
            if (pos>a.pos) return 0;
            if (pos<a.pos) return 1;
            if (txr>a.txr) return 0;
            if (txr<a.txr) return 1;
            if (nor<a.nor) return 1;
            return 0;
            }
        void ld(int p,int t,int n) { pos=p; txr=t; nor=n; }
        };

    class vertexes
        {
    public:
        List<vertex> pv;    // vertexes in order
        List<int> ix;       // inex sort ASC for faster access
        int m;              // power of 2 >= ix.num
        vertexes(){}; vertexes(vertexes& a){ *this=a; }; ~vertexes(){}; vertexes* operator = (const vertexes *a) { *this=*a; return this; }; /*vertexes* operator = (const vertexes &a) { ...copy... return this; };*/
        void reset() { m=0; pv.num=0; ix.num=0; }
        bool get(int &idx,vertex &v)        // find idx so pv[idx]<=v and return if new vertex was added
            {
            int i,j;
            // handle first point
            if (ix.num<=0)
                {
                m=1;
                idx=0;
                pv.add(v);
                ix.add(0);
                return true;
                }
            // bin search closest idx
            for (j=0,i=m;i;i>>=1)
                {
                j|=i;
                if (j>=ix.num) { j^=i; continue; }
                if (v<pv.dat[ix.dat[j]]) j^=i;
                }
            // stop if match found
            idx=ix.dat[j];
            if (v==pv.dat[idx]) return false;
            // add new index,vertex if not
            idx=pv.num; pv.add(v); j++;
            if (j>=ix.num) ix.add(idx);
             else ix.ins(j,idx);
            if (ix.num>=m+m) m<<=1;
            return true;
            }
        };

    struct material
        {
        AnsiString nam,txr;
        material(){}; material(material& a){ *this=a; }; ~material(){}; material* operator = (const material *a) { *this=*a; return this; }; /*material* operator = (const material &a) { ...copy... return this; };*/
        };

    List<material> mat;
    OpenGL_VAO obj;

    model_obj();
    ~model_obj();
    void reset();

    void load(AnsiString name);
    int  save(OpenGL_VAOs &vaos);
    };
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
model_obj::model_obj()
    {
    reset();
    }
//---------------------------------------------------------------------------
model_obj::~model_obj()
    {
    reset();
    }
//---------------------------------------------------------------------------
void model_obj::reset()
    {
    obj.reset();
    mat.reset();
    }
//---------------------------------------------------------------------------
void model_obj::load(AnsiString name)
    {
    AnsiString path=ExtractFilePath(name);
    int   adr,siz,hnd;
    BYTE *dat;

    reset();
    siz=0;
    hnd=FileOpen(name,fmOpenRead);
    if (hnd<0) return;
    siz=FileSeek(hnd,0,2);
        FileSeek(hnd,0,0);
    dat=new BYTE[siz];
    if (dat==NULL) { FileClose(hnd); return; }
    FileRead(hnd,dat,siz);
    FileClose(hnd);

    AnsiString s,s0,t;
    int     a,i,j;
    double  alpha=1.0;
    List<double> f;
    List<int> pos,txr,nor;
    List<double> ppos,pcol,pnor,ptxr;   // OBJ parsed data
    vertex v;
    vertexes pver;
    material m0,*m=NULL;

    f.allocate(6);
    pver.reset();

    ppos.num=0;
    pcol.num=0;
    pnor.num=0;
    ptxr.num=0;
    obj.reset();
//                              purpose,    location,                   type,datatype,datacomponents,pack_acc);
    obj.addVBO(_OpenGL_VBO_purpose_pos ,vbo_loc_pos ,        GL_ARRAY_BUFFER,GL_FLOAT,             3,     0.0001);
    obj.addVBO(_OpenGL_VBO_purpose_col ,vbo_loc_col ,        GL_ARRAY_BUFFER,GL_FLOAT,             4,     0.0001);
    obj.addVBO(_OpenGL_VBO_purpose_txr0,vbo_loc_txr0,        GL_ARRAY_BUFFER,GL_FLOAT,             2,     0.0001);
    obj.addVBO(_OpenGL_VBO_purpose_nor ,vbo_loc_nor ,        GL_ARRAY_BUFFER,GL_FLOAT,             3,     0.0001);
    obj.addVBO(_OpenGL_VBO_purpose_fac ,          -1,GL_ELEMENT_ARRAY_BUFFER,  GL_INT,             3,     0.0);
    obj.draw_mode=GL_TRIANGLES;
    obj.rep.reset();
    obj.filename=name;

    _progress_init(siz); int progress_cnt=0;
    for (adr=0;adr<siz;)
        {
        progress_cnt++; if (progress_cnt>=1024) { progress_cnt=0; _progress(adr); }

        s0=txt_load_lin(dat,siz,adr,true);
        a=1; s=str_load_str(s0,a,true);

        // clear temp vector in case of bug in obj file
        f.num=0; for (i=0;i<6;i++) f.dat[i]=0.0;

        if (s=="v")
            {
            f.num=0;
            for (;;)
                {
                s=str_load_str(s0,a,true);
                if ((s=="")||(!str_is_num(s))) break;
                f.add(str2num(s));
                }
            if (f.num>=3)
                {
                ppos.add(f[0]);
                ppos.add(f[1]);
                ppos.add(f[2]);
                }
            if (f.num==6)
                {
                pcol.add(f[3]);
                pcol.add(f[4]);
                pcol.add(f[5]);
                }
            }
        else if (s=="vn")
            {
            f.num=0;
            for (;;)
                {
                s=str_load_str(s0,a,true);
                if ((s=="")||(!str_is_num(s))) break;
                f.add(str2num(s));
                }
            pnor.add(f[0]);
            pnor.add(f[1]);
            pnor.add(f[2]);
            }
        else if (s=="vt")
            {
            f.num=0;
            for (;;)
                {
                s=str_load_str(s0,a,true);
                if ((s=="")||(!str_is_num(s))) break;
                f.add(str2num(s));
                }
            ptxr.add(f[0]);
            ptxr.add(f[1]);
            }
        else if (s=="f")
            {
            pos.num=0;
            txr.num=0;
            nor.num=0;
            for (;;)
                {
                s=str_load_str(s0,a,true); if (s=="") break;
                for (t="",i=1;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) pos.add(str2int(t)-1);
                for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) txr.add(str2int(t)-1);
                for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t)))