c++openglglslglm-mathopengl-4

OpenGL GLM rotate 2D shape around Z-axis


This is similar to this question, but I have a 2D triangle I'm trying to rotate solely about the z-axis, so I don't think I have to do any negative rotations. I have intentionally picked a triangle that is centered around the center of the screen (I calculated the centroid with this webpage). Essentially, I want it to look like the triangle is rotating clockwise or counter-clockwise about the center of the window, but by changing around the order I define my vertices, I've discovered that it seems to be rotating around the last vertex I define.

Here's my C++ code; I've removed comments for brevity. I don't have any input handling at the moment - I'm running in vscode so I've been terminating it through the IDE.

// Third-party library
#include <SDL2/SDL.h>
// Include GLAD
#include <glad/glad.h>
#include <glm/vec3.hpp>
#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/string_cast.hpp>

// C++ Standard Libraries
#include <iostream>
#include <fstream>
#include <vector>
#include <string>

// Screen dimensions
int gScreenHeight = 480;
int gScreenWidth  = 640;
SDL_Window* gGraphicsApplicationWindow = nullptr;
SDL_GLContext gOpenGLContext = nullptr;

// Main loop flag
bool gQuit = false;  // if true, we quit

GLuint gGraphicsPipelineShaderProgram = 0;

GLuint gVertexArrayObject = 0;
GLuint gVertexBufferObject  = 0;
GLuint gVertexBufferObject2 = 0;

glm::mat4 gVertexData(
    -0.4f, -0.4f,  0.0f, 0.0f,  // point 1 - bottom left
     0.4f, -0.4f,  0.0f, 0.0f,  // point 2 - bottom right
     0.0f,  0.8f,  0.0f, 0.0f,  // point 3 - top
     0.0f,  0.0f,  0.0f, 1.0f   // 4th dimension
);
const auto gVertexBytes = gVertexData.length() * gVertexData[0].length()
        * sizeof(gVertexData[0][0]);

std::string loadShaderAsString(const std::string& filename) {
    std::string result = "";
    std::string line = "";
    std::ifstream myFile(filename);
    if (myFile.is_open()) {
        while (std::getline(myFile, line)) {
            result += line + '\n';
        }
        myFile.close();
    }
    return result;
}

GLuint compileShader(GLuint type, const std::string& source) {
    GLuint shaderObject;
    if (type == GL_VERTEX_SHADER) {
        shaderObject = glCreateShader(GL_VERTEX_SHADER);
    } else if (type == GL_FRAGMENT_SHADER) {
        shaderObject = glCreateShader(GL_FRAGMENT_SHADER);
    }
    const char* src = source.c_str();
    glShaderSource(shaderObject, 1, &src, nullptr);
    glCompileShader(shaderObject);
    int result;
    glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(shaderObject, GL_INFO_LOG_LENGTH, &length);
        char* errorMessages = new char[length];  // Could also use alloca here.
        glGetShaderInfoLog(shaderObject, length, &length, errorMessages);
       if (type == GL_VERTEX_SHADER) {
            std::cout << "ERROR: GL_VERTEX_SHADER compilation failed!\n"
                << errorMessages << "\n";
        } else if (type == GL_FRAGMENT_SHADER) {
            std::cout << "ERROR: GL_FRAGMENT_SHADER compilation failed!\n"
                << errorMessages << "\n";
        }
        delete[] errorMessages;
        glDeleteShader(shaderObject);
        return 0;
    }
    return shaderObject;
}

GLuint createShaderProgram(const std::string& vertexShaderSource,
                           const std::string& fragmentShaderSource) {
    GLuint programObject = glCreateProgram();
    GLuint myVertexShader   = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    GLuint myFragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
    glAttachShader(programObject, myVertexShader);
    glAttachShader(programObject, myFragmentShader);
    glLinkProgram(programObject);
    glValidateProgram(programObject);
    glDetachShader(programObject, myVertexShader);
    glDetachShader(programObject, myFragmentShader);
    glDeleteShader(myVertexShader);
    glDeleteShader(myFragmentShader);
    return programObject;
}

void createGraphicsPipeline() {
    std::string vertexShaderSource   = loadShaderAsString("../shaders/vert.glsl");
    std::string fragmentShaderSource = loadShaderAsString("../shaders/frag.glsl");
    gGraphicsPipelineShaderProgram = createShaderProgram(vertexShaderSource,
                                                        fragmentShaderSource);
}

void getOpenGLVersionInfo() {
    std::cout << "Vendor: " << glGetString(GL_VENDOR) << std::endl;
    std::cout << "Renderer: " << glGetString(GL_RENDERER) << std::endl;
    std::cout << "Version: " << glGetString(GL_VERSION) << std::endl;
    std::cout << "Shading Language: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
}

