How to Rotate Models by using OpenGL Maths (GLM)

What is it about the dreaded rotation matrix that makes it so daunting to try and understand? For me it was not having a clue about what the sine and cosine terms inside the model matrix represented.

It turns out that those horrible collection of sine and cosine terms which are multiplied together, are nothing more than high school trigonometry. Understandably, people are going to be mystified if they're not shown what the internal workings (i.e. relationships) of rotation matrices represent.

I've made this tutorial for the following purposes...

  1. To show how to rotate models in OpenGL by using the GLM maths library
  2. To demonstrate the difference between local and global axis rotations
  3. To graphically animate the geometric representations of rotation matrices


Source code: C++... main.cpp

File: main.cpp
  1. #include <glad/glad.h> // GLAD: https://github.com/Dav1dde/glad
  2. #include <GLFW/glfw3.h>
  3.  
  4. #define STB_IMAGE_IMPLEMENTATION
  5. #include "stb_image.h"
  6.  
  7. // OpenGL Mathematics(GLM) ... https://github.com/g-truc/glm/blob/master/manual.md
  8. // ---------------------------
  9. // GLM Headers
  10. // -----------
  11. #include <glm/glm.hpp>
  12. #include <glm/gtc/matrix_transform.hpp>
  13. #include <glm/gtc/type_ptr.hpp>
  14.  
  15. #include <assimp/Importer.hpp>
  16. #include <assimp/scene.h>
  17. #include <assimp/postprocess.h>
  18.  
  19. #include <vector>
  20. #include <iostream>
  21. #include <fstream> // Used in "shader_configure.h" to read the shader text files
  22.  
  23. #include "load_model_meshes.h"
  24. #include "shader_configure.h" // Used to create the shaders
  25.  
  26. int main()
  27. {
  28.     // (1) GLFW: Initialise & Configure
  29.     // --------------------------------
  30.     if (!glfwInit())
  31.         exit(EXIT_FAILURE);
  32.  
  33.     glfwWindowHint(GLFW_SAMPLES, 4); // Anti-aliasing
  34.     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
  35.     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  36.     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  37.  
  38.     const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
  39.  
  40.     int monitor_width = mode->width; // Monitor's width
  41.     int monitor_height = mode->height;
  42.  
  43.     int window_width = (int)(monitor_width * 0.75f); // Window size will be 50% the monitor's size...
  44.     int window_height = (int)(monitor_height * 0.75f); // ... Cast is simply to silence the compiler warning
  45.  
  46.     GLFWwindow* window = glfwCreateWindow(window_width, window_height, "OpenGL Maths (GLM) - Transformations", NULL, NULL);
  47.  
  48.     if (!window)
  49.     {
  50.         glfwTerminate();
  51.         exit(EXIT_FAILURE);
  52.     }
  53.     glfwMakeContextCurrent(window); // Set the window to be used and then centre that window on the monitor
  54.     glfwSetWindowPos(window, (monitor_width - window_width) / 2, (monitor_height - window_height) / 2);
  55.  
  56.     glfwSwapInterval(1); // Set VSync rate 1:1 with monitor's refresh rate.
  57.  
  58.     // (2) GLAD: Load OpenGL Function Pointers
  59.     // ---------------------------------------
  60.     if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
  61.     {
  62.         glfwTerminate();
  63.         exit(EXIT_FAILURE);
  64.     }
  65.     glEnable(GL_DEPTH_TEST); // Enabling depth testing allows rear faces of 3D objects to be hidden behind front faces
  66.     glEnable(GL_MULTISAMPLE); // Anti-aliasing
  67.     glEnable(GL_BLEND); // GL_BLEND for OpenGL transparency which is further set within the fragment shader
  68.     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  69.  
  70.     // (3) Compile Shaders Read from Text Files
  71.     // ----------------------------------------
  72.     const char* vert_shader = "../../Shaders/shader_glsl.vert";
  73.     const char* frag_shader = "../../Shaders/shader_glsl.frag";
  74.  
  75.     Shader main_shader(vert_shader, frag_shader);
  76.     main_shader.use();
  77.  
  78.     unsigned int view_matrix_loc = glGetUniformLocation(main_shader.ID, "view");
  79.     unsigned int projection_matrix_loc = glGetUniformLocation(main_shader.ID, "projection");
  80.     unsigned int rotation_mat_loc = glGetUniformLocation(main_shader.ID, "rotation_mat");
  81.  
  82.     unsigned int image_sampler_loc = glGetUniformLocation(main_shader.ID, "image");
  83.     unsigned int camera_position_loc = glGetUniformLocation(main_shader.ID, "camera_position");
  84.  
  85.     // (4) Load Models & Set Camera
  86.     // ----------------------------
  87.     Model plane("Plane_CAP_232.obj");
  88.  
  89.     glm::vec3 camera_position(0.0f, 0.0f, 15.0f); // -Z is into the screen
  90.     glm::vec3 camera_target(0.0f, 0.0f, 0.0f);
  91.     glm::vec3 camera_up(0.0f, 1.0f, 0.0f);
  92.  
  93.     glActiveTexture(GL_TEXTURE0); // Reusing the same texture unit for each model mesh
  94.     glUniform1i(image_sampler_loc, 0);
  95.  
  96.     glUniform3f(camera_position_loc, camera_position.x, camera_position.y, camera_position.z);
  97.  
  98.     glm::mat4 view = glm::lookAt(camera_position, camera_target, camera_up);
  99.     glUniformMatrix4fv(view_matrix_loc, 1, GL_FALSE, glm::value_ptr(view)); // Transfer view matrix to vertex shader uniform
  100.  
  101.     glm::mat4 projection = glm::perspective(glm::radians(55.0f), (float)window_width / (float)window_height, 0.1f, 100.0f);
  102.     glUniformMatrix4fv(projection_matrix_loc, 1, GL_FALSE, glm::value_ptr(projection));
  103.  
  104.  
  105.     glm::mat4 rotation_mat1(1.0f); // 1.0f initializes the mat4 to an identity matrix
  106.     glm::mat4 rotation_mat2(1.0f);
  107.     glm::mat4 rotation_comb(1.0f);
  108.  
  109.     float counter = 0;
  110.  
  111.     while (!glfwWindowShouldClose(window)) // Main Loop
  112.     {
  113.         counter += 0.7f;
  114.  
  115.         //if (counter < 35)
  116.             rotation_mat1 = glm::rotate(rotation_mat1, glm::radians(1.0f), glm::normalize(glm::vec3(0.5f, 0, 0.1f)));
  117.         //if (counter > 35 && counter < 70)
  118.             rotation_mat2 = glm::rotate(rotation_mat2, glm::radians(1.0f), glm::normalize(glm::vec3(0.9f, 2.4f, 0)));
  119.  
  120.         rotation_comb = rotation_mat2 * rotation_mat1;
  121.  
  122.         glUniformMatrix4fv(rotation_mat_loc, 1, GL_FALSE, glm::value_ptr(rotation_comb)); // Pass model matrix to vertex shader
  123.  
  124.         // (5) Clear the Screen & Draw Model Meshes
  125.         // ----------------------------------------
  126.         glClearColor(0.30f, 0.55f, 0.65f, 1.0f);
  127.         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  128.  
  129.         for (unsigned int i = 0; i < plane.num_meshes; ++i)
  130.         {
  131.             glBindTexture(GL_TEXTURE_2D, plane.mesh_list[i].tex_handle); // Bind texture for the current mesh
  132.  
  133.             glBindVertexArray(plane.mesh_list[i].VAO);
  134.             glDrawElements(GL_TRIANGLES, (GLsizei)plane.mesh_list[i].vert_indices.size(), GL_UNSIGNED_INT, 0);
  135.             glBindVertexArray(0);
  136.         }
  137.         glfwSwapBuffers(window);
  138.         glfwPollEvents();
  139.     }
  140.     // (6) Exit the Application
  141.     // ------------------------
  142.     glDeleteProgram(main_shader.ID); // This OpenGL function call is talked about in: shader_configure.h
  143.  
  144.     /* glfwDestroyWindow(window) // Call this function to destroy a specific window */
  145.     glfwTerminate(); // Destroys all remaining windows and cursors, restores modified gamma ramps, and frees resources
  146.  
  147.     exit(EXIT_SUCCESS); // Function call: exit() is a C/C++ function that performs various tasks to help clean up resources
  148. }

