我目前正在尝试为 OpenGL 项目制作 Wavefront (.obj) 文件加载器.我目前使用的方法是逐行分离向量 (std::vectors) 中的顶点位置、纹理位置和法线位置,我将它们的索引(顶点、纹理和法线索引)存储在三个独立的向量(来自文件的f"行,对于每个面).
我无法根据纹理索引对充满纹理坐标的向量进行排序.我能够在正确的位置渲染顶点,因为我的加载器"类调用索引,但我无法弄清楚如何以任何方式对纹理坐标进行排序,因此纹理在某些三角形上看起来偏移结果.
具有偏移纹理的立方体图像:
纹理 (.png) 图像,它应该如何在每个面上:
这是 .obj 文件和 .mtl 文件的链接.
如果你看纹理坐标:
vt 0.736102 0.263898vt 0.263898 0.736102vt 0.263898 0.263898vt 0.736102 0.263898vt 0.263898 0.736102vt 0.263898 0.263898vt 0.736102 0.263898vt 0.263898 0.736102vt 0.263898 0.263898vt 0.736102 0.263898vt 0.263898 0.736102vt 0.263898 0.263898vt 0.736102 0.263898vt 0.263898 0.736102vt 0.263898 0.263898vt 0.736102 0.736102vt 0.736102 0.736102vt 0.736102 0.736102vt 0.736102 0.736102vt 0.736102 0.736102
只有两个数字:
0.7361020.263898
这对于纹理中不存在于纹理中的轴对齐的四边形或方形子图像是有意义的.另外纹理点的数量没有意义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
数据重新索引为所有现有索引的唯一组合.要做到这一点,你需要跟踪你已经得到的东西.为此,我很喜欢这个结构:
类顶点{上市:int pos,txr,nor;顶点(){};顶点(顶点& a){ *this = a;};~顶点(){};顶点* 运算符 = (const 顶点 *a) { *this=*a;返回这个;};/*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++ 类(但它取决于引擎本身,因此您不能直接使用它,只是为了查看代码以及如何对其进行编码......因为从头开始可能会很困难).
//---------------------------------------------------------------------//--- Wavefront obj 库版本:2.11 --------------------------------------//---------------------------------------------------------------------------#ifndef _model_obj_h#define _model_obj_h//---------------------------------------------------------------------------//---------------------------------------------------------------------------//---------------------------------------------------------------------------类 model_obj{上市:类顶点{上市:int pos,txr,nor;顶点(){};顶点(顶点& a){ *this = a;};~顶点(){};顶点* 运算符 = (const 顶点 *a) { *this=*a;返回这个;};/*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 对象;模型对象();~model_obj();无效重置();无效负载(AnsiString名称);int save(OpenGL_VAOs &vaos);};//---------------------------------------------------------------------------//---------------------------------------------------------------------------//---------------------------------------------------------------------------model_obj::model_obj(){重启();}//---------------------------------------------------------------------------model_obj::~model_obj(){重启();}//---------------------------------------------------------------------------void model_obj::reset(){obj.reset();}//---------------------------------------------------------------------------void model_obj::load(AnsiString 名称){int adr,siz,hnd;字节 *dat;重启();尺寸=0;hnd=FileOpen(name,fmOpenRead);如果 (hnd<0) 返回;siz=FileSeek(hnd,0,2);FileSeek(hnd,0,0);数据=新字节[大小];if (dat==NULL) { FileClose(hnd);返回;}FileRead(hnd,dat,siz);文件关闭(hnd);AnsiString s,s0,t;int a,i,j;双阿尔法=1.0;列表<double>F;列表pos,txr,nor;列表<double>ppos、pcol、pnor、ptxr;//OBJ 解析的数据顶点 v;列表<顶点>光伏;f.分配(6);ppos.num=0;pcol.num=0;pnor.num=0;ptxr.num=0;obj.reset();//目的、位置、类型、数据类型、数据组件、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;对于 (adr=0;adr=1024) { progress_cnt=0;_progress(adr);}s0=txt_load_lin(dat,siz,adr,true);a=1;s=str_load_str(s0,a,true);//清除临时向量以防 obj 文件中的错误f.num=0;对于 (i=0;i<6;i++) f.dat[i]=0.0;如果(s==v"){f.num=0;为了 (;;){s=str_load_str(s0,a,true);if ((s=="")||(!str_is_num(s))) 中断;f.add(str2num(s));}如果 (f.num>=3){ppos.add(f[0]);ppos.add(f[1]);ppos.add(f[2]);}如果(f.num==6){pcol.add(f[3]);pcol.add(f[4]);pcol.add(f[5]);}}否则如果(s==vn"){f.num=0;为了 (;;){s=str_load_str(s0,a,true);if ((s=="")||(!str_is_num(s))) 中断;f.add(str2num(s));}pnor.add(f[0]);pnor.add(f[1]);pnor.add(f[2]);}否则如果(s==vt"){f.num=0;为了 (;;){s=str_load_str(s0,a,true);if ((s=="")||(!str_is_num(s))) 中断;f.add(str2num(s));}ptxr.add(f[0]);ptxr.add(f[1]);}否则如果(s==f"){pos.num=0;txr.num=0;nor.num=0;为了 (;;){s=str_load_str(s0,a,true);如果 (s=="") 中断;for (t="",i=1;i<=s.Length();i++) if (s[i]=='/') break;否则 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;否则 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;否则 t+=s[i];if ((t!="")&&(str_is_num(t))) nor.add(str2int(t)-1);}//如果需要,重新索引和/或复制顶点对于 (i=0;i0) v.txr=txr[i];否则 v.txr=-1;if (nor.num>0) v.nor=nor[i];否则 v.nor=-1;//存在于 VBO 中吗?对于 (j=0;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);否则 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;idata.num==0) obj.nor_compute();vaos.vao=目标;vaoix0=vaos.add(obj);返回 vaoix0;}//---------------------------------------------------------------------------//---------------------------------------------------------------------------//---------------------------------------------------------------------------#万一//---------------------------------------------------------------------------
它还没有使用 *.mtl
文件(我硬编码了预览的纹理).
PS.如果我使用它作为纹理:
结果如下:
我在这里使用了很多我自己的东西,所以一些解释:
str_load_str(s,i,true)
返回表示字符串 s
中索引 i
中第一个有效单词的字符串.true 意味着 i
被更新为 s
中的新位置.
str_load_lin(s,i,true)
返回表示行的字符串(直到 CR
或 LF
或 CRLF
或 LFCR
) 来自字符串 s
中的索引 i
.真正的意思是 i
在该行之后用新位置更新.
txt_load_...
是相同的,但它不是从字符串中读取,而是根据需要从 BYTE*
或 CHAR*
中读取.
注意AnsiString
是1
和BYTE*,CHAR*
的索引形式,来自0
.
我也使用我的动态列表模板,所以:
List
等同于 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 库版本:2.11 --------------------------------------//---------------------------------------------------------------------------#ifndef _model_obj_h#define _model_obj_h//---------------------------------------------------------------------------类 model_obj{上市:类顶点{上市:int pos,txr,nor;顶点(){};顶点(顶点& a){ *this = a;};~顶点(){};顶点* 运算符 = (const 顶点 *a) { *this=*a;返回这个;};/*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 运算符 <(顶点&a){如果 (pos>a.pos) 返回 0;如果 (pos<a.pos) 返回 1;如果 (txr>a.txr) 返回 0;如果 (txrI'm currently trying to make a Wavefront (.obj) file loader for an OpenGL project. The method I'm currently using goes line-by-line and separates the vertex positions, texture positions and normal positions in vectors (std::vectors) and I'm storing their indices (vertex, texture and normal indices) in three separate vectors (from the 'f' lines of the file, for each face).
I'm having trouble sorting the vector full of texture coordinates based on the texture indices. I'm able to render the vertices in the correct positions because my 'loader' class calls for the indices, but I can't figure out how to sort the texture coordinates in any way, so the textures look offset on some triangles as a result.
Image of cube with offset textures:
Image of texture (.png), how it should be on each face:
EDIT: Here is a link to both the .obj file and .mtl file.
Google Drive.
Here is my OBJLoader.cpp file:
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);
}
I've tried inserting the values (vector.insert) from texCoords[textureIndices[i]] in a loop but that didn't work and made the output worse. I tried a simple:
tempVec[i] = texCoords[textureIndices[i]]
in a for loop but that didn't work either.
I've gone through the whole project and I determined that the sorting is the cause of the issue, because when I plug in hard-coded values for the cube it works perfectly and the textures aren't offset at all. (The OpenGL commands / image loader are working as they should.)
Ultimately, is there another way to sort the texCoords based on the textureIndices?
解决方案 I wanted to implement this (adding textures for obj file) into my engine for a long time and your Question got me the mood to actually do it :).
The image you provided as texture looks more like a preview than a texture. Also the texture coordinates does not correspond to it as you can see in preview:
If you look at the texture coordinates:
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
there are just 2 numbers:
0.736102
0.263898
Which makes sense for axis aligned quad or square sub-image in the texture which isnot present in your texture. Also the number of texture points makes no sense 20
it should be just 4
. Hence the confusions you got.
Anyway Rabbid76 is right you need to duplicate points ... Its relatively easy so:
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,
};
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.
Just for sure here is my Wavefront OBJ loader C++ class I am using in my engine (but it depends on the engine itself so you can not use it directly it is just to see the structure of code and how to encode this... as starting with this from scratch might be difficult).
//---------------------------------------------------------------------------
//--- 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
//---------------------------------------------------------------------------
It does not use the *.mtl
file yet (I hardcoded the texture for the preview).
PS. if I use this as texture:
The result looks like this:
I use a lot of mine own stuff here so some explanations:
str_load_str(s,i,true)
returns string representing first valid word from index i
in string s
. The true means just that i
is updated with new position in s
.
str_load_lin(s,i,true)
returns string representing line (till CR
or LF
or CRLF
or LFCR
) from index i
in string s
. The true means just that i
is updated with new position after that line.
txt_load_...
is the same but instead of reading from string it reads form BYTE*
or CHAR*
if you want.
Beware AnsiString
is indexed form 1
and BYTE*,CHAR*
from 0
.
I also use mine dynamic list template so:
List<double> xxx;
is the same as double xxx[];
xxx.add(5);
adds 5
to end of the list
xxx[7]
access array element (safe)
xxx.dat[7]
access array element (unsafe but fast direct access)
xxx.num
is the actual used size of the array
xxx.reset()
clears the array and set xxx.num=0
xxx.allocate(100)
preallocate space for 100
items
Here the updated faster reindex code with textures from mtl file (otherstuff is ignored and only single object/texture is supported for now):
//---------------------------------------------------------------------------
//--- 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 the added materials (just texture and material name for now) I changed the re-indexing so the vertextes are index sorted and binary search is used to obtain vertex index on demand. With this 100K faces Standford dragon (3.4MByte) is loaded in 3.7sec:
这篇关于如何根据 Wavefront (.obj) 文件中给出的纹理索引对纹理位置进行排序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!