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

5
我目前正在尝试为OpenGL项目制作Wavefront (.obj)文件加载器。我目前使用的方法是逐行进行,并将顶点位置、纹理位置和法线位置分别存储在向量 (std::vectors) 中,并将它们的索引 (顶点、纹理和法线索引) 存储在三个独立的向量中 (从文件的 'f' 行开始,对于每个面)。
我在对装满纹理坐标的向量根据纹理索引进行排序时遇到了问题。我能够以正确的位置渲染顶点,因为我的 'loader' 类调用了这些索引,但我无法弄清楚如何以任何方式对纹理坐标进行排序,因此一些三角形上的纹理看起来错位。
带有偏移纹理的立方体图像: img 纹理图像 (.png) 的图像,应该在每个面上: img 编辑:这是 .obj 文件和 .mtl 文件的链接。 Google Drive。 这是我的 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);
}

我尝试在循环中插入来自texCoords [textureIndices [i]]的值(vector.insert),但这并没有起作用,反而使输出更糟。 我尝试了一个简单的方法:

tempVec[i] = texCoords[textureIndices[i]] 

我曾经尝试在for循环中进行排序,但也没有起作用。

我已经遍历了整个项目并确定排序是问题的原因,因为当我为立方体插入硬编码值时,它可以完美地工作,并且纹理没有任何偏移。(OpenGL命令/图像加载器正常工作。)

最终,是否有另一种方法可以根据textureIndices对texCoords进行排序?


1
如果顶点坐标和纹理坐标有不同的索引,则必须“复制”顶点位置。 顶点坐标及其属性(如纹理坐标)形成数据记录。您可以将3D顶点坐标和2D纹理坐标想象为单个5D坐标。 请参见使用多个索引渲染网格 - Rabbid76
@Rabbid76 对的,这就是它们存储在顶点数组对象中的方式。位置 (x,y,z) 接着是纹理坐标 (x,y)。但我不确切知道为什么顶点位置需要复制。(稍微提供一些背景信息,在创建 .obj 文件时,我确保每条边都被拆分了) - user9114500
“但我不太确定为什么需要复制顶点位置。”因为你有三组索引vertexIndicestextureIndicesnormalIndices。你必须将它们合并为一个索引集,该集合引用长度相等的三个数组,包括顶点坐标、纹理坐标和法向量。 - Rabbid76
1
最好也分享模型(obj、mtl、纹理)... - Spektre
1
@Spektre 已编辑并附上了 .mtl 和 .obj 文件的链接。贴子中已经包含了贴图。 - user9114500
显示剩余3条评论
2个回答

3
如果顶点坐标和纹理坐标有不同的索引,那么顶点位置必须被“复制”。 顶点坐标及其属性(例如纹理坐标)形成一个元组。每个顶点坐标必须具有自己的纹理坐标和属性。您可以将3D顶点坐标和2D纹理坐标视为单个5D坐标。 请参见使用多个索引渲染网格
假设您有一个像这样的.obj文件:
v -1 -1 -1
v  1 -1 -1
v -1  1 -1
v  1  1 -1
v -1 -1  1
v  1 -1  1
v -1  1  1
v  1  1  1 

vt 0 0
vt 0 1
vt 1 0
vt 1 1

vn -1  0  0 
vn  0 -1  0
vn  0  0 -1
vn  1  0  0
vn  0  1  0
vn  0  0  1

f 3/1/1 1/2/1 5/4/1 7/3/1
f 1/1/2 2/2/2 3/4/2 6/3/2
f 3/1/3 4/2/3 2/4/3 1/3/3
f 2/1/4 4/2/4 8/4/4 6/3/4
f 4/1/5 3/2/5 7/4/5 8/3/5
f 5/1/6 6/2/6 8/4/6 7/3/6

从这里开始,您需要找到面规范中使用的所有顶点坐标、纹理纹理坐标和法向量索引的组合:

 0 : 3/1/1 
 1 : 1/2/1
 2 : 5/4/1
 3 : 7/3/1
 4 : 1/1/2
 5 : 2/2/2
 6 : 3/4/2
 7 : 6/3/2
 8 : ...

然后你需要创建一个顶点坐标、纹理坐标和法向量数组,与索引组合数组相对应。 顶点坐标及其属性可以组合在一个数据集中,也可以分为三个具有相同属性数量的数组:

 index   vx vy vz     u v     nx ny nz
 0 :     -1  1 -1     0 0     -1  0  0
 1 :     -1 -1 -1     0 1     -1  0  0
 2 :     -1 -1  1     1 1     -1  0  0
 3 :     -1  1  1     1 0     -1  0  0
 4 :     -1 -1 -1     0 0      0 -1  0
 5 :      1 -1 -1     0 1      0 -1  0
 6 :     -1  1 -1     1 1      0 -1  0
 7 :      1 -1  1     1 0      0 -1  0
 8 : ...

