OpenGL Animation with Modern C++: Rotation, Translation, and Scaling

Lesson 3: Bringing Your OpenGL Scene to Life with Animation

Welcome back! In this tutorial, we’re going to take the static scene from Lesson 2 and make it dance. We’ll cover three core transformations: rotation, translation, and scaling, all driven by a simple timer.

We’ll be using modern OpenGL (3.3 core profile) with GLFW for window management and GLEW for loading extensions. The concepts are the same as the old fixed-pipeline days, but we’ll implement them with shaders and vertex buffers.

Setting Up the Timer

First, we need a global variable to track elapsed time. We’ll use glfwGetTime() instead of a custom idle function:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

float time = 0.0f;

Initialization

We initialize GLFW, create a window, set up GLEW, and define our viewport and projection. In modern OpenGL, we set up a projection matrix in the vertex shader:

void init(GLFWwindow* window) {
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

For the projection, we’ll use an orthographic matrix in the vertex shader. I’ll skip the shader code for brevity, but you can find the full source on GitHub.

The Display Loop

Instead of a display callback, we use a loop. We clear the buffer, update uniforms with the current time, and draw each object with its own transformation. In modern OpenGL, transformations are done via uniform matrices in the shader.

int main() {
    // ... initialization ...
    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT);
        time = glfwGetTime();

        // Red triangle - rotating around Z
        glm::mat4 model = glm::rotate(glm::mat4(1.0f), time, glm::vec3(0, 0, 1));
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, &model[0][0]);
        glUniform3f(colorLoc, 1, 0, 0);
        glBindVertexArray(triangleVAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // Green quad - translating along X
        model = glm::translate(glm::mat4(1.0f), glm::vec3(time/50.0f, 0, 0));
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, &model[0][0]);
        glUniform3f(colorLoc, 0, 1, 0);
        glBindVertexArray(quadVAO);
        glDrawArrays(GL_QUADS, 0, 4);

        // Polygon with per-vertex colors - scaling
        model = glm::scale(glm::mat4(1.0f), glm::vec3(time/200.0f));
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, &model[0][0]);
        glBindVertexArray(polygonVAO);
        glDrawArrays(GL_POLYGON, 0, 5);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    // ... cleanup ...
}

Notice that we no longer have glPushMatrix/pop. Instead, each object has its own model matrix. The rotation, translation, and scaling effects are exactly the same as the original lesson. The triangle rotates around the origin (which is why it orbits), the quad slides horizontally, and the polygon scales up until we reset the timer (we can do that when time > 360).

Why This Matters

Understanding these transformations is fundamental to any 3D graphics work. Whether you’re building a game, a simulation, or a data visualization, being able to position and animate objects is key. Modern OpenGL gives you more control and efficiency, but the math is identical.

Next Steps

Try playing with the transformation values and see what happens. In Lesson 4, we’ll look at user input to control animations interactively.

Feel free to ask questions below!

Topic Summary: Modern OpenGL animation with C++: rotation, translation, scaling via model matrices and shaders. Explore instancing, compute shaders, and RAII for efficient real-time graphics.

:open_book: Topic Overview (Wikipedia):

OpenGL is a cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-accelerated rendering.
Read more on Wikipedia

---
title: OpenGL Animation Pipeline
---
flowchart TD
    A[Initialize OpenGL] --> B[Create Shaders]
    B --> C[Load Vertex Data]
    C --> D[Animation Loop]
    D --> E{User Input?}
    E -- Yes --> F[Update Transform]
    E -- No --> G[Apply Rotation]
    F --> G
    G --> H[Apply Translation]
    H --> I[Apply Scaling]
    I --> J[Render Frame]
    J --> D

This tutorial does a solid job of bridging the classic fixed-pipeline transformations with modern OpenGL’s shader-based approach. The core math–rotating, translating, and scaling via model matrices–remains identical, but the implementation shift to uniforms and VBOs brings greater flexibility and performance. One area worth exploring further is how these transformations tie into a full scene graph, especially when dealing with hierarchical animations like a solar system or a character skeleton. In modern engines, each node in the scene graph holds its own model matrix, and parent-child relationships are computed by multiplying matrices down the tree. This eliminates the need for glPushMatrix/glPopMatrix entirely, relying instead on uniform buffers or dynamic updates per draw call.

Beyond simple per-frame updates, modern C++ and OpenGL allow for more sophisticated animation techniques. For example, using instanced rendering with a single draw call for thousands of objects, each with its own model matrix stored in a buffer. This is how particle systems and crowd simulations achieve high particle counts. Compute shaders can even update these matrices on the GPU, offloading the CPU entirely. Another trend is the use of double buffering and triple buffering for smooth animations, which is already handled by GLFW’s swap buffers call, but tuning the frame rate with a fixed time step can prevent physics or animation jitter.

Looking forward, integrating user input to control transformation parameters is a natural next step, but also consider leveraging libraries like GLM for matrix math and spdlog for debugging. Modern C++17/20 features like std::chrono for precise timers and constexpr for compile-time shader literals can further optimize the code. The foundation laid here–understanding model, view, and projection matrices–is essential for any graphics work, whether it’s rendering a spinning logo or building a full 3D game.

For those wanting to push further, experiment with hierarchical transformations, instancing, and compute-based animation. The possibilities are endless, and the math never changes.