Page 1 of 2

Runtime loading of .OBJ meshes

Posted: Sun Oct 14, 2012 8:25 pm
by Rado1
Being inspired by Kjell's loader of ZModeler files from this topic, I created a simple OBJ (Wavefront) file loader. It loads just vertices and normals of triangulated meshes. Comparing to .z3d, .obj file format is relatively common and can be produced by many 3d modeling tools. The attached examples I produced/modified by my favorite Wings3D and normals were corrected in Meshlab.

The code is not probably optimized yet, but I plan to have a closer look at it as part of work on Toyland Shooter - I'll use obj loader for user-defined meshes.

BTW I plan to test obj loader also on Android where meshes will be loaded from e.g. /sdcard/Meshes directory. I hope it will work.

Any comments, suggestions, etc. are welcome.

Posted: Wed Oct 17, 2012 12:41 am
by Rado1
OBJ loader has successfully been tested also on Android. It is relatively slow for larger meshes (2-10 seconds for faces > 1000); do you have the same experience, or your device is faster? To try it, just install OBJ Loader-debug.apk and move any number of .obj files to /sdcard/Meshes/ directory (you can use any .obj files with triangulated meshes). Mesh files should be named obj<num>.obj.

Posted: Wed Oct 17, 2012 9:57 am
by Kjell
Hi Rado,

Perhaps you can try copying the entire file into a buffer ( array ) first, and then parse the data in a single while-loop .. should speed things up. Creating the mesh using OpenGL calls ( glBufferData ) instead of using a Mesh component might help as well.

By the way, Z3D isn't a "real" format :wink:

K

Posted: Wed Oct 17, 2012 10:56 am
by Rado1
Kjell wrote:Perhaps you can try copying the entire file into a buffer ( array ) first, and then parse the data in a single while-loop .. should speed things up.
Hmmm... I'm not sure about this, what's the difference between reading + parsing at the same time and reading + consequent parsing? Of course, the input file can be opened for shorter time, which is an advantage, but how this could make the reading time shorter? I can try it anyway.

Also setting of buffer sizes depending on file size could help little bit.
Kjell wrote:Creating the mesh using OpenGL calls ( glBufferData ) instead of using a Mesh component might help as well.
This is good idea! I hope it will run on my GPU and OpenGL ES as well.

Posted: Wed Oct 17, 2012 11:04 am
by Kjell
Hi Rado,
Rado1 wrote:I'm not sure about this, what's the difference between reading + parsing at the same time and reading + consequent parsing? Of course, the input file can be opened for shorter time, which is an advantage, but how this could make the reading time shorter? I can try it anyway.
Because when you use a ZExpression in a Repeat component all local variables are (re-)allocated each iteration ( afaik ).
Kjell wrote:I hope it will run on my GPU and OpenGL ES as well.
Hint :wink:

K

Posted: Tue Oct 23, 2012 9:17 am
by Rado1
Hi Kjell,

attached is my intermediate attempt to avoid mesh recalculation by utilizing vertex arrays. It is faster than the previous version, however, this is still not performance optimal. In another version I also used glInterleavedArrays, but it is not working on Android and surprisingly its performance was worse than non-interleaved arrays on my GPU; I thrown it away. Unfortunately, trials to use VBOs failed. For instance, what value to put to glVertexPointer's 4th parameter called pointer (ZGE allows to specify only DefineArray as value for xptr AFAIK)? Could you please help me to modify obj loader to use buffer objects, if possible? Thanks.

Rado1.

Posted: Tue Oct 23, 2012 9:34 am
by Kjell
Hi Rado,
Rado1 wrote:In another version I also used glInterleavedArrays, but it is not working on Android and surprisingly its performance was worse than non-interleaved arrays on my GPU
Never use glInterleavedArrays. If you want to use interleaved buffers, do this client-side and set the location of the individual attributes using strides.
Rado1 wrote:For instance, what value to put to glVertexPointer's 4th parameter called pointer (ZGE allows to specify only DefineArray as value for xptr AFAIK)?
You can also use NULL :wink: The name of this parameter is slightly confusing though, since it's used as a relative offset, not a absolute address.
Rado1 wrote:Could you please help me to modify obj loader to use buffer objects, if possible?
I'll have a peek later today.

K

Posted: Tue Oct 23, 2012 3:14 pm
by Rado1
Kjell, is this usage of buffer objects correct? See the attachment. Are there some further optimizations possible? I tested it only on obj files from ObjLoader_002.zip, but performance seems comparable to usage of simple vertex arrays (obj_loader_003.zgeproj) :?

Posted: Tue Oct 23, 2012 5:43 pm
by Kjell
Hi Rado1,
Rado1 wrote:Kjell, is this usage of buffer objects correct?
Looks fine yes :)
Rado1 wrote:Are there some further optimizations possible? I tested it only on obj files from ObjLoader_002.zip, but performance seems comparable to usage of simple vertex arrays.
Rendering performance of VBO's ( especially non-interleaved ) will be similar to Vertex Arrays for ( relatively ) low-poly meshes. But .. I thought you were trying to optimize loading & parsing?

