May 10, 2021, Mobile

OpenGL on Android – working with textures

Tomasz Kądziołka Flutter Developer

In this short series of articles I will try to present a basic Android application that uses OpenGL and shows simple pitch with an interactive soccer ball :). The first part covered the basics and gesture handling. The second one expanded information on painting and drawing with shaders, and in the third one I’ll focus on working with textures.

The imitation game

The second part of this series has ended with a simple white circle that we were able to move and scale. This example that has been created is quite uncomplicated and with OpenGL only slightly harder to write. Logic and hardest work is done! But… it doesn’t make wows…

The magic of the computers is the magic of the imitation. Indeed, behind every complex application and game stands a list of the basics tricks and logical conditions. That’s it!

In this case the situation is the same. We have to provide some graphical layers in order to increase the immersion. The answer is texture.

Declare the images to show

The way in Android to display a PNG image is to decode it to the Bitmap. To display images via OpenGL you have to take the Bitmap, generate texture from it and bind to the program.

In the MainActivity which is an entry point of application, we declare Bitmaps and pass them to the renderer object. In the target app you should of course move generating bitmaps to the background thread. For the purposes of this guide we’ll do it as shown below.

For the MainActivity that is all. Now, let’s jump to the GestureAwareRenderer and see how to pass bitmaps to the shape.

There is not much work to do here as well. Just invoke simple setters for the foreground and background fields in the shape object.

From Bitmap to Texture

When working with textures in the shape object, we follow similar steps as for the vertices.

  1. Define points
  2. Allocate memory (create buffer)
  3. Apply coordinates to the program
  4. Use values in the shaders

Following these points, firstly, let’s declare a buffer for the background texture’s vertices. For the foreground variant it will differ and I will show it a little bit later.

The most important part here is to keep in mind that texture’s coordinates are different from the vertices’ coordinates. Ball and field images are squares, so I also declare points that will fill a square.

Now, let’s see the part with binding textures. In order to make code more flexible, I am basing on the nullable variables.

I highly recommend you to take a peek into the repository and check the steps done in the generateTexture method. Generally it generates the texture with a handler, and binds bitmap to the handler’s position.

Variables will indicate whether texture has been generated or not. When we have this information, we can complete the draw() method.

For the back texture steps are almost identical to vertices. As you can notice, for the front texture I am generating calculated vertices. Why calculated?

As the circle on the surface is movable and scalable, texture should follow it’s center point and draw exactly in the circle’s boundaries. It can be accomplished by the following method:

Usage of the textures in a shader

First thing to do is to declare coordinates for the textures and pass them to the fragment shader.

In the fragment shader we are taking information about the current fragment. If it’s value is for circle (1.0) it should color the fragment with front texture, otherwise background texture.

The most important thing is that texture is represented by sampler2D type. This sampler is used by texture2D function. For this function we also pass earlier defined in code coordinates.

At the end, in the ShaderGL’s implementation we have to provide a body for the applyUniforms(), applyBackTexture() and applyBackTexture() methods.

Now the logic for the working application is completed! Although the final app looks very simple, many OpenGL concepts and processes may cause difficulties, especially if you have experience only with Android and Kotlin.
Take your time, check GLUtil class which contains the hardest code and don’t stick with my app architecture. Every project that uses OpenGL may need a different approach, other than mine.