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;
}
}