OpenGL ES系列 之 应用 - 1:Wavefront .obj加载

 

因为Houdini可以直接导出Wavefront .obj,而且该文件格式非常公开,在网上可以找到详细的spec,所以耍了耍。.Obj文件格式在Houdini模型导出那里已经提过。这里再重复一下,Obj模型结构大致如下:
wavefront obj file format
这个结构有几点跟GLES不匹配的地方:
1、Facet可能是多边形。每个facet由一个'f'行表示,一行可能会有多个顶点表示是一个多边形。这个可以在导出模型时预先三角化,也可以取读模型时即时三角化。将多边形分解成对应的一组三角形有两个思路,一个是取任一顶点与其它顶点形成三角扇(TRIANGLE_FAN),另一个是按 TRIANGLE_STRIP顺序分解。
2、独立索引。'f'行每个顶点由三个索引组成,分别索引vertex/texCoord/normal。这种索引的好处是大大减少了texCoord的数量,另外对于多个面的共享顶点也能方便的记录其在每个面上的uv及n。但是,GLES的DrawElements对每个顶点只接受一个索引,并用这个索引来查看所有的Array。这需要一个转换,将Mat的所有顶点及相关数据复制成GLES所需要的Array。
3、光照模型。Mat里记录有光照模型,其中模型2跟GLES的硬件光照模型基本一致,其它模型需要软件模拟,有几个涉及光线追踪的会非常麻烦。
4、多种贴图。DiffuseMap可以直接用一般的贴图来实现,BumpMap可以用MultiTexture+ dot3RGB combine实现,其它的也都可以模拟但较麻烦。

.Obj文件的格式请google之。总的说来,这是一个纯文本文件,通常配有.mtl文件汇总用到的所有材质。文件内容是过程式记录的,第一个非"v" 行表示顶点数据结束,这时才知道有多少个顶点,然后是vt/vn。一个usemtl标识着一个新Mesh的开始,这个mesh的材质用usemtl后面跟的名字去MatLib里查。接着是"f"行,每行是一个facet,可能是多边形。直到下一个usemtl或者文件结束才知道当前mesh结束了,它有多少个facet了。

写这样一个Parser并不复杂,逐行分析就是了。如果要保持程序清楚简单,直接构造上图所示的对象网络,然后将GLES可接受格式封装成另一个类似的类树,做一个转换器就好了。

不过我做的时候只能利用零散时间想一点做一点,所以写成一边读一边转了,相对复杂一点。好在是写着玩的,能工作就好。做为转换器的副产品,还得到了一个 ObjViewer,归功于以前整理的Toolchain和程序框架,这个ObjViewer可以直接编译成PC Emu版本或者X51v WM5版本。这部分的结构如下图所示:
Wavefront .Obj Viewer Structure Overview


这个程序在PC上工作比较合适,试过打开一个300万个三角形的模型,吃掉了大约800M内存。这个Viewer需要预先把纹理图片转成PVRTC格式,并且对此不做检查,如果没对应的.pvr,直接出Accessing Violation。这个转换可能得分两步,先缩放成正方形,然后用PVRTextureTool压缩。对于前一步我是用Java imageio kit写了个小程序来处理并保存成未压缩24bit BMP的。

Parser的主要代码如下:

//
//
// @ Project : Wavefront Obj Loader
// @ File Name : ObjConverter.cpp
// @ Date : 2007-9-4
// @ Author : pinxue
//
// @ Copyright : 2007-9 Example source license.
// By keep this header comment,
// you may use this source file in your project for non-commicial or commicial purpose.

//

#include "ObjConverter.h"
#include <windows.h>
#include <stdio.h>

#include "gles/gl.h"
#include "fixmath.h"

#include "Obj.h"
#include "Vertex.h"
#include "UV.h"
#include "Normal.h"
#include "Mesh.h"
#include "Face.h"
#include "MatLib.h"
#include "Mat.h"
#include "Vector3i.h"

#include "Buf.h"


bool debugging = true;
void debug(const char * msg)
{
if ( debugging )
printf("%s", msg);
}

