Haighstrom's picture

[Solved] Help getting started with OpenTK

Hi all,

I have been making games in XNA for a couple of years, but would like to step away from it because it is no longer supported in new versions of Visual Studio and it requires the XNA framework to be installed to work.

I worked my way through a tutorial in Windows forms using GDI+ but the frame rate was horrible. So I have come here in the hope of learning to use OpenTK, but it seems punishingly difficult for me so far.

I am familiar with most C# and general programming concepts, but have no experience of graphical programming (I have only used XNA's extremely high level functionality).

I've been trying to work my way through the Introduction pages but the samples are incomplete and often obsolete due to version 3.2, and the documentation has a lot of gaps. I'll try and share what I'm trying to achieve as a starting point, what I think I have understood, and where I'm having trouble. I know this will be a difficult post to answer, but would be really grateful if someone can help me get started.

I want to make a 2D game. My first goal in that is to draw a .png sprite to the screen. Later I'll worry about things like clipping, rotation, resizing, layers and so on.

I've got a game window set up, and that's about as far as I've got.

From what I've read, I understand the approach is to use double buffering, and this means I do all my render operations to a frame (using the class GL) while the other frame is showing on the screen, and then switch them (putting the frame I just created on the screen, and start working on the other one).

From what I can gather, there is no real concept of 2D in OpenTK, and really what I need to do is load my .png as a texture, create a surface representing the full front of the screen and put a texture on part of it. But I'm not sure.

So I think I've loaded my texture into memory, using the code from the tutorial:

        public int LoadTexture(string path)
        {
            //check file exists
            if (String.IsNullOrEmpty(path)) throw new ArgumentException(path);
 
            //I gather this creates a unique identifier for a new texture
            int id = GL.GenTexture(); 
 
            //documentation isn't helpful here. What does this do? Assigns memory for the texture?
            GL.BindTexture(TextureTarget.Texture2D, id);
 
            //create a bitmap from my file
            Bitmap BMP = new Bitmap(path); 
 
            //I guess this is locking the file for readonly use, and specifying the compression format (whatever that might mean)?
            BitmapData bmpData = BMP.LockBits(new Rectangle(0, 0, BMP.Width, BMP.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
 
            //Again, don't know what this is for. Assume this is doing something with the buffer that I'm intended to draw shortly, but that's unclear from the documenatation.
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmpData.Width, bmpData.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0);
 
            //release the bitmap
            BMP.UnlockBits(bmpData);
 
            //code said something about mipmaps. don't know what they are, but let's include this anyway...
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
 
            return id;
        }

OK. Some code written. Not much understanding attained.

Now I have to somehow draw this texture in my OnRenderFrame method. I started out with:

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            //clears the buffer. No idea what the arguments mean specifically.
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            //move the buffer I just drew onto the screen
            SwapBuffers();
        }

Now for the bit in between clearing the buffer, and drawing it, I have to do... something. I have an "int" representing my Texture, and that's about it.

The tutorial says "If you are not using shaders, you should enable texturing (with GL.Enable) and bind the texture (GL.BindTexture) prior to rendering.". I don't know what a shader is, but assume I don't need this. I tried adding GL.Enable and GL.BindTexture with random arguments that had "Texture2D" in them to my OnRenderFrame but of course that didn't do anything, I haven't found anywhere to specify where I should draw my Texture, or found a command to actually add it to the buffer.

What I really need is a proper tutorial but haven't found one with samples that still work in 3.2. Does anyone know of any, or could someone help me get started?

Many thanks
Haighstrom


Comments

Comment viewing options

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

Everything OpenGL from 1.1 and up is going to work everywhere. You dont have to worry about being 3.2, core profiles, deprecation etc. The incompatibility rule is mostly for mobile platforms, where the ES version 2.0 is not backwards compatible with 1.1
Try to insert this between GL.Clear and SwapBuffers

 
GL.Viewport(0, 0, Width, Height);
float aspect_ratio = Width / (float)Height;
OpenTK.Matrix4 perspective = OpenTK.Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspect_ratio, 1, 64);
GL.MatrixMode(MatrixMode.Projection);
GL.LoadMatrix(ref perspective);
 
Matrix4 lookat = Matrix4.LookAt(-2, 0, -3, 1, 1, 1, 0, 1, 0);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadMatrix(ref lookat);
GL.Enable(EnableCap.Texture2D);
GL.BindTexture(TextureTarget.Texture2D, id); //id is the id from your code
 
GL.Begin(PrimitiveType.Quads);
GL.TexCoord2(0,0); GL.Vertex3(0, 0, 0);
GL.TexCoord2(1,0); GL.Vertex3(1, 0, 0);
GL.TexCoord2(1,1); GL.Vertex3(1, 1, 0);
GL.TexCoord2(0,1); GL.Vertex3(0, 1, 0);
GL.End();

