ZioN's picture

Rendering 2D Labels on 3D Objects

Hi I am new to OpenTK, love the fact I can program with OpenGL in C#, two of my most favorite things :)

I have been giving a job at work to represent the data in our database, (with is a structure) in 3D.
I have search various places, come close with this thread here

But I am using OpenTK 1.0 2010-10-06, and the TextPrinter class seems to be missing.
I would like to know how to print a label (witch is text), and put in on a 3D Object, and as the camera moves around, the labels need to stay with the objects.

Using windows XP, Microsoft Visual Studio 2008, C# .Net, and the GameWindow class.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Robmaister's picture

The TextPrinter is part of OpenTK.Compatibility. Replace your project's OpenTK reference with a OpenTK.Compatibility reference.

ZioN's picture

Thanks I have found the TextPrinter class now! But I see there are a few more differences between the OpenTK and OpenTK.Compatibility references.

I am using GameWindow, and I see the GameWindow class does not exist with the OpenTK.Compatibility, or did I forget to include some 'using'.
All I did was take out my reference to the OpenTK and added the OpenTK.Compatibility reference.
Also here are some few of the error messages I got:

  1. The type or namespace name 'GameWindow' could not be found (are you missing a using directive or an assembly reference?)
  2. The type or namespace name 'Vector3' could not be found (are you missing a using directive or an assembly reference?)
  3. The type or namespace name 'FrameEventArgs' could not be found (are you missing a using directive or an assembly reference?)

If you want to have a look at my coding here it is:

Program.CS

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
//using OpenTK.Graphics.OpenGL;
//using OpenTK.Input;
 
namespace Labels
{
    class Game : GameWindow
    {
        ArrayList ObjectsToDisplay;
 
        public Game()
            : base(800, 600, GraphicsMode.Default, "Drawing Labels on objects")
        {
            VSync = VSyncMode.On;
 
            ObjectsToDisplay = new ArrayList();
 
            ObjectsToDisplay.Add((DisplayObject)new Node(new Vector3(0.0f, 0.0f, 0.0f)));
        }
 
        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            float[] ambientLight = { 0.1f, 0.1f, 0.1f, 1.0f };
            float[] diffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
            float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
            float[] LigthPos = { 100.0f, 100.0f, 100.0f, 1.0f };
 
 
            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
            GL.Enable(EnableCap.Lighting);
 
            // Set up lighting
            GL.Light(LightName.Light0, LightParameter.Ambient, ambientLight);
            GL.Light(LightName.Light0, LightParameter.Diffuse, diffuse);
            GL.Light(LightName.Light0, LightParameter.Specular, specular);
            GL.Light(LightName.Light0, LightParameter.Position, LigthPos);
 
            GL.Enable(EnableCap.Light0);
 
            GL.Enable(EnableCap.ColorMaterial);
 
            GL.ColorMaterial(MaterialFace.Front, ColorMaterialParameter.AmbientAndDiffuse);
 
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, specular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, 128);
 
            GL.Enable(EnableCap.Normalize);
        }
 
        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);            
        }
 
        float RotateX = 0.0f;
        float RotateY = 0.0f;
 
        float MoveX = 0.0f;
        float MoveY = 0.0f;
        float MoveZ = 10.0f;
 
        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
 
            if (Keyboard[Key.Escape])
                Exit();
 
            if (Keyboard[Key.Up])
            {
                if (RotateX == 360)
                {
                    RotateX = 0;
                }
                else
                {
                    RotateX++;
                }
            }
            if (Keyboard[Key.Down])
            {
                if (RotateX == 0)
                {
                    RotateX = 360;
                }
                else
                {
                    RotateX--;
                }
            }
            if (Keyboard[Key.Left])
            {
                if (RotateY == 360)
                {
                    RotateY = 0;
                }
                else
                {
                    RotateY++;
                }
            }
            if (Keyboard[Key.Right])
            {
                if (RotateY == 0)
                {
                    RotateY = 360;
                }
                else
                {
                    RotateY--;
                }
            }
 
            if (Keyboard[Key.W])
                MoveY++;
            if (Keyboard[Key.S])
                MoveY--;
            if (Keyboard[Key.A])
                MoveX--;
            if (Keyboard[Key.D])
                MoveX++;
            if (Keyboard[Key.Plus])
                MoveZ--;
            if (Keyboard[Key.Minus])
                MoveZ++;
        }
 
        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);
 
            GL.PushMatrix();
            GL.Translate(MoveX, MoveY, MoveZ);
            GL.Rotate(RotateX, 1.0f, 0.0f, 0.0f);
            GL.Rotate(RotateY, 0.0f, 1.0f, 0.0f);
 
            foreach (DisplayObject disObject in ObjectsToDisplay)
            {
                GL.CallList(disObject.DisplayList);
            }           
 
            GL.PopMatrix();
 
            SwapBuffers();
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            using (Game game = new Game())
            {
                game.Run(30.0);
            }
        }
    }
}

Objects.CS

namespace Labels
{
    public class DisplayObject
    {
        protected string _ClassName;
        public string ClassName { get { return _ClassName; } set { ; } }
 
