Dr_Asik's picture

Issues converting OpenGL tutorials to OpenTK

I'm trying to convert these popular tutorials to OpenTK. The tutorial shows how to mix two textures and draw the result. For some reason when I do it in OpenTK, though, the texture seems translated slightly to the right and it repeats at the left. Like this:

As far as I can tell, I've translated the code in the tutorial quite literally.

Here's the fragment shader:

#version 330
 
uniform float fade_factor;
uniform sampler2D textures[2];
in vec2 texcoord;
out vec4 outputColor;
 
void main()
{
	outputColor = mix(
		texture2D(textures[0], texcoord),
		texture2D(textures[1], texcoord),
		fade_factor
	);
}

The vertex shader:

#version 330
 
in vec2 position;
out vec2 texcoord;
 
void main()
{	
	gl_Position = vec4(position, 0.0, 1.0);
	texcoord = position * vec2(0.5) + vec2(0.5);
}

And the C# code:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
 
using OpenTK;
using OpenTK.Graphics.OpenGL;
using GL = OpenTK.Graphics.OpenGL.GL;
 
namespace GLSL {
 
 
    class Game : GameWindow {
        struct Uniforms {
            public int[] TextureLocations;
            public int PositionLocation;
            public int FadeFactorLocation;
 
            public int[] Textures;
            public int Positions;
            public float FadeFactor;
        }
 
        float mFadeVariation = 0.01f;
        int mProgram;
        Uniforms mUniforms;
        int[] mVertexIndices = { 0, 1, 2, 3 };
        int mVertexIndicesBuffer;
 
        /// <summary>
        /// Sets the window's size.
        /// </summary>
        public Game() : base(500, 500) { }
 
        /// <summary>
        /// Is called once when the window is first loaded.
        /// Any initialization is performed here.
        /// </summary>
        protected override void OnLoad(EventArgs e) {
            mUniforms.FadeFactor = 0.5f;
            mUniforms.Textures = new int[]{ 
               CreateTexture("gl2-hello-1.png"), 
               CreateTexture("gl2-hello-2.png")
           };
            var vertexPositions = new float[] {
                // These are the vertex of the rectangle
                -1.0f, -1.0f,
                 1.0f, -1.0f, 
                -1.0f,  1.0f,
                 1.0f,  1.0f
            };
            mVertexIndicesBuffer = CreateBufferObject(mVertexIndices, BufferTarget.ElementArrayBuffer, BufferUsageHint.StreamDraw);
 
            mUniforms.Positions = CreateBufferObject(vertexPositions, BufferTarget.ArrayBuffer, BufferUsageHint.StreamDraw);
            mProgram = CreateProgram(new[] { 
                CreateShader(ShaderType.VertexShader, "hello-gl.v.glsl"), 
                CreateShader(ShaderType.FragmentShader, "hello-gl.f.glsl") 
            });
 
            mUniforms.TextureLocations = new int[] {
                GetUniformLocation("textures[0]"),
                GetUniformLocation("textures[1]")
            };
            mUniforms.PositionLocation = GetAttribLocation("position");
            mUniforms.FadeFactorLocation = GetUniformLocation("fade_factor");
        }
 
        /// <summary>
        /// Draws the image onscreen. Is called periodically by GameWindow.Run().
        /// </summary>
        protected override void OnRenderFrame(FrameEventArgs e) {
 
            mUniforms.FadeFactor += mFadeVariation;
            if (mUniforms.FadeFactor < 0.0f || mUniforms.FadeFactor > 1.0f) {
                mFadeVariation = -mFadeVariation;
                mUniforms.FadeFactor += 2 * mFadeVariation;
            }
 
            GL.ClearColor(Color.Black);
            GL.Clear(ClearBufferMask.ColorBufferBit);
 
            GL.UseProgram(mProgram);
            GL.Uniform1(mUniforms.FadeFactorLocation, mUniforms.FadeFactor);
 
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, mUniforms.Textures[0]);
            GL.Uniform1(mUniforms.TextureLocations[0], 0);
 
            GL.ActiveTexture(TextureUnit.Texture1);
            GL.BindTexture(TextureTarget.Texture2D, mUniforms.Textures[1]);
            GL.Uniform1(mUniforms.TextureLocations[1], 1);
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, mUniforms.Positions);
            GL.EnableVertexAttribArray(mUniforms.PositionLocation);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, mVertexIndicesBuffer);
            GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
 
            GL.DrawElements(BeginMode.TriangleStrip, mVertexIndices.Length, DrawElementsType.UnsignedInt, 0);
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
            GL.DisableVertexAttribArray(0);
            GL.UseProgram(0);
 
            SwapBuffers();
        }
 
        /// <summary>
        /// Is called when the window is resized.
        /// </summary>
        protected override void OnResize(EventArgs e) {
            // Easiest way to respect aspect ratio
            if (Width > Height) {
                GL.Viewport(0, 0, Height, Height);
            }
            else {
                GL.Viewport(0, 0, Width, Width);
            }
        }
 
        /// <summary>
        /// Compile a shader from source
        /// </summary>
        /// <returns>OpenGL handle to the compiled shader</returns>
        int CreateShader(ShaderType shaderType, string fileName) {
            int shader = GL.CreateShader(shaderType);
            var source = File.ReadAllText(fileName);
            GL.ShaderSource(shader, source);
            GL.CompileShader(shader);
 
            int status;
            GL.GetShader(shader, ShaderParameter.CompileStatus, out status);
            if (status == 0) {
                var errorLog = GL.GetShaderInfoLog(shader);
                throw new Exception(String.Format("Compile failure in {0}: {1}\n", fileName, errorLog));
            }
            return shader;
        }
 
        /// <summary>
        /// Links compiled shaders to create a program
        /// </summary>
        /// <param name="shaders">Compiled shaders</param>
        /// <returns>OpenGL handle to the program</returns>
        int CreateProgram(IEnumerable<int> shaders) {
            int program = GL.CreateProgram();
            foreach (var shader in shaders) {
                GL.AttachShader(program, shader);
            }
            GL.LinkProgram(program);
 
            int status;
            GL.GetProgram(program, ProgramParameter.LinkStatus, out status);
            if (status == 0) {
                throw new Exception(String.Format("Linker failure : {0}", GL.GetProgramInfoLog(program)));
            }
            return program;
        }
 
        /// <summary>
        /// Allocates and assigns data to a new VBO
        /// </summary>
        /// <typeparam name="T">Basic data type of the vertex data</typeparam>
        /// <param name="buffer">An array containing vertex data</param>
        /// <returns>OpenGL handle to the new VBO</returns>
        int CreateBufferObject<T>(T[] buffer, BufferTarget target, BufferUsageHint usageHint) where T : struct {
            int handle;
            GL.GenBuffers(1, out handle);
            GL.BindBuffer(target, handle);
            GL.BufferData(target, (IntPtr)(Marshal.SizeOf(typeof(T)) * buffer.Length), buffer, usageHint);
            GL.BindBuffer(target, 0);
            return handle;
        }
 
        int CreateTexture(string fileName) {
            int handle;
            GL.GenTextures(1, out handle);
            GL.BindTexture(TextureTarget.Texture2D, handle);
 
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
 
            var bmp = new Bitmap(fileName);
            var ms = new MemoryStream();
            bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            GL.TexImage2D(TextureTarget.Texture2D, 0,
                PixelInternalFormat.Rgb8,
                bmp.Width, bmp.Height, 0,
                PixelFormat.Rgb, PixelType.UnsignedByte,
                ms.ToArray());
 
            return handle;
        }
 
        int GetUniformLocation(string uniform) {
            return GetLocation(uniform, GL.GetUniformLocation);
        }
 
        int GetAttribLocation(string attrib) {
            return GetLocation(attrib, GL.GetAttribLocation);
        }
 
        int GetLocation(string name, Func<int, string, int> glFunc) {
            int rv = glFunc(mProgram, name);
            if (rv == -1) {
                throw new Exception("Could not find location of " + name);
            }
            return rv;
        }
    }
}

Any thoughts?


Comments

Comment viewing options

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

I've looked at TexLib.cs and that gave me some ideas. When I generate the texture, I currently use a memory stream as temporary buffer:

            var bmp = new Bitmap(fileName);
            var ms = new MemoryStream();
            bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            GL.TexImage2D(TextureTarget.Texture2D, 0,
                PixelInternalFormat.Rgb8,
                bmp.Width, bmp.Height, 0,
                PixelFormat.Rgb, PixelType.UnsignedByte,
                ms.ToArray());

I've tried simply locking the bits and passing that to GL.TexImage2D instead:

            var bmp = new Bitmap(fileName);
            var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.TexImage2D(TextureTarget.Texture2D, 0, 
                PixelInternalFormat.Rgba,
                bmp.Width, bmp.Height, 0,
                PixelFormat.Bgra, PixelType.UnsignedByte,
                bmpData.Scan0);

The result is that there is no more translation, the texture is properly aligned. However, it is upside-down. I have no idea why this method works and not the other, and I don't see why the image is upside down in this case and not the other. ?

flopoloco's picture

Good idea to port this example to OpenTK. :)
I was browsing some archives and found this, it's from the Bitiopia site of MiguelTK, check him out, he rocks!

// The Bitmap class loads images and orients them top-down.
// OpenGL expects bitmap images to be oriented bottom-up.
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

smiley80's picture

Your vertex shader flips the texcoords. E.g. (-1.0, -1.0) is the left-bottom vertex but it gets (0, 0) as the texcoord.
To get the correct result change the calculation to:
texcoord = vec2(position.x * 0.5 + 0.5, 1 - (position.y * 0.5 + 0.5));