Source code: C++... shader_configure.h & load_model_meshes.h

File shader_configure.h and file load_model_meshes.h can be found here: OpenGL Tutorial 5 (Quick Start) – Model Loading – Assimp Blender & Lighting



Source code: GLSL... shader_glsl.vert (vertex shader)

File: shader_glsl.vert
  1. #version 420 core
  2.  
  3. layout (location = 0) in vec3 aPos; // Attribute data: vertex(s) X, Y, Z position via VBO set up on the CPU side
  4. layout (location = 1) in vec3 aNormal;
  5. layout (location = 2) in vec2 aTexCoord;
  6.  
  7. out vec3 vertex_normal;
  8. out vec2 texture_coordinates;
  9. out vec3 vert_pos_transformed; // Transformed model vertex position passed as interpolated
  10.  
  11. uniform mat4 view;
  12. uniform mat4 projection;
  13. uniform mat4 rotation_mat;
  14.  
  15. void main()
  16. {
  17.     texture_coordinates = aTexCoord;
  18.     vert_pos_transformed = vec3(rotation_mat * vec4(aPos, 1.0)); // Transformed position values used for the lighting effects
  19.  
  20.     mat3 normal_matrix = transpose(inverse(mat3(rotation_mat)));
  21.     vertex_normal = normal_matrix * aNormal;
  22.  
  23.     if (length(vertex_normal) > 0)
  24.         vertex_normal = normalize(vertex_normal);
  25.  
  26.     gl_Position = projection * view * rotation_mat * vec4(aPos, 1.0);
  27. }


