"We offer new support options and therefor the forums are now in read-only mode! Please check out our Support Center for more information." - Vuforia Engine Team

How to Render Text

Hello,

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: - http://fractiousg.blogspot.fr/2012/04/rendering-text-in-opengl-on-android.html - https://github.com/d3alek/Texample2

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.     @Override     public void onDrawFrame(GL10 gl)     {         if (!mIsActive)             return;

        // Call our function to render content from AppRenderer class         mAppRenderer.render();     }

    public void setActive(boolean active)     {         mIsActive = active;

        if(mIsActive)             mAppRenderer.configureVideoBackground();     }

    // Called when the surface is created or recreated.     @Override     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):         applicationSession.onSurfaceCreated();

        mAppRenderer.onSurfaceCreated();

        // 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         GLES20.glEnable(GLES20.GL_BLEND);         GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);         // TODO Fin     }

    // Called when the surface changed size.     @Override     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,                 useForOrtho/2,                 -useForOrtho/2,                 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         mAppRenderer.onConfigurationChanged(mIsActive);

        initRendering();     }

    // 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.glTexParameterf(GLES20.GL_TEXTURE_2D,                 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);             GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,                 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);             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(             CubeShaders.CUBE_MESH_VERTEX_SHADER,             CubeShaders.CUBE_MESH_FRAGMENT_SHADER);

        vertexHandle = GLES20.glGetAttribLocation(shaderProgramID,             "vertexPosition");         textureCoordHandle = GLES20.glGetAttribLocation(shaderProgramID,             "vertexTexCoord");         mvpMatrixHandle = GLES20.glGetUniformLocation(shaderProgramID,             "modelViewProjectionMatrix");         texSampler2DHandle = GLES20.glGetUniformLocation(shaderProgramID,             "texSampler2D");

        if(!mModelIsLoaded) {             mTeapot = new Teapot();             //mTeapot = new FlyingSaucer();

            try {                 mBuildingsModel = new Application3DModel();                 mBuildingsModel.loadModel(mActivity.getResources().getAssets(),                         "ImageTargets/Buildings.txt");                 mModelIsLoaded = true;             } catch (IOException e) {                 Log.e(LOGTAG, "Unable to load buildings");             }

            // Hide the Loading Dialog             mActivity.loadingDialogHandler                     .sendEmptyMessage(LoadingDialogHandler.HIDE_LOADING_DIALOG);         }

    }

    public void updateConfiguration()     {         mAppRenderer.onConfigurationChanged(mIsActive);     }

    // 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()         mAppRenderer.renderVideoBackground();

        GLES20.glEnable(GLES20.GL_DEPTH_TEST);

        // handle face culling, we need to detect if we are using reflection         // to determine the direction of the culling         GLES20.glEnable(GLES20.GL_CULL_FACE);         GLES20.glCullFace(GLES20.GL_BACK);

        // Did we find any trackables this frame?         for (int tIdx = 0; tIdx < state.getNumTrackableResults(); tIdx++) {             TrackableResult result = state.getTrackableResult(tIdx);             Trackable trackable = result.getTrackable();             printUserData(trackable);             Matrix44F modelViewMatrix_Vuforia = Tool                     .convertPose2GLMatrix(result.getPose());             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,                         OBJECT_SCALE_FLOAT);                 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             GLES20.glUseProgram(shaderProgramID);

            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());

                GLES20.glEnableVertexAttribArray(vertexHandle);                 GLES20.glEnableVertexAttribArray(textureCoordHandle);

                // activate texture 0, bind it, and pass to shader                 GLES20.glActiveTexture(GLES20.GL_TEXTURE0);                 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,                         mTextures.get(textureIndex).mTextureID[0]);                 GLES20.glUniform1i(texSampler2DHandle, 0);

                // pass the model view matrix to the shader                 GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false,                         modelViewProjection, 0);

                // finally draw the teapot                 GLES20.glDrawElements(GLES20.GL_TRIANGLES,                         mTeapot.getNumObjectIndex(), GLES20.GL_UNSIGNED_SHORT,                         mTeapot.getIndices());

                // disable the enabled arrays                 GLES20.glDisableVertexAttribArray(vertexHandle);                 GLES20.glDisableVertexAttribArray(textureCoordHandle);             } else {                 GLES20.glDisable(GLES20.GL_CULL_FACE);                 GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT,                         false, 0, mBuildingsModel.getVertices());                 GLES20.glVertexAttribPointer(textureCoordHandle, 2,                         GLES20.GL_FLOAT, false, 0, mBuildingsModel.getTexCoords());

                GLES20.glEnableVertexAttribArray(vertexHandle);                 GLES20.glEnableVertexAttribArray(textureCoordHandle);

                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);                 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,                         mTextures.get(3).mTextureID[0]);                 GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false,                         modelViewProjection, 0);                 GLES20.glUniform1i(texSampler2DHandle, 0);                 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,                         mBuildingsModel.getNumObjectVertex());

                Utils.checkGLError("Renderer DrawBuildings");             }

            Utils.checkGLError("Render Frame");

        }

        GLES20.glDisable(GLES20.GL_DEPTH_TEST);     }

    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;

    }

}