        public int DisplayList;
        protected bool isDrawn = false;
 
        public DisplayObject()
        {
            _ClassName = "DisplayObject";
        }
 
        protected virtual void CreateDisplayList(Vector3 Origin)
        {
        }
    }
    public class Node : DisplayObject
    {
        public Vector3 Origin;
 
        public Node(Vector3 origin)
        {
            _ClassName = "Node";
 
            Origin = origin;
 
            CreateDisplayList(origin);
        }
        protected virtual void CreateDisplayList(Vector3 Origin)
        {
            if (!isDrawn)
            {
 
                DisplayList = GL.GenLists(1);
 
                GL.PushMatrix();
                GL.Translate(Origin);
                GL.NewList(DisplayList, ListMode.Compile);
 
                #region Draw Axises
                GL.PushMatrix();
                GL.Disable(EnableCap.Lighting);
                GL.Begin(BeginMode.Lines);
 
                GL.Color3(1.0f, 0.0f, 0.0f);
                GL.Vertex3(0.0f, 0.0f, 0.0f);
                GL.Vertex3(5.0f, 0.0f, 0.0f);
 
                GL.Color3(0.0f, 1.0f, 0.0f);
                GL.Vertex3(0.0f, 0.0f, 0.0f);
                GL.Vertex3(0.0f, 5.0f, 0.0f);
 
                GL.Color3(0.0f, 0.0f, 1.0f);
                GL.Vertex3(0.0f, 0.0f, 0.0f);
                GL.Vertex3(0.0f, 0.0f, 5.0f);
                GL.End();
                GL.Enable(EnableCap.Lighting);
                GL.PopMatrix();
 
                #endregion
                #region Draw cude
                GL.PushMatrix();
                GL.Color3(Color.Salmon);
 
                GL.Begin(BeginMode.Quads);
 
                GL.Normal3(0.0f, 0.0f, -1.0f);// Front side
                GL.Vertex3(-1.0f, 1.0f, -1.0f);
                GL.Vertex3(1.0f, 1.0f, -1.0f);
                GL.Vertex3(1.0f, -1.0f, -1.0f);
                GL.Vertex3(-1.0f, -1.0f, -1.0f);
 
                GL.Normal3(0.0f, 1.0f, 0.0f);// Top Face
                GL.Vertex3(-1.0f, 1.0f, 1.0f);
                GL.Vertex3(1.0f, 1.0f, 1.0f);
                GL.Vertex3(1.0f, 1.0f, -1.0f);
                GL.Vertex3(-1.0f, 1.0f, -1.0f);
 
                GL.Normal3(0.0f, -1.0f, 0.0f);// Bottom Face
                GL.Vertex3(-1.0f, -1.0f, -1.0f);
                GL.Vertex3(1.0f, -1.0f, -1.0f);
                GL.Vertex3(1.0f, -1.0f, 1.0f);
                GL.Vertex3(-1.0f, -1.0f, 1.0f);
 
                GL.Normal3(0.0f, 0.0f, 1.0f);// Back Face
                GL.Vertex3(1.0f, 1.0f, 1.0f);
                GL.Vertex3(-1.0f, 1.0f, 1.0f);
                GL.Vertex3(-1.0f, -1.0f, 1.0f);
                GL.Vertex3(1.0f, -1.0f, 1.0f);
 
                GL.Normal3(1.0f, 0.0f, 0.0f);// Rigth Face    
                GL.Vertex3(1.0f, 1.0f, -1.0f);
                GL.Vertex3(1.0f, 1.0f, 1.0f);
                GL.Vertex3(1.0f, -1.0f, 1.0f);
                GL.Vertex3(1.0f, -1.0f, -1.0f);
 
                GL.Normal3(-1.0f, 0.0f, 0.0f);// Left Face   
                GL.Vertex3(-1.0f, 1.0f, 1.0f);
                GL.Vertex3(-1.0f, 1.0f, -1.0f);
                GL.Vertex3(-1.0f, -1.0f, -1.0f);
                GL.Vertex3(-1.0f, -1.0f, 1.0f);
                GL.End();
                GL.PopMatrix();
                #endregion
                #region Draw Label
 
                #endregion
 
                GL.EndList();
                GL.PopMatrix();
 
                isDrawn = true;
            }
        }
 
        ~Node()
        {
        }
    }
}
Robmaister's picture

for the Vector3, you need to use OpenTK.Math;. As for the other two, I'm not really sure... you may have to reference both OpenTK and OpenTK.Compatibility. Alternatively you could just use QuickFont. http://www.opentk.com/node/2628

My only gripe with QuickFont is that it forces immediate mode, so in a day or two I rolled my own font rendering that used shaders and was more integrated to my project. That's also an option if you absolutely can't have the extra overhead from immediate mode, but for most cases that overhead is nothing.

ZioN's picture

Well I have tried QuickFont, was able to display text. But all my 3D objects disappear, after struggling, to get it back. I gave up and took it out.

Well let me try your font rendering, if you don't mind posting a link here, when you roll it out. Thanks.

Robmaister's picture

