Can't get OpenGL text drawing to work

Another fun question for you guys to answer.

Using stuff I’ve found on this forum, I made a big utility class for drawing various 2D things in OpenGL. It all works, except for text, which is of course hopelessely complicated.

I have one simple question for now, hopefully it’s the problem (as I think). In the Nehe tutorial 13 on text, everything works fine as text is drawn to my screen. I took most of that code into my utility class and everything runs, but I can’t see text anywhere. This is probably because I don’t understand how to position it.

In the Nehe code, he uses glTranslate to position the text on the screen from what I can tell. When checking what values are passed to that, they seem to be the text’s relationship with the center of the screen. I’ve looked it up and don’t understand all this matrix nonsense very well. How can I use translate these float values to regular x and y position integers?


GL11.glTranslatef(-0.9f + 0.05f * ((float)Math.cos(cnt1)), 0.32f * ((float)Math.sin(cnt2)), -2.0f);

I’m just using my general programming common sense here, I really have not much clue what I’m talking about. Bought a book on OpenGL online, so that’s the first step, but help would be great.

I don’t know to which NeHe code do you reffer… Those math functions for position looks weird, I think that is part of some effect.
What I do is turn orto mode on:

    public static void setOrthoOn() {

        // prepare to render in 2D
        GL11.glDisable(GL11.GL_DEPTH_TEST); // so text stays on top of scene
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glPushMatrix(); // preserve perspective view
        GL11.glLoadIdentity(); // clear the perspective matrix
        GL11.glOrtho(Common.viewportX, Common.viewportX + Common.viewportW, Common.viewportY, Common.viewportY + Common.viewportH, -1, 1);

        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glPushMatrix(); // Preserve the Modelview Matrix
        GL11.glLoadIdentity(); // clear the Modelview Matrix
    }

And then just write with pre-builded text:

GL11.glTranslatef(x, y, 0); // Position The Text (in pixels coords) 0,0 is bottom right corner

After that, back to perspective:

    public static void setOrthoOff() {

        // restore the original positions and views
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glPopMatrix();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glPopMatrix();
        GL11.glEnable(GL11.GL_DEPTH_TEST); // turn Depth Testing back on
    }

I already have ortho set correctly, so then I guess glTranslate isn’t my problem. (I have most of a game drawn correctly already, I’m just trying to add text in as well)

So then here’s my other thought.


IntBuffer buf = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
        GL11.glGenTextures(buf); // Create Texture In OpenGL

This is part of the code for generating a font from the nehe tutorial. When I use glGenTextures, does it bind a specific IntBuffer to the GL context, meaning I can only use one? My textures for my images use a completely separate IntBuffer context than this one, and they all show up correctly. Should I instead use a call to the other IntBuffer? Will that work, as that one was created with "BufferUtils.createIntBuffer(1); " rather than with the ByteBuffer.allocateDirect call?

The reason it’s hard to adapt Nehe’s tutorial is because he assumes you only want to draw text, and nothing else. I need to adapt it to work with all the sprites I am drawing.

