Updates:
* February 2013 - Added a version that works with OpenGL ES 2.0, provided by Aleksandar Kodzhabashev. * January 2013 - Added a version that includes z-values for rendering text in 3D space. It was written by Jocelyn Demoy. * September 2012 - Added a mirror for the code download. - Added a Scala port of the code, provided by Alexey Aksenov.
This post is intended for developers. It describes the method I used for rendering high-quality dynamic text efficiently using OpenGL ES 1.0, with TrueType/OpenType font files.
One of the most frustrating things I have encountered while working with 3D APIs (such as OpenGL) is the complete lack of support for text rendering. Because of this unexplainable shortcoming, we have to implement text rendering on our own. Before starting with my Android game, I had already done this multiple times for both Direct3D and OpenGL, using methods ranging from static text on a bitmap to complete bitmap fonts (usually generated via a tool).
Sadly, these implementations did not translate well to OpenGL ES....so I had to do it again :)
Interested? Keep reading for details and a download of full source code that can be used immediately in any project, without limitation!
Initially, I went with a quick implementation using Codehead's Bitmap Font Generator (CBFG). It was easy, fast and great to have text display implemented without any real work :) It's a great tool.
However, at some point I realized that in order for me to support high quality text (i.e. without using scaling) at all resolutions, I would have to generate a font bitmap for each resolution. This was compounded by the fact that I wanted two different sized fonts (and again did not want to do any scaling). The result was around 6-10 Megabytes worth of uncompressed font bitmaps (and that was without support for obscure resolutions). It just wasn't going to work out for me...so I decided that I needed a better solution.
I wanted to build a dynamic text engine that works with OpenGL ES in Android. The idea of this system is to "generate" the same type of font bitmap that CBFG does, but it does so at run-time using a standard TrueType (.ttf) or OpenType (.otf) font file. Using this method I would only need to include the font file(s), which are usually quite small, and it could then generate correctly scaled fonts for whatever resolution the game was run on.
I had implemented a dynamic text rendering system like this once before using Direct3D on Windows, but never in OpenGL. Also, the Direct3D one was using Windows GDI for generating the font bitmaps so it was basically useless for Android. I could not find any description or code that does exactly this anywhere on the web. The closest was some Android SDK example which renders full strings to a texture and then displays the texture. This was not nearly "dynamic" enough for strings that change "per frame", but it provided the groundwork for what I had in mind.
NOTE: As far as I know, libgdx uses this same way of rendering text, but I did not want to spend the time "unhooking" their implementation from the rest of the engine. I wanted code that I could use without having to employ an entire game engine.
But enough about what led me to this solution. Let's look at how it works:
The entire generation of the font bitmap (or atlas, as some people call it) is performed in a single method. The following image shows what a font bitmap, generated by the code I use here, looks like.
Note that it is rendered on a grey background; the actual bitmap has a transparent background. Also, my method uses alpha only for the characters, and they can be rendered in any color.
The whole process is actually quite easy. We generate the bitmap (as a texture), calculate and store the size of each character, as well as it's location on the texture (UV coordinates). There are some other finer details, but we'll get to that.
The Code
License: The code is released under the CC0 1.0 public domain license - in English it means that there are NO restrictions placed on usage of the code in any way.
Download the source code (mirror).
Download the source code (includes a copy of the CC0 1.0 public domain license).
Update (Sept 2012): The code has been ported to Scala by Alexey Aksenov. It can be found here.
Update (Jan 2013): A new version of the example which uses z-values for rendering (allowing placement in 3D space) was done by Jocelyn Demoy. It can be found here.
Update (Feb 2013): A new version of the example which uses OpenGL ES 2.0 for rendering is provided by Aleksandar Kodzhabashev. It can be found here.
The included source files are as follows:
- Texample.java and TexampleRenderer.java - these files are from the OpenGL ES 1.0 Android SDK sample. They only serve as a place to test the rest of the code. Ignore these if you intend to use the code in an existing project.
- Vertices.java and SpriteBatch.java - these are used for "batch-rendering" characters, which is the only efficient way of rendering dynamic text. These provide no special text-related operations and can be replaced if you have an existing rendering solution.
- TextureRegion.java - this is just a little helper class that makes it easier to define a region on a texture (i.e. the UV coordinates) for each character.
- GLText.java - this holds the GLText class which does all of the text-related work (generation, rendering, etc.). If you replace the render calls, this is the only file that you need.
So let's look at the code for GLText and I'll discuss the subtle details :)
Constants & Members
First, we need to define some constant values:
public final static int CHAR_START = 32; public final static int CHAR_END = 126; public final static int CHAR_CNT = (((CHAR_END - CHAR_START)+1)+1 ); public final static int CHAR_NONE = 32; public final static int CHAR_UNKNOWN = (CHAR_CNT-1); public final static int FONT_SIZE_MIN = 6; public final static int FONT_SIZE_MAX = 180; public final static int CHAR_BATCH_SIZE = 100;
- CHAR_START and CHAR_END define the ASCII character range of the characters we want.
- CHAR_CNT is the total number of characters (the extra +1 is for the "unknown" character).
- CHAR_NONE defines which ASCII character to use for "unknown" characters. Here I used '32', which is the ASCII code for space, but you could change this to any valid ASCII code.
- CHAR_UNKNOWN is the index of the "unknown" character.
- FONT_SIZE_MIN/MAX is the minimum and maximum pixel size supported for creating fonts. The minimum size is not that important but maximum is needed since maximum texture size is limited.
- CHAR_BATCH_SIZE is the number of characters that are batched when rendering (this value should be tweaked depending on requirements...smaller values use less memory, larger values use more).
Next up, the members of the class:
GL10 gl; AssetManager assets; SpriteBatch batch; int fontPadX, fontPadY; float fontHeight; float fontAscent; float fontDescent; int textureId; int textureSize; TextureRegion textureRgn; float charWidthMax; float charHeight; final float[] charWidths; TextureRegion[] charRgn; int cellWidth, cellHeight; int rowCnt, colCnt; float scaleX, scaleY; float spaceX;
- gl and assets - these are obtained from the GLSurfaceView and Context (respectively) and cached.
- fontPadX, fontPadY - these are padding values to use for each character. They indicate the number of spacing pixels to add on each axis between characters. Padding is added to both sides of each axis (i.e. a fontPadX value of 1 means 1 pixel on the left and one pixel on the right). Use these if characters in a font overlap. Note that these values do not affect spacing beween characters when rendering (use spaceX for that).
- fontHeight, fontAscent, fontDescent - these are the metrics of the font obtained from Android. Use them manually when rendering for accurate text positioning.
- textureId, textureSize, textureRgn - the ID of the generated texture (containing the font characters), it's size (square, power of two), and a region used to render it (for debug purposes).
- charWidthMax - the width of the widest character in the font.
- charHeight - the height of all characters (maximum).
- charWidths - the array of character widths (exact width per character).
- charRgn - the array of character regions on the texture.
- cellWidth, cellHeight - the width and height of each cell on the texture (including padding, etc).
- rowCnt, colCnt - the number of rows and columns on the texture.
- scaleX, scaleY - these are rendering values used to scale the rendered text. A value of 1.0 means unscaled, values less than one make the text smaller, and values larger than one make it bigger.
- spaceX - this value indicates the amount of extra spacing (on the X-axis) to add between characters when rendering.
The class contains various accessors and utility methods which are self explanatory (and well commented). Here I will only focus on two methods: load() and draw(), which create the font from a file and renders a string with it, respectively.
The load() Method
First up is the load() method, which has the following prototype:
public boolean load(String file, int size, int padX, int padY)
- file - the filename of the TrueType (.ttf) or OpenType (.otf) font file in the assets folder. You could use files from other locations and it should be easy to do if required (see the code below).
- size - the desired size (height) of the font.
- padX, padY - the desired X and Y axis padding to use.
And now for the actual code:
Typeface tf = Typeface.createFromAsset( assets, file ); Paint paint = new Paint(); paint.setAntiAlias( true ); paint.setTextSize( size ); paint.setColor( 0xffffffff ); paint.setTypeface( tf ); Paint.FontMetrics fm = paint.getFontMetrics(); fontHeight = (float)Math.ceil( Math.abs( fm.bottom ) + Math.abs( fm.top ) ); fontAscent = (float)Math.ceil( Math.abs( fm.ascent ) ); fontDescent = (float)Math.ceil( Math.abs( fm.descent ) );
Here we create a TypeFace instance from the specified font file (if you want to use font files from locations other than the assets folder, this call would need to be changed). Then we create and configure a Paint instance, which will be used to draw the actual characters to the texture. We also set the typeface for the Paint instance to the one created from the file. And finally, we get the font metrics from the Paint instance and save the values for future use.
char[] s = new char[2];
charWidthMax = charHeight = 0;
float[] w = new float[2];
int cnt = 0;
for ( char c = CHAR_START; c <= CHAR_END; c++ ) {
s[0] = c;
paint.getTextWidths( s, 0, 1, w );
charWidths[cnt] = w[0];
if ( charWidths[cnt] > charWidthMax )
charWidthMax = charWidths[cnt];
cnt++;
}
s[0] = CHAR_NONE;
paint.getTextWidths( s, 0, 1, w );
charWidths[cnt] = w[0];
if ( charWidths[cnt] > charWidthMax )
charWidthMax = charWidths[cnt];
cnt++;
charHeight = fontHeight;
Next we use the Paint instance (with set TypeFace) to determine the exact width of each character, and save that to an array which is used at render time to properly space the characters in a string. During this operation we also track and save the widest character's width. Finally we simply set the character height to the font height.
cellWidth = (int)charWidthMax + ( 2 * fontPadX );
cellHeight = (int)charHeight + ( 2 * fontPadY );
int maxSize = cellWidth > cellHeight ? cellWidth : cellHeight;
if ( maxSize < FONT_SIZE_MIN || maxSize > FONT_SIZE_MAX )
return false;
At this point we calculate the cell sizes to use for each character on the texture. Each cell is the exact same size, so we need to use a size that is large enough to contain the widest character. We also need to take the specified padding into consideration. Finally we get the largest dimension (from width and height) and make sure that the cell size fits within our defined minimum and maximum size range (if not, we return an error).
if ( maxSize <= 24 )
textureSize = 256;
else if ( maxSize <= 40 )
textureSize = 512;
else if ( maxSize <= 80 )
textureSize = 1024;
else
textureSize = 2048;
Now that we have the maximum size, we need to determine how large the texture must be in order to hold all of the defined characters. Here I have done that manually, but it could be computed from the cell size (if desired). The main thing is that these values will require modification if the defined character range changes.
Bitmap bitmap; bitmap = Bitmap.createBitmap( textureSize, textureSize, Bitmap.Config.ALPHA_8 ); Canvas canvas = new Canvas( bitmap ); bitmap.eraseColor( 0x00000000 ); colCnt = textureSize / cellWidth; rowCnt = (int)Math.ceil( (float)CHAR_CNT / (float)colCnt );
At this point we can create a Bitmap of the determined texture size. Notice that we use only an alpha channel for the bitmap. This is not compulsory - you could use full ARGB (it would be required if you intend to use stroked fonts). However, I found that using alpha only created better looking text (and I have no intention of ever using stroked fonts). Use of an alpha channel only also decreases the memory requirement of the texture. We also create a Canvas and attach the Bitmap to it so that we can draw to it. We then set the bitmap to have a transparent background (alpha = 0). Finally, we calculate the number of columns and rows for the characters on the bitmap (while this is not actually needed in this implementation, I left it since it may be useful at some point).
float x = fontPadX;
float y = ( cellHeight - 1 ) - fontDescent - fontPadY;
for ( char c = CHAR_START; c <= CHAR_END; c++ ) {
s[0] = c;
canvas.drawText( s, 0, 1, x, y, paint );
x += cellWidth;
if ( ( x + cellWidth - fontPadX ) > textureSize ) {
x = fontPadX;
y += cellHeight;
}
}
s[0] = CHAR_NONE;
canvas.drawText( s, 0, 1, x, y, paint );
Finally we can generate our font bitmap. First, we set the (x,y) coordinate (bottom/left of character) for the first character (note that padding is included in this). Then we loop for each of our defined characters and render them to the canvas (i.e. the bitmap) using our configured paint instance (which has the font file set as typeface). After rendering, we advance on the x-axis for the next character. Finally we test to see if the edge of the bitmap has been reached, and if so we reset the x-axis and move down to the next line on the y-axis. After rendering the defined characters we also render the "unknown" character.
int[] textureIds = new int[1];
gl.glGenTextures( 1, textureIds, 0 );
textureId = textureIds[0];
gl.glBindTexture( GL10.GL_TEXTURE_2D, textureId );
gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_NEAREST );
gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR );
gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE );
gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE );
GLUtils.texImage2D( GL10.GL_TEXTURE_2D, 0, bitmap, 0 );
gl.glBindTexture( GL10.GL_TEXTURE_2D, 0 );
bitmap.recycle();
Next we generate a new texture, bind it, and set the filtering and wrapping to use. Very basic OpenGL stuff. Then we transfer the bitmap to the texture using GLUtils (which makes it very simple). Finally we unbind the texture and release the bitmap.
x = 0;
y = 0;
for ( int c = 0; c < CHAR_CNT; c++ ) {
charRgn[c] = new TextureRegion( textureSize, textureSize,
x, y, cellWidth-1, cellHeight-1 );
x += cellWidth;
if ( x + cellWidth > textureSize ) {
x = 0;
y += cellHeight;
}
}
Finally we setup a texture region (ie. UV coordinates) for each character on the bitmap. We could have done this while rendering the characters, but I prefer to keep it separate. The rendering code will use these regions to quickly select the correct character to render. Note that these do not use the padding values, they use to the entire cell size (which includes the padding already).
And that is that. Our font bitmap is created, and we have all the information required to render strings using it. Which brings us to...
The draw() Method
The draw() method has the following prototype:
public void draw(String text, float x, float y)
- text - the string to render.
- x, y - the (x,y) position to render the text at. Note that this is the bottom/left corner of the first character.
The code for the draw method is very short and simple:
float chrHeight = cellHeight * scaleY;
float chrWidth = cellWidth * scaleX;
int len = text.length();
x += ( chrWidth / 2.0f ) - ( fontPadX * scaleX );
y += ( chrHeight / 2.0f ) - ( fontPadY * scaleY );
for ( int i = 0; i < len; i++ ) {
int c = (int)text.charAt( i ) - CHAR_START;
if ( c < 0 || c >= CHAR_CNT )
c = CHAR_UNKNOWN;
batch.drawSprite( x, y, chrWidth, chrHeight, charRgn[c] );
x += ( charWidths[c] + spaceX ) * scaleX;
}
This code renders the actual string. First we calculate our scaled cell width and height (we do this here since we may want to change scaling values between draw calls, so caching them won't work).
Next we calculate the length of the string (i.e. the number of characters it contains).
Then we setup the initial rendering position based on the given (x,y) coordinates. The reason for the weird looking code is that the SpriteBatch used renders sprites centered at (x,y) but we want text to originate at the bottom/left. What is important however is that we include the scale and font padding in this calculation, to properly position the first character.
Once we have our starting position we loop for each character in the string (i.e. the remaining code is executed for each character).
We extract an index for the character, based on it's ASCII code. Basically, we get the code of the character (the charAt() call) and then adjust it by our start character's ASCII code (since that would be at position 0 in our arrays). We also need to validate that the character exists within our defined range, and if not we use the "unknown" character instead.
The sprite batch then takes care of the drawing, given the (x,y) coordinate, the size to render, as well as the region on the texture for this character. Again, any rendering code can be used here, all that is important is to use these values for rendering.
Finally, we adjust the x-axis coordinate for the next character. To do this we advance by the current character's width (as calculated in load(), not the cell width) as well as by the currently set spaceX value. The resulting amount must also be scaled by the currently set scaleX value.
And that is that. Dynamic text rendering using font files...check!
Usage & Notes
Included in the source code is an example of using the class. See the TexampleRenderer.java file for full details (and comments).
First we need to create an instance and then load the font - this should be done at initialization time, for example in the onSurfaceCreated() method. We can create any number of font instances, as required. This code assumes that the context member has been set, and a font file called "Roboto-Regular.ttf" exists in the assets folder. The code looks as follows:
private GLText glText;
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glText = new GLText( gl, context.getAssets() );
glText.load( "Roboto-Regular.ttf", 14, 2, 2 );
}
Next, and this is very important, we must prepare for rendering by enabling 2D texturing, as well as alpha blending (and setting a blend function). Without these, rendering won't work (regardless of what method you use to render). The reason being that the font bitmap is a texture which requires alpha blending. This code would go in the onDrawFrame() method (or a similar location, where rendering is required). The code looks as follows (very basic example):
public void onDrawFrame(GL10 gl) {
gl.glClear( GL10.GL_COLOR_BUFFER_BIT );
gl.glMatrixMode( GL10.GL_MODELVIEW );
gl.glLoadIdentity();
gl.glEnable( GL10.GL_TEXTURE_2D );
gl.glEnable( GL10.GL_BLEND );
gl.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
glText.begin( 1.0f, 1.0f, 1.0f, 1.0f );
glText.draw( "Test String :)", 0, 0 );
glText.draw( "Line 1", 50, 50 );
glText.draw( "Line 2", 100, 100 );
glText.end();
glText.begin( 0.0f, 0.0f, 1.0f, 1.0f );
glText.draw( "More Lines...", 50, 150 );
glText.draw( "The End.", 50, 150 + glText.getCharHeight() );
glText.end();
gl.glDisable( GL10.GL_BLEND );
gl.glDisable( GL10.GL_TEXTURE_2D );
}
NOTE: the glText.begin() and glText.end() methods were not discussed, but basically they just start and end a batch rendering. They also provide an easy way to set the font color (using this rendering system, font color is set per batch, not per draw call).
Source Code
License: The code is released under the CC0 1.0 public domain license - in English it means that there are NO restrictions placed on usage of the code in any way.
Download the source code (mirror).
Download the source code (includes a copy of the CC0 1.0 public domain license).
Update (Sept 2012): The code has been ported to Scala by Alexey Aksenov. It can be found here.
Update (Jan 2013): A new version of the example which uses z-values for rendering (allowing placement in 3D space) was done by Jocelyn Demoy. It can be found here.
Update (Feb 2013): A new version of the example which uses OpenGL ES 2.0 for rendering is provided by Aleksandar Kodzhabashev. It can be found here.
The archive contains two folders:
"Texample" - full IntelliJ IDEA CE (my IDE of choice for Android development) project
"Texample (Code Only)" - just the 5 .java source files (create your own project, using your preferred IDE)
For this example I used a min-sdk=3 and a target-sdk=15 (i.e. Android 1.6 to Android 4.0), since the code should work on all android versions.
Hope this makes text rendering much easier for some people :) Feel free to leave comments and check out my free game flipt on the Android market for an example of the text rendering in action, or just for some fun!

