Rotation w/ Rectangular Pixels (2D & VAO)

Hi everyone,

I’ve been trying to learn some OpenGL this past couple of weeks. It’s for my own interest, and just a 2D program I would like to create. I should also add that my maths is not great.

I want to display lots of images on the screen at once, each being moved and rotated, and all in 2D. Each image is just two triangles, with the image texture. All pretty basic stuff (or should be!).

After much Googling over the past couple of nights, I’ve worked out how to convert from the OpenGL co-ordinates to screen co-ordinates. However when I do so, and then rotate an image, squares become rectangles as the degree of rotation moves closer to 180. I’ve determined that it’s because of rectangular pixels, but I just can’t work out what I need to do to adjust for the pixel size/shape. I apologise because I know that co-ordinate conversion must be asked so often, but I’ve had so much trouble finding a solution in part because so many answers are non-VAO code.

The code below is my test code, and all it does is display a blue square and rotates it on the Z-axis. (I realise that GL_MODELVIEW_MATRIX and GL_PROJECTION_MATRIX are deprecated and haven’t yet looked into what they should be replaced with, but if it’s a quick answer then it would also be appreciated.)

Oh, and this is using MinGW on a Windows 7 system.

Thank you.

Tim


#include <cstdio>
#include <GL/glew.h>
#include <GL/glfw.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


#define WINDOW_WIDTH 720.0f
#define WINDOW_HEIGHT 450.0f

#define POSITIONS_LOC 0


GLuint ProgramId( void )
{
    const GLuint r_program_id = glCreateProgram();

    const char * vertex_shader_src = "\
        uniform mat4 model_matrix;\
        in vec3 position;\
        void main()\
        {\
            gl_Position = model_matrix * vec4( position, 1.0 );\
        }";
    const GLuint vertex_shader_id = glCreateShader( GL_VERTEX_SHADER );
    glShaderSource( vertex_shader_id, 1, &vertex_shader_src , NULL );
    glCompileShader( vertex_shader_id );
    glAttachShader( r_program_id, vertex_shader_id );

    const char * fragment_shader_src = "\
        out vec4 colour;\
        void main()\
        {\
            colour = vec4( 0, 0, 1, 1 );\
        }";
    const GLuint fragment_shader_id = glCreateShader( GL_FRAGMENT_SHADER );
    glShaderSource( fragment_shader_id, 1, &fragment_shader_src , NULL );
    glCompileShader( fragment_shader_id );
    glAttachShader( r_program_id, fragment_shader_id );

    glLinkProgram( r_program_id );
    glDeleteShader( vertex_shader_id );
    glDeleteShader( fragment_shader_id );

    return r_program_id;
}


void ScreenToOglPositions( const unsigned short i_num_positions, GLdouble * const io_positions )
{
    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];

    glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
    glGetDoublev( GL_PROJECTION_MATRIX, projection );
    glGetIntegerv( GL_VIEWPORT, viewport );

    for ( unsigned short i = 0; i < i_num_positions; i += 3 )
    {
        gluUnProject( io_positions[i], io_positions[i+1], io_positions[i+2],
            modelview, projection, viewport,
            &io_positions[i], &io_positions[i+1], &io_positions[i+2] );
    }
}


