Loading models using LibGDX

In the previous tutorial, we’ve seen how to setup libgdx for rendering a 3D scene. We’ve setup a camera, added some lights and rendered a green box. Now let’s make things a bit more iteresting by adding loading a model instead of creating a box.

The full source, assets and a runnable tests of this tutorial can be found on this github repository.

You can start your favorite modeling application or grab an existing model. I used the ship model that comes with LibGDX gdx-invaders, you can find it here. You can extract it to the data folder within the assets folder of the android project. Notice that it contains three files, which must be included in the same folder:

  • ship.obj: the wavefront model file we’re going to load
  • ship.mtl: the wavefront material file the model uses
  • ship.png: the texture the material uses

Please note that this model is in the wavefront (OBJ) file format. This is only done for the sake of this tutorial. The OBJ format is not fully supported and should be avoided. Later on in this tutorial we will see how to convert a model to a more suitable file format. If you use your own (OBJ) model, you might notice that it might not render correctly. That’s to be expected and will be resolved later on.

Now let’s change the Basic3DTest to use that model instead of the box we created earlier:

public class LoadModelsTest implements ApplicationListener {
...	
	@Override
	public void create () {
		modelBatch = new ModelBatch();
		environment = new Environment();
		environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
		environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
		
		cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		cam.position.set(1f, 1f, 1f);
		cam.lookAt(0,0,0);
		cam.near = 1f;
		cam.far = 300f;
		cam.update();

		ModelLoader loader = new ObjLoader();
		model = loader.loadModel(Gdx.files.internal("data/ship.obj"));
		instance = new ModelInstance(model);

		camController = new CameraInputController(cam);
		Gdx.input.setInputProcessor(camController);
	}
...
}

View full source code on github

Just a few changes in there. First off I set the camera closer to the origin, because the ship model is rather small. Next we removed the ModelBuilder and instead created a ModelLoader and made it to load the ship model, that’s it.
modeltest1
That’s nice for our test, but in bigger applications you’ll probably want to use AssetManager to manage the models. So let’s add AssetManager:

public class LoadModelsTest implements ApplicationListener {
	public PerspectiveCamera cam;
	public CameraInputController camController;
	public ModelBatch modelBatch;
	public AssetManager assets;
	public Array<ModelInstance> instances = new Array<ModelInstance>();
	public Environment environment;
	public boolean loading;
	
	@Override
	public void create () {
		modelBatch = new ModelBatch();
		environment = new Environment();
		environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
		environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
		
		cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		cam.position.set(1f, 1f, 1f);
		cam.lookAt(0,0,0);
		cam.near = 1f;
		cam.far = 300f;
		cam.update();

		camController = new CameraInputController(cam);
		Gdx.input.setInputProcessor(camController);
		
		assets = new AssetManager();
		assets.load("data/ship.obj", Model.class);
		loading = true;
	}

	private void doneLoading() {
		Model ship = assets.get("data/ship.obj", Model.class);
		ModelInstance shipInstance = new ModelInstance(ship); 
		instances.add(shipInstance);
		loading = false;
	}
	
	@Override
	public void render () {
		if (loading && assets.update())
			doneLoading();
		camController.update();
		
		Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

		modelBatch.begin(cam);
		modelBatch.render(instances, environment);
		modelBatch.end();
	}
	
	@Override
	public void dispose () {
		modelBatch.dispose();
		instances.clear();
		assets.dispose();
	}

	public void resume () {
	}

	public void resize (int width, int height) {
	}

	public void pause () {
	}
}

View full source code on github

Let’s go through the changes. We removed the Model instance and replaced it by the AssetManager. Instead of one ModelInstance we replaced it with an array of instances, which is more realistic scenario and allows us to render more instaces later on. We also added a flag to indicate if we are still loading.

Now in the Create method we create the asset manager and tell it to load the ship model. Next we set the loading flag, so we know we need to update the assetmanager. In our render method we check if the loading flag is set and if so we call assets.update(). If assets.update() return true we know it’s done loading so we call a new method called doneLoading(). Also in the render method we render all the instances instead of just one. If the assets aren’t loaded yet, this means the array is empty.

