Log in or register to post comments

How to Render Text

April 5, 2017 - 8:11am #1

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;

    }

}

 

 

 

Log in or register to post comments