By the way, your parser doesn't work properly with OBJ's exported from Softimage.

Code: Select all

# XSI Wavefront OBJ Export v3.0
o cube
# Hierarchy (from self to top father)
g cube

#begin 8 vertices
v -1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v -1.000000 -1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v 1.000000 1.000000 1.000000
#end 8 vertices

#begin 36 normals
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 -0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 0.000000 1.000000 0.000000
vn -0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 -0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
#end 36 vertex normals

#begin 12 faces
f 1//1 4//3 2//4 
f 1//5 6//7 5//8 
f 1//9 7//11 3//12 
f 2//13 8//15 6//16 
f 3//17 8//19 4//20 
f 5//21 8//23 7//24 
f 3//2 4//26 1//25 
f 2//6 6//28 1//27 
f 5//10 7//30 1//29 
f 4//14 8//32 2//31 
f 7//18 8//34 3//33 
f 6//22 8//36 5//35 
#end 12 faces
K

Posted: Tue Oct 23, 2012 6:43 pm
by Rado1
Kjell wrote:But .. I thought you were trying to optimize loading & parsing?
Yes parsing was optimized, now it is only one expression called once per file. By "performance" I meant FPS, not the loading time.
Kjell wrote:By the way, your parser doesn't work properly with OBJ's exported from Softimage.
I know that. The problem are points of faces for which vertex index is different than normal index. How is it possible to call glDrawElements (or another function) where vertex indices and normal indices are different and stored in different arrays (or one interleaved array)? I would like to avoid inserting of artificial vertex copies corresponding to normals, drawing triangle by triangle, direct accessing by glArrayElement, or so. The solution should be OpenGL 2.0-compatible to be testable on my ATI Radeon X1400. Any ideas? BTW slow ObjLoader_002 should be able to load also these obj files correctly.

Rado1.

Posted: Tue Oct 23, 2012 7:10 pm
by Kjell
Hi Rado,
Rado1 wrote:Yes parsing was optimized, now it is only one expression called once per file. By "performance" I meant FPS, not the loading time.
Alright .. in that case glDrawArrays or glDrawElements shouldn't make much of a difference no. However, since buffers can be uploaded to the GPU, this approach enables you to re-use the vertex / normal Arrays, which makes loading multiple meshes allot easier.
Rado1 wrote:How is it possible to call glDrawElements (or another function) where vertex indices and normal indices are different and stored in different arrays (or one interleaved array)? I would like to avoid inserting of artificial vertex copies corresponding to normals, drawing triangle by triangle, direct accessing by glArrayElement, or so.
Not possible. OpenGL requires a uniform vertex format per draw call.

K

Posted: Wed Oct 24, 2012 5:02 pm
by Rado1
Hi Kjell,
Kjell wrote:Not possible. OpenGL requires a uniform vertex format per draw call.
I made a workaround for reused vertices or normals - extending the vertex/normal array + copying of vertices/normals to the positions where they are used by normals/vertices. This results in uniform (aligned) arrays of vertices and normals. So the Softimage's OBJ files of triangulated meshes can be loaded now correctly. Performance is just a little bit worse than without aligning vertex arrays.

I also tried 2-step processing: buffering files into an array and consequent processing of that array. I tried string, integer and float arrays, but everything was slower, from 10% to 50%, than the direct file reading. Therefore, I removed file buffer.

See the attached update. Any comments are welcome.

Rado1.

Posted: Sun Oct 28, 2012 7:08 pm
by Rado1
Hi,

after private discussion with Kjell I tried to make the obj loader even faster. All transformations of input chars to strings and consequent parsing of string tokens were removed. I think the only bottleneck of the current version is relatively slow loading of files in ZGE - FileMoveData component.

Posted: Sun Oct 28, 2012 7:36 pm
by VilleK
Really neat trick of calling the MoveData component inside a for-loop :). It's great to see features used in ways you did not envision when implementing them.

String handling is relatively slow so I understand the impact of removing that. File handling is probably slow because of the component overhead. The actual file is always read in full to memory before the components see the content so there are no slow file streaming involved.

Posted: Sun Oct 28, 2012 8:29 pm
by Kjell
Hi guys,
Villek wrote:File handling is probably slow because of the component overhead. The actual file is always read in full to memory before the components see the content so there are no slow file streaming involved.
Whoops. I initially thought this was the case, then inspected the source and must have completely missed line 178* as I thought i was mistaken :oops: Anyway, it does make you wonder how significant the performance penalty of FileMoveData's ( silly ) char to float conversion is in the entire overhead of the call.

*CurInStream := TZInputStream.CreateFromFile(S,True)

K