Search code examples
c++openglglslshadertextures

How to properly upscale a texture in opengl?


Let's say, for the simplicity of this question, I want to create a texture containing a color gradient from black to red. I know there are much easier ways to achieve this but it is a good example for my problem.

I figured, i could simply create a texture from an float array like so:

float values[4] {
    0.f, 1.f,
    0.f, 1.f
}

// gen texture
unsigned int texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);

// set filter and wrap
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

// copy values
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, 2, 2, 0, GL_RED, GL_FLOAT, values);

And draw it:

float vertices[8] {
    -1.f, -1.f, 0.f, 0.f,
     1.f, -1.f, 1.f, 0.f,
     1.f,  1.f, 1.f, 1.f,
    -1.f,  1.f, 0.f, 1.f
}

// generate vertex buffer, vertex buffer layout and vertex array

unsigned int[6] indices {
    0, 1, 2,
    2, 3, 1
}

// generate index buffer

// bind everything

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

Where the bound vertex shader looks like this:

#version 330 core

layout(location=0) in vec2 in_position;
layout(location=1) in vec2 in_texCoords;

out vec2 texCoords;

void main() {
    gl_Position = vec4(in_position, 0.f, 1.f);
    texCoords = in_texCoords;
}

And the fragment shader like this:

#version 330 core

layout(location = 0) out vec4 out_color;

in vec2 texCoords;

uniform sampler2D u_gradient;

void main() {
    float red = texture(u_gradient, texCoords).r;
    out_color = vec4(red, 0.f, 0.f, 1.f);
}

Following this I'd expect to get an color gradient from left to right (black to red) across my entire window. However what I get is a gradient from about 1/4 to 3/4 of the window in x and y direction (See image below). I've also noted that the wrap of repeat does not seem to apply here as what I get looks like mirror-repeat.

Result:

I've played around with the fragment shader using fixed values instead of texCoords and figured that a range from .25 to .75 on the x axis represents the entire gradient (.25 maps to 0 for red and .75 to 1).

Changing the amount of 'steps' passed as values to the texture (eg. a 4x4 array) did not change the results.

I've also tried using an image as texture (loaded with stb_image, resolution 1920x1080) which works perfectly fine and spreads across the entire screen.

For clarification: Why do the texCoords for the gradient range from .25 to .75 and not from 0 to 1 and how can I fix it (besides adjusting the texCoords itself)?


Solution

  • Lets assume you have a 2x2 texture

    2x2 Texture

    If this texture is wrapped on a grid of 6x6 fragments, the the center of a texel is on exactly on the texel in the middel of 3x3 tile of the 6x6 square:

    6x6 quad

    The color of the other fragments depends on the the texture parameters - see glTexParameter.

    Since the texture is magnified, the GL_TEXTURE_MAG_FILTER is significant.

    If it is GL_NEAREST, then the color of the fragments is that one of the closest texel, to the texture coordinates of the fragment:

    If it is GL_LINEAR, then the color is interpolated, by the weighted average of the 4 pixels which are closest to the texture coordinates.

    The interpolation at the borders of the 6x6 quad depends on the wrap parameters GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T.

    If the parameters are GL_REPEAT (which is default), the the texture is treated as an endless texture and the interpolation of the interpolation of the color at the borders takes into account the texels on the opposite side of the texture. This is used for seamless textures and tiling:

    If it is GL_CLAMP_TO_EDGE, then the interpolated color at the borders is clamped to the color of the texels at the border of the texture:


    Apply the texture wrap parameter GL_CLAMP_TO_EDGE to the texture object,

    float values[4] { 0.f, 1.f, 0.f, 1.f };
    
    unsigned int texId;
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);
    
    // set filter and wrap
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    // copy values
    glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, 2, 2, 0, GL_RED, GL_FLOAT, values);
    

    to get the following gradient: