Using Blender 3D models in LibGDX

This tutorial will show you how to export a simple 3D model from Blender and use it in the LibGDX Java game development framework. It is in no way a comprehensive guide on any of the subjects covered, but will give you something to get started. At the end we will have a fully working program that shows a very simple textured model. To make sure we won’t spend hours sculpting some advanced high resolution model, I’m creating a cube shaped crate, which coincidentally is the default mesh you get when you create a new file in Blender.

Screenshot of the finished application.

Screenshot of the finished application.

To complete this tutorial, you will need the following programs:

  • Blender 3D modelling program. You only need a standard installation without any additional plugins. In this tutorial, I’m using version 2.69, the latest at the time of writing.
  • Free crate texture from DeviantART, or draw your own. It is a handy 512×512 pixels image, although having a power-of-two texture size is not a requirement for OpenGL ES 2.0.
  • Fbx-conv model converter, get a pre-compiled binary from here.
  • LibGDX framework library
  • Eclipse Java development IDE with the Android SDK installed. You’re quite welcome to use any other IDE, but you’ll need to know how to import an auto-generated LibGDX Eclipse project or to create one from scratch.

Creating your model in Blender

If you know how to create and texture a model in Blender, you can safely skip this section. If you don’t, you probably won’t learn it from here, as this is not a modelling tutorial. In any case, this section is here to provide a nice self-contained example that requires no prior knowledge.

When you create a new blender file, it starts with 2x2x2 cube as the default mesh. As we’re creating a cube-shaped crate, the modelling step is pretty much done. One thing to note: when you’re exporting your model, the origin of the model will be at Blender’s (0,0,0). It means that when you position your object in the 3D world, that will be the reference point you set, and when you rotate the object, it will rotate around that point. As the crate usually sits flat on a surface, move the cube to sit on the XY-plane. Simply click on the + button on the right side of your 3D viewport to open the additional sidebar, and change the Z location to 1. Keep in mind that Blender’s coordinate system is Z-up. While we are here in this extra sidebar, tick the Textured Solid box under Shading. This will show the textures on the model and help with the texturing process.

Click here to open 3D view sidebar.

Click here to open 3D view sidebar.

Change these in the sidebar.

Change these in the sidebar.

Now that our model is done and the view is set up, it’s time to texture the crate. There are a few commands in Blender that you need to know for this part of the tutorial:

  • Right clicking on an object/vertex/face will select it, depending on which editing mode you’re in. Holding shift will add to your current selection. Pressing A will select/deselect all.
  • Use the middle mouse button to rotate the 3D view.
  • While your mouse pointer is over the correct window (e.g. 3D viewport), you can enter transformation modes by pressing the correct button:
    • Press S to scale.
    • Press G to move (grab).
    • Press R to rotate. Hold ctrl to constrain rotation to 5 degree steps.
  • During a transformation left click will accept the changes and right click will cancel it. Pressing XY or Z will constrain transformations to the corresponding axis.
  • Blender’s 3D view has two modes that we need, press tab to switch between them:
    • In object mode you manipulate your objects as a whole to set up your scene. This is the default.
    • You have to switch to edit mode to edit your selected object’s mesh or texture coordinates.

To start the texturing process, switch to edit mode and unwrap the model to generate UV texture coordinates. To do so, make sure all vertices are selected, then press U and select unwrap. To see what it’s done, split the 3D view by dragging its top-right corner to the left, then switch the new view’s type on the bottom-left corner to UV/Image Editor.

Drag here to split a view.

Drag here to split a view.

Click here to change view type.

Click here to change view type.

To help visualise the texturing process, load up the crate texture in this new view by clicking on Image menu / Open Image.

This is what you get right after unwrapping.

This is what you get right after unwrapping.

As you can see, all 6 cube faces have been assigned the same texture coordinates, and the whole texture is showing on each face. You have to manually move the UV texture coordinates on the image view for each face of the cube. Change the 3D view’s selection mode to face, so that you can select whole faces with a single click to make this process easier. The two views are linked, whichever face you select in the 3D view, you can move/scale/rotate the corresponding UV regions on the image view.