The new method doneLoading() fetches the ship model we just loaded, creates an instance called shipInstance and adds it to the instances array, causing it to be rendered. Finally we need to set the loading flag to false, so the assets.update() method isn’t called anymore.

If you run this you’ll see the output is the same as before. Although you might shortly see a black screen before the ship pops in, which is caused by the model being loaded asynchronous now.

Since we now support multiple model instances, let’s add a few more.

public class LoadModelsTest implements ApplicationListener {
...
	@Override
	public void create () {
		...
		cam.position.set(7f, 7f, 7f);
		...
	}

	private void doneLoading() {
		Model ship = assets.get("data/ship.obj", Model.class);
		for (float x = -5f; x <= 5f; x += 2f) {
			for (float z = -5f; z <= 5f; z += 2f) {
				ModelInstance shipInstance = new ModelInstance(ship);
				shipInstance.transform.setToTranslation(x, 0, z);
				instances.add(shipInstance);
			}
		}
		loading = false;
	}
...
}

View full source code on github

Here we move the camera a bit away from the origin, so we can see all our ships. Note that you can also scroll the mouse to zoom in or out. In the doneLoading method we now create multiple instances and position them in a grid on the XZ plane.
modeltest2
Using an obj (wavefront) file is nice for testing. But it is not suitable to use in a real application, because the file format doesn’t include enough information for rendering complex models. In fact, the ObjLoader that is included with LibGDX is only intended for testing and doesn’t implement all functionality you might want to use.

Luckily there’s fbx-conv, which converts models exported from modeling software to a format suitable for rendering using LibGDX. Unlike the name might suggest, fbx-conv is suitable for converting many file formats (including obj), although fbx is the preferred file format because almost every modeling application support that format. There are two file formats LibGDX support, g3dj (which is json textual for easy debugging) and g3db (binary which you should use on release, because it’s smaller and faster to load).

So let’s convert our ship model. Download fbx-conv and call:

fbx-conv ship.obj

Make sure the other files (like ship.mtl) are within the same folder. Note that the actual executable might be named differently, e.g. fbx-conv-win32, in which case you should use that name accordingly. I assume you know how to execute command line (CLI) utilities on your platform. If not, I’d recommend learning so. Don’t drag and drop your fbx file on fbx-conv, it will not work for most scenario’s.

This should give you a file called ship.g3db, which we can use within our test application:

public class LoadModelsTest implements ApplicationListener {
...	
	@Override
	public void create () {
		...
		assets.load("data/ship.g3db", Model.class);
		...
	}

	private void doneLoading() {
		Model ship = assets.get("data/ship.g3db", Model.class);
		...
	}
...
}

View full source code on github

Next: Loading a scene with LibGDX

-= All Xoppa libGDX 3D Tutorials =-
  1. 1. Basic 3D using libGDX
  2. 2. Loading models
  3. 3. Loading a scene
  4. 4. Behind the scenes part 1
  5. 5. Behind the scenes part 2
  6. 6. Creating a shader
  7. 7. Using materials
  8. 8. Frustum culling
  9. 9. Ray picking
  10. 10. Collision shapes
  11. 11. 3D collision detection
  12. 12. 3D physics simulation