Thank you ...
ReplyDeleteIt works great out-of-the-box.
Thank you.Can you reupload the source code?
ReplyDeleteThe download seems to be working...maybe just a temporary problem. I added a download mirror just to be safe :)
ReplyDeleteFrom this I was able to create a version for OpenGL ES 2.0 (though I had to take some liberties)
ReplyDeleteCan't thank you enough
Could you please post a version for OpenGL ES 2.0 somwhere?
DeleteThank you very much. That's what I was looking for. :D
ReplyDelete@JP: would you mind sharing your results? :)
ReplyDeleteFantastic! This is a great solution to text rendering. Thanks for sharing your code and taking the time to explain how it works.
ReplyDeleteYou mention that the "full source code .. can be used immediately in any project, without limitation!". I couldn't find any copyright or license notices in the files I downloaded. Can you please add a notice of some kind to these files so I feel better about reusing them in my (closed source) project?
Thanks again for sharing your solution. :)
Sure, no problem. Can't be too careful nowadays I suppose :)
DeleteI have added a source package that includes the CC0 1.0 public domain license :)
thanks...great solution. Actually I cannot get it working on my Galaxy, but I learnt lot of stuff.
ReplyDeleteNow... what I wanted to use it in a different way, let's say, in an augmented Reality App?
I would need to draw specific strings only when a certain marker is detected (I use AndAR), so the solution of drawing text in the OnDrawFrame would not work.
How could I create "several" GLText objects and draw different string depending on the markers in the scene?
Anyone can help me with that?
Go have a look here for various solutions that might fit your needs
Deletehttp://stackoverflow.com/questions/1339136/draw-text-in-opengl-es-android
Otherwise you could just have a signal (some variables or whatver) that you could SET when an event occurs, and in OnDrawFrame just check if these are set (i.e. signalled) and render the correct string.
Great job!!
ReplyDeleteThis saves me a bunch of time, Thank you very much!
ReplyDeletebut i found, that it measure wrogly wigth of some characters. But it looks like problem of Android API, because for example in Photoshop same font is displayed correctly.
DeleteI haven't experienced this personally. Did it only happen with one font or have you seen this with various fonts? Could be that Android and Photoshop handles some advanced font details (such as kerning) differently.
DeleteI have had fonts where a lot of the characters overlap (but they were that way in photoshop too). That is why I added the padding values :)
You could try to use something other than 'paint.getTextWidths' for obtaining the the widths (there are a couple of ways as far as I recall) and see if that fixes the problem.
I'm trying to get this to work over a 3d scene. Any idea what changes I'd have to make to get it working with a perspective rather than and orthographic view? All my attempts are proving useless.
ReplyDeleteIf you mean using this to display text "in" the 3D scene (i.e. not mapped to the display's X,Y), then you would simply need to replace the "batch.drawSprite()" call in the "draw()" method with code that would generate quads in the 3D space (i.e. which would include z coordinates).
DeleteThe easiest way would be to include a Z coordinate and set it to zero (0). Then once you have this geometry (which would be a group of quads at z=0), you could just transform it like any other geometry in 3D space. You may have to alter the origin of the quads depending on how you want to transform the "string" - for a start you would always use (0,0) for (x,y) since you would actually position it with transformations instead of placing them directly in world space.
The included SpriteBatch, while completely functional, is just an example I added to illustrate usage. It would be best to write your own code to generate the quads (in local or world space, as needed). The draw() method calculates/provides position (x,y), character sizes, and texture coords (u,v) for each character.
If you are looking for an example of this type of thing, check out an example of Billboarding. Same basic idea, except that you would not actually need to align the quads to the eye (camera); unless that is what you are trying to do ;)
Hope that helps :)
I've got it working aligned to the camera now. Thanks
DeleteHere is port of your example to Scala. I hope that Apache license is compatible with CC0 1.0 public domain license. Thank you very much for your efforts. https://github.com/ezh/android-DigiLib/tree/develop-0.3/src/main/scala/org/digimead/digi/ctrl/lib/androidext/gl
ReplyDeleteKing regards,
Alexey
Thanks for the effort and for sharing. I will add your link to the post :D
DeleteI decomposed my library and moved opengl stuff to separate part http://ezh.github.com/digi-lib-opengl. There is only your code now. There are autogenerated web site, API, and maven repository with artifacts for Scala 2.8.0, 2.8.1, 2.8.2, 2.9.0, 2.9.0-1, 2.9.1, 2.9.2. Please, give me a sign if you think that I need change description (maybe add your to authors ?)
ReplyDeleteThank you very much
Alexey
I see no problem with it the way it is. Thanks for including the link back here, that is more than enough :) You are the author of that particular implementation, so I really don't need to be credited for it.
DeleteI put this up to make it easier for others to quickly get a working solution and appreciate that you have contributed to this by porting it and making it available, since under CC0 that is not a requirement.
So thanks to you too :)
PS: I will update the links to the new location.
Thank you a lot it saves me lot of time. You should add it on github.
ReplyDeleteMy pleasure :) I had thought about adding it to a repository, but would like to clean it up a bit and add some improvements that I've made since - and maybe do a OpenGL ES 2.0 implementation as well. Right now I am a bit swamped, but I'll get around to it.
DeleteThis is very cool. And very helpful.
ReplyDeleteThank you!
I screwed around with placing text in 3d but I seem to get z-fighting shimmering when I rotate the camera around the text. The first char of a string never shimmers but subsequent chars do especially if narrow (which is why I concluded it is z fighting with the next two triangles). Been playing with it and can't figure out how to tweak the code but I am a bit of an OpenGL noob. Can anyone offer insights?
Glad you found it helpful :)
DeleteI have not personally used this system in 3D space, but I don't see how the z-order can cause problems for the overlapping triangles since they have zero opacity at the overlapping areas - no pixels are rendered there, so the z-buffer "should" not be updated. Unless the OpenGL ES implementation has some strange bug that causes this, it should NOT happen - that said, of course for you it IS happening and this is not much help.
Have you tried disabling Z-Buffering temporarily (to ensure that it is in fact causing the problem)?
It could also be that the texture filtering (or bit depth) is causing "very low" alpha values (instead of zero) at these areas, which could then causes some z-fighting even though the area appears to be transparent.
You could also try to increase the x-axis spacing (using the spaceX value in the class) to ensure that no triangles overlap - use a large value just to see if that fixes the problem.
I hope it helps, but without actually testing it I'm really just guessing :)
Is there an easy way to configure this to display in landscape orientation?
ReplyDeleteSure, just add android:screenOrientation="landscape" (or "sensorLandscape") to the activity tag in the AndroidManifest.xml file and you should be good to go :)
DeleteThat does work, however, it changes the rest of my graphics to display in landscape mode as well (obviously). My problem is that the activity's screen orientation is in portrait mode, but I draw my game as if it is in landscape mode. It doesn't look like there is an easy, built in way for your GLText class to work as I want it to. Let me know if there is, otherwise I'll probably need to make some modifications.
DeleteUnfortunately the renderer of the example is very basic - I wanted to focus the attention on the text generation and only provided the rendering code as an example of how to use it.
DeleteIf you need to render into a non-standard coordinate system then you will have to replace (or modify) the render code.
It was working fine since i decided to use 3D
ReplyDeleteI use to configure my renderer with
GLU.gluOrtho2D(gl, 0, x, 0, y);
And now I use z index :
GLU.gluPerspective(gl, 45.0f, (float) x / (float) y, 0.1f, 10000.0f);
draw text does not display anymore, please could you give me some hints to make it works ? I would really appreciate.
Hi, actually I managed to make to text display correctly on (x,y), the only litte problem I have is when my camera move on the z axix the text doesnt. How could I add the z traslation ?
Deletegl.glTranslatef(0 , 0, zindex) // this has no effect on text draw.
Thank you
Hi Jocelyn,
DeleteThe sample renderer (SpriteBatch.java) only uses x and y for its vertices. You will need to add a z axis to the vertices.
Change the value of VERTEX_SIZE size from 4 to 5. And add an extra argument to the Vertices() constructor call with a value of 'true'. Then you would need to alter the drawSprite() method to add z values to the vertex buffer - you could send them as an argument, or use a constant value (such as 0).
That should get you started...good luck :)
Big thank for your help man, I ll send you a free copy of my game with a unique achievement made just for u. I'll give you feedback.
Deleteyour explainations were neat : I did the changes and the little extra z coord on the methods signatures and it work on the first time !
DeleteWould you like me to put the update on github ?
I'm glad that you got it working :)
ReplyDeleteIf you do put the updated renderer on github, I will link to it in the post above so that others can benefit from the changes you made.
Here is the git hub repo with a functional example of z index use.
Deletehttps://github.com/jodem/glText-Android
Thanks for the update and the link. I have added it to the post so that everyone can benefit from the work you've done :) Good luck with your game!
DeleteFeel free to join my first beta version, every text used on the game comes from your library !
Deletehttps://plus.google.com/u/0/communities/101483204826024792834
Hi! Thanks a lot for this code :) I'm implementing it for an application and it's helping me a lot, but I'm having a problem :(,when I use it with another OpenGL's objects that use textures, the text appears over my other images with a blue background, and when I press de Go Back Button, all my images appear for a second and then the activity finishes, do you have any idea why it could be happening? Thanks for your help
ReplyDeleteHey there,
DeleteAs to the text having a blue background when rendering, I suspect that alpha blending is disabled. For the text to render properly, texture mapping and alpha blending must be enabled (see the DrawFrame() method in the TexampleRenderer.java file).
And as for why the activity finishes, that will depend on what exactly happens when you Go Back. I would check the logcat when that happens and see if there are any errors (or warnings) related to your app when the activity ends prematurely.
Hope that helps.
Thanks for your answer! Well I did what you said and I'm having the same problem, this is my onDrawFrame code:
Deletepublic void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
gl.glClear( GL10.GL_COLOR_BUFFER_BIT );
gl.glMatrixMode( GL10.GL_MODELVIEW );
gl.glLoadIdentity();
gl.glEnable( GL10.GL_TEXTURE_2D );
gl.glEnable( GL10.GL_BLEND );
gl.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
dibujarLayout(gl);
gl.glDisable( GL10.GL_BLEND );
gl.glDisable( GL10.GL_TEXTURE_2D );
}
Where "dibujarLayout(gl)" it's a Method that I use to set all the objects in the screen. And about the go back, it seems like if the screen redraws and then it goes back to the previous activity, so maybe theres is a problem in how I'm drawing the text or that's what I think, thanks for your help, I really apreciate it, and sorry for my bad english jeje
Just as a test, try to draw some text in onDrawFrame() before the call to the dibujarLayout(gl) - if that works then the problem can be isolated to somewhere in that call.
DeleteThe best way is to perform the rendering in different places to try and isolate the area that is causing problems.
Going back is supposed to go to the previous activity so it could be that it is behaving as it should. You have to intercept the call to back press in order to prevent that. Unless I am misunderstanding you.
Thanks for your help, I did it as you said but I still having the problem with the text :(. about the go back button don't worry about it, I solved the problem, it was a lil misunderstood. Have a nice day and I will be triying some ideas I have, maybe my problem it's in how I'm rendering my objects and my text. Thanks a lot fot taking your time to answer me.
DeleteWell I think that I'm a lil bit stuck with this, I tried everything that it comes to my mind and still doesn't working. The problem is that it's like if the app first renders my images and then overlap the text, so my images are behind the text texture. Hope you can help me, and I really thanks you for the time that you spend trying to help me.
DeleteWithout actually seeing a screenshot and/or the rendering code I can only guess what the cause of your problem is.
DeleteHowever, as a test I added a textured triangle (behind the text) to the example, and everything renders properly - you can grab the code for the modified example here:
http://sites.google.com/site/fractiousgames/Texample2CodeWithLicense.zip
(the included texture should go into assets)
An important thing to remember about OpenGL is that states (such as color, texture, etc.) remain set until they are changed. To find your problem you will have to look at which states the other objects in your rendering code change and how they could be affecting the text rendering.
Maybe you can see something in the new example that might explain why your code is not working as expected :) I hope this helps!
Thanks! I'm checking your code, here's a link via dropbox to my project.
Deletehttps://www.dropbox.com/s/jdf6tifl7i5a7cz/codigo.rar
The class "objetosLayout" it's the one that I use to set my textures, and the problem is in my class called "pantallaInicial" I copied your classes to my project to try to fix the problem, but it's not working. I'm testing my code in a LG phone and in a Samsung tablet. I hope this information give to you a better idea about what I want to do. Thank you.
I think I understand the problem now. As I mentioned, OpenGL states remain set/unset until they are changed. The batch renderer I use for the text will set the states it requires in the .begin() call, and will unset them in the .end() call. Maybe I just missed it, but I could not find where you enable the vertex/texture_coord array states, and I know that the text rendering will set AND unset them...so give the following a try :) In the "onDraw" method of the "pantallaInicial" class:
DeleteAdd the following immediately BEFORE the "fondo.dibujar()" call:
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
Then add the following BEFORE the "texto_OpenGL.begin()" call:
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
You may also need to add a "gl.glLoadIdentity()" before rendering text, but I'm not sure if it will be needed - just add it if the text renders in the wrong place/orientation.
Give that a try and let me know if you still have problems/questions :)
It's working!! reading your code I had a similar idea but I was not sure where I had to put the code. Thanks a lot for all the time you dedicated to my problem, it's nice to found people that it's really interested in help others.
DeleteGreat, I'm glad you got it working...glad to help :) Good luck with your project!
DeleteThanks for the code! I used it as a base to start my own openGL Android app. Lots of work to do yet, but this went a huge long way to getting me on my feet!
ReplyDeleteGlad to help! Good luck with your app!
DeleteHi! Thank you very much for your code - I used it as a base for a OpenGL 2 ES port which I use in my game. Here is the code - https://github.com/d3kod/Texample2. Let me know if you approve it or not! Thanks again, really saved me tons of time :-)
ReplyDeleteGlad you found the code useful...and thank you for taking the time to post an OpenGL ES 2.0 version! I just checked it out and it works like a charm :) This will save others working in GLES 2.0 a bunch of time - +1000 karma points :D
DeleteI will add a link to the code in the post so that everyone can benefit from your work! Thanks!
Thanks! Glad you like it ^^
DeleteThanks you for posting this great article and making available the source!
ReplyDeleteI am developing a little game and this is my first Android project. I need to draw changing text on screen so this is a really big help for me.:)
It is a pleasure! Have fun with android :)
DeleteI have added the OpenGL 2.0 version to my code but only the first letters of each line get rendered properly. The draw texture works fine but each line your get the first letter and the rest will be pixels. Any idea?
ReplyDeleteI'm not exactly sure what you mean with "pixels" - do you mean a single pixel per extra letter (after the first)?
DeleteHere are some things you could try to determine where the problem lies:
* Use a completely different font file (just to make sure that it is not your font file).
* Move the position of the GLText.draw() call around (some state that you set before or after the call may be affecting it).
* Step through the load() and draw() calls in debug mode - pay attention to the values (such as position and scale) to see that they are set correctly.
* Make sure the spaceX and scaleX values are set properly as these will affect the placement of all characters following the first.
* Try to remove some of your other render calls temporarily and see if that eliminates the problem - if it does you could isolate the problem by eliminating them one by one until the problem goes away.
As I have not personally used the OpenGL 2.0 version in any of my projects I am not exactly sure what other factors may be affecting it. One thing that has tripped me up a couple of times when using OpenGL is the fact that states persist - even if the state is set after a particular call, it will affect that call the next frame unless the state is reset (or set to some other value).
Unfortunately it is very hard to guess the cause given this little info. If you are still having trouble don't hesitate to ask, but please provide a bit more detail and context.