Build in 3D for Android Using LibGDX in Kotlin | by Federico Jordan | Jul, 2022

First steps into the 3D space

Photo by Amokrane Ait-Kaci on Unsplash

Have you ever thought about building something in 3D for mobile?

What about doing it multi-platform?

Of course, we know that there are many native alternatives. For instance, you can use SceneKit if you want to build for iOS or maybe Vulkan on Android. But the idea is to build it once and deploy both targets. Also, desktop or web if we want.

The framework I choose to play with 2d or 3d graphics is LibGDX. So what is it exactly? It’s just a wrapper around OpenGL low-level APIs. It has a great community, and there are many articles and tutorials around the web.

What about other graphics multi-platforms tools? Well, in my opinion, I prefer LibGDX because you have it all in the code. There are other options, I know, but these you combine a kind of script with a nonoptional GUI system. Maybe you can do it all by code, but that’s not the idea here. With LibGDX, you have to do it all by code, and I think that you learn a bit more and have more control about what you want to do

To start a new LibGDX project, we must go to the project generation page and download the Stable Release installer.

At the moment of writing this article, the stable version will install LibGDX 1.10.0

This will download a gdx-setup_latest.jar file. Click on it

(If you’re on macOS and experienced a problem opening the file, just allow it from System Preferences -> Security & Privacy)

If you open the tool, you’ll see a dialog like this:

LibGDX setup tool

Let’s complete the information:

  • Name: Name of the project. In my case will be LibGDX3DTest
  • Package: This’ll be the package name in Android or the bundle identifier in iOS
  • Game class: Main class and start point of the LibGDX app
  • Destination: Where the project will be located
  • Android SDK: Default destination for android SDK
  • Sub Projects: Here, we’ll use just Android and iOS by now
  • Extensions: No one for now. If we want to simulate physics in the future, we’ll need Box2d if it’s a 2D project or Bullet for a 3D one. These are LibGDX wrappers to not mess with the original SDK.
Final settings

Another thing is that we need to enable Kotlin to use it on this project. Otherwise, it will be generated just to use Java. Tap on Advanced, and there is the setting:

Enable “Use Kotlin” and tap on save

Let’s tap on Generate, and it will create the project in the desired folder.

More information about generating a LibGDX project here

Now we need to open the project in Android Studio. I’m currently using AS 2020.3.1, but should not be any problem with a newer version.

Run the project in Android Studio. It should show the following screen:

First screen of a LibGDX app

The app will show a red screen with the original logo of the old owner of LibGDX, BadLogicGames.

We can change the app orientation in the AndroidManifest.xml file, in the android:screenOrientation property, setting it to portrait instead of landscape :

...
android:screenOrientation="portrait"
...
Screen in “portrait” mode

Before start drawing a basic cube on our screen, we’ll get rid of all the code in LibGDX3DTest.kt file:

LibGDX setup creates a Java file, but since we’ll use Kotlin, we delete the LibGDX3DTest.java file and replace with this LibGDX3DTest.kt file.

LibGDX apps normally run with an implementation of the ApplicationListener interface, but in this case, we’ll use a class called ApplicationAdapterwhich implements ApplicationListener interface and let us get rid of some unused methods. That’s why we override just the methods we need.

This basic example contains three LibGDX lifecycle methods:

  • create(): Method called once when the application is created. Used to create the whole environment for our app/game
  • render(): Method called by the game loop from the application every time rendering should be performed. Game logic updates are usually also performed in this method.
  • dispose(): Called when the application is destroyed.
Complete flow diagram of a LibGDX application lifecycle

Other methods are being called in a LibGDX application run. More information about the complete lifecycle here

In this minimal example, we create two variables in our LibGDX3DTest class:

  • lateinit var batch: SpriteBatch
  • lateinit var img: Texture

SpriteBatch

Because of the way OpenGL works, it is best to group or “batch” draw calls together to get the optimum performance. A SpriteBatch is a Batch which is used for drawing sprites. More specifically, it deals with drawing a bunch of textures on Quads in OpenGL.

This grouping is why you will get an error if you try to draw to a Batch without first calling batch.begin() as this signals the Batch to start the work of grouping draw calls. When you call draw() On a batch, it isn’t doing the work of drawing yet, it is only organizing the data you give it. It will wait to draw to the screen until batch.end() is called.

At the end, we call batch.dispose() to free the memory assigned for that specific resource.

