When I first started learning OpenGL, I wanted to implement object transformations around a pivot point—just like how it works in Unity. I was keen to understand and apply the actual maths behind it. Although I found a few resources online, none of them were particularly helpful. Now that I’ve figured it out, I’d like to share what I’ve learned so others don’t have to go through the same struggle.
We have a rectangle that we want to translate, rotate, or scale in 2D space—but around a pivot point. The pivot is defined in the local space of the rectangle, meaning its coordinates are relative to the rectangle itself. For example, (1, 1) refers to the top-right corner, (–1, 1) to the top-left, and so on.
I assume you have a basic understanding of OpenGL—how to set up a rendering context, work with shaders, and draw simple shapes. You should also be familiar with how transformations like translation, rotation, and scaling are applied using transformation matrices.
For the interface, I’m using ImGui, a popular immediate-mode GUI library. All the transformation values—position, rotation, and scale—come from ImGui inputs. These values are updated interactively in real-time, and each frame uses the latest inputs to compute the delta changes for transformation.
If you’re not familiar with it, don’t worry—it’s only used here to tweak values like position, angle, and pivot interactively. The core transformation logic is independent of ImGui.
Now that we understand the math, let’s see how to actually code it.
The first thing we need is the existing transformation matrix—in the code, we call it the oldMatrix
. This matrix represents the object’s current transformation from the origin (0, 0). It includes any previous translation, rotation, or scaling already applied. (We will update this at the end of the update loop.)
When we apply a new transformation (like rotating or scaling around a pivot), we do it relative to this oldMatrix
. So instead of starting fresh each time, we build on top of the previous transformation.
I hope this breakdown helps you avoid the confusion I faced and gives you a solid starting point for your own implementations.
Please feel free to reach out if you have any questions, spot a bug, or just want to discuss anything related to this.