Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon

3D Modeling

Save for later
  • 7 min read
  • 05 Feb 2015

article-image

In this article by Suryakumar Balakrishnan Nair and Andreas Oehlke, authors of Learning LibGDX Game Development, Second Edition, you will learn how to load a model and create a basic 3D scene. In a game, we need an actual model exported from Blender or any other 3D animation software.

(For more resources related to this topic, see here.)

Loading a model

Copy these three files to the assets folder of the android project:

  • car.g3dj: This is the model file to be used in our example
  • tiretext.jpg and yellowtaxi.jpg: These are the materials for the model

Replacing the ModelBuilder class in our ModelTest.java file, we add the following code:

          assets = new AssetManager();
          assets.load("car.g3dj", Model.class);
          assets.finishLoading();
          model = assets.get("car.g3dj", Model.class);
          instance = new ModelInstance(model);

Additionally, a camera input controller is also added to inspect the model from various angles as follows:

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

          camController.update();

This camera input controller will be updated on each render() by calling camController.update().

The completed MyModelTest.java is as follows:

public class MyModelTest extends ApplicationAdapter  {
   public Environment environment;
   public PerspectiveCamera cam;
   public CameraInputController camController;
   public ModelBatch modelBatch;
   public Model model;
   public ModelInstance instance;
   public AssetManager assets ;

   @Override
   public void create() {
   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));

          modelBatch = new ModelBatch();

          cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
          cam.position.set(1,1,1);
          cam.lookAt(0, 0, 0);
          cam.near = 1f;
          cam.far = 300f;
          cam.update();

          assets = new AssetManager();
          assets.load("car.g3dj", Model.class);
          assets.finishLoading();
          model = assets.get("car.g3dj", Model.class);
          instance = new ModelInstance(model);

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

   }

   @Override
   public void render() {
          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(instance, environment);
          modelBatch.end();
   }

   @Override
   public void dispose() {
          modelBatch.dispose();
assets.dispose() ;
   }

}

The new additions are highlighted. The following is a screenshot of the render scene. Use the W , S , A , D keys and mouse to navigate through the scene.

3d-modeling-img-0

Model formats and the FBX converter

LibGDX supports three model formats, namely Wavefront OBJ, G3DJ, and G3DB. Wavefront OBJ models are intended for testing purposes only because this format does not include enough information for complex models. You can export your 3D model as .obj from any 3D animation or modeling software, however LibGDX does not fully support .obj, hence, if you use your own .obj model, then it might not render correctly. The G3DJ is a JSON textual format supported by LibGDX and can be used for debugging, whereas the G3DB is a binary format and is faster to load.

One of the most popular model formats supported by any modeling software is FBX. LibGDX provides a tool called FBX converter to convert formats such as .obj and .fbx into the LibGDX supported formats .g3dj and .g3db.

To convert car.fbx to a .g3db format, open the command line and call fbx-conv-win32, as shown in the following screenshot:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime

3d-modeling-img-1

Make sure that the fbx-conv-win32.exe file is in the same folder as car.fbx. Otherwise, you will have to use the full path of the source file to convert.

To find out more about FBX converter visit https://github.com/libgdx/fbx-conv and https://github.com/libgdx/libgdx/wiki/3D-animations-and-skinning. Also, you can download FBX converter from http://libgdx.badlogicgames.com/fbx-conv.

Creating a basic 3D scene

Create a simple scene with a ball and ground, as shown in the following screenshot:

3d-modeling-img-2

Add the following code to MyCollisionTest.java:

package com.packtpub.libgdx.collisiontest;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
...
import com.badlogic.gdx.utils.Array;