I think QuickFont doesn't unbind the texture or disable anything, make sure to call GL.BindTexture(TextureTarget.Texture2D, 0); after drawing anything with QuickFont and skim through the rendering code to see if they enable/disable anything that you need set to render properly. Also make sure to push the matrix before drawing text and pop it back after.

And I'm not releasing my font rendering as a separate library, it's just a part of a game I'm working on and hoping to eventually sell. If I wanted to release it in it's current state I'd have to also expose most of the game's graphics code and you would have to heavily modify either my code or your code to work together, especially since you're using immediate mode/display lists and I'm using core OpenGL 3.3. It would be impractical and I would be exposing a lot of the work I've done for something I'm planning on selling, sorry.

ZioN's picture

After some investigating I was about to to get QuickFont working, and not making my 3D objects disappear. I made a call to OpenGL to disable Blending and that seem to do the trick. Not sure what the Blending has to do with it.

For those reading this:
I disable Blending with the following code, which is after my 'myText.Print' call

// To stop my 3D objects from disappearing
myText.Print("My Label, It works :)", new Vector2(50.0f, 50.0f));
GL.Disable(EnableCap.Blend);

Here is my working code. Must still work out how to display the label on the 3D object.

Program.CS

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using QuickFont;
 
namespace Labels
{
    class Game : GameWindow
    {
        ArrayList ObjectsToDisplay;
        QFont myText;
 
        public Game()
            : base(800, 600, GraphicsMode.Default, "Drawing Labels on objects")
        {
            VSync = VSyncMode.On;
 
            // Create QuickFont
            myText = new QFont(new Font(FontFamily.GenericSansSerif, 36));
            myText.Options.Colour = Color4.Green;
 
            ObjectsToDisplay = new ArrayList();
 
            ObjectsToDisplay.Add((DisplayObject)new Node(new Vector3(0.0f, 0.0f, 0.0f)));
        }
 
        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            float[] ambientLight = { 0.1f, 0.1f, 0.1f, 1.0f };
            float[] diffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
            float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
            float[] LigthPos = { 100.0f, 100.0f, 100.0f, 1.0f };
 
 
            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
            GL.Enable(EnableCap.Lighting);
 
            // Set up lighting
            GL.Light(LightName.Light0, LightParameter.Ambient, ambientLight);
            GL.Light(LightName.Light0, LightParameter.Diffuse, diffuse);
            GL.Light(LightName.Light0, LightParameter.Specular, specular);
            GL.Light(LightName.Light0, LightParameter.Position, LigthPos);
 
            GL.Enable(EnableCap.Light0);
 
            GL.Enable(EnableCap.ColorMaterial);
 
            GL.ColorMaterial(MaterialFace.Front, ColorMaterialParameter.AmbientAndDiffuse);
 
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, specular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, 128);            
 
            GL.Enable(EnableCap.Normalize);
        }
 
        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);            
        }
 
        float RotateX = 0.0f;
        float RotateY = 0.0f;
 
        float MoveX = 0.0f;
        float MoveY = 0.0f;
        float MoveZ = 10.0f;
 
        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
 
            if (Keyboard[Key.Escape])
                Exit();
 
            if (Keyboard[Key.Up])
            {
                if (RotateX == 360)
                {
                    RotateX = 0;
                }
                else
                {
                    RotateX++;
                }
            }
            if (Keyboard[Key.Down])
            {
                if (RotateX == 0)
                {
                    RotateX = 360;
                }
                else
                {
                    RotateX--;
                }
            }
            if (Keyboard[Key.Left])
            {
                if (RotateY == 360)
                {
                    RotateY = 0;
                }
                else
                {
                    RotateY++;
                }
            }
            if (Keyboard[Key.Right])
            {
                if (RotateY == 0)
                {
                    RotateY = 360;
                }
                else
                {
                    RotateY--;
                }
            }
 
            if (Keyboard[Key.W])
                MoveY++;
            if (Keyboard[Key.S])
                MoveY--;
            if (Keyboard[Key.A])
                MoveX--;
            if (Keyboard[Key.D])
                MoveX++;
            if (Keyboard[Key.Plus])
                MoveZ--;
            if (Keyboard[Key.Minus])
                MoveZ++;
        }
 
        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);
 
            GL.PushMatrix();
            GL.Translate(MoveX, MoveY, MoveZ);
            GL.Rotate(RotateX, 1.0f, 0.0f, 0.0f);
            GL.Rotate(RotateY, 0.0f, 1.0f, 0.0f);
 
            foreach (Node disObject in ObjectsToDisplay)
            {
                disObject.Draw();
            }           
 
            GL.PopMatrix();
 
            GL.PushMatrix();
            GL.Disable(EnableCap.Lighting);
            QFont.Begin();
            myText.Print("My Label, It works :)", new Vector2(50.0f, 50.0f));
            QFont.End();
            GL.Enable(EnableCap.Lighting);
            GL.Disable(EnableCap.Blend);
            GL.PopMatrix();
 
            SwapBuffers();
        }
    }
 
 
 
    class Program
    {
        static void Main(string[] args)
        {
            using (Game game = new Game())
            {
                game.Run(30.0);
            }
        }
    }
}