Thought I would include the entire buildFont() method I got from Nehe.


    //Creates the font Courier New to be used as the drawable font
    private void buildFont()
    {                          // Build Our Bitmap Font
        Font font;                                      // Font object
        String fontName = "Courier New";                // Name of the font to use
        BufferedImage fontImage;                        // image for creating the bitmap
        int bitmapSize = 512;                           // set the size for the bitmap texture
        boolean sizeFound = false;
        boolean directionSet = false;
        int delta = 0;
        int fontSize = 24;

        //Finds the size of the widest character (W)
        while(!sizeFound)
        {
            font = new Font(fontName, Font.PLAIN, fontSize);  // Font Name
            // use BufferedImage.TYPE_4BYTE_ABGR to allow alpha blending
            fontImage = new BufferedImage(bitmapSize, bitmapSize, BufferedImage.TYPE_4BYTE_ABGR);
            Graphics2D g = (Graphics2D)fontImage.getGraphics();
            g.setFont(font);
            FontMetrics fm = g.getFontMetrics();
            int width = fm.stringWidth("W");
            int height = fm.getHeight();
            int lineWidth = (width > height) ? width * 16 : height * 16;
            if(!directionSet)
            {
                if(lineWidth > bitmapSize)
                    delta = -2;
                else
                    delta = 2;
                directionSet = true;
            }
            if(delta > 0)
            {
                if(lineWidth < bitmapSize)
                    fontSize += delta;
                else
                {
                    sizeFound = true;
                    fontSize -= delta;
                }
            }
            else if(delta < 0)
            {
                if(lineWidth > bitmapSize)
                    fontSize += delta;
                else
                {
                    sizeFound = true;
                    fontSize -= delta;
                }
            }
        }

        /* Now that a font size has been determined, create the final image, set the font and draw the
         * standard/extended ASCII character set for that font.
         */
        font = new Font(fontName, Font.BOLD, fontSize);  // Font Name
        // use BufferedImage.TYPE_4BYTE_ABGR to allow alpha blending
        fontImage = new BufferedImage(bitmapSize, bitmapSize, BufferedImage.TYPE_4BYTE_ABGR);
        Graphics2D g = (Graphics2D)fontImage.getGraphics();
        g.setFont(font);
        g.setColor(new Color(0xFFFFFFFF, true));
        g.setBackground(new Color(0x00000000, true));
        FontMetrics fm = g.getFontMetrics();
        for(int i = 0; i < 256; i++)
        {
            int x = i % 16;
            int y = i / 16;
            char ch[] = {(char)i};
            String temp = new String(ch);
            g.drawString(temp, (x * 32) + 1, (y * 32) + fm.getAscent());
        }

        //      Flip Image
        AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
        tx.translate(0, -fontImage.getHeight(null));
        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        fontImage = op.filter(fontImage, null);

        // Put Image In Memory
        ByteBuffer scratch = ByteBuffer.allocateDirect(4 * fontImage.getWidth() * fontImage.getHeight());

        byte data[] = (byte[])fontImage.getRaster().getDataElements(0,0,fontImage.getWidth(),fontImage.getHeight(),null);
        scratch.clear();
        scratch.put(data);
        scratch.rewind();
 
        // Create A IntBuffer For Image Address In Memory
        IntBuffer buf = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
        GL11.glGenTextures(buf); // Create Texture In OpenGL

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
        // Typical Texture Generation Using Data From The Image

        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
        // Generate The Texture
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, fontImage.getWidth(), fontImage.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, scratch);


        fontTexture = buf.get(0);                           // Return Image Address In Memory

        base = GL11.glGenLists(256);                    // Storage For 256 Characters

        /* Generate the display lists.  One for each character in the standard/extended ASCII chart.
         */
        float textureDelta = 1.0f / 16.0f;
        for(int i=0;i<256;i++)
        {
            float u = ((float)(i % 16)) / 16.0f;
            float v = 1.f - (((float)(i / 16)) / 16.0f);
            GL11.glNewList(base + i, GL11.GL_COMPILE);
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTexture);
            GL11.glBegin(GL11.GL_QUADS);
                GL11.glTexCoord2f(u, v);
                GL11.glVertex3f(-0.0450f, 0.0450f, 0.0f);
                GL11.glTexCoord2f((u + textureDelta), v);
                GL11.glVertex3f(0.0450f, 0.0450f, 0.0f);
                GL11.glTexCoord2f((u + textureDelta), v - textureDelta);
                GL11.glVertex3f(0.0450f, -0.0450f, 0.0f);
                GL11.glTexCoord2f(u, v - textureDelta);
                GL11.glVertex3f(-0.0450f, -0.0450f, 0.0f);
            GL11.glEnd();
            GL11.glEndList();
        }
    }

Then the method to call it:


public static void glString(String str, int x, int y)
    {
    		GL11.glTranslatef(x, y, 0); // Position The Text (in pixels coords) 0,0 is bottom right corner
    		if(str != null)
    		{
    			GL11.glBindTexture(GL11.GL_TEXTURE_2D, BestGameEver.textureLoader.fontTexture());
    			for(int i = 0; i < str.length(); i++)
    			{
    				GL11.glCallList(BestGameEver.textureLoader.base() + str.charAt(i));
    				GL11.glTranslatef(0.05f, 0.0f, 0.0f);
    			}
    		}
    }

If you have Ortho mode set up normally, then 0,0 should be the top left.

Yeah yeah, knew that. Just copied it from hvor2 to show him I was using his method. Anyway, I changed it to be like example 17, only this time I got it to work.

However, I can’t kill the black box around the text. Any way around this?


public static void glString(String msg, int x, int y, int set)	// Where The Printing Happens
    {
        if (set > 1)
            set = 1;
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, BestGameEver.textureLoader.fontTexture()); // Select Our Font Texture
        GL11.glDisable(GL11.GL_DEPTH_TEST);                           // Disables Depth Testing
        GL11.glMatrixMode(GL11.GL_PROJECTION);                        // Select The Projection Matrix
        GL11.glPushMatrix();                                     // Store The Projection Matrix
        GL11.glLoadIdentity();                                   // Reset The Projection Matrix
        GL11.glOrtho(0, 640, 0, 480, -1, 1);                          // Set Up An Ortho Screen
        GL11.glMatrixMode(GL11.GL_MODELVIEW);                         // Select The Modelview Matrix
        GL11.glPushMatrix();                                     // Store The Modelview Matrix
        GL11.glLoadIdentity();                                   // Reset The Modelview Matrix
        GL11.glTranslatef(x, y, 0);                                // Position The Text (0,0 - Bottom Left)
        int baseOffset = BestGameEver.textureLoader.base() - 32 + (128 * set); // Choose The Font Set (0 or 1)
        for(int i = 0; i < msg.length(); i++)
        {
            GL11.glCallList(baseOffset + msg.charAt(i));
            GL11.glTranslatef(1.0f, 0.0f, 0.0f);
        }
        GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glMatrixMode(GL11.GL_PROJECTION);                        // Select The Projection Matrix
        GL11.glPopMatrix();                                      // Restore The Old Projection Matrix
        GL11.glMatrixMode(GL11.GL_MODELVIEW);                         // Select The Modelview Matrix
        GL11.glPopMatrix();                                      // Restore The Old Projection Matrix
    }