Reference to the SpriteBatch.java source code here

Texture

A Texture wraps a standard OpenGL ES texture. A Texture can be managed. If the OpenGL context is lost, all managed textures get invalidated. This happens when a user switches to another application or receives an incoming call. Managed textures get reloaded automatically.

In our example, we use a LibGDX texture from a file called badlogic.jpgwhich is being assigned in memory, used within the batchand finally disposed of memory when the lifecycle ends.

Reference to the Texture.java source code here

ScreenUtils

In our example, we use the ScreenUtils.clear method to clear the whole screen, with a red background. We can change its color if we change that method’s values. Currently, maybe there is no need to do it, but if we would have an animated sprite, we would need to clear the screen to avoid seeing the previous animation frames.

Reference to the ScreenUtils source code here.

Now that we understand the basics of a LibGDX application, we’ll continue with the 3D drawing.

We’ll update the LibGDX3DTest.kt file with the following:

This particular code will give us the following screen:

Let’s see how this code works. I’ve organized the code in methods to make it easier to understand.

First of all, we’ll review the new components that we use now:

  • PerspectiveCamera: It will give us the vision of the 3D scene. We can adjust position, orientation, and range. Reference here.
  • ModelBatch: Like the SpriteBatch but for a 3D rendering. Reference here
  • Model: It’s a reference to a particular model that we want to render. Contains information about vertex data, texture mapping, and more. Reference here.
  • ModelInstance: Uses a Model reference to create an actual instance with that specific data. In our example, if we’d want to create many cubes, we’ll use one Model and many ModelInstance instances. Reference here.
  • Environment: It helps us to set up the lights to see better the 3D world. Reference here.

The process of rendering that cube is easily explained with the methods’ names:

create()

  1. We create the PerspectiveCamera and adjust the position and range of vision
  2. Then we create the ModelBatchthe Model and the ModelInstance which will use that Model
  3. Finally, we create the Environment with a specific light type, DirectionalLight with its respective position, orientation, and color.

render()

  1. First of all, we clear the screen with a black color
  2. Every time the render is performed (FPS rate depends on the hardware), we spin the camera in a random direction
  3. We need to update the camera’s position for every render frame
  4. The most important part is rendering the content of the ModelBatch in screen, for a given ModelInstance and an Environment

dispose()

Here we finally free memory resources for the ModelBatch and the cube Model

We’ll add the following code to our project, related to the CameraInputControllerand remove the spinCamera() method to stop automatic moving:

class LibGDX3DTest: ApplicationAdapter() {
...
private lateinit var cameraInputController: CameraInputController
...
override fun create() {
...
createCameraInput()
...
}
...
private fun updateCamera() {
camera.update()
cameraInputController.update()
}
...

Here is the complete code:

A CameraInputController will delegate the camera movement to the touch/swipe gestures of the user on the screen. As simple as that. That’s why we can now move the cube as we want in the app. In this example, we do it with the cursor since we are using the Android Emulator:

Now that we know the basics of 3D rendering with LibGDX, we can continue with the next step: Drawing a Blender model.

What’s Blender?

Blender is a free and open source 3D creation software. It supports the entirety of the 3D pipeline — modeling, rigging, animation, simulation, rendering, compositing and motion tracking, and even video editing and game creation.

We’ll use a Blender-made model to get a format that LibGDX supports rendering. I’ve chosen a car model from this page. The file we need to download is R8 Low Poly Model.fbx.

This particular file needs to be converted into a .g3dj file with this specific tool. The command will be something like this:

./fbx-conv -v ../model.fbx model.g3dj

This will create a .g3dj file ready to use in our LibGDX application.

In our project, we’ll need to replace this line:

cubeModel = modelBuilder.createBox(2f, 2f, 2f, material, attributes)

with that one:

carModel = G3dModelLoader(JsonReader()).loadModel(Gdx.files.internal("model.g3dj"))

So we can generate the Model data from the .g3dj file. That file will be added in the assets folder:

We also adjust the camera to fully see the car model.

The final code will be something like this:

And here is the demo of the car rendering:

The bad resolution is because the MOV to GIF converter tool I’ve used 😛

Using LibGDX with Kotlin is a simple way to experiment with 2D and 3D rendering. With a few lines, we can start rendering objects on our mobile screen in an easy way.

In further articles, I’ll show how to compile on an iOS device, how to use Box2D to simulate physics, and much more.

Thanks for reading!

Leave a Comment