看看这个非常简单的C++函数,可以读取像你链接的那样的.obj文件。 该函数读取文件并将数据写入元素向量和属性向量。
请注意,该函数可以进行优化,并且不关心性能。 对于小文件(如您所喜欢的cube3.obj),这并不重要,但是对于大文件, 特别是索引表中的线性搜索,必须改进。
我只是试图给您一个想法,如何读取.obj文件以及如何创建元素和属性向量,这些向量可以直接用于使用OpenGL绘制网格。
#include <vector>
#include <array>
#include <string>
#include <fstream>
#include <strstream>
#include <algorithm>

bool load_obj( 
    const std::string          filename, 
    std::vector<unsigned int> &elements,
    std::vector<float>        &attributes )
{
    std::ifstream obj_stream( filename, std::ios::in );
    if( !obj_stream )
        return false;

    // parse the file, line by line
    static const std::string white_space = " \t\n\r";
    std::string token, indices, index;
    float value;
    std::vector<float> v, vt, vn;
    std::vector<std::array<unsigned int, 3>> f;
    for( std::string line; std::getline( obj_stream, line ); )
    {
        // find first non whispce characterr in line
        size_t start = line.find_first_not_of( white_space );
        if ( start == std::string::npos )
            continue;

        // read the first token
        std::istringstream line_stream( line.substr(start) );
        line_stream.exceptions( 0 );
        line_stream >> token;

        // ignore comment lines
        if ( token[0] == '#' )
            continue;

        // read the line
        if ( token == "v" ) // read vertex coordinate
        {
            while ( line_stream >> value )  
                v.push_back( value );
        }
        else if ( token == "vt" ) // read normal_vectors 
        {
            while ( line_stream >> value )
                vt.push_back( value );
        }
        else if ( token == "vn" )  // read normal_vectors 
        {
            while ( line_stream >> value )
                vn.push_back( value );
        }
        else if ( token == "f" )
        {
            // read faces
            while( line_stream >> indices )
            {
                std::array<unsigned int, 3> f3{ 0, 0, 0 };
                // parse indices
                for ( int j=0; j<3; ++ j )
                {
                    auto slash = indices.find( "/" );
                    f3[j] = std::stoi(indices.substr(0, slash), nullptr, 10);
                    if ( slash == std::string::npos )
                        break;
                    indices.erase(0, slash + 1);
                }
            
                // add index
                auto it = std::find( f.begin(), f.end(), f3 );
                elements.push_back( (unsigned int)(it - f.begin()) );
                if ( it == f.end() )
                    f.push_back( f3 );
            }
        }
    }

    // create array of attributes from the face indices
    for ( auto f3 : f )
    {
        if ( f3[0] > 0 )
        {
            auto iv = (f3[0] - 1) * 3;
            attributes.insert( attributes.end(), v.begin() + iv, v.begin() + iv + 3 );
        }

        if ( f3[1] > 0 )
        {
            auto ivt = (f3[1] - 1) * 2;
            attributes.insert( attributes.end(), vt.begin() + ivt, vt.begin() + ivt + 2 );
        }

        if ( f3[2] > 0 )
        {
            auto ivn = (f3[2] - 1) * 3;
            attributes.insert( attributes.end(), vn.begin() + ivn, vn.begin() + ivn + 3 );
        }
    }

    return true;
}

谢谢你的回答。我太菜了,没法实现它,所以我暂时会使用别人的加载器。你的方法看起来是正确的。祝你好运! - user9114500
1
@StefanIvanovski 我添加了一个简单的函数,可以加载 .obj* 文件。也许它能帮到你。 - Rabbid76
谢谢,非常感激 :) - user9114500

1
我很久以前就想在我的引擎中实现这个功能(为obj文件添加纹理),而你的问题让我有了实际去做它的心情:)。
你提供的图像看起来更像是预览而不是纹理。此外,如您在预览中所见,纹理坐标与其不对应。

3D preview

如果您查看纹理坐标:
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 

这里只有2个数字:

0.736102
0.263898

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

