0

I'm writing an OpenGL application (C# and OpenTK) and I'm confused about how OpenGL transformations work.

I've set up a perspective projection and I'm at the default location with the default orientation (+X to the right, +Y up, +Z coming at me.) Now I draw a quad in my XY plane, on the Z-axis at -10.

GL.Begin(PrimitiveType.Quads);
GL.Color3(Color.Green);
GL.Vertex3(-1, 1, -10);
GL.Vertex3(1, 1, -10);
GL.Vertex3(1, -1, -10);
GL.Vertex3(-1, -1, -10);
GL.End();

This works as expected. But now I want to rotate the quad along its local Y-axis, so I added in a rotate, which is being applied to my identity matrix. Here's the relevant section of code:

GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();

GL.Rotate(10, 0, 1, 0);

GL.Begin(PrimitiveType.Quads);
GL.Color3(Color.Green);
GL.Vertex3(-1, 1, -10);
GL.Vertex3(1, 1, -10);
GL.Vertex3(1, -1, -10);
GL.Vertex3(-1, -1, -10);
GL.End();

But this will rotate the plane around my "world" Y-axis, so now the plane is no longer speared through my -Z axis, but it's at an angle.

Question

How can I keep the object at the desired location (at -10 along my Z axis) but have it rotated about its own axis?

What I've tried

I've tried first translating to the origin, performing the rotation, drawing, then moving back, but this doesn't work either, presumably because once I've rotated, now I'm no longer translating along the "world" axis, but the rotated axis. This is the code:

GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();

GL.Translate(0, 0, 10);
GL.Rotate(10, 0, 1, 0);

GL.Begin(PrimitiveType.Quads);
GL.Color3(Color.Green);
GL.Vertex3(-1, 1, -10);
GL.Vertex3(1, 1, -10);
GL.Vertex3(1, -1, -10);
GL.Vertex3(-1, -1, -10);
GL.End();

GL.Translate(0, 0, -10);

I have a feeling I need to be using PushMatrix and PopMatrix, but I'm not quite sure I understand how they come into play. If I push my matrix before performing any operations, then pop it, shouldn't my view return back to normal? If so, why doesn't this work:

GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();

// Push current matrix onto the stack
GL.PushMatrix();

// Perform operations on newly pushed matrix
GL.Translate(0, 0, 10);
GL.Rotate(10, 0, 1, 0);

GL.Begin(PrimitiveType.Quads);
GL.Color3(Color.Green);
GL.Vertex3(-1, 1, -10);
GL.Vertex3(1, 1, -10);
GL.Vertex3(1, -1, -10);
GL.Vertex3(-1, -1, -10);
GL.End();

// Pop it off, so return to my previous matrix
GL.PopMatrix();
Reticulated Spline
  • 1,892
  • 1
  • 18
  • 20

1 Answers1

3

You were on the right track with your second attempt, but it still misses a couple of aspects:

  • All transformations need to be specified before you start drawing. Only transformations that are active at the time the draw call is made will be applied to the coordinates in that draw call. Having a translation after glEnd() will do nothing.
  • The order is backwards. The transformation specified last is applied to the vertices first. So the translation that moves your geometry to the origin needs to be specified last, and the one that moves it back to its position needs to be specified first.

The code sequence will then look like this:

GL.Translate(0, 0, -10);
GL.Rotate(10, 0, 1, 0);
GL.Translate(0, 0, 10);

GL.Begin(PrimitiveType.Quads);
...
GL.End();

The PushMatrix() and PopMatrix() are not needed in this code example because you call LoadIdentity() at the start of your draw function, which will restore the current transformation to the identity transform. And at least based on what's shown, you are not drawing anything after this code, so there is no need to restore the previous matrix.

An alternative is to not call LoadIdentity() for each frame, but instead bracket the code segment above with PushMatrix() at the start and PopMatrix() at the end. That's in fact more extendable, because it restores the matrices for additional rendering if needed.

I explained some of this in more detail in an answer to a similar question here: Rotating an object around a fixed point using glMultMatrix.

Community
  • 1
  • 1
Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • That worked, thank you! Why are the operations performed in reverse order, though? Is that just due to how matrix multiplication works? I must admit it seems counter-intuitive from a programming perspective to have to reverse the flow of operations. – Reticulated Spline Nov 28 '14 at 04:46
  • I would also like to know: why do `PushMatrix` and `PopMatrix` have no effect? Whether or not I wrap my code in it, the results are the same. – Reticulated Spline Nov 28 '14 at 04:49
  • I added a section about `PushMatrix` and `PopMatrix`. The transformation order question is a bigger topic. Yes, it has to do with how the matrices are multiplied. The behavior also makes sense in most cases. For example, it allows you to specify the view transformation first, and then start drawing your objects, while specifying the model transformation for each one. The model transformation will then be applied first, then the view transformation, which is the way it needs to be. It becomes even more useful if you have hierarchies of objects with relative transformations at each level. – Reto Koradi Nov 28 '14 at 05:44
  • @ReticulatedSpline: Yes, this is how the matrix semantics as used by OpenGL works. The idea is, that you can easily build transformation heirachies, by "adding" at the end. To implement that, OpenGL uses column vectors which multiply with matrices from the right. If everything got transposed then the order would be otherway round. However what counterintuitive it may be to a beginner, ordering matrix application in that way is hugely beneficial and simplifies a lot of things. – datenwolf Nov 28 '14 at 09:10