The image texture used to load the font from is a 16x16 grid of letters. Its type is bmp. I tried changing it to gif and introducing transparency into it, but that was not successful. It garbled the result as well as leaving the black box. Fix?

Wwll, I think that you shoul enegle blending
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
BEFORE your call to
GL11.glCallList(baseOffset + msg.charAt(i));,

and then disable it after if you want…
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_BLEND);

If you are still using the code from example 13, then the font texture is generated, not loaded from a file. If you have switched to loading from a file, then the file has to be in a format that supports an alpha channel. TGA and PNG support an alpha channel.

When you are creating the texture, you have to use GL_RGBA like this:

GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, fontImage.getWidth(), 
                  fontImage.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, scratch);

Then make sure blending is enabled with the correct values.

Okay, I’ll check these methods out, although I did try putting the blending in front already and it made no difference, whereas with my sprites I put it after and it works fine. I’ve looked over the code in so many methods of doing this now I think I pretty much understand it all, in any case. It’s why lesson 13 is boggling me, I have absolutely no clue why it refuses to work. I’ve swapped stuff around in just about every possible way with no positive result.

I did in fact change the text image to a png instead of a bmp and that was one of the formats that caused entirely incorrect breaking up of the image. Does it need to be a certain filesize?

In the end, I think I’m just going to look up how to theoretically do this and then attempt to write it from scratch. It’s a much better learning process, anyway. (if only the GL11 methods had better docs)

The c versions of the GL11 methods can be found here:
http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/

Demonpants, i am using Tahoma font, that is an 256x256 .png image.

Got that image somewhere?

Okay, so I tried doing it from scratch doing the AWT write into a BufferedImage method, but the result of this makes the screen white and screws up drawing for everything else. Any hints?


    public static void glString(String str, int x, int y, Font font)
    {
    		GL11.glOrtho(0, 800, 600, 0, -1, 1);
    		BufferedImage fontImage = new BufferedImage(512, 512, BufferedImage.TYPE_4BYTE_ABGR);
    		Graphics2D g = (Graphics2D)fontImage.getGraphics(); g.setFont(font);
    		FontMetrics fm = g.getFontMetrics();
    		int width = fm.stringWidth(str); int height = fm.getHeight();
    		
    		fontImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
    		g = (Graphics2D)fontImage.getGraphics(); g.setFont(font);
    		fm = g.getFontMetrics();
    		g.setColor(java.awt.Color.BLACK);
    		g.setBackground(java.awt.Color.WHITE);
    		g.drawString(str,0,fm.getAscent());
    		
    		//Put Image In Memory
    		ByteBuffer scratch = ByteBuffer.allocateDirect(4 * fontImage.getWidth() * fontImage.getHeight());

    		byte data[] = (byte[])fontImage.getRaster().getDataElements(0,0,fontImage.getWidth(),fontImage.getHeight(),null);
    		scratch.clear(); scratch.put(data); scratch.rewind();
     
    		// Create an IntBuffer For Image Address In Memory
    		IntBuffer buf = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
    		GL11.glGenTextures(buf); // Create Texture In OpenGL
    		GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
    		// Linear Filtering
    		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
    		// Linear Filtering
    		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
    		// Generate The Texture
    		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, fontImage.getWidth(), fontImage.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, scratch);
    		
    		GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
    		GL11.glTranslatef(x, y, 0);

    		// draw a quad textured to match the sprite
    		GL11.glBegin(GL11.GL_QUADS);
    		{
    			GL11.glTexCoord2f(0, 0);
    			GL11.glVertex2f(0, 0);

    			GL11.glTexCoord2f(0, height);
    			GL11.glVertex2f(0, height);

    			GL11.glTexCoord2f(width, height);
    			GL11.glVertex2f(width, height);

    			GL11.glTexCoord2f(width, 0);
    			GL11.glVertex2f(width, 0);
    		}
    		//Sprite sprite = new Sprite(buf.get(0));
    		//sprite.draw(x,y,sprite.getWidth(),sprite.getHeight());
    }