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