"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

Text Rendering

Hello,  I would like to be able to recognize a target and then to render dynamic text onto this target. 

I found on another topic that these two links provide a way to do this using openGL : 

- http://fractiousg.blogspot.fr/2012/04/rendering-text-in-opengl-on-android.html - https://github.com/d3alek/Texample2 (using openGL 2.0)

I tried different things with that sample projects in order to adapt it to the Vuforia Rendering class with openGL. Unfortunately, I'm currently not able to find the right solution.  Could someone please help me on the injection mechanism with the existing Vuforia model ? I think this is an openGL question since it directly interacts with it. I'm developing with Android Studio, based on the sample project provided by Vuforia (ImageTarget, ObjectRecognition, etc...)

EDIT : Here is my ImageTargetRenderer.java file content, I guess there is something to fix and, as I'm an OpenGL beginner, I still don't find the solution. Maybe some experts could find quickly what is wrong.

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;

    }

}