We will now discuss the matrix stacks
and transforms that we need in OpenGL,
especially in the context of drawing the four pillars.
Let's first summarize the entire OpenGL pipeline
for vertex transformations.
We start with an object defined in object coordinates
that's in the native coordinate system of the object.
In our case, this will always be
in homogeneous coordinates (x,y,z,w).
Thereafter you apply the modelview transforms
which corresponds to the modelview matrix.
Note that this has both a model transformation,
which is the object transformation,
and the view transformation, which is glm::lookAt,
which positions the camera appropriately.
Thereafter you apply the projection matrix,
which goes from 3D to 2D,
and this corresponds to glm::perspective.
Now, the eye coordinates are obtained
after the modelview transformation;
actually, before the projection transformation,
and those correspond to the coordinates
in the 3D world for lighting.
But after you have the projection transformation,
then what you get are clip coordinates.
Here everything is in the range
of -1 to +1.
We are mapping the world essentially into a unit cube,
actually, a cube of two units,
from -1 to +1.
After this, you can do
the prospective division of dehomogenization,
whereupon you get what is known
as normalized device coordinates,
which are then moved on to the pixels in the screen
by using the viewport transformation
and finally appear on your window.
We are most interested in this lecture
in the modelview and projection transformations,
which are usually represented using matrix stacks
corresponding to the modelview and projection matrices.
Matrix stacks are especially useful
for hierarchically defined figures
such as you have a human body with a torso,
then you have the hands, the legs,
and effectively you can see this
as being a scene graph in OpenGL
or a hierarchy of different shapes.
They're also very useful in our case
for placing the pillars where we have
the same geometry for the pillars,
but we are placing them
with different transformations and different colors.
In old OpenGL,
matrix stacks were handled with a number of GL commands,
such as glPushMatrix, glPopMatrix,
glLoad a matrix onto the stack,
glMult(iply) a matrix to the right of the stack.
However, these are all deprecated,
and the current recommendation is
you maintain the stack yourself
either using STL commands in C++
or using any other technique,
and this is in fact what is done in mytest2,
and you must manage the stack yourself for homework 2.
As far as the transformations are concerned,
we write our own translate, scale, and rotate,
and you will need to do that
for homework 1 and homework 2.
Be somewhat careful of OpenGL convention.
In old-style OpenGL,
one right-multiplies the current matrix
on top of the stack,
which essentially means the last command in code
is the first applied.
glm operations essentially follow this,
but when you are doing the multiplication,
you need to be careful to follow this.
In old OpenGL, we also had commands
like gluLookAt and gluPerspective,
which have the glm equivalents
of glm::lookAt and glm::perspective,
and these are just matrices like any other transform.
They right-multiply the top of the stack.
But that is the way in which you implement
the modelview and projection matrices.
If you are stuck on how to implement
these concepts in homework 2,
look at the mytest sequence of demos,
which will give you some ideas
of how to best implement this.
Let us talk about drawing the pillars
in the display routine.
In the first pillar we right-multiply
the modelview as in old OpenGL.
Notice that the entire pillar drawing
is between these pushMatrix and popMatrix blocks.
In old OpenGL,
these would be glPushMatrix and glPopMatrix.
In our case, we have to implement it ourselves,
and so we push the matrix modelview on the stack,
pop the matrix modelview from the stack.
We'll show the stack commands in a bit.
But let's look at the code within that.
So first we right-multiply the modelview matrix
as in old OpenGL.
modelview is equal to modelview * glm::translate.
The inputs for glm::translate
are the matrix that you want to multiply by,
which in this case is just the identity,
and you have the translation amount,
which is simply a vec3(-0.4,-0.4,0.0).
This just builds the translation matrix.
The next command is this glUniformMatrix4fv,
which simply provides the modelview matrix
as a uniform mat4 to the shader,
and it does it in location modelviewPos.
You're only providing one matrix,
you don't transpose this matrix,
and you give it the input
for the first element of the matrix.
That's what the &(modelview)[0][0] stands for.
The second pillar is exactly the same code,
except now the translation is different,
so it goes to (0.4, -0.4, 0)
instead of (-0.4, -0.4, 0).
We have the same glUniformMatrix4fv,
and we draw the cube.
In this case it's drawcolor of CUBE with color 1.
So color 0 corresponds to red,
color 1 corresponds to green, and so on.
We now talk about the third pillar,
which is exactly the same,
and the fourth pillar,
which is again exactly the same.
Let's look at what our push and pop functions correspond to.
In this case, we need to implement them ourselves.
The pushMatrix function,
given a matrix which is, in this case, the modelview matrix,
pushes it onto the modelview stack.
Earlier we defined the stack of mat4
for modelviewStack.
And you push the matrix corresponding to that.
The popMatrix function pops the matrix
from the modelview stack.
It is a void function,
so it's the void popMatrix function,
and it checks the modelview stack size.
If there is a matrix on the modelview stack,
then you return this matrix.
So you would set mat is equal
to the top element of the stack.
That's what modelviewStack.back() does,
and the pop_back() just removes that element from the stack.
Otherwise you just set it to the identity
to prevent errors when popping an empty stack.
Let me now show you demo 1,
which has not just the floor, but also the pillars.
First I want to refresh your memory.
This is demo 0, which is essentially mytest1,
which has just the floor.
Now we have this DEMO variable which I alluded to earlier,
and I can set it to 1.
Notice that the demo variable is used in a lot of places.
See here I say DEMO >= 2
Here I say if (DEMO > 0)
then I actually draw the pillars,
which is exactly the same code we saw earlier.
And notice that I'm drawing the floor here.
Having done this, I exit out of my program,
I compile, and now I can run the program.
Here I just have my floor and my four pillars.
I can move in and out as before.
This is working perfectly fine,
but I have a question for you,
which is does the order of drawing matter?
What if I move the floor which I drew first
after the pillars in the code,
and is this desirable, or if not, what can I do about it?
So think about this for a little bit
while I write the code to change that.
The code to change that is very simple.
I will comment out the floor drawing routine here
and instead, I will add it later.
So see, does the order of drawing matter?
What happens if I put it here?
For convenience, I have already included
the commands here with a comment.
I go back now to my program
and I will exit out of it.
I make the new program
where I changed the order of the drawing.
And voila, the floor now appears on top of the pillars.
This is not surprising,
because I specified the drawing
for the floor after the pillars,
so it's reasonable it should overwrite the pillars,
but this is not the desirable behavior,
because the floor actually lies behind the pillars.
In the next segment,
we will talk about z-buffering to address this issue.