public class MyCollisionTest extends ApplicationAdapter {

PerspectiveCamera cam;
ModelBatch modelBatch;
Array<Model> models;
ModelInstance groundInstance;
ModelInstance sphereInstance;
Environment environment;
ModelBuilder modelbuilder;
@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(0, 10, -20);
   cam.lookAt(0, 0, 0);
   cam.update();
   models = new Array<Model>();
   modelbuilder = new ModelBuilder();
   // creating a ground model using box shape
   float groundWidth = 40;
   modelbuilder.begin();
   MeshPartBuilder mpb = modelbuilder.part("parts", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal | Usage.Color, new Material(ColorAttribute.createDiffuse(Color.WHITE)));
   mpb.setColor(1f, 1f, 1f, 1f);
   mpb.box(0, 0, 0, groundWidth, 1, groundWidth);
   Model model = modelbuilder.end();
   models.add(model);
   groundInstance = new ModelInstance(model);
   // creating a sphere model
   float radius = 2f;
   final Model sphereModel = modelbuilder.createSphere(radius, radius, radius, 20, 20, new Material(ColorAttribute.createDiffuse(Color.RED), ColorAttribute.createSpecular(Color.GRAY), FloatAttribute.createShininess(64f)), Usage.Position | Usage.Normal);
   models.add(sphereModel);
   sphereInstance = new ModelInstance(sphereModel);
   sphereinstance.transform.trn(0, 10, 0);
}
public void render() {
   Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
   Gdx.gl.glClearColor(0, 0, 0, 1);
   Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
   modelBatch.begin(cam);
   modelBatch.render(groundInstance, environment);
   modelBatch.render(sphereInstance, environment);
   modelBatch.end();

}

@Override
public void dispose() {
   modelBatch.dispose();
   for (Model model : models)
          model.dispose();
}
}

The ground is actually a thin box created using ModelBuilder just like the sphere. Now that we have created a simple 3D scene, let's add some physics using the following code:

public class MyCollisionTest extends ApplicationAdapter {
...
private btDefaultCollisionConfiguration collisionConfiguration;
private btCollisionDispatcher dispatcher;
private btDbvtBroadphase broadphase;
private btSequentialImpulseConstraintSolver solver;
private btDiscreteDynamicsWorld world;
private Array<btCollisionShape> shapes = new Array<btCollisionShape>();
private Array<btRigidBodyConstructionInfo> bodyInfos = new Array<btRigidBody.btRigidBodyConstructionInfo>();
private Array<btRigidBody> bodies = new Array<btRigidBody>();
private btDefaultMotionState sphereMotionState;
@Override
public void create() {
...
    // Initiating Bullet Physics 
   Bullet.init();
    //setting up the world
   collisionConfiguration = new btDefaultCollisionConfiguration();
   dispatcher = new btCollisionDispatcher(collisionConfiguration);
   broadphase = new btDbvtBroadphase();
   solver = new btSequentialImpulseConstraintSolver();
   world = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
   world.setGravity(new Vector3(0, -9.81f, 1f));
   // creating ground body
   btCollisionShape groundshape = new btBoxShape(new Vector3(20, 1 / 2f, 20));
      shapes.add(groundshape);
   btRigidBodyConstructionInfo bodyInfo = new btRigidBodyConstructionInfo(0, null, groundshape, Vector3.Zero);
   this.bodyInfos.add(bodyInfo);
   btRigidBody body = new btRigidBody(bodyInfo);
   bodies.add(body);
   world.addRigidBody(body);
   // creating sphere body
  sphereMotionState = new btDefaultMotionState(sphereInstance.transform);
   sphereMotionState.setWorldTransform(sphereInstance.transform);
   final btCollisionShape sphereShape = new btSphereShape(1f);
   shapes.add(sphereShape);
   bodyInfo = new btRigidBodyConstructionInfo(1, sphereMotionState, sphereShape, new Vector3(1, 1, 1));
   this.bodyInfos.add(bodyInfo);
   body = new btRigidBody(bodyInfo);
   bodies.add(body);
   world.addRigidBody(body);

}

public void render() {
   Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
   Gdx.gl.glClearColor(0, 0, 0, 1);
   Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
   world.stepSimulation(Gdx.graphics.getDeltaTime(), 5);
   sphereMotionState.getWorldTransform(sphereInstance.transform);
   modelBatch.begin(cam);
   modelBatch.render(groundInstance, environment);
   modelBatch.render(sphereInstance, environment);
   modelBatch.end();
}

@Override
public void dispose() {
   modelBatch.dispose();
   for (Model model : models)
          model.dispose();
   for (btRigidBody body : bodies) {
          body.dispose();
   }
   sphereMotionState.dispose();
   for (btCollisionShape shape : shapes)
          shape.dispose();
   for (btRigidBodyConstructionInfo info : bodyInfos)
          info.dispose();
   world.dispose();
   collisionConfiguration.dispose();
   dispatcher.dispose();
   broadphase.dispose();
   solver.dispose();
   Gdx.app.log(this.getClass().getName(), "Disposed");
}
}

The highlighted parts are the addition to our previous code. After execution, we see the ball falling and colliding with the ground.

Summary

In this article, you learned how to load a 3D model of a car and created a basic 3D scene.

Resources for Article:


Further resources on this subject: