Search code examples
c#shadertexturesopentk

How to fit an image (texture) inside a quad in OpenTK?


I've been trying to look into different examples for texturing in OpenTK, however, little to no code examples use the same approach as I desire or a lot of pointless workarounds are required that do not fit my needs. I am simply trying to draw images in OpenTK without their UVs being distorted or malformed. Or rather, how do I malform them to fit the primitive (in this case quad/square) wherever its positioned at in the 2D world?

Consider this image (It's my texture I'm trying to fit inside a quad primitive): enter image description here

This is the unwanted result. As you can see, it is cropped. I don't care about the wrapping because I plan on fitting the whole image inside the square (No aspect ratio needed). Different wrapping settings did nothing. The image's center is still outside the square. enter image description here

The transparency and palette is my thing to worry about, I only need help fitting the whole image inside the square!

This is my code for loading textures:

    public Texture(Bitmap image)
    {
        ID = GL.GenTexture();

        GL.ActiveTexture(TextureUnit.Texture0);
        GL.BindTexture(TextureTarget.Texture2D, ID);

        BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);

        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.Repeat);
        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

        GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
    }

Then here's my code for loading the vertex data into the shaders and drawing the primitive:

        List<float> vertex_data = { 

            -.25f, -.25f,
            -.25f, .25f, 
            .25f, .25f,
            .25f, -.25f
        };

        // Load the 2D object
        GL.UseProgram(program);

        GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
        GL.BufferData(BufferTarget.ArrayBuffer, vertex_data.Count * sizeof(float), vertex_data.ToArray(), BufferUsageHint.DynamicDraw);
        
        GL.EnableVertexAttribArray(attr_pos);
        GL.VertexAttribPointer(attr_pos, 2, VertexAttribPointerType.Float, false, 2 * sizeof(float), 0);
        GL.EnableVertexAttribArray(attr_uv);
        GL.VertexAttribPointer(attr_uv, 2, VertexAttribPointerType.Float, false, 2 * sizeof(float), 0);
        
        // ^^ Using the same position data for the UV as well.
        
        // ...
        
        // Drawing the 2D object
        GL.UseProgram(program);

        GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);

        GL.ActiveTexture(TextureUnit.Texture0);
        GL.BindTexture(TextureTarget.Texture2D, ID);

        GL.DrawArrays(PrimitiveType.Quads, 0, 4);

And the vertex and fragment shaders:

vert:

#version 330 core

layout(location = 1) in vec3 vertex_pos;
layout(location = 2) in vec2 vertex_uv;

out vec2 uv;

uniform mat4 mvp;

void main() {

    gl_Position = mvp * vec4(vertex_pos, 1);
    uv = vertex_uv;
}

frag:

#version 330 core

in vec2 uv;

out vec4 color;

uniform sampler2D texture0;

void main() {
    
    color = texture(texture0, uv);
}

Solution

  • All you need to do is to specifytexture coordinates in range [0.0, 1.0]:

    List<float> vertex_data = { 
    
    //   x      y     u     v 
        -.25f, -.25f, 0.0f, 0.0f,
        -.25f,  .25f, 0.0f, 1.0f,
         .25f,  .25f, 1.0f, 1.0f,
         .25f, -.25f, 1.0f, 0.0f,
    };
    
    // Load the 2D object
    GL.UseProgram(program);
    
    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
    GL.BufferData(BufferTarget.ArrayBuffer, vertex_data.Count * sizeof(float),
        vertex_data.ToArray(), BufferUsageHint.DynamicDraw);
    
    GL.EnableVertexAttribArray(attr_pos);
    GL.VertexAttribPointer(attr_pos, 2, VertexAttribPointerType.Float, false,
        4 * sizeof(float), 0);
    GL.EnableVertexAttribArray(attr_uv);
    GL.VertexAttribPointer(attr_uv, 2, VertexAttribPointerType.Float, false,
        4 * sizeof(float), 2 * sizeof(float));
    

    The texture coordinate (0, 0) address the bottom left edge of the texture and the texture coordinate (1, 1) address the top right edge of the texture.
    You must associate the texture coordinate (0, 0) to the bottom left of the quad and the texture coordinate (1, 1) the top right of the quad.

    The 4th argument of GL.VertexAttribPointe (strid) specifies the byte offset between consecutive generic vertex attributes. Since each attribute consists of 4 elements of type flaot (x, y, u, v), this is 4*sizeof(float). The last argument is the byte offset of the attribute. The offset of the vertex coordinates is 0 and and the offset of the texture coordinates is 2*sizeof(float)


    Of course you can use 2 separate the attribute arrays. In this case, stride is 2*sizeof(float) for the vertex coordinates and texture coordinates. The offset of the vertex coordinates is 0 and and the offset of the texture coordinates is the size of all the vertex coordinates (8*sizeof(float)):

    Use GL.BufferSubData to initialize buffer data:

    List<float> vertex_data = { 
        
    //   x      y  
        -.25f, -.25f, 
        -.25f,  .25f,
         .25f,  .25f,
         .25f, -.25f,
    }
    
    List<float> texture_data = { 
    //   u      v  
         0.0f, 0.0f,
         0.0f, 1.0f,
         1.0f, 1.0f,
         1.0f, 0.0f,
    };
    
    // Load the 2D object
    GL.UseProgram(program);
    
    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
    GL.BufferData(BufferTarget.ArrayBuffer, 
        vertex_data.Count * sizeof(float) + texture_data.Count * sizeof(float), 
        IntPtr.Zero, BufferUsageHint.DynamicDraw);
    GL.BufferSubData(BufferTarget.ArrayBuffer, 0, 
        vertex_data.Count * sizeof(float), vertex_data.ToArray())
    GL.BufferSubData(BufferTarget.ArrayBuffer, vertex_data.Count * sizeof(float), 
        texture_data.Count * sizeof(float), texture_data.ToArray())
    
    GL.EnableVertexAttribArray(attr_pos);
    GL.VertexAttribPointer(attr_pos, 2, VertexAttribPointerType.Float, false, 
        2 * sizeof(float), 0);
    GL.EnableVertexAttribArray(attr_uv);
    GL.VertexAttribPointer(attr_uv, 2, VertexAttribPointerType.Float, false, 
        2 * sizeof(float), vertex_data.Count * sizeof(float));