Click here to switch to face selection mode while in edit mode. If it's not visible, drag the toolbar with the middle mouse button.

Click here to switch to face selection mode while in edit mode. If the view is too small, drag the toolbar with the middle mouse button to scroll it.

The finished, texture mapped crate model.

The finished, texture mapped crate model.

Above is how our finished crate should look with correct texture coordinates. The top of the crate should have the text properly readable when looking at it from the front. To switch to front view, press 1 on the numeric keypad over the 3D view, then tilt the view up from there to check. This is how we’ll have a reference point to check the LibGDX application’s coordinate system against Blender’s.

Now if you press F12 over the 3D view, you can render the box from the current camera view. You’ll instantly notice that our texture is not applied to the model. This is because you haven’t actually assigned the texture to the cube, just used it as a guide for texturing. To assign the texture, press esc to go back to 3D view, switch to object mode, select the cube object, and do the following in the properties sidebar on the right:

  • Select the texture tab.
  • Set type to Image or Movie.
  • In the Image section click the left selection button and select the already loaded image from the list.
  • In the Mapping section, change coordinates to UV
  • In the Influence section, Color should be ticked (it’s on by default).
Blender texture settings

Blender texture settings

If you followed the steps above, your crate should render properly and it’s ready to export.

Exporting the model in Wavefront format

To bring up the export screen, select File menu / exportWavefront (.obj). There are quite a few settings on it, but the default ones only need a few tweaks:

  • Make sure include normals is ticked. This will export the vertex normals with our model, which is very important to save on fill rate via back-face culling, as well as using directional lights in our application. It is also essential if you’re using normal maps (not covered in this tutorial).
  • Triangulate faces is recommended. If you don’t do it, LibGDX will when loading the model. Quads will be simply split into two triangles. Try not to have more complex polygons in your model to avoid bugs and tessellation errors.
  • Forward and up should be set to match your game’s coordinate system. I decided to use Blender’s in this tutorial, which is Y forward and Z up.
  • Path mode changes how your materials will reference the texture files, for this tutorial strip path will be sufficient, as we’ll put the texture file in the same asset folder with the model in the LibGDX app.

When you save your model, it will be saved in two files, an .obj file with the vertex and normal data, and an .mtl file with the material and texture details. You will need both these files for the next step.

Converting the model from .obj to .g3db

The .obj file can already be loaded in LibGDX, but it’s only recommended for testing. It is a verbose text-based format, for production use it’s recommended to convert it to the more efficient binary .g3db format.

The conversion process is quite simple, run the following from the command line:

fbx-conv -f modelfile.obj

LibGDX texture coordinates have to be flipped, the -f parameter makes sure this is done in the model file, so it can be loaded straight in, without the need to call flipping methods in the Java app.

At this point the model is ready to use, all that’s needed is the .g3db file and the texture image(s), let’s get on with the programming part.

Creating a LibGDX project

These instructions are for Windows, but I presume it’s quite similar on all platforms. Extract the downloaded libgdx-0.9.9.zip file, but make sure to keep the zip package, as we’ll need it for the code generation process. Run gdx-setup-ui.jar by either double-clicking on it, or by running java -jar gdx-setup-ui.jar from the command line.

This should open the LibGDX project setup interface, click on the Create button to start a new project. Set the project name, package and game class, then pick a destination folder for your project. As we’re creating a simple project that can be tested on the development machine, only desktop project have to be checked in addition to the mandatory core and android boxes. Finally, click on the folder icon next to LibGDX in the library selection section and locate the originally downloaded libgdx-0.9.9.zip file. When done, click on the Open generation screen button.

LibGDX project setup

LibGDX project setup

There is not much to do on the project generation screen, click Launch and your project should be ready in a few seconds.

LibGDX project generation screen

LibGDX project generation screen