GLuint VaoId( const GLdouble x, const GLdouble y, const GLdouble w, const GLdouble h )
{
    GLdouble positions[] =
    {
        ( x - w/2 ), ( y - h/2 ), 0,
        ( x + w/2 ), ( y - h/2 ), 0,
        ( x - w/2 ), ( y + h/2 ), 0,
        ( x - w/2 ), ( y + h/2 ), 0,
        ( x + w/2 ), ( y - h/2 ), 0,
        ( x + w/2 ), ( y + h/2 ), 0
    };

    GLuint vao_id;
    GLuint positions_buffer_id;

    ScreenToOglPositions( 18, positions );
    printf( "height = %f, width = %f
", ( positions[7] * 2 ), ( positions[3] * 2 )); // height = 0.444444, width = 0.277778

    glGenVertexArrays( 1, &vao_id );
    glBindVertexArray( vao_id );

    glGenBuffers( 1, &positions_buffer_id );
    glBindBuffer( GL_ARRAY_BUFFER, positions_buffer_id );
    glBufferData( GL_ARRAY_BUFFER, sizeof ( positions ), positions, GL_STATIC_DRAW );
    glVertexAttribPointer( POSITIONS_LOC, 3, GL_DOUBLE, GL_FALSE, 0, 0 );
    glEnableVertexAttribArray( POSITIONS_LOC );

    glBindBuffer( GL_ARRAY_BUFFER, 0 );
    glBindVertexArray( 0 );

    return vao_id;
}


int main( const int argc, const char * const argv[] )
{
    glfwInit();
    glfwOpenWindow( WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0, 0, 0, 32, 0, GLFW_WINDOW );
    glfwSwapInterval( 1 );
    glewInit();

    const GLuint program_id = ProgramId();
    const GLuint vao_id = VaoId(( WINDOW_WIDTH / 2 ), ( WINDOW_HEIGHT /2 ), 100, 100 );
    GLfloat rotation = 0.0f;

    glUseProgram( program_id );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );

    do
    {
        rotation = fmod(( rotation + 1 ), 360 );
        glm::mat4 model_matrix = glm::mat4( 1.0f );
        model_matrix = glm::translate( model_matrix, glm::vec3( 0, 0, 0 ));
        model_matrix = glm::rotate( model_matrix, 0.0f, glm::vec3( 1, 0, 0 ));
        model_matrix = glm::rotate( model_matrix, 0.0f, glm::vec3( 0, 1, 0 ));
        model_matrix = glm::rotate( model_matrix, rotation, glm::vec3( 0, 0, 1 ));
        model_matrix = glm::scale( model_matrix, glm::vec3( 1.0f ));
        glUniformMatrix4fv( glGetUniformLocation( program_id, "model_matrix" ), 1, GL_FALSE, glm::value_ptr( model_matrix ));

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        glBindVertexArray( vao_id );
        glDrawArrays( GL_TRIANGLES, 0, 6 );
        glBindVertexArray( 0 );
        glfwSwapBuffers();
    } while (( glfwGetKey( GLFW_KEY_ESC ) == GLFW_RELEASE ) && glfwGetWindowParam( GLFW_OPENED ));

    return 0;
}

After working at this some more tonight, I realise that of course I should be making the co-ordinates conversion in the shader after the transformation, and not before buffering the co-ordinates. So I believe gluUnProject is for code which does not use VAOs/VBOs, and so my ScreenToOglPositions function should not be used and instead I should be passing matrices to the shader using glm. I used to pass in two other matrices and have added them again (alongside the model matrix), but they don’t convert the screen co-ordinates to OpenGL co-ordinates and I wonder: is there another matrix I need to pass in to make the conversion?

Matrices passed to vertex shader, in addition to model matrix already in code above:

glUniformMatrix4fv( glGetUniformLocation( program_id, "view_matrix" ), 1, GL_FALSE,
    glm::value_ptr( glm::lookAt( glm::vec3( 0, 0, 3 ), glm::vec3( 0, 0, -1 ), glm::vec3( 0, 1, 0 ))));
glUniformMatrix4fv( glGetUniformLocation( program_id, "projection_matrix" ), 1, GL_FALSE,
    glm::value_ptr( glm::perspective( 45.0f, WINDOW_ASPECT_RATIO, 0.1f, 15.0f )));

Shader:

gl_Position = projection_matrix * view_matrix * model_matrix * vec4( position, 1.0 );

(I should really just multiply the projection/view/model matrices before passing them to the vector.)

Tim

(I don’t know if posts should be edited when replying to one’s own posts, or a new post added. And I couldn’t see any rules regarding this. So if this should have been an edit: sorry!)

I’ve realised where I went wrong. The first thing was of course, what I referred to last night just before going to bed. I was using a now deprecated non-VAO method in a VAO context. So this morning I took a look at the documentation for gluUnProject, and noticed it had the computation detailed there. So I’ve taken that and used it to make the calculation in the vertex shader, using an inverse matrix calculated using glm and passed in as a uniform value.

That was all pretty easy (assuming I got it right).

I’ve spent the remainder of the day trying to work out why rotation causes so much flicker, and translation causes the vertices to disappear. And it took me hours to realise my second mistake, which was that I don’t want to rotate or translate any vertices which I display at a fixed point on the screen. So … that was a big chunk of time wasted!

However, if there is a way to rotate (without flicker) and translate these elements around the screen, then it would be great if someone could describe how. It could be useful for animated interface controls.

In case there is any other beginner looking how to position something in screen-coordinates, I’ve attached the code below. It doesn’t really change much from the code in my first post, but rather does it correctly (I think).

Tim


#include <cstdio>
#include <vector>
#include <GL/glew.h>
#include <GL/glfw.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


#define WINDOW_WIDTH 720.0f
#define WINDOW_HEIGHT 450.0f
#define WINDOW_ASPECT_RATIO ( float )(( float )WINDOW_WIDTH / ( float )WINDOW_HEIGHT )

#define POSITIONS_LOC 0