#define CLEAN_FGETS(buf) buf[strlen(buf)-2]=0;
bool ObjConverter::loadWavefrontMtl(const char * src)
{
char buf[1024];

Mat curMat;
Buf<Mat> matBuf(128);

matLib->fileName = _strdup(src);

long matCount = 0;

FILE *fp = fopen(src, "rb");
if ( fp == NULL ) {
printf("File %s cannot open!/n", src);
return false;
}

bool lineReaded = false;
bool firstMat = true;
bool readFailed = false;
while(!feof(fp))
{
if ( ! lineReaded ){readFailed = NULL==fgets(buf, 1024, fp); CLEAN_FGETS(buf);}
else lineReaded = false;

if ( ! readFailed )
{
if ( buf[0] == '#' )
{
continue;
}else if ( 0 == strncmp("newmtl", buf, 6) )
{
// finish privious mat
if ( !firstMat ) { matBuf << curMat; }
else firstMat = false;
memset(&curMat, 0, sizeof(Mat));

// start new mat
curMat.name = _strdup(& buf[7]);// skip "newmtl "
// parse mat parameters: Ka, Kd, Ks, illum, Ns
float r,g,b,a=1.0f;
while(!feof(fp))
{
readFailed = NULL==fgets(buf, 1024, fp); CLEAN_FGETS(buf);
if ( ! readFailed )
{
// skip tab and space
char * bp = buf;
while( (*bp == ' ') || (*bp == '/t') ) ++bp;
if ( 0 == strncmp("Ka ", bp, 3) )
{
sscanf(bp + 3, "%f %f %f", &r, &g, &b );
curMat.ambient.r = Float2Fixed(r);
curMat.ambient.g = Float2Fixed(g);
curMat.ambient.b = Float2Fixed(b);
curMat.ambient.a = Float2Fixed(a);
}else if ( 0 == strncmp("Kd ", bp, 3) )
{
sscanf(bp + 3, "%f %f %f", &r, &g, &b );
curMat.diffuse.r = Float2Fixed(r);
curMat.diffuse.g = Float2Fixed(g);
curMat.diffuse.b = Float2Fixed(b);
curMat.diffuse.a = Float2Fixed(a);
}else if ( 0 == strncmp("Ks ", bp, 3) )
{
curMat.specular.r = Float2Fixed(r);
curMat.specular.g = Float2Fixed(g);
curMat.specular.b = Float2Fixed(b);
curMat.specular.a = Float2Fixed(a);
}else if ( 0 == strncmp("Ns ", bp, 3) )
{
float f;
sscanf(bp + 3, "%f", & f);
curMat.specularExponent = Float2Fixed(f);
}else if ( 0 == strncmp("illum ", bp, 6) )
{
// not full support yet: illumination mode
// 0 : Color and Ambient off { constant color illumination model, color = Kd }
// 1 : Color and Ambient on { diffuse illMod with lambertian shading,
// color = Ka*Ia + Kd * [SUM j=1..ls, (N * Lj)Ij] }

// 2 : Highlight on { GL matching! diffuse and specular illMod
// using lambertian shading and Blinn-Phone specular illMod.
// color = Ka*Ia + Kd * {SUM j, (N*Lj)Ij} + Ks * {SUM j, ((H*Hj)^Ns)Ij}

// following is too complex if there is no shader support,
// i.e. GLES 1.x, refer .mtl spec for details

// 3 : Reflection on and Raytrace on
// 4 : Transparency: Glass on; Reflection: Raytrace on
// 5 : Reflection: Fresnel on and Raytrace on
// 6 : Transparentcy: Refraction on; Reflection: Fresnel off and Raytrace on
// 7 : Transparency: Refraction on; Reflection: Fresnel on and Raytrace on
// 8 : Reflection on and Raytrace off
// 9 : Transparenc: Glass on; Reflection: Raytrace off
//10 : Cast shadows onto invisible surfaces
sscanf(bp + 6, "%d", & curMat.illumMode);
}else if ( 0 == strncmp("d ", bp, 2) )
{
// not support yet: dissolve for current mat
}else{
// a lot of capability are not supported: sharpness, Ni, Tf and options for the tag supported
lineReaded = true;
break;
}
}else{
if ( ! feof(fp) )
{
debug("get next line failed in parsing mat");
debug(matBuf.last().name);
debug("/n");
}
break;
}
}
}
// texture maps (apply to Ka/Kd/Ks/Ns/d
else if ( 0 == strncmp("map_bump", buf, 8) || 0 == strncmp("map_Bump", buf, 8) )
{
// this is not standard tag, but it does used by some exporter instead of 'bump'
// and we will use the first one met.
if ( curMat.bumpMap == 0 )
curMat.bumpMap = _strdup(&buf[9]);
}else if ( 0 == strncmp("map_Ka", buf, 6) || 0 == strncmp("map_kA", buf, 6) )
{
curMat.ambientMap = _strdup(&buf[7]);
}else if ( 0 == strncmp("map_Kd", buf, 6) || 0 == strncmp("map_kD", buf, 6) )
{
curMat.diffuseMap = _strdup(&buf[7]);
}else if ( 0 == strncmp("map_Ks", buf, 6) || 0 == strncmp("map_kS", buf, 6) )
{
curMat.specularMap = _strdup(&buf[7]);
}else if ( 0 == strncmp("map_Ns", buf, 6) || 0 == strncmp("map_nS", buf, 6))
{
// not support yet : apply to Ns, specular exponent
}else if ( 0 == strncmp("bump", buf, 4) )
{
if ( curMat.bumpMap == 0 )
curMat.bumpMap = _strdup(&buf[5]);
}else{
// not support yet: map_d, map_aat on, decal{ tex_color(tv)*decal(tv)+mtl_color*(1.0-decal(tv) },
// disp, refl and options
debug("unknown type");
debug(buf); debug("/n");
}
}else{
if ( ! feof(fp) )
debug("read next line failed in mat lib/n");
}
}

// handle last mat
matBuf << curMat;
// to avoid curMat free the file names of last mat
memset(&curMat, 0, sizeof(Mat));
matLib->matCount = matBuf.count;
matLib->mats = matBuf.expose();

return true;
}