Assuming your project name is modeltutorial and you didn’t change the default naming scheme, you should have these subfolders created in your project folder:

  • modeltutorial contains your main, cross-platform application code.
  • modeltutorial-android contains Android specific code and settings, e.g. the Android manifest file. As the Android build process doesn’t support a linked asset folder, all your game data (models, textures, etc.) goes into the modeltutorial-android/assets subfolder, all other platforms reference it from here.
  • modeltutorial-desktop is your desktop specific code, you can run it on your development machine.

Importing your project into Eclipse

This is an easy step, as the generated subfolders contain properly set-up Eclipse projects. Simply click on FileImport…. In the import window, select Existing Projects into Workspace under the General category, click Next. Select the project’s root directory, in my case it’s D:\Projects\ModelTutorial, make sure all three projects are checked, then click Finish. All three projects should appear in your workspace’s package explorer window.

The model viewer application

First, you have to include all your graphics assets into your application. Delete the default assets/data/libgdx.png file from the Android project, and copy the converted crate model and its texture in there. Mine is called crate.g3db and box_texture.png.

Next we’ll just make some minor changes to Main.java in the desktop project. I just gave the window a proper title and made the window slightly bigger than the default. I also enabled OpenGL ES 2.0, according to Google’s Android Dashboard, 99.9% of active devices support it. It uses a fully programmable pipeline, which will give you much more features in the future. It also means you have to write vertex and fragment shaders to even render the simplest things, but LibGDX has a nice default shader included, so you don’t have to worry about writing your own for simple projects.

package com.codebin.modeltutorial;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Main {
	public static void main(String[] args) {
		LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
		cfg.title = "Model Tutorial";
		cfg.useGL20 = true; // Enable OpenGL ES 2.0
		cfg.width = 640;
		cfg.height = 480;

		new LwjglApplication(new ModelTutorial(), cfg);
	}
}

In order to use OpenGL ES 2.0 on Android you also have to change MainActivity.java in modeltutorial-android:

package com.codebin.modeltutorial;

import android.os.Bundle;

import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class MainActivity extends AndroidApplication {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
		cfg.useGL20 = true; // Enable OpenGL ES 2.0

		initialize(new ModelTutorial(), cfg);
	}
}

You also need to declare that you intend to use it in the Android manifest file (AndroidManifest.xml) by adding the following line:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

It is time to write the actual application code that will load and render the model as well as give the user some minimal interactivity. The entire application code is within the class that’s instantiated in the last line of the main function above, in my case it’s in ModelTutorial.java in the main modeltutorial project. Without further ado, here’s the commented code:

package com.codebin.modeltutorial;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
import com.badlogic.gdx.utils.Array;

public class ModelTutorial implements ApplicationListener {
	ModelBatch modelBatch;
	Environment environment;
	PerspectiveCamera camera;
	AssetManager assets;
	CameraInputController cameraController;

	int screenWidth;
	int screenHeight;

	Array<ModelInstance> instances = new Array<ModelInstance>();

	@Override
	public void create() {
		// Get screen dimensions
		screenWidth = Gdx.graphics.getWidth();
		screenHeight = Gdx.graphics.getHeight();

		// Create ModelBatch that will render all models using a camera
		modelBatch = new ModelBatch();

		// Create a camera and point it to our model
		camera = new PerspectiveCamera(70, screenWidth, screenHeight);
		camera.position.set(0f, -4f, 3f);
		camera.lookAt(0, 0, 0);
		camera.near = 0.1f;
		camera.far = 300f;
		camera.update();

		// Create the generic camera input controller to make the app interactive
		cameraController = new CameraInputController(camera);
		Gdx.input.setInputProcessor(cameraController);

		// Create an asset manager that lets us dispose all assets at once
		assets = new AssetManager();
		assets.load("data/crate.g3db", Model.class);
		assets.finishLoading();

		// Create an instance of our crate model and put it in an array
		Model model = assets.get("data/crate.g3db", Model.class);
		ModelInstance inst = new ModelInstance(model);
		instances.add(inst);

		// Set up environment with simple lighting
		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, -0.8f, 0.3f, -1f));
	}

	@Override
	public void dispose() {
		// Release all resources
		modelBatch.dispose();
		instances.clear();
		assets.dispose();
	}

	@Override
	public void render() {
		// Respond to user events and update the camera
		cameraController.update();

		// Clear the viewport
		Gdx.gl.glViewport(0, 0, screenWidth, screenHeight);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

		// Draw all model instances using the camera
		modelBatch.begin(camera);
		modelBatch.render(instances, environment);
		modelBatch.end();
	}

	@Override
	public void resize(int width, int height) {
		// Update screen dimensions
		screenWidth = width;
		screenHeight = height;

		// Update viewport size and refresh camera matrices
		camera.viewportWidth = width;
		camera.viewportHeight = height;
		camera.update(true);
	}

	@Override
	public void pause() {
	}

	@Override
	public void resume() {
	}
}

