因为Houdini可以直接导出Wavefront
.obj,而且该文件格式非常公开,在网上可以找到详细的spec,所以耍了耍。.Obj文件格式在Houdini模型导出那里已经提过。这里再重复一
下,Obj模型结构大致如下:

这个结构有几点跟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版本。这部分的结构如下图所示:
这个程序在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
|