Peacewise's picture

Y-Texture Coordinate Flipped in OpenTK?

All my textures are vertically flipped (the textures look upside-down compared to what I expect).

When texture mapping in OpenGL, if you specify texture coordinate (0, 0) that is the lower-left of the texture. (1, 1) is the upper-right. Like this.
I've been setting my texture coordinates with this in mind. But this flips them from what I expect.

It appears to me that OpenTK uses a different texture coordinate system with (0, 0) in the upper-left, like this.

Here's the OpenTK sample code for texture mapping, this is texturing a quad. I've added comments:

            GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(-0.6f, -0.4f);   // Vertex is lower-left of the quad.
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(0.6f, -0.4f);     // Vertex is lower-right
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(0.6f, 0.4f);     // Vertex is upper-right
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(-0.6f, 0.4f);  // Vertex is upper-left

The lower-left vertex of the quad uses a texture coordinate of (0, 1) when I'd expect it to be (0, 0). All the Y-dimensions of the texture coordinates look wrong. Here's what I'd expect:

            GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(-0.6f, -0.4f);   // Vertex is lower-left of the quad.
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(0.6f, -0.4f);     // Vertex is lower-right
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(0.6f, 0.4f);     // Vertex is upper-right
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(-0.6f, 0.4f);  // Vertex is upper-left

... but that produces upside-down textures on quads for me.

Is that right? Does OpenTK use (0, 0) as the upper-left of a texture, and (1, 1) as the lower-right, instead of OpenGL's (0,0) as the lower-left and (1,1) as the upper-right?

Or am I missing something?

Thanks!


Comments

Comment viewing options

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

The y-axis is flipped when you use an identity matrix.
I.e. the top left corner is (-1, 1) and the bottom right is (1, -1).

So, GL.Vertex2(-0.6f, -0.4f) is bottom-left meaning GL.TexCoord2(0.0f, 1.0f) is correct.

the Fiddler's picture

The interpretation of texture coordinates depends on your texture matrix (OpenGL 1.x) or your vertex shaders (OpenGL 2.x+). As smiley80 said, texcoord (0,0) specifies the top-left part of the texture by default.

tksuoran's picture

Flipping textures can happen at least in 8(!) places in OpenGL:

1. Does your viewport flip the image? Where is 0,0 - bottom left?
2. Does your modelview transform flip vertex y coordinate?
3. Does your projection transform flip vertex y coordinate?
4. Does your texture transform flip v coordinate?
5. Are your untransformed vertex y specified the right way?
6. Are your untransformed vertex texture v specified the right way?
7. Did your texture loading code get texture the way it was written/specified? Most picture formats have top left first.
8. Did your TexImage2D upload texture the way OpenGL expects it? First comes bottom left.

I would pay extra attention to the last two points, 7 and 8.

Edit: Notice that you can get the "right" result by having an even number of things "wrong" in the above list..

elaverick's picture

Quick question... are you using the DDS loader for compressed textures? If so then you'll find that OpenGL and DirectX treat their texture co-ordinates differently. There is an option you can use (TextureLoaderParameters.FlipImages = false;) to prevent this.

Peacewise's picture

Thank you all for such detailed explanations! I was not aware that so many things could cause this to happen. I have to admit, I'm still stumped as to why it's happening because I don't know enough about each possibility to be able to look at it and say, "yes, that's what I'm doing wrong." I'm going to learn more about these things, but for now I can only weed out some of the possibilities, not all of them.

Also, I'm not using a DDS loader, and I'm not trying to do anything fancy. All I really did (for the sake of this issue) is use the texture mapping example that OpenTK provides.

Since the OpenTK example gives me the same confusion, I'll just paste the whole example here. Can someone tell me what causes the (0,0) of the texture to be the upper-left instead of the lower-left in this example? That would answer my question and set me straight.

(Note that the "logo.jpg" file this code loads is a standard .jpg image that appears right-side up both when viewing the image file and when running the code and seeing it mapped onto a quad.)

To run the example, this is in \OpenTK\OpenTK1.0\Binaries\OpenTK\Release\Examples.exe. It's the GL --> Texture Mapping example.

#region --- License ---
/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos
 * See license.txt for license info
 */
#endregion
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
 
using System.Drawing;
using System.Drawing.Imaging;
 
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
 
namespace Examples.Tutorial
{
    /// <summary>
    /// Demonstrates simple OpenGL Texturing.
    /// </summary>
    [Example("Texture mapping", ExampleCategory.OpenGL, "1.x", 5, Documentation="Textures")]
    public class Textures : GameWindow
    {
        Bitmap bitmap = new Bitmap("Data/Textures/logo.jpg");
        int texture;
 
        public Textures() : base(800, 600) { }
 
        #region OnLoad
 
        /// <summary>
        /// Setup OpenGL and load resources here.
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            GL.ClearColor(Color.MidnightBlue);
            GL.Enable(EnableCap.Texture2D);
 
            GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
 
            GL.GenTextures(1, out texture);
            GL.BindTexture(TextureTarget.Texture2D, texture);
 
            BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
 
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
                OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
 
            bitmap.UnlockBits(data);
 
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
        }
 
        #endregion
 
        #region OnUnload
 
        protected override void OnUnload(EventArgs e)
        {
            GL.DeleteTextures(1, ref texture);
        }
 
        #endregion
 
        #region OnResize
 
        /// <summary>
        /// Respond to resize events here.
        /// </summary>
        /// <param name="e">Contains information on the new GameWindow size.</param>
        /// <remarks>There is no need to call the base implementation.</remarks>
        protected override void OnResize(EventArgs e)
        {
            GL.Viewport(0, 0, Width, Height);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 4.0);
        }
 
        #endregion
 
        #region OnUpdateFrame
 
        /// <summary>
        /// Add your game logic here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        /// <remarks>There is no need to call the base implementation.</remarks>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            if (Keyboard[OpenTK.Input.Key.Escape])
                this.Exit();
        }
 
        #endregion
 
        #region OnRenderFrame
 
        /// <summary>
        /// Add your game rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        /// <remarks>There is no need to call the base implementation.</remarks>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            GL.Clear(ClearBufferMask.ColorBufferBit);
 
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            GL.BindTexture(TextureTarget.Texture2D, texture);
 
            GL.Begin(BeginMode.Quads);
 
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(-0.6f, -0.4f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(0.6f, -0.4f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(0.6f, 0.4f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(-0.6f, 0.4f);
 
            GL.End();
 
            SwapBuffers();
        }
 
        #endregion
 
        #region public static void Main()
 
        /// <summary>
        /// Entry point of this example.
        /// </summary>
        [STAThread]
        public static void Main()
        {
            using (Textures example = new Textures())
            {
                // Get the title and category  of this example using reflection.
                ExampleAttribute info = ((ExampleAttribute)typeof(Textures).GetCustomAttributes(false)[0]);
                example.Title = String.Format("OpenTK | {0} {1}: {2}", info.Category, info.Difficulty, info.Title);
                example.Run(30.0, 0.0);
            }
        }
 
        #endregion
    }
}

Thank you again for all your help everyone!

tksuoran's picture

I think BitmapData.scan0 is the first scanline, in top down order, while GL.TexImage2D() is expecting the last scanline, bottom up order.

smiley80's picture

Since you use identity matrices for modelview and projection, the y-axis will point upwards.
So if you increase a y-value the point will move towards the top of the screen, which means for the example:

GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(-0.6f, -0.4f); // left bottom
GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(0.6f, -0.4f); // right bottom
GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(0.6f, 0.4f); // right top
GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(-0.6f, 0.4f); // left top