Source code: GLSL... shader_glsl.frag (fragment shader)

File: shader_glsl.frag
  1. #version 420 core
  2.  
  3. out vec4 fragment_colour;
  4.  
  5. // Must be the exact same name as declared in the vertex shader
  6. // ------------------------------------------------------------
  7. in vec3 vert_pos_transformed; // Transformed model position coordinates received as interpolated
  8. in vec3 vertex_normal;
  9. in vec2 texture_coordinates;
  10.  
  11. uniform sampler2D image;
  12. uniform vec3 camera_position; // -Z is into the screen... camera_position is set in main() on CPU side
  13.  
  14. void main()
  15. {
  16.     vec3 view_direction = normalize(camera_position - vert_pos_transformed);
  17.  
  18.     vec3 light_position = vec3(0.0, 20.0, 0.0); // A position used as a light source acts as a point light (Not a directional light)
  19.     vec3 light_direction = normalize(vec3(light_position - vert_pos_transformed));
  20.  
  21.     vec4 image_colour = texture(image, texture_coordinates);
  22.  
  23.     float ambient_factor = 0.65; // Intensity multiplier
  24.     vec4 ambient_result = vec4(ambient_factor * image_colour.rgb, 1.0);
  25.  
  26.     // Perpendicular vectors dot product = 0
  27.     // Parallel vectors in same direction dot product = 1
  28.     // Parallel vectors in opposite direction dot product = -1
  29.     // -------------------------------------------------------
  30.     float diffuse_factor = 0.85;
  31.     float diffuse_angle = max(dot(light_direction, vertex_normal), -0.45); // [-1.0 to 0] down to -1 results in darker lighting past 90 degrees
  32.     vec4 diffuse_result =  vec4(diffuse_factor * diffuse_angle * image_colour.rgb, 1.0);
  33.  
  34.     vec3 specular_colour = vec3(0.5, 0.5, 0.5);
  35.     vec3 reflect_direction = normalize(reflect(-light_direction, vertex_normal)); // Light direction is negated here
  36.     float specular_strength = pow(max(dot(view_direction, reflect_direction), 0), 32);
  37.     vec4 specular_result = vec4(specular_colour * specular_strength, 1.0);
  38.  
  39.     fragment_colour = ambient_result + diffuse_result + specular_result;
  40. }