#define UNKNOWN_TYPE 1
#define UNKNOWN_LINE 2
#define BUF_DELTA 1024
bool ObjConverter::loadWavefrontObj(const char * src)
{
char buf[1024];

Vertex vertex;
Buf<Vertex> vertexBuf(4096);
UV uv;
Buf<UV> uvBuf(1024);
Normal normal;
Buf<Normal> normalBuf(4096);

Vector3i tri;
Buf<Vector3i> triBuf(1024);

Mesh mesh;
Buf<Mesh> meshBuf(4096);
Face face;
Buf<Face> meshFaceBuf(4096);

Vertex meshVertex;
Buf<Vertex> meshVertexBuf(4096);
UV meshUV;
Buf<UV> meshUVBuf(4096);
Normal meshNormal;
Buf<Normal> meshNormalBuf(4096);

bool firstMesh = true;
int error = 0;

// assume there are enough free memory
// skip null check for all memory allocation.
FILE * fp = fopen(src, "rb");
if ( fp == NULL ) {
printf("File %s cannot open!/n", src);
return false;
}

bool readFailed = false;
while(!feof(fp))
{
readFailed = NULL == fgets(buf, 1024, fp); CLEAN_FGETS(buf);
if ( ! readFailed )
{
switch(buf[0])
{
case '#'://ignore
break;
case 'm'://parse matlib
if ( 0 == strncmp("mtllib", buf, 6) )
{
matLib = new MatLib();
matLib->fileName = _strdup(&buf[7]);
loadWavefrontMtl( matLib->fileName );
}else{
error = UNKNOWN_TYPE;
}
break;
case 'g'://start of obj, get obj name
if ( buf[1] == ' ' )
{
if ( obj == 0 )
{
obj = new Obj();
obj->name = _strdup( &buf[2] );
} // todo : support more than one obj, and handle group of non-object like faces
// (houdini exports a lot group without name)

else {
printf("warning: multiple g tag, we don't support yet!/n");
}
}else{
error = UNKNOWN_TYPE;
}
break;
case 'v':
//'v ' vertex
if ( buf[1] == ' ' )
{
float x,y,z;
sscanf( &buf[2], "%f %f %f", &x, &y, &z );
vertex.x = Float2Fixed(x);
vertex.y = Float2Fixed(y);
vertex.z = Float2Fixed(z);
vertexBuf << vertex;
}else
//'vt ' vertex texcoord
if ( buf[1] == 't' && buf[2] == ' ' )
{
float x,y;
sscanf( &buf[2], "%f %f", &x, &y );
uv.x = Float2Fixed(x);
uv.y = Float2Fixed(y);
uvBuf << uv;
}else
//'vn ' vertex normal
if ( buf[1] == 'n' && buf[2] == ' ' )
{
float x,y,z;
sscanf( &buf[2], "%f %f %f", &x, &y, &z );
normal.x = Float2Fixed(x);
normal.y = Float2Fixed(y);
normal.z = Float2Fixed(z);
normalBuf << normal;
}else{
error = UNKNOWN_TYPE;
}
break;
case 'u'://parse usemtl and mat name
// this means a new mesh too
if ( 0 == strncmp("usemtl", buf, 6) )
{
char * matName = & buf[7];
Mat * mat = matLib->get( matName );

// finish privous mesh
if ( ! firstMesh ) // if this is NOT the first mesh then we have privious mesh
{
mesh.vertexCount = meshVertexBuf.count;
mesh.vertexs = meshVertexBuf.expose();

// In current design, uvs and normals have same number of elements as vertexs
mesh.uvs = meshUVBuf.expose();
mesh.normals = meshNormalBuf.expose();

mesh.faceCount = meshFaceBuf.count;
mesh.faces = meshFaceBuf.expose();
if ( mesh.faceCount > 65536 )
printf("warning: mesh with %d faces/n", mesh.faceCount);
else { if (debugging) printf("mesh with %d faces/n", mesh.faceCount); }
meshBuf << mesh;
} else { firstMesh = false; }

// create new mesh
// by use new Buf wrapper, we just reuse the temporary mesh.
// and as all buffers are exposed, we even need not clean up it (it is reset internally)
mesh.mat = mat;
}
break;
case 'f'://face
if ( buf[1] == ' ' )
{
// face may contains a series of index
// each index contains v/vt/vn
// num/num/num num/num/num '/'
#define MAX_FACE_LINE_SIZE 4096
char line[MAX_FACE_LINE_SIZE];
char *p = line; int num=0, idx=0;
strncpy(line, &buf[3], MAX_FACE_LINE_SIZE);
bool startNewSet = true;
// skip leading space in the line, this is important because we use space to separate set of index
// in fact, we can counting idx to avoid the dependent, but anyway,
// the set of index is separated by space.

while( *p == ' ' || *p == '/t') ++p;
while( *p != 0 )
{
switch( *p )
{
case ' ': // next triple index set
idx = 0; num = 0; startNewSet = true;
++p;
break;
case '/': // next index of current set
startNewSet = false; // we're handling it.
num = 0; ++idx; ++p;
break;
case '//':// a new new and a new set starting
startNewSet = true; num = 0; idx = 0;
readFailed = NULL==fgets( line, 1024, fp); CLEAN_FGETS(line);
p=line;
break;
default:
startNewSet = false; // we're handling it.
if ( *p == '-' )
num = -num;
else if ( isdigit(*p) )
{
num = num * 10 + (*p - '0');
switch(idx){
case 0:
tri.a = num;
break;
case 1:
tri.b = num;
break;
case 2:
tri.c = num;
break;
default: debug("Warning: face point with more than 3 index!/n");
}
}else{
debug("invalid char in face line/n");
}
++p;
}

if ( startNewSet ) {
triBuf << tri;
} // what about last set?
}
// the last tri
triBuf << tri;

if ( triBuf.count > 3 ) // triangular(triBuf);
{
// p0, [1, 2] [2,3] [3,4] ..., we will gen triCount - 2 triangles as FAN
// todo : generate TRIANGLE STRIP instead of FAN
Buf<Vector3i> tmpTriBuf(1024);
for ( long i = 0; i < triBuf.count-2; ++i )
{
// point 0
tmpTriBuf << triBuf[0];
// point +1
tmpTriBuf << triBuf[i+1];
// point +2
tmpTriBuf << triBuf[i+2];
}

triBuf.exchange(tmpTriBuf);
}

// put the face into mesh including append v/vt/vn pointed by face into mesh
bool existed=false;
long j = 0;
Vertex * cv;
for ( long i = 0; i < triBuf.count; ++i )
{
cv = &vertexBuf[triBuf[i].a -1]; // Wavefront Obj face index start from 1
existed=false;
for ( j = 0 ; j < meshVertexBuf.count; ++j )
{
if ( meshVertexBuf[j].x == cv->x
&& meshVertexBuf[j].y == cv->y
&& meshVertexBuf[j].z == cv->z )
{ existed = true; break; }
}
if ( existed )
{
// todo : check normal and uv at j is same
}else{
// append it
meshVertexBuf << *cv;
meshUVBuf << uvBuf[triBuf[i].b-1]; // Wavefront Obj face index start from 1
meshNormalBuf << normalBuf[triBuf[i].c-1]; // Wavefront Obj face index start from 1
}

switch(i % 3)
{
case 0: face.a = j; break;
case 1: face.b = j; break;
case 2: face.c = j;
// record the face
meshFaceBuf << face;
break;
}
}
triBuf.cleanup();

//++ faceGroup;
}
break;
default:
// ignore
debug("/nignore unknown line: ");
debug(buf); debug("/n");
;

}
}else{
if ( ! feof(fp) ){
printf( "failed to read a line!/n");
return false;
}
}
}

// finish last mesh
mesh.vertexCount = meshVertexBuf.count;
mesh.vertexs = meshVertexBuf.expose();
mesh.uvs = meshUVBuf.expose();
mesh.normals = meshNormalBuf.expose();
mesh.faceCount = meshFaceBuf.count;
mesh.faces = meshFaceBuf.expose();
meshBuf << mesh;
if ( mesh.faceCount > 65536 )
printf( "warning: mesh with %d faces!", mesh.faceCount);
else { if (debugging) printf("mesh with %d faces./n"); }

// we got it!
obj->meshCount = meshBuf.count;
obj->meshs = meshBuf.expose();

return true;
}