无论如何,Rabbid76 是正确的,您需要复制点……这相对容易,所以:

  1. extract all positions, colors, texture points and normals

    from your obj file into separate tables. So parse lines starting with v,vt,vn and create 4 tables from it. Yes 4 as color is sometimes encoded in v as v x y z r g b as output from some 3D scanners.

    So you should have something like this:

    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,
        };
    
  2. process faces f

    now you should handle the above tables as temp data and create real data for your mesh from scratch into new structure (or load it directly to VBOs). So what you need is to reindex all the f data to unique combinations of all the indexes present. To do that you need to keep track what you already got. For that I amusing this structure:

    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); }
        };
    

    so create empty list of vertex now process first f line and extract indexes

    f 1/1/1 2/2/2 3/3/3
    

    so for each point (process just one at a time) in the face extract its ppos,ptxr,pnor index. Now check if it is already present in your final mesh data. If yes use its index instead. If not add new point to all tables your mesh have (pos,col,txr,nor) and use index of newly added point.

    When all points of a face was processed add the face with the reindexed indexes into your final mesh faces and process next f line.

这是我在我的引擎中使用的Wavefront OBJ加载器C++类(但它依赖于引擎本身,因此您不能直接使用它,它只用于查看代码结构和如何编码...从头开始可能会很困难)。

//---------------------------------------------------------------------------
//--- 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); }
        };

    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();
    }
//---------------------------------------------------------------------------
void model_obj::load(AnsiString 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;
    List<vertex> pv;

    f.allocate(6);

    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))) nor.add(str2int(t)-1);
                }
            // reindex and or duplicate vertexes if needed
            for (i=0;i<pos.num;i++)
                {
                // wanted vertex
                               v.pos=pos[i];
                if (txr.num>0) v.txr=txr[i]; else v.txr=-1;
                if (nor.num>0) v.nor=nor[i]; else v.nor=-1;
                // is present in VBO?
                for (j=0;j<pv.num;j++)
                 if (v==pv[j])
                  { pos[i]=j; j=-1; break; }
                // if not add it
                if (j>=0)
                    {
                    j=v.pos; j=j+j+j;   if (pcol.num>0) obj.addpntcol(ppos[j+0],ppos[j+1],ppos[j+2],pcol[j+0],pcol[j+1],pcol[j+2],alpha);
                                         else           obj.addpnt   (ppos[j+0],ppos[j+1],ppos[j+2]);
                    j=v.nor; j=j+j+j;   if (v.nor>=0)   obj.addnor   (pnor[j+0],pnor[j+1],pnor[j+2]);
                    j=v.txr; j=j+j;     if (v.txr>=0)   obj.addtxr   (ptxr[j+0],ptxr[j+1]);
                    pos[i]=pv.num; pv.add(v);
                    }
                }
            for (i=2;i<pos.num;i++) obj.addface(pos[0],pos[i-1],pos[i]);
            }
        }
    _progress_done();
    delete[] dat;


    }