void vertexSpecification() {
    const std::vector<GLfloat> vertexColors {
         1.0f,  0.0f,  0.0f,  // vertex 1 - Left
         0.0f,  1.0f,  0.0f,  // vertex 2 - Right
         0.0f,  0.0f,  1.0f   // vertex 3 - Top
    };
    glGenVertexArrays(1, &gVertexArrayObject);
    glBindVertexArray(gVertexArrayObject);
    glGenBuffers(1, &gVertexBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER,
                 gVertexBytes,
                 &gVertexData[0][0],
                 GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0,
                          gVertexData[0].length(),
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          nullptr);

    glGenBuffers(1, &gVertexBufferObject2);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject2);
    glBufferData(GL_ARRAY_BUFFER,
                vertexColors.size() * sizeof(GL_FLOAT),
                vertexColors.data(),
                GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT,
                          GL_FALSE, 0, nullptr);
    glBindVertexArray(0);
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
}

void initializeProgram() {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cout << "SDL could not be initialized: " <<
                  SDL_GetError();
        exit(1);
    }

    std::cout << "SDL video system is ready to go\n";

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    gGraphicsApplicationWindow = SDL_CreateWindow("C++ SDL2 Window",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            gScreenWidth,
            gScreenHeight,
            SDL_WINDOW_OPENGL);
    if (gGraphicsApplicationWindow == nullptr) {
        std::cout << "SDL WIndow was not able to be created" << std::endl;
        exit(1);
    }
    gOpenGLContext = SDL_GL_CreateContext(gGraphicsApplicationWindow);
    if (gOpenGLContext == nullptr) {
        std::cout << "OpenGL context not available" << std::endl;
        exit(1);
    }
    if (!gladLoadGLLoader(SDL_GL_GetProcAddress)) {
        std::cout << "Glad was not initialized" << std::endl;
        exit(1);
    }
    getOpenGLVersionInfo();
}

void predraw() {
    static glm::vec3 zNorm(0.0f, 0.0f, 1.0f);
    gVertexData = glm::rotate(gVertexData, glm::radians(0.10f), zNorm);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER,
                 gVertexBytes,
                 &gVertexData[0][0],
                 GL_DYNAMIC_DRAW);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glViewport(0, 0, gScreenWidth, gScreenHeight);
    glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glUseProgram(gGraphicsPipelineShaderProgram);
}

void draw() {
    glBindVertexArray(gVertexArrayObject);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glUseProgram(0);
}

void mainLoop() {
    while (!gQuit) {
        predraw();
        draw();
        SDL_GL_SwapWindow(gGraphicsApplicationWindow);
    }
}

void cleanup() {
    SDL_DestroyWindow(gGraphicsApplicationWindow);
    SDL_Quit();
}

int main(int argc, char* argv[]) {
    initializeProgram();
    vertexSpecification();
    createGraphicsPipeline();
    mainLoop();
    cleanup();
    return 0;
}

Here's the vertex shader code:

#version 410 core

layout(location=0) in vec4 position;
layout(location=1) in vec3 vertexColors;

out vec3 v_vertexColors;

void main()
{
   v_vertexColors = vertexColors;
   
   gl_Position = vec4(position.x, position.y, position.z, 1.0f);
}

And the fragment shader:

#version 410 core

in vec3 v_vertexColors;

out vec4 color;

void main()
{
   color = vec4(v_vertexColors.r, v_vertexColors.g, v_vertexColors.b, 1.0f);
}

I've gone through these matrices and rotations tutorials, but I haven't been able to figure out what I'm doing wrong.


Solution

  • You have to transform the vertex coordinates in the vertex shader. See LearnOpenGL - Transformations (bottom of the page)

    Use a uniform variable of type mat4 and transform the vertices in the vertex shader:

    #version 410 core
    
    layout(location=0) in vec4 position;
    layout(location=1) in vec3 vertexColors;
    
    out vec3 v_vertexColors;
    uniform mat4 model_matrix;
    
    void main()
    {
       v_vertexColors = vertexColors;
       
       gl_Position = model_matrix * vec4(position.xyz, 1.0f);
    }
    

    Set the matrix after installing the program:

    int model_loc = glGetUniformLocation(gGraphicsPipelineShaderProgram, "model_matrix")
    
    glm::mat4 rotation_matrix(1.0f);
    rotation_matrix = glm::rotate(rotation_matrix, glm::radians(angle), zNorm);
    
    glUseProgram(gGraphicsPipelineShaderProgram);
    glUniformMatrix4fv(model_loc, 1, false, glm::value_ptr(rotation_matrix));
    

    The variable angle needs to be increased in every frame. e.g.: angle = 0.1f;