This concludes this tutorial on how to import simple Blender models into a LibGDX application. Below are some random thoughts about the code above, feel free to skip any section if no explanation is needed.

ApplicationListener interface

The main game class implements this interface to hide the platform-specific details of the rendering loop and application state. In a bigger project you’d want to have multiple game screens using the Screen interface which has similar methods, but for the purpose of this tutorial it’s more than enough.

The create() method is called at your application start, this is where you create the objects that will be used throughout your application. While your application is running, LibGDX will regularly call render() which is your main loop where all the drawing is done.

Your application may receive some events while running. The resize() method is called when the application’s window is resized, or in case of a mobile device, when the screen orientation changes. On Android your application may enter a paused state and subsequently resumed as the user switches applications, when this happens, the pause() and resume() methods are called respectively. Finally, dispose() is called when your application closes, you need to dispose of all resources that are not garbage collected to avoid memory leaks.

PerspectiveCamera and CameraController

In this tutorial the PerspectiveCamera class is responsible to set up the projection matrix in a developer-friendly way. The constructor sets the field of view and the viewport size, while the position.set() and lookAt() methods change the camera’s position and direction respectively. With the two variables we set the near and far clipping plane, any objects closer or farther away will not be drawn. It could also affect the accuracy of the depth buffer, as all possible values will be distributed between these two extremes. It is important to call update every time the camera’s parameters change, which will recalculate the matrix.

The CameraController is just a generic built-in input processor that will move the camera in response to mouse or touch actions by the user. Perfect for a simple model viewer like this one. Make sure to call its update() method in the render loop to update the camera.

AssetManager

While the size of this program doesn’t justify it, it’s better to get used to using this class. The AssetManager is a central place to store all assets, load them asynchronously without freezing your application and dispose them one by one as needed or all at once. You can also implement a loading screen for bigger projects where the loading stage takes more than a fraction of a second.

The best feature of it is that it loads shared resources only once, saving precious memory. If for example two of your models use the same texture, the texture will only be loaded once. All assets are reference counted, so the texture will be kept in memory while either of the models is loaded, then automatically disposed when not in use anymore.

As this app is very simple and loading doesn’t really take any time, the finishLoading() method is called to load all resources synchronously right away, ready to be used and rendered.

ModelInstance array

This is again used for convenience and useful if the app is developed further, this way all models can be rendered with a single method call. To use duplicates of the same model, create a ModelInstance object which can have its own transformations. For example you can create two more instances of the crate model, move each instance a bit by calling translate() on their transform objects, push them on the instances array, and they will all be rendered using the same single call in the render loop.

Environment

The Environment object is where the lights for the scene are set up. Make sure the converted model has normals included, otherwise directional lights will not work. Along with the camera and the model instance array, this completes the full rendering context that’s needed for a simple static scene using the default shader.

Share Button
Posted in Blog Tagged with: , , , ,
0 comments on “Using Blender 3D models in LibGDX
1 Pings/Trackbacks for "Using Blender 3D models in LibGDX"
  1. […] at rendering 3D geometry and/or sprites in arbitrary low resolutions in LibGDX. I’m taking my previous article with the crate model imported from Blender and change it to render the view into a low-resolution framebuffer then display it on the screen so […]

Leave a Reply