Reputation: 398
I'm trying to get multiple textures to appear pixel-for-pixel in a GLFW window. How I'm doing it is I have a shared shader that takes a uniform matrix that scales and transforms the texture. However, it seems that my scaling isn't perfect, as the texture appears corrupted(?).
This is when the filters are set to GL_NEAREST
. When set to GL_LINEAR
, it comes out blurry, which I don't want either. This made me suspect it was improper scaling, adding on to the fact that glGetError()
returns GL_NO_ERROR
. That didn't make sense though, because I'm scaling it to be "pixel-perfect" with respect to the screen pixels and the bitmap pixels... (see below code)
Through extensive research and testing everything I could think of, I found out the root cause: when the width of the bitmap is uneven, the corruption occurs. When I add code to pad the bitmap when it's uneven...
The corruption fades and the texture renders perfectly! (Tested more than the above)
Why is this and how can I get around it?
Texture creation code (excerpted):
...
float vertices[] = {
// positions // texture coords
1.0, 1.0, 1.0f, 1.0f, // top right
1.0, -1.0, 1.0f, 0.0f, // bottom right
-1.0, -1.0, 0.0f, 0.0f, // bottom left
-1.0, 1.0, 0.0f, 1.0f // top left
};
unsigned int indices[] = {
0, 1, 3,
1, 2, 3
};
// `VA`, `VB`, `EB`, and `TX` are class variables
glGenVertexArrays(1, &VA);
glGenBuffers(1, &VB);
glGenBuffers(1, &EB);
glGenTextures(1, &TX);
glBindTexture(GL_TEXTURE_2D, TX);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
// Sharp, easier to tell when texture is rendering incorrectly
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Blurry
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindVertexArray(VA);
glBindBuffer(GL_ARRAY_BUFFER, VB);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EB);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
constexpr int stride = 4 * sizeof(float);
// position attribute
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*)0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
// `shader` is a shader program for the below shaders
glUseProgram(shader);
glUniform1i(glGetUniformLocation(shader, "Tex"), 0);
...
Texture loading code, called once per texture (excerpted):
...
unsigned char *bitmap = loadImage(&width, &height,...);
glBindTexture(GL_TEXTURE_2D, TX);
// Before you ask: yes, the image is in RBGA
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
...
Texture rendering code, called every frame (again, excerpted):
...
glm::mat4 model(1.0f);
// Probally not relevent
const float xPos = (x + width / 2) / windowWidth * 2 - 1;
const float yPos = ((y + height / 2) / windowHeight * 2 - 1) * -1;
model = glm::translate(model, glm::vec3(xPos, yPos, 0.0f));
model = glm::scale(model, glm::vec3((float)width / windowWidth, (float)height / windowHeight, 0.0f));
// `modelLocation` was previously set to glGetUniformLocation(shader, "model")
glUniformMatrix4fv(modelLocation, 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(VA);
glBindBuffer(GL_ARRAY_BUFFER, VB);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EB);
glBindTexture(GL_TEXTURE_2D, TX);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
...
Vertex shader:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
void main() {
gl_Position = model * vec4(aPos, 0.0, 1.0);
TexCoord = aTexCoord;
}
Fragment shader:
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D Tex;
void main() {
FragColor = texture(Tex, TexCoord);
}
Upvotes: 1
Views: 251
Reputation: 45332
When you want pixel-exact texture mapping, it is not enough to scale your m
xn
texels big texture so that it will be mapped to an m
xn
pixel big rectangle on screen. You must also assure that the mapping will be correct at the sub-pixel level - i.e. that the texel centers are mapped to the corresponding pixel centers. In your case, you use the full [0,1]
range as tex coords (which is fine), so your geometry must be mapped so that the vertices end up on pixel corners (such that it maps the corresponding texel corners directly onto them 1:1).
You have not exactly specified which types width
and height
have in this example, but the construct (float)width / windowWidth
makes me assume that these are integral types. So let's do a simple example:
Let x=0
, width=3
and windowWidth=10
. What happens is:
xPos = (0.0f + 3 / 2) / 10 * 2 - 1 = 1.0f / 10 * 2 - 1 = - 0.8f
The scale factor for the x dimension will be 0.3
, and the first row of your model matrix will end up as 0.3 0 0 -0.24
. When multiplied with your left and right points, you'll get x_left = -0.54
and x_right = 0.06
. The pixel borders in NDC would be at {-1, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8, 10}
fo a 10 pixel wide viewport, so you are drawing not in a pixel-exact way.
Your translation simply doesn't mase sense for pixel-exact drawing, especially with the odd way of first translating and then scaling the resulting space.
Upvotes: 1