Log in or register to post comments

How to Render Text

April 5, 2017 - 8:11am #1


I'm developing and Android AR application and I'd like to render text once a target is detected in order to display some informations about this target.

I found these two links that demonstrate how to create a font and how to inject it to an openGL mechanism with the Android structure:



Someone on another topic said that he was able to integrate this to the Vuforia SDK and its rendering system, without providing the mean to achieve it.

So I tried to adapt this project to fit with my own Vuforia project but, as I am an OpenGL beginner, I still can't find the proper solution to do what I want.

I'm sure that an experienced developer with Vuforia / OpenGL could find a solution to this problem with a quick look at my code.

Here is my ImageTargetRenderer.java file content:


import android.content.Context;

import android.opengl.GLES20;

import android.opengl.GLSurfaceView;

import android.opengl.Matrix;

import android.util.Log;

import com.soprasteria.arstore.business.TextRendering.GLText;

import com.vuforia.Device;

import com.vuforia.Matrix44F;

import com.vuforia.State;

import com.vuforia.Tool;

import com.vuforia.Trackable;

import com.vuforia.TrackableResult;

import com.vuforia.Vuforia;

import com.soprasteria.arstore.vuforia.AppRenderer;

import com.soprasteria.arstore.vuforia.AppRendererControl;

import com.soprasteria.arstore.vuforia.ApplicationSession;

import com.soprasteria.arstore.vuforia.utils.CubeShaders;

import com.soprasteria.arstore.vuforia.utils.LoadingDialogHandler;

import com.soprasteria.arstore.vuforia.utils.Application3DModel;

import com.soprasteria.arstore.vuforia.utils.Utils;

import com.soprasteria.arstore.vuforia.utils.Teapot;

import com.soprasteria.arstore.vuforia.utils.Texture;

import java.io.IOException;

import java.util.Vector;

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

// The renderer class for the ImageTargets

public class ImageTargetRenderer implements GLSurfaceView.Renderer, AppRendererControl


    private static final String LOGTAG = "ImageTargetRenderer";

    private ApplicationSession applicationSession;

    private ImageTargets mActivity;

    private AppRenderer mAppRenderer;

    private Vector<Texture> mTextures;

    private int shaderProgramID;

    private int vertexHandle;

    private int textureCoordHandle;

    private int mvpMatrixHandle;

    private int texSampler2DHandle;

    private Teapot mTeapot;

    //private FlyingSaucer mTeapot;

    private float kBuildingScale = 0.012f;

    private Application3DModel mBuildingsModel;

    private boolean mIsActive = false;

    private boolean mModelIsLoaded = false;

    private static final float OBJECT_SCALE_FLOAT = 0.003f;

    // TODO : Text Rendering test

    private GLText glText;                             // A GLText Instance

    private Context context;                           // Context (from Activity)

    private int width = 100;                           // Updated to the Current Width + Height in onSurfaceChanged()

    private int height = 100;

    private float[] mProjMatrix = new float[16];

    private float[] mVMatrix = new float[16];

    private float[] mVPMatrix = new float[16];

    //TODO fin

    public ImageTargetRenderer(ImageTargets activity, ApplicationSession session)


        mActivity = activity;

        applicationSession = session;

        // AppRenderer used to encapsulate the use of RenderingPrimitives setting

        // the device mode AR/VR and stereo mode

        mAppRenderer = new AppRenderer(this, mActivity, Device.MODE.MODE_AR, false, 0.01f , 5f);

        // TODO

        context = mActivity.getApplicationContext();


    // Called to draw the current frame.


    public void onDrawFrame(GL10 gl)


        if (!mIsActive)


        // Call our function to render content from AppRenderer class



    public void setActive(boolean active)


        mIsActive = active;




    // Called when the surface is created or recreated.


    public void onSurfaceCreated(GL10 gl, EGLConfig config)


        Log.d(LOGTAG, "GLRenderer.onSurfaceCreated");

        // Call Vuforia function to (re)initialize rendering after first use

        // or after OpenGL ES context was lost (e.g. after onPause/onResume):



        // TODO début

        // Create the GLText

        glText = new GLText(context.getAssets());

        // Load the font from file (set size + padding), creates the texture

        // NOTE: after a successful call to this the font is ready for rendering!

        glText.load( "Roboto-Regular.ttf", 14, 2, 2 );  // Create Font (Height: 14 Pixels / X+Y Padding 2 Pixels)

        // enable texture + alpha blending



        // TODO Fin


    // Called when the surface changed size.


    public void onSurfaceChanged(GL10 gl, int width, int height) {

        Log.d(LOGTAG, "GLRenderer.onSurfaceChanged");

        // TODO début

        GLES20.glViewport(0, 0, width, height);

        float ratio = (float) width / height;

        // Take into account device orientation

        if (width > height) {

            Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);


        else {

            Matrix.frustumM(mProjMatrix, 0, -1, 1, -1/ratio, 1/ratio, 1, 10);


        // Save width and height

        this.width = width;                             // Save Current Width

        this.height = height;                           // Save Current Height

        int useForOrtho = Math.min(width, height);

        //TODO: Is this wrong?

        Matrix.orthoM(mVMatrix, 0,




                useForOrtho/2, 0.1f, 100f);

        // TODO FIN

        // Call Vuforia function to handle render surface size changes:

        applicationSession.onSurfaceChanged(width, height);

        // RenderingPrimitives to be updated when some rendering change is done




    // Function for initializing the renderer.

    private void initRendering()


        GLES20.glClearColor(0.0f, 0.0f, 0.0f, Vuforia.requiresAlpha() ? 0.0f

                : 1.0f);

        for (Texture t : mTextures)


            GLES20.glGenTextures(1, t.mTextureID, 0);

            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, t.mTextureID[0]);





            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,

                t.mWidth, t.mHeight, 0, GLES20.GL_RGBA,

                GLES20.GL_UNSIGNED_BYTE, t.mData);


        shaderProgramID = Utils.createProgramFromShaderSrc(



        vertexHandle = GLES20.glGetAttribLocation(shaderProgramID,


        textureCoordHandle = GLES20.glGetAttribLocation(shaderProgramID,


        mvpMatrixHandle = GLES20.glGetUniformLocation(shaderProgramID,


        texSampler2DHandle = GLES20.glGetUniformLocation(shaderProgramID,


        if(!mModelIsLoaded) {

            mTeapot = new Teapot();

            //mTeapot = new FlyingSaucer();

            try {

                mBuildingsModel = new Application3DModel();



                mModelIsLoaded = true;

            } catch (IOException e) {

                Log.e(LOGTAG, "Unable to load buildings");


            // Hide the Loading Dialog





    public void updateConfiguration()




    // The render function called from AppRendering by using RenderingPrimitives views.

    // The state is owned by AppRenderer which is controlling it's lifecycle.

    // State should not be cached outside this method.

    public void renderFrame(State state, float[] projectionMatrix)


        // Renders video background replacing Renderer.DrawVideoBackground()



        // handle face culling, we need to detect if we are using reflection

        // to determine the direction of the culling



        // Did we find any trackables this frame?

        for (int tIdx = 0; tIdx < state.getNumTrackableResults(); tIdx++) {

            TrackableResult result = state.getTrackableResult(tIdx);

            Trackable trackable = result.getTrackable();


            Matrix44F modelViewMatrix_Vuforia = Tool


            float[] modelViewMatrix = modelViewMatrix_Vuforia.getData();

            int textureIndex = trackable.getName().equalsIgnoreCase("stones") ? 0

                    : 1;

            textureIndex = trackable.getName().equalsIgnoreCase("tarmac") ? 2

                    : textureIndex;

            // deal with the modelview and projection matrices

            float[] modelViewProjection = new float[16];

            if (!mActivity.isExtendedTrackingActive()) {

                Matrix.translateM(modelViewMatrix, 0, 0.0f, 0.0f,


                Matrix.scaleM(modelViewMatrix, 0, OBJECT_SCALE_FLOAT,

                        OBJECT_SCALE_FLOAT, OBJECT_SCALE_FLOAT);

            } else {

                Matrix.rotateM(modelViewMatrix, 0, 90.0f, 1.0f, 0, 0);

                Matrix.scaleM(modelViewMatrix, 0, kBuildingScale,

                        kBuildingScale, kBuildingScale);


            Matrix.multiplyMM(modelViewProjection, 0, projectionMatrix, 0, modelViewMatrix, 0);

            // TODO début

            // TEST: render the entire font texture

            glText.drawTexture( width/2, height/2, mVPMatrix);            // Draw the Entire Texture

            // TEST: render some strings with the font

            glText.begin( 1.0f, 1.0f, 1.0f, 1.0f, mVPMatrix );         // Begin Text Rendering (Set Color WHITE)

            glText.drawC("Test String 3D!", 0f, 0f, 0f, 0, -30, 0);

//  glText.drawC( "Test String :)", 0, 0, 0 );          // Draw Test String

            glText.draw( "Diagonal 1", 40, 40, 40);                // Draw Test String

            glText.draw( "Column 1", 100, 100, 90);              // Draw Test String

            glText.end();                                   // End Text Rendering

            glText.begin( 0.0f, 0.0f, 1.0f, 1.0f, mVPMatrix );         // Begin Text Rendering (Set Color BLUE)

            glText.draw( "More Lines...", 50, 200 );        // Draw Test String

            glText.draw( "The End.", 50, 200 + glText.getCharHeight(), 180);  // Draw Test String

            glText.end();                                   // End Text Rendering

            // TODO Fin

            // activate the shader program and bind the vertex/normal/tex coords


            if (!mActivity.isExtendedTrackingActive()) {

                GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT,

                        false, 0, mTeapot.getVertices());

                GLES20.glVertexAttribPointer(textureCoordHandle, 2,

                        GLES20.GL_FLOAT, false, 0, mTeapot.getTexCoords());



                // activate texture 0, bind it, and pass to shader




                GLES20.glUniform1i(texSampler2DHandle, 0);

                // pass the model view matrix to the shader

                GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false,

                        modelViewProjection, 0);

                // finally draw the teapot


                        mTeapot.getNumObjectIndex(), GLES20.GL_UNSIGNED_SHORT,


                // disable the enabled arrays



            } else {


                GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT,

                        false, 0, mBuildingsModel.getVertices());

                GLES20.glVertexAttribPointer(textureCoordHandle, 2,

                        GLES20.GL_FLOAT, false, 0, mBuildingsModel.getTexCoords());






                GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false,

                        modelViewProjection, 0);

                GLES20.glUniform1i(texSampler2DHandle, 0);

                GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,


                Utils.checkGLError("Renderer DrawBuildings");


            Utils.checkGLError("Render Frame");




    private void printUserData(Trackable trackable)


        String userData = (String) trackable.getUserData();

        Log.d(LOGTAG, "UserData:Retreived User Data \"" + userData + "\"");


    public void setTextures(Vector<Texture> textures)


        mTextures = textures;






Log in or register to post comments