You'd find that the XNA's math library and OpenTK's are very similar, be it Matrix, Vector structs or straight out math functions.
Most OpenGL tutorials on the net would be in C++ but most functions are straightforward intellisense use in the Visual Studio ( glVertex3f becomes GL.Vertex3( etc)

Haighstrom's picture

This is a really helpful starting point, thank you for attempting to assist a beginner!

Now my bear (as it happened to be) is appearing upside down and at an angle. I'm assuming there's some geometry out of place here. I'm happy to look at this on my own and work it out, but I'll need to understand what each matrix/function does in order to do that. My aim is to wrap all this up in some nice classes and functions once it's working, so I can just call something like Draw(int Texture, int x, int y) once I know what's going on with the code.

Could you clarify each of the lines? One thing I'm struggling with is how I can get from what I believe is a camera and a square with a texture on it, to working back to screen coordinates. But I guess once I understand what each matrix does I can just dig up my old geometry textbooks...

GL.Viewport - assume this is just the area of the screen that is being drawn to

OpenTK.Matrix4 perspective = OpenTK.Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspect_ratio, 1, 64) - this looks to be setting a matrix representing the camera's position and angle? Why do we only define an angle in the y-axis?

GL.MatrixMode(MatrixMode.Projection) - from the documentation, it looks like you can set which Matrix is currently being edited. This one looks to be the camera.

GL.LoadMatrix(ref perspective); - presume this is setting the "Projection" matrix to the one above. Not sure why it takes it by reference?

GL.MatrixMode(MatrixMode.Modelview); - what's the modelview for?

GL.Enable(EnableCap.Texture2D); - what does this do?

GL.BindTexture(TextureTarget.Texture2D, Bear); - I guess this is preparing us for our Quad creation - sets the current Texture as Bear, of type Texture2D?

GL.Begin(PrimitiveType.Quads) - guess this is getting us read to draw a shape, which will be a quadrilateral surface

GL.TexCoord2(0,0); GL.Vertex3(0,0,0); Is this pairing up positions of the texture with positions of the quads?

GL.End() - finished defining our shape, draw it?

Getting closer...

Thanks
Haighstrom

winterhell's picture

perspective is the matrix that gets your projection. With perspective projection the objects in the distance appear smaller, the first parameter would be the field of view, or how wide angle lenses are we using. Last 2 parameters are the draw distances, between this and that depth.

GL.LoadMatrix(ref perspective); takes the matrix by reference since the function is setup that way. Matrix4 is a struct, thus if you pass it like a regular parameter it'd make a copy of itself and waste bandwidth, with ref its faster with the same result.

The Modelview matrix in OpenGL is both camera and object's matrices(position and orientation) multiplied together. In DirectX and XNA you pass them as separate matrices.

GL.Enable(EnableCap.Texture2D); tells OpenGL that you'd want to draw with textures for the time being(until you call GL.Disable(EnableCap.Texture2D); next monday).

You can google any of this and will get all the mountains of answers.
See also the pop-in description of the Matrix4.LookAt() parameters. Its used to create a camera matrix with a certain position looking at a certain direction.

Haighstrom's picture

OK. I have used some mathematics (i.e. applied trial and error), and got a better handle on this now.

I don't know if there's a convention on this, but I set my camera at (0,0,0), have it pointing in the +ve z-direction, and have placed my Quad on the z = 3 plane. It seems to be working.

So I've got three questions now.

Question 1 - My image is blurry, and I would prefer for it to have a pixelated effect. How is this achieved?

In XNA, the equivalent is using SamplerState.PointWrap within the SpriteBatch.Begin function.

Question 2 - The image seems to be wrapping around to the top. The original has brown only at the bottom, but I have a fuzzy brown line at the top of it in-game. Is this related to question 1?

Question 3 - In order to get to screen co-ordinates from a camera, I have to do a bunch of calculations involving trigonometry. The actual width and height of the image on the screen (from the current setup I have) is the apparent size of an square of width 2, which is 3 distance away, with the camera screenshot 2 distance away, with a field of view of 90 degrees. That's quite awkward to compute, and will give me some horrible decimal values for the widths and heights. Has someone worked out a good field-of-vision/camera distance combination which yields nicer numbers? I'd be worried about rounding/floating point problems if, for example, I wanted my 20x20 pixel image to appear as 20x20 pixels on the screen.

winterhell's picture

For pixelated textures

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);

To have texture wrapping you use

 
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

If you dont want the texture to repeat itself, TextureWrapMode.Clamp or something.

If you want to make a 2D game,

 
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(0, Width, Height, 0, 0, 1);
            Matrix4 modelview = Matrix4.LookAt(0, 0, 0, 0, 0, 1, 0, 1, 0);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);

Depending on if the camera is facing down the Z+ or Z- axis and how you want to render, you might want to switch the places of left/right or top/bottom arguments in GL.Ortho.
Ortographic projection keep the object size regardless of the distance to the camera, like an architectural blue print. So you dont have to worry about the Z parameter, as long its between the near and far clip planes on the projection matrix ( in this case between 0 and 1).
GL.Ortho itself makes an Ortographics projection and loads it. You can do it manually with Matrix4.CreateOrthographic

With this setup of projection and modelview matrices try to render using pixels for units in GL.Vertex()

You dont have to worry about floating point errors, with the Single precision ones used everywhere, the integers between -8 and +8 million are represented exactly.

Haighstrom's picture

Brilliant, thank you so much.

Had to make it GL.Ortho(0, -Width, Height, 0, 0, 1) to get it working by screen co-ordinates, but everything working as originally intended now!

Will spend a few days playing around making functions and classes to tidy everything up, then no doubt I'll be back with more questions.

Thanks again,
Haighstrom