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!