41 thoughts on “Loading models using LibGDX

  1. Very cool. Nice and basic. Here’s simple addition to render() method for rotating directional light:

    float time = 0;

    @Override
    public void render () {
    time += Gdx.graphics.getDeltaTime();
    for (DirectionalLight light : lights.directionalLights) {
    light.set(0.8f, 0.8f, 0.8f, (float) Math.sin(time), (float) Math.cos(time), -0.2f);
    }


    }

  2. Hi Xoppa,

    I’ve a problem with the g3db format. When I use fbx-conv with the ship model, I’ve got successfuly the g3db file, but when I go to load the file with the AssetManager and run the app, I’ve got an exception:

    Exception in thread “LWJGL Application” com.badlogic.gdx.utils.GdxRuntimeException: com.badlogic.gdx.utils.GdxRuntimeException: Couldn’t load dependencies of asset ‘data/ship.g3db’

    and below

    Caused by: java.util.concurrent.ExecutionException: com.badlogic.gdx.utils.SerializationException: Error parsing file: data/ship.g3db

    I have all files in the same folder (ship.mtl, ship.obj and ship.png). However, if i use fbx-conv to get the G3DJ file and I load it, it works perfectly. What can it be?

    I’m on Ubuntu 12.04 64-bits.

    Thanks in advance.

      • I have the same g3db parsing problem, but update of libgdx has not helped. I am using fbx-conv from the 1st November… I would use an older one, but I can’t find older… should I wait until newer version of libgdx is available? I updated libgdx today (I used nightlies)

  3. Hi, congrats for the tutorial.
    I want to export and save de g3d file in real time, because my app read 3d model information from a database. Is there a way I can save it? thanks

  4. I have a little problem when exporting 3D objects from Blender.

    No matter what I do, it will be shown to me no texture under libGDX.

    Also if I load a demo model in Blender and then Export, me no texture is displayed even though the original model is displayed without problem.

    Where this is the problem when exporting from Blender?
    Do I have something specific setting?

    Greeting Longri

    • If you use the wavefront obj format, check if your obj file contains texture coordinates – look for the lines starting with “vt”. Additionally check if obj refers to the correct mtl file – look for “usemtl”, avoid of spaces in the file name. Finally check the mtl file for reference to the texture png file, there should be something like “map_Kd ship.png” – it should not contain path to the file. Then you have to store all three files (obj, mtl and png) in the assets/data directory. If everything seems to be correct, check the logcat if there is any error message. Does this help?

    • I had the same problem when trying to export to .obj from a .blend file. It seems that Blender does not export the texture by default.

      To solve the problem I had to go to:
      File -> External Data -> Unpack into Files

      That should create a folder containing the textures. In my case, it created a folder named “textures” in the same location as my .blend file.

      Hope this helps.

      • I not found any abnormal info in Log. and the textures is still could not display. Your ship.obj can work good, but my blend file can’t work for now. Could you support your Ship.blend for me to test? Very thanks. I’m Chinese, English is not too skill. if could, please send the Ship.blend to here:

        • I know this is a relatively old post, but I was having problems getting textures to work with my blender exported .obj models as well.

          The first issue I encountered seems to be a bug in the AssetManager when it encounters a material name with a period in it. In blender, the auto-generated material names come out with periods (ex: Material.001, Material.002, etc), which seems to cause the problem. If you are editing the material in a text editor, make sure to use the same material name in both the .mtl and .obj file.

          Immediately after I fixed the material naming problem, and it took a little while to realize what was going on, I encountered the next issue with blender .obj outputs. My model was being rendered completely invisible. As I was using a BlendingAttribute on the model and thought that may have something to do with it, I removed the attribute. As a result, my model was being rendered completely black. Remembering the -f option for fbx-conv for flipping the v of texture coordinates, I took a look at the vt coordinates in my model’s .obj. Sure enough, they were in an unused area of my texture_atlas.png Libgdx expects 0==top and 1==bottom, however, blender output had those reversed. Fortunately I had very few uvs and it was a quick fix.

          tl;dr
          1. If your .obj is not getting textured, ensure that the names of your materials do not contain periods in them (ex. Material.001 BAD, my_material GOOD)
          2. If you are getting black/wrong textures on your objects, look into flipping (1.0 – current value) the v values of your uv coordinates. These are the coordinate pairs starting with vt. Leave the u alone. (ex. vt 0.5 0.75 –> vt 0.5 0.25)

          Good luck and keep on coding.

  5. Pls hint how to fix the following issue: I want to render 3d object on specific gps location on the Google Map (API v2). I have managed to set up transient GLSurfaceView above the MapView and followed this tutorial to load my test model from json format file. I use GL20 but did not write any shader, I assume some default shader is provided by the libgdx framework in this case. The result is ok on my Nexus7 but wrong on my HTC Sensation – test object is opaque on the Nexus as intended but on the HTC the object is transient and flickering. I guess the reason is probably the alpha blending, but I do not understand why it works on Nexus 7. What should I check in my code? Thx. Lubos

  6. Is there anyone out there that can explain this better?

    Download fbx-conv and call:
    1 fbx-conv ship.obj

    (specifically, the call part. Is this a command line using the path?)
    Also, I can get the stock ship model to work in the second part of the tutorial but my testcube from blender wasn’t loading once I wasn’t using ObjLoader(),(I think).

  7. Hi Xoppa,
    I have problem with fbx-conv to g3db: It loose the texture. My 3d object became white.
    I am sure that the texture is in the same folder. How I can solve this?
    Thanks
    Valeria

      • I did however get warnings.
        Loading source file…
        WARNING: Node ship_root uses RrSs mode, transformation might be incorrect.
        WARNING: Node cube1_cube1_auv uses RrSs mode, transformation might be incorrect.

        • Also having this issue. I’ve insured the normals have been corrected, I used UV mapping to ensure vt texture mapping lines are present, I manually modified the path to the texture in the .mtl file to point to the same directory and all then converted to .g3db using the conversion utility. I did get the above warnings, and the object draws without a texture. In this case it’s a simple plane with a grass texture.. been tearing hair out for 6 hours now, no Idea why it doesn’t work. IRC is pretty quiet right now too.

          • Almost the same problem here, it seems when using transparency in UV mapping that causes it. I downloaded a character that uses transparency to texturized the hair, but it gets bald when I have converted it to g3db and render it in Libgdx. Is it a bug?

          • Nevermind, I was wrong, I have it to other blender file, it seems there is a problem regarding the exporting of it to the one that is not drawing on the screen. I guess that might be blender UV mapping operation, not exactly in LibGdx, but I might be wrong, as I am both newbie using blender and LibGdx’s 3D API.

    • I had a little fun with this one as well for an hour or so. Looks like the original ship.obj and ship.mtl were generated with Wings – which is a fine 3D tool…however the syntax is slightly different from what Blender generates by default. This is most likely to my noobness of 3D modeling but I found a quick and dirty way to get a Blender exported .obj file to work with this tutorial. If you open the ship.obj and ship.mtl files in a text editor and the Blender files (for Windows users – use Wordpad/Visual Studio if you’ve got it since Blender generates with /n by default) you can see the differences. I used blender 2.69, but inserted the syntax from the ship.obj/ship.mtl files from this incredible tutorial (thanks Xoppa) with mods to point to my blender exported .mtl and .png UV mapped files and viola! Blender works just fine. I think I’ll be scripting something for blender files to create a “Wings compatible” .obj file – or keep digging to make sure I understand the application of the material/texture in blender to Wavefront files prior to exporting.

      Just make sure the following lines are correct for your exported model – i.e. change ship.mtl to .mtl and ship.png to .png (line numbers in front):
      # ship.obj
      2 mtllib ship.mtl
      3 o cube1

      373 g cube1_cube1_auv
      374 usemtl cube1_auv

      # ship.mtl
      1 newmtl cube1_auv
      2 illum 4
      3 Kd 1.00 1.00 1.00
      4 Ka 0.00 0.00 0.00
      5 Tf 1.00 1.00 1.00
      6 map_Kd ship.png
      7 Ni 1.00

      Hope this helps someone save some time fiddling around with Blender. libgdx is by far, the most awesome cross-platform library I’ve used in my many years of programming. Thank you and Keep it up guys!

      • Oops, correction there. The following should be

        Just make sure the following lines are correct for your exported model – i.e. change ship.mtl to yourblenderfile.mtl and ship.png to yourblenderfile.png (line numbers in front):

        I had HTML tags before and after yourblenderfile were removed after I posted.

  8. hi! thanks for this great tutorial!
    very useful.
    i have problem! i can load models with your source code but i cant load models in my project…
    i put the assets in my android data folder and call this cod to get ship model:

    ModelLoader loader = new ObjLoader();
    model = loader.loadModel(Gdx.files.internal(“data/ship.obj”));
    instance = new ModelInstance(model);

    but in every times i got this error:
    Exception in thread “LWJGL Application” java.lang.NullPointerException… …

    this is my desktop starter:

    public class Main {
    public static void main(String[] args) {
    LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
    cfg.title = “tower”;
    cfg.useGL20 = false;
    cfg.width = 640;
    cfg.height = 480;
    new LwjglApplication(new mainscene(), cfg);
    }
    }

    i changed the usegl20 value to true but nothing happend…
    i run my project on android too, but again i got error…
    what should i do?
    thanks again.

  9. Hello there!

    I have a question, how can I rotate+translate an object at the same time?

    Tried this:

    shipInstance.transform.setToTranslation(x/10, 0, z/10);
    shipInstance.transform.setToRotation(Axis.X, (float) (Math.random()*360));

    I need something spesial: scale(10) then translate(x,y,z) then rotate(…)

    but the last one working always… thanks!

  10. Sweet! Thanks for this. I found a cat .obj file, and now I have an ARMY OF CATS! Not my initial goal, it just sorta happened…

  11. This is a very neat tutorial =)
    The only thing i do not understand is:

    Download fbx-conv and call:
    fbx-conv ship.obj

    You don’t need to explain this here in the comment section but
    it would be really nice to have a link to a click-by-click
    tutorial about what fbx-conv is and how to use it.
    (i know it converts dae/obj/etc.. to binary but is it a library, an application etc…
    and where do i have to call this command, out of my window? xD)

    • You need to run it from a “Dos” Command Prompt Window if you are using Windows. If so, click on start, and type “cmd” in the ‘Search & run’ box. Hit enter. Then you have to navigate to the folder that contains fbx-conv as well as your other files (like ship.mtl, or whatever all have to be in the same directory as fbx-conv)
      If you don’t know how to navigate within a dos environment you need to read up on how to change directories so you can navigate to the folder on your computer that contains your files and fbx-conv. Once you are in that folder just type “fbx-conv ship.obj” You will now find that a new file called “ship.g3db” is in the same folder. Easy!

  12. If I wanted to render this 3D model onto a 2D tile surface would I use 2 different cameras? I need to use an OrthographicCamera for my top down tile map. Can 2 different cameras be used like this?

    public void render(float delta) {
    Gdx.gl.glClearColor(0, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    renderer.setView(camera); //using orthographic camera for tile renderer
    camera.update();
    renderer.render(); //render tiles

    modelBatch.begin(cam); //now using perspective camera for model
    modelBatch.render(instances, environment);
    modelBatch.end();

  13. I am getting nothing but a BLACK window with absolutely nothing in it. No errors..I am using the most current ver of libGdx. Any ideas?

  14. I get nothing but a black screen on some modell.
    My modell is 2200 Triangle, and I use this code to load:

    __create__
    texture = new Texture(Gdx.files.internal(imagefile));

    this.loading = true;
    this.objfile = objfile;
    assets = new AssetManager();
    assets.load(objfile, Model.class);

    __doneLoading___
    private void doneLoading() {
    model = assets.get(objfile, Model.class);
    instance = new ModelInstance(model, transform);
    instance.materials.get(0).set(TextureAttribute.createDiffuse(texture));

    environment = new Environment();
    environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));

    loading = false;
    }

    __render___

    if (loading) {
    if ( assets.isLoaded(objfile) ) doneLoading();
    Log.e(“loading…”, “”+objfile);
    return;
    }
    }

    texture.bind();
    modelBatch.render(instance);

    But I get always “loading” … what is the probleme here?

  15. Hello Xoppa,

    Can you help me? ObjLoader and G3DBLoader can handle files that contains more items? Or just only one item per obj file?

    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>