这里面大量使用的Buf是个短小但实用的小容器,其实你也完全可以用STL库的,不过eVC4好象没带STL的实现。

//
//
// @ Project : Wavefront Obj Loader
// @ File Name : Main.cpp
// @ Date : 2007-9-12
// @ Author : pinxue
//
// @ Copyright : 2007-8 Example source license.
By keep this header comment,
you may use this source file in your project for non-commicial or commicial purpose.

//
#ifndef BUF_H
#define BUF_H
#include <stdlib.h>
#include <string.h>

template <typename T>
class Buf
{
private:
T * _buf;
long cap;
public:
long count;

private:
void enlargeBuf() { cap += 1024; _buf = (T *) realloc( _buf, cap * sizeof(T) ); }
void reset(long initCap){cap=initCap; count=0; _buf = (T *) malloc( cap * sizeof(T) ); memset( _buf, 0, cap * sizeof(T)); }

public:
Buf(long initCap){ reset(initCap); }
~Buf(){ if ( _buf != 0 ) free( _buf ); }
void cleanup(void){ count = 0; };

const Buf* append(const T & t)
{
//_buf[count] = t;
memcpy( & _buf[count], &t , sizeof(T) );
++count;
if ( count >= cap ) enlargeBuf();
return this;
}
const Buf* operator <<(const T& t){ return append(t); }

T& operator [](long idx) { return _buf[idx]; }

const T* getBuf(void){ return _buf; }
T& firstFree(void){ return _buf[count]; }
/** User promise there has at least one element by calling this function */
T& first(void){ return _buf[0]; }
/** User promise there has at least one element by calling this function */
T& last(void){ return _buf[count-1]; }

T* expose(void){
T* tmp = (T*) realloc(_buf, count * sizeof(T)) ;
reset(cap);
return tmp;
}

void exchange(Buf & b){
long t; T * p;
p = _buf; _buf = b._buf; b._buf = p;
t = count; count = b.count; b.count = t;
t = cap; cap = b.cap; b.cap = t;
}
};

#endif //ifndef BUF_H
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值