//---------------------------------------------------------------------------
int model_obj::save(OpenGL_VAOs &vaos)
    {
    int vaoix0=-1;
    OpenGL_VBO *vn=obj.getVBO(_OpenGL_VBO_purpose_nor );
    if (vn->data.num==0) obj.nor_compute();
    vaos.vao=obj;
    vaoix0=vaos.add(obj);
    return vaoix0;
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

它目前尚未使用 *.mtl 文件(我在预览中硬编码了纹理)。请注意,如果我将此作为纹理使用:

texture

结果如下所示:

preview

我在这里使用了很多自己的东西,因此需要解释一下:


str_load_str(s,i,true) 返回表示字符串 s 中从索引 i 开始的第一个有效单词的字符串。true 表示更新 is 中的新位置。
str_load_lin(s,i,true) 返回表示字符串 s 中从索引 i 开始的一行(直到 CRLFCRLFLFCR)。true 表示更新 i 为该行后面的新位置。
txt_load_... 与上述类似,但它是从 BYTE*CHAR* 中读取而不是从字符串中读取。

请注意,AnsiString 的索引从 1 开始,而 BYTE*,CHAR* 的索引从 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))) nor.add(str2int(t)-1);
                }
            // reindex and or duplicate vertexes if needed
            for (i=0;i<pos.num;i++)
                {
                // wanted vertex
                               v.pos=pos[i];
                if (txr.num>0) v.txr=txr[i]; else v.txr=-1;
                if (nor.num>0) v.nor=nor[i]; else v.nor=-1;
                if (pver.get(pos[i],v)) // is present in VBO? if not add it
                    {
                    j=v.pos; j=j+j+j;   if (pcol.num>0) obj.addpntcol(ppos[j+0],ppos[j+1],ppos[j+2],pcol[j+0],pcol[j+1],pcol[j+2],alpha);
                                         else           obj.addpnt   (ppos[j+0],ppos[j+1],ppos[j+2]);
                    j=v.nor; j=j+j+j;   if (v.nor>=0)   obj.addnor   (pnor[j+0],pnor[j+1],pnor[j+2]);
                    j=v.txr; j=j+j;     if (v.txr>=0)   obj.addtxr   (ptxr[j+0],ptxr[j+1]);
                    }
                }
            for (i=2;i<pos.num;i++) obj.addface(pos[0],pos[i-1],pos[i]);
            }
        else if (s=="mtllib")
            {
            AnsiString s1;
            int   adr,siz,hnd;
            BYTE *dat;
            // extract mtl filename
            s=str_load_str(s0,a,true);
            s+=str_load_lin(s0,a,true);
            // load it to memory
            siz=0;
            hnd=FileOpen(path+s,fmOpenRead);
            if (hnd<0) continue;
            siz=FileSeek(hnd,0,2);
                FileSeek(hnd,0,0);
            dat=new BYTE[siz];
            if (dat==NULL) { FileClose(hnd); continue; }
            FileRead(hnd,dat,siz);
            FileClose(hnd);
            // extract textures and stuff
            m=&m0;
            for (adr=0;adr<siz;)
                {
                s1=txt_load_lin(dat,siz,adr,true);
                a=1; s=str_load_str(s1,a,true);
                if (s=="newmtl")
                    {
                    s=str_load_str(s1,a,true);
                    s+=str_load_lin(s1,a,true);
                    mat.add();
                    m=&mat[mat.num-1];
                    m->nam=s;
                    m->txr="";
                    }
                else if (s=="map_Kd")
                    {
                    s=str_load_str(s1,a,true);
                    s+=str_load_lin(s1,a,true);
                    m->txr=s;
                    }
                }
            delete[] dat;
            m=NULL;
            }
        else if (s=="usemtl")
            {
            // extract material name
            s=str_load_str(s0,a,true);
            s+=str_load_lin(s0,a,true);
            // find it in table
            for (m=mat.dat,i=0;i<mat.num;i++,m++)
             if (m->nam==s) { i=-1; break; }
            if (i>=0) m=NULL;
            }
        }
    // textures
    for (i=0;i<mat.num;i++)
     if (mat[i].txr!="")
        {
        OpenGL_VAO::_TXR txr;
        txr.ix=-1;
        txr.unit=txr_unit_map;
        txr.filename=mat[i].txr;
        txr.txrtype=GL_TEXTURE_2D;
        txr.repeat=GL_REPEAT;
        obj.txr.add(txr);
        }

    _progress_done();
    delete[] dat;
    }
//---------------------------------------------------------------------------
int model_obj::save(OpenGL_VAOs &vaos)
    {
    int vaoix0=-1,i;
    OpenGL_VBO *vn=obj.getVBO(_OpenGL_VBO_purpose_nor );
    if (vn) if (vn->data.num==0) obj.nor_compute();
    vaos.vao=obj;

    vaoix0=vaos.add(obj);
    return vaoix0;
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

除了添加的材质(目前只有纹理和材质名称),我还改变了重新索引的方式,使顶点按索引排序,并使用二分查找来按需获取顶点索引。使用这种方法,100K faces Standford dragon (3.4MByte) 可以在3.7秒内加载:

dragon


非常感谢!我会看看能否根据迄今为止的答案实现自己的版本。真的非常感激 :) 编辑:是的,纹理有点缩放,因此纹理坐标不是1和0。 - user9114500
1
@StefanIvanovski 顺便说一下,我刚刚更新了我的加载器,加入了mtl文件解析器,并且不得不重写重新索引的“vertex”到索引排序列表,以提高加载速度,因为朴素的O(n ^ 2)重新索引对于大型模型来说太慢了。如果你想要,我可以发布更新后的代码,但它不像我发布的那个简单,并且可能更难理解(所以我在犹豫是否添加它...) - Spektre
1
@StefanIvanovski,我决定也添加新代码(幸运的是它仍适合SO限制)。我还添加了一些关于我使用的东西的描述,以帮助理解它的作用... PS重要的事情(重新索引)发生在代码的else if (s=="f")条件部分。 - Spektre
再次感谢您!我会确保再次审查代码并理解发生了什么 :) - user9114500
1
@StefanIvanovski imgur终于又可以使用了,所以我终于可以添加龙的预览,并找到了获取100K龙obj模型的链接,因此您可以拥有相同的网格进行速度测试(但该网格不需要重新索引,仅用于速度比较)。 - Spektre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接