GLuint ProgramId( void )
{
    const GLuint r_program_id = glCreateProgram();

    const char * vertex_shader_src = "\
        uniform int viewport[4];\
        uniform mat4 ivp;\
        uniform mat4 vp;\
        in vec3 position;\
        void main()\
        {\
            vec4 unprojected_position = ivp * vec4(\
                (( 2.0 * ( position.x - viewport[0] )) / viewport[2] ) - 1.0,\
                (( 2.0 * ( position.y - viewport[1] )) / viewport[3] ) - 1.0,\
                ( 2.0 * position.z ) - 1.0,\
                1.0 );\
            gl_Position = vp * unprojected_position;\
        }";
    const GLuint vertex_shader_id = glCreateShader( GL_VERTEX_SHADER );
    glShaderSource( vertex_shader_id, 1, &vertex_shader_src , NULL );
    glCompileShader( vertex_shader_id );
    glAttachShader( r_program_id, vertex_shader_id );

    const char * fragment_shader_src = "\
        out vec4 colour;\
        void main()\
        {\
            colour = vec4( 0, 0, 1, 1 );\
        }";
    const GLuint fragment_shader_id = glCreateShader( GL_FRAGMENT_SHADER );
    glShaderSource( fragment_shader_id, 1, &fragment_shader_src , NULL );
    glCompileShader( fragment_shader_id );
    glAttachShader( r_program_id, fragment_shader_id );

    glLinkProgram( r_program_id );
    glDeleteShader( vertex_shader_id );
    glDeleteShader( fragment_shader_id );

    return r_program_id;
}


GLuint VaoId( const GLdouble x, const GLdouble iy, const GLdouble w, const GLdouble h )
{
    const GLdouble y = ( WINDOW_HEIGHT - iy );

    const GLdouble positions[] =
    {
        ( x + 0 ), ( y - 0 ), 0,
        ( x + w ), ( y - 0 ), 0,
        ( x + 0 ), ( y - h ), 0,
        ( x + w ), ( y - 0 ), 0,
        ( x + 0 ), ( y - h ), 0,
        ( x + w ), ( y - h ), 0
    };
    
    GLuint r_vao_id;
    GLuint positions_buffer_id;

    glGenVertexArrays( 1, &r_vao_id );
    glBindVertexArray( r_vao_id );

    glGenBuffers( 1, &positions_buffer_id );
    glBindBuffer( GL_ARRAY_BUFFER, positions_buffer_id );
    glBufferData( GL_ARRAY_BUFFER, sizeof ( positions ), positions, GL_STATIC_DRAW );
    glVertexAttribPointer( POSITIONS_LOC, 3, GL_DOUBLE, GL_FALSE, 0, 0 );
    glEnableVertexAttribArray( POSITIONS_LOC );

    glBindBuffer( GL_ARRAY_BUFFER, 0 );
    glBindVertexArray( 0 );

    return r_vao_id;
}


int main( const int argc, const char * const argv[] )
{
    glfwInit();
    glfwOpenWindow( WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0, 0, 0, 32, 0, GLFW_WINDOW );
    glfwSwapInterval( 1 );
    glewInit();

    const GLuint program_id = ProgramId();
    const GLint viewport_loc = glGetUniformLocation( program_id, "viewport" );
    const GLint ivp_loc = glGetUniformLocation( program_id, "ivp" );
    const GLint vp_loc = glGetUniformLocation( program_id, "vp" );
    GLint viewport[4];
    std::vector<GLuint> vaos;

    glUseProgram( program_id );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );

    glGetIntegerv( GL_VIEWPORT, viewport );
    glUniform1iv( viewport_loc, 4, viewport );
    glm::mat4 projection = glm::perspective( 45.0f, WINDOW_ASPECT_RATIO, 0.1f, 15.0f );
    glm::mat4 view = glm::lookAt( glm::vec3( 0, 0, 3 ), glm::vec3( 0, 0, -1 ), glm::vec3( 0, 1, 0 ));

    vaos.push_back( VaoId( 5, 20, WINDOW_WIDTH - 10, 2 ));
    vaos.push_back( VaoId( WINDOW_WIDTH - 130, 30, 100, 100 ));
    vaos.push_back( VaoId( 10, WINDOW_HEIGHT - 40, WINDOW_WIDTH - 20, 20 ));

    do
    {
        glUniformMatrix4fv( ivp_loc, 1, GL_FALSE, glm::value_ptr( glm::inverse( projection * view )));
        glUniformMatrix4fv( vp_loc, 1, GL_FALSE, glm::value_ptr( projection * view ));

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        for ( std::vector<GLuint>::iterator itr = vaos.begin(); itr != vaos.end(); itr++ )
        {
            glBindVertexArray( *itr );
            glDrawArrays( GL_TRIANGLES, 0, 6 );
        }

        glBindVertexArray( 0 );
        glfwSwapBuffers();
    } while (( glfwGetKey( GLFW_KEY_ESC ) == GLFW_RELEASE ) && glfwGetWindowParam( GLFW_OPENED ));

    return 0;
}