如何根据 Wavefront (.obj) 文件中给定的纹理索引对纹理位置进行排序?
How do I sort the texture positions based on the texture indices given in a Wavefront (.obj) file?
我目前正在尝试为 OpenGL 项目制作 Wavefront (.obj) 文件加载器。我目前使用的方法是逐行分离向量 (std::vectors) 中的顶点位置、纹理位置和法线位置,我将它们的索引(顶点、纹理和法线索引)存储在三个单独的位置向量(来自文件的 'f' 行,每张脸)。
我无法根据纹理索引对充满纹理坐标的向量进行排序。我能够在正确的位置渲染顶点,因为我的 'loader' class 调用索引,但我不知道如何以任何方式对纹理坐标进行排序,所以纹理看起来结果在一些三角形上偏移。
具有偏移纹理的立方体图像:
纹理图像 (.png),它在每个面上应该如何显示:
编辑:这是 .obj 文件和 .mtl 文件的 link。
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进行排序?
如果顶点坐标和纹理坐标的索引不同,则顶点位置必须“重复”。
顶点坐标及其属性(如纹理坐标)形成一个元组。每个顶点坐标必须有自己的纹理坐标和属性。您可以将 3D 顶点坐标和 2D 纹理坐标视为单个 5D 坐标。
See Rendering meshes with multiple indices.
假设您有一个像这样的 .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),没关系,但对于大文件,
尤其是索引中的线性搜索table,将不得不改进。
我只是想告诉你如何阅读 .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;
}
我想在我的引擎中实现这个(为 obj 文件添加纹理)很长时间了,你的问题让我有心情实际去做:)。
您作为纹理提供的图像看起来更像是预览而不是纹理。正如您在预览中看到的那样,纹理坐标也与它不对应:
如果你看纹理坐标:
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
这对于纹理中不存在的轴对齐四边形或正方形 sub-image 有意义。纹理点的数量也没有意义 20
它应该只是 4
。因此你得到了困惑。
无论如何Rabbid76是对的你需要复制点......它相对容易所以:
提取所有位置、颜色、纹理点和法线
从您的 obj 文件到单独的表中。因此解析以 v,vt,vn
开头的行并从中创建 4 个表。是 4,因为颜色有时在 v
中编码为 v x y z r g b
作为某些 3D 扫描仪的输出。
所以你应该有这样的东西:
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,
};
处理人脸f
现在您应该将上述表格作为临时数据处理,并从头开始为您的网格创建新结构的真实数据(或将其直接加载到 VBO)。因此,您需要的是将所有 f
数据重新索引为所有现有索引的唯一组合。为此,您需要跟踪您已经拥有的东西。为此,我取笑了这个结构:
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); }
};
因此创建 vertex
的空列表现在首先处理 f
行并提取索引
f 1/1/1 2/2/2 3/3/3
所以对于面部中的每个点(一次只处理一个点)提取其 ppos,ptxr,pnor
索引。现在检查它是否已经存在于您的最终网格数据中。如果是,请改用其索引。如果不向您的网格 (pos,col,txr,nor
) 的所有表添加新点并使用新添加点的索引。
处理完一个面的所有点后,将具有重新索引索引的面添加到最终网格面中并处理下一个 f
行。
确定这是我的 Wavefront OBJ 加载程序 C++ class 我在我的引擎中使用(但这取决于引擎本身,所以你不能使用它直接就是看看代码的结构以及如何对其进行编码...因为从头开始可能很困难)。
//---------------------------------------------------------------------------
//--- 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
文件(我为预览硬编码了纹理)。
PS. 如果我将其用作纹理:
结果如下所示:
我这里用了很多自己的东西,所以解释一下:
str_load_str(s,i,true)
returns 表示字符串 s
中索引 i
的第一个有效单词的字符串。 true 意味着 i
更新为 s
中的新位置。
str_load_lin(s,i,true)
returns 表示索引 [=31= 直到 CR
或 LF
或 CRLF
或 LFCR
的行的字符串] 在字符串 s
中。 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
项预分配 space
这里是更新的更快的重新索引代码,带有来自 mtl 文件的纹理(其他内容被忽略,目前只支持单个 object/texture):
//---------------------------------------------------------------------------
//--- 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
//---------------------------------------------------------------------------
Appart 添加的 materials(现在只是纹理和 material 名称)我更改了 re-indexing 所以顶点是索引排序的,二进制搜索用于获取顶点索引一经请求。使用此 100K faces Standford dragon (3.4MByte) 在 3.7 秒内加载:
我目前正在尝试为 OpenGL 项目制作 Wavefront (.obj) 文件加载器。我目前使用的方法是逐行分离向量 (std::vectors) 中的顶点位置、纹理位置和法线位置,我将它们的索引(顶点、纹理和法线索引)存储在三个单独的位置向量(来自文件的 'f' 行,每张脸)。
我无法根据纹理索引对充满纹理坐标的向量进行排序。我能够在正确的位置渲染顶点,因为我的 'loader' class 调用索引,但我不知道如何以任何方式对纹理坐标进行排序,所以纹理看起来结果在一些三角形上偏移。
具有偏移纹理的立方体图像:
纹理图像 (.png),它在每个面上应该如何显示:
编辑:这是 .obj 文件和 .mtl 文件的 link。 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进行排序?
如果顶点坐标和纹理坐标的索引不同,则顶点位置必须“重复”。
顶点坐标及其属性(如纹理坐标)形成一个元组。每个顶点坐标必须有自己的纹理坐标和属性。您可以将 3D 顶点坐标和 2D 纹理坐标视为单个 5D 坐标。
See Rendering meshes with multiple indices.
假设您有一个像这样的 .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),没关系,但对于大文件, 尤其是索引中的线性搜索table,将不得不改进。
我只是想告诉你如何阅读 .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;
}
我想在我的引擎中实现这个(为 obj 文件添加纹理)很长时间了,你的问题让我有心情实际去做:)。
您作为纹理提供的图像看起来更像是预览而不是纹理。正如您在预览中看到的那样,纹理坐标也与它不对应:
如果你看纹理坐标:
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
这对于纹理中不存在的轴对齐四边形或正方形 sub-image 有意义。纹理点的数量也没有意义 20
它应该只是 4
。因此你得到了困惑。
无论如何Rabbid76是对的你需要复制点......它相对容易所以:
提取所有位置、颜色、纹理点和法线
从您的 obj 文件到单独的表中。因此解析以
v,vt,vn
开头的行并从中创建 4 个表。是 4,因为颜色有时在v
中编码为v x y z r g b
作为某些 3D 扫描仪的输出。所以你应该有这样的东西:
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, };
处理人脸
f
现在您应该将上述表格作为临时数据处理,并从头开始为您的网格创建新结构的真实数据(或将其直接加载到 VBO)。因此,您需要的是将所有
f
数据重新索引为所有现有索引的唯一组合。为此,您需要跟踪您已经拥有的东西。为此,我取笑了这个结构: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); } };
因此创建
vertex
的空列表现在首先处理f
行并提取索引f 1/1/1 2/2/2 3/3/3
所以对于面部中的每个点(一次只处理一个点)提取其
ppos,ptxr,pnor
索引。现在检查它是否已经存在于您的最终网格数据中。如果是,请改用其索引。如果不向您的网格 (pos,col,txr,nor
) 的所有表添加新点并使用新添加点的索引。处理完一个面的所有点后,将具有重新索引索引的面添加到最终网格面中并处理下一个
f
行。
确定这是我的 Wavefront OBJ 加载程序 C++ class 我在我的引擎中使用(但这取决于引擎本身,所以你不能使用它直接就是看看代码的结构以及如何对其进行编码...因为从头开始可能很困难)。
//---------------------------------------------------------------------------
//--- 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
文件(我为预览硬编码了纹理)。
PS. 如果我将其用作纹理:
结果如下所示:
我这里用了很多自己的东西,所以解释一下:
str_load_str(s,i,true)
returns 表示字符串 s
中索引 i
的第一个有效单词的字符串。 true 意味着 i
更新为 s
中的新位置。
str_load_lin(s,i,true)
returns 表示索引 [=31= 直到 CR
或 LF
或 CRLF
或 LFCR
的行的字符串] 在字符串 s
中。 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
项预分配 space
这里是更新的更快的重新索引代码,带有来自 mtl 文件的纹理(其他内容被忽略,目前只支持单个 object/texture):
//---------------------------------------------------------------------------
//--- 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
//---------------------------------------------------------------------------
Appart 添加的 materials(现在只是纹理和 material 名称)我更改了 re-indexing 所以顶点是索引排序的,二进制搜索用于获取顶点索引一经请求。使用此 100K faces Standford dragon (3.4MByte) 在 3.7 秒内加载: