Runtime loading of .OBJ meshes

Share your ZGE-development tips and techniques here!

Moderator: Moderators

User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Runtime loading of .OBJ meshes

Post 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.
Attachments
ObjLoader_001.zip
Runtime .OBJ file loader
(147.63 KiB) Downloaded 753 times
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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.
Attachments
ObjLoader_002.zip
OBJ loader also for android
(601.25 KiB) Downloaded 746 times
User avatar
Kjell
Posts: 1876
Joined: Sat Feb 23, 2008 11:15 pm

Post 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
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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.
User avatar
Kjell
Posts: 1876
Joined: Sat Feb 23, 2008 11:15 pm

Post 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
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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.
Attachments
obj_loader_003.zgeproj
obj loader with vertex arrays
(11.77 KiB) Downloaded 699 times
User avatar
Kjell
Posts: 1876
Joined: Sat Feb 23, 2008 11:15 pm

Post 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
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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) :?
Attachments
obj_loader_005.zgeproj
obj loader with buffer objects
(13.01 KiB) Downloaded 789 times
User avatar
Kjell
Posts: 1876
Joined: Sat Feb 23, 2008 11:15 pm

Post 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
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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.
User avatar
Kjell
Posts: 1876
Joined: Sat Feb 23, 2008 11:15 pm

Post 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
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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.
Attachments
obj_loader_006.zgeproj
obj loader aligning vertex arrays
(16.05 KiB) Downloaded 679 times
User avatar
Rado1
Posts: 775
Joined: Wed May 05, 2010 12:16 pm

Post 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.
Attachments
obj_loader_007.zgeproj
faster obj loader
(19.77 KiB) Downloaded 792 times
Last edited by Rado1 on Sun Oct 28, 2012 9:43 pm, edited 1 time in total.
User avatar
VilleK
Site Admin
Posts: 2274
Joined: Mon Jan 15, 2007 4:50 pm
Location: Stockholm, Sweden
Contact:

Post 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.
User avatar
Kjell
Posts: 1876
Joined: Sat Feb 23, 2008 11:15 pm

Post 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
Post Reply