I'm implementing a small image viewer that is capable of rotating an image and zooming in/out. I'm doing that purposely by using OpenGL and GLM for self studies. Everything works out just fine so far, the only problem that occured is that the image hinches heavily when I rotate it. This occurs especially when the width and height of the image are strongly different. After a 90 degree rotation it seems that the texture is hinched until it fits, which leads to a distorted image. Am I understanding something completely wrong about how rotation is done here? As far as I understood it, it's just multiplication of the polygon coordinates with a rotation matrix. But then I dont understand why it would lead to this bulging of the texture.
I tried to play with the perspective and even went so far to set up a fictional camera using glm::lookAt()
, but no success so far.
Implementation of Vertex Shader:
const GLchar* vertexSource = R"glsl(
#version 150 core
in vec2 position;
in vec3 color;
in vec2 texcoord;
out vec3 Color;
out vec2 Texcoord;
uniform mat4 model;
void main()
{
Color = color;
Texcoord = texcoord;
gl_Position = model * vec4(position, 0.0, 1.0);
}
)glsl";
Implementation of rotation:
//...
model = glm::mat4(1.0f);
//Get the transformation into the shader
uniTrans = glGetUniformLocation(shaderProgram, "model");
//...
else if(windowEvent.key.code == sf::Keyboard::Left){
degree = 5;
model = glm::rotate(model, glm::radians(degree), glm::vec3(0.0f, 0.0f, 1.0f));
glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(model));
}
else if(windowEvent.key.code == sf::Keyboard::Right){
degree = -5;
model = glm::rotate(model, glm::radians(degree), glm::vec3(0.0f, 0.0f, 1.0f));
glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(model));
}
//...
Implementation of vertices array (the factors are there to make the texture not fill out the whole window. They are generated in a way that the border will always be 200 pixels):
//...
GLfloat vertices[] = {
//Position //Color //Texcoords
facX*(-1.0f), facY*( 1.0f), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, //top-left
facX*( 1.0f), facY*( 1.0f), 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, //top-right
facX*( 1.0f), facY*(-1.0f), 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, //bottom-right
facX*(-1.0f), facY*(-1.0f), 1.0f, 1.0f, 1.0f, 0.0f, 1.0f //bottom-left
};
//...
Setting up the textures:
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.getX(), image.getY(), 0, GL_BGRA, GL_UNSIGNED_BYTE, (const GLvoid*)data);
//Defining how to proceed if the texture is smaller than the space
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
//Specify Border Color
float color[] = {1.0f, 0.0f, 0.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
//Set up filter for up and downscaling of the texture (linear smooth, nearest would give pixelised result)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
To describe the occurring problem more visually, when I rotate an image with width != height for 90 degrees, the "new" height would be the old width. What is happening though, is that the height stays the same which of course then results in a bulged image. Does somebody have an idea about what I'm doing wrong?
This occurs especially when the width and height of the image are strongly different
You've to use an orthographic projection matrix, which takes into account the aspect ratio of the viewport.
The projection matrix transforms all vertex data from view coordinates to the clip coordinates. The clip coordinates are transformed to the normalized device coordinates (NDC) (Perspective divide).
The normalized device coordinates are in range (-1, -1, -1)
to (1, 1, 1)
and form a perfect cube volume.
With an orthographic projection, the eye space coordinates are linearly mapped to the NDC.
If the viewport is rectangular this has to be considered by mapping the coordinates.
Add a projection matrix to the vertex shader:
const GLchar* vertexSource = R"glsl(
#version 150 core
in vec2 position;
in vec3 color;
in vec2 texcoord;
out vec3 Color;
out vec2 Texcoord;
uniform mat4 projection;
uniform mat4 model;
void main()
{
Color = color;
Texcoord = texcoord;
gl_Position = projection * model * vec4(position, 0.0, 1.0);
}
Set an orthographic projection matrix by ortho()
, which "scales" the geometry according to the aspect ratio and set the matrix to the uniform:
uniProjection = glGetUniformLocation(shaderProgram, "projection");
int widht = ...; // width of the viewport (window)
int height = ...; // height of the viewport (window)
float aspect = (float)width / height;
glm::mat4 project = glm::ortho(-aspect, aspect, -1.0f, 1.0f, -1.0f, 1.0f);
glUniformMatrix4fv(uniProjection, 1, GL_FALSE, glm::value_ptr(project ));
Note, if you want to draw a rectangular shape, then you've to form this rectangle by the vertex coordinates.
The orthographic projection matrix is intended to ensure that a square is also square when it is projected on the viewport.