- We are now going to talk about drawing in OpenGL.
So far we've taken a number of steps,
understanding the overall framework of OpenGL,
we've talked about buffers and matrices,
we've talked about callbacks,
but we haven't actually drawn anything on the screen yet.
We'll correct that in this segment.
Let's first talk about the primitives that OpenGL
has available for drawing.
In old style OpenGL,
there used to be a large number of additional primitives,
but many of them have been removed in new OpenGL.
We still have points, which are just specified
in homogeneous coordinates.
Lines, you can also have multiple lines
determined by a line strip or a line loop.
You have triangles, which is perhaps the most important
primitive for drawing.
In old OpenGL there used to be quads, general polygons,
and quad strips, which have all been removed in new OpenGL
for simplicity.
Effectively we can use triangles and
two efficiency improvements for collections of triangles.
The triangle strip essentially takes a single triangle,
and then adds new triangles where you have to specify
only one additional vertex at a time.
So if you have this triangle specified,
the next triangle is just specified by taking
one additional vertex and using the previous two vertices.
Similarly, the subsequent triangle will just need
one additional vertex, one additional vertex,
one additional vertex.
The triangle fan is similar in the sense
that it has one point about which
the different triangles effectively form a fan.
Again, with one additional vertex you can specify
an entire triangle.
To reiterate,
the points are GL_POINTS stored in homogeneous coordinates,
line segments, triangles, which is the most important,
GL_TRIANGLES, you also have strips and fans.
More complex primitives used to be available in GLUT,
in particular spheres, teapots and cubes.
These must now be converted into triangles,
the skeleton code that we are providing you does
do this conversion, and we do not use the deprecated
GLUT sphere, GLUT cube or GLUT teapot commands.
We'll now talk about the mechanics of drawing.
First I have a couple of slides on old style OpenGL drawing,
which although it's deprecated is actually much simpler
to understand and can be a first step for you
to understand the process.
In old style OpenGL drawing,
one could enclose vertices between a pair of
glBegin and glEnd commands.
And within those commands one could include normal C code
and attributes like colors.
So it would be very common to specify a color,
then specify a vertex.
There would be commands like glVertex3f, and glColor3f.
You might be wondering what the 3f stands for.
Remember that OpenGL came into being in the early 90s,
and well before the C++ and object-oriented programming.
In fact OpenGL is written in C.
Therefore in order to have functions that take in
different arguments, three floating point numbers
versus three integers, or versus four floating point numbers
and homogeneous coordinates,
instead of overloading the function call,
we have different functions which are
specified by attributes like 3f,
which simply means the input is
three floating point numbers.
In old style OpenGL, it was also important
that the color would be set before the vertex.
Therefore you might have something like
glColor3f
to set the color,
and then you might have something like glVertex.
This was also done in an assembly line model,
so you pass vertices to the server, they get transformed,
they get shaded within the OpenGL pipeline.
Nowadays, of course, you have modern vertex and
fragment shaders and the graphics processing unit.
In old OpenGL there was also the concept of immediate mode
wherein you specify triangles,
it was immediately sent to the server and drawn.
Let me just show you one example of old OpenGL drawing,
which again I want to emphasize is not used
in your skeleton code or in your homework assignments
where you will be using modern OpenGL.
In the display routine,
we first clear the color buffer bit,
and then we draw a polygon with the vertices
having appropriate colors, blue, white, red and green,
similar to the polygon I showed you in our probe.
You begin with the GL_POLYGON, you set the color, red,
RGB, this is red.
You draw a vertex at (0.5 0.5 0.0),
these are all vertices on the plane z=0.
This is at x=0.5, y=0.5, it's red.
Thereafter you draw another vertex with color green
at (-0.5, 0.5).
Similarly you set the color to blue,
you draw a vertex (-0.5, -0.5),
set the color to white,
you draw a vertex here at (0.5, -0.5).
glEnd ends the sequence of primitives.
This is now sent immediately to the server for drawing.
glFlush flushes the set of commands,
and they are drawn immediately.
I talked about clients and servers.
In fact old OpenGL operates on a client-server model,
even if the client and server are on the same machine,
which is typically the case;
wherein the client, which is the user program,
generates vertices. The server draws them,
even if the server is really running on the same machine
as the client.
There are a couple of synchronization commands.
glFlush forces the client
to send network packets for drawing,
and glFinish waits for an ack and is sparingly used
for synchronization.
In modern OpenGL, all of this has been replaced
using the notion of vertex array objects,
which we will discuss next.
There is a fair warning that this is more complicated,
but is useful for modularity and efficiency.
In modern OpenGL, I specify the floor as follows.
First I have an array of the four vertices of the floor,
with the three locations in the array
for the x, y and z coordinates.
And that's just the same way I specified it earlier,
the (0.5 0.5 0.0), (0.5 0.5, 0.0).
It's the same floor we saw earlier in old style OpenGL.
But I will also specify an array for colors
instead of doing it in immediate mode,
where the colors are red, green, blue and white
as before.
Then there is the specification of indices.
Essentially, by defining the floor vertices,
I put the set of vertices in an array,
but I still need to figure out the way of connecting them.
In this case, I have my floor,
which I can draw like this.
And I have connected it this way,
(0.5 0.5) is vertex 0,
(-0.5 0.5) is vertex 1,
(-0.5 -0.5) is vertex 2,
and (0.5 -0.5) is vertex 3.
So when I'm looking at the indices and
the elements for construction,
(0,1,2) corresponds to the triangle
joining these vertices.
Remember that I only have triangles in OpenGL,
so I have to connect these,
and (0,2,3) is correspondingly this triangle.
So I specified the floor with two triangles.
That's what these indices correspond to.
I also specify the coordinates for my second plane,
with the same vertices except that the z coordinate
is now equal to one.
If you look here, the z coordinates are all one,
and the colors are also different in this case
in that all of the colors are red.
However, the indices are exactly the same
as what we had earlier,
in order to specify the triangles for the second plane,
which is above the first plane.
Let's now talk about the representation of
vertex array objects.
The first line in this code defines
the number of objects as 2,
corresponding to the two planes.
The number of buffers per object is 3,
corresponding to vertices, colors and indices.
Thereafter I use GLuint to define
a number of vertex array objects.
You will need a vertex array object per object.
Then I have the number of buffers,
which is the number per object times the number of objects.
The objects which we'll store like object IDs again
is the number of objects.
The primitive types is the type of the object,
which is triangles or strips,
and finally the number of elements,
or the number of triangles or geometric elements
for each object.
The floor geometry is specified with a vertex array.
We'll define an enum for vertices, colors and elements,
it's just we'll set them to zero, one and two,
and similarly for floor and floor two.
Below that, in the init function,
we will create the buffer objects for later use.
So glGenVertexArrays tells OpenGL
to generate the number of vertex array objects
corresponding to the number of objects.
This just creates the unique identifiers for the objects.
glGenBuffers number per object,
which is three in this case, times number of objects,
generates buffers for vertices, colors and elements.
And finally we have this DeleteBuffers,
which operates like a destruct tail when
a buffer is no longer needed.
Let us now talk about the initobject command,
which effectively initializes the objects
and initializes the buffers needed for drawing an object.
Again, you're give as input the integer ID of the object.
And you're given a pointer to the vertex array,
the size of the vertex array,
a pointer to the color aray,
the size of the color array,
a pointer to the array of indices,
the size of the indices array.
Note that in this case we just use unsigned characters
or bytes for the indices, but of course if we had
more complex objects we would need to use integers.
And then the type, which in this case is triangle.
So the offset is just saying for different objects,
as you increase the object ID,
we will be offset in terms of the buffers
by the object number times the number of buffers per object.
The first thing you do is bind the vertex array
corresponding to the given object, that is
the vertex array object of the given object ID.
Thereafter you bind the corresponding buffer,
which will be given by offset plus vertices,
colors and elements.
You specify the buffer data,
so it's an array buffer, the size of the vertices.
vert is where it starts.
GL_STATIC_DRAW is a hint to OpenGL,
this is meant for static drawing.
We also need to interact with the shader,
and within the shader we will use layout location 0
for the vertices.
So you enable the vertex attribute array zero,
so that you can pass this information to the shader,
Then there is this glVertexAttribPointer command,
so 0 is the layout location,
3 says that this is now a vertex array,
so each element of the array has x, y and z coordinates.
This is GL_FLOAT, which is the data type here.
GL_FALSE corresponds to the fact of whether the data
should be normalized.
Here we talk about the stride in the data;
what is the difference in bytes between
one element and the other element.
And of course it would be three for x y z
times the size of the float variable.
0 is a pointer to how much you have to offset
into the buffer,
and in this case the information is right at the start,
so it's set to zero.
Binding the buffer now corresponding to the colors,
specifying the data for the colors.
We use layout location 1 for the colors.
You enable the vertex attribute array one.
You similarly set up the vertexAttribPointer
for location 1. Again, it's just the same way as vertices,
the same set of arguments.
Binding the buffer for the elements and the indices now,
reading in that buffer data.
Finally we set the primitive type for the object
to the type that is input,
and the number of elements for the object
to the size of the indices array.
Once you have done with this vertex array object,
you can unbind it to prevent unwanted modifications later,
so you should always, as a best practice,
bind 0 in order to unbind
the current vertex array object.
After we have done all of this setup
to initialize the object,
drawing of the actual vertex object
is surprisingly simple.
The drawobject command just takes in the integer value
corresponding to the object,
you bind the vertex array for that object,
and you draw the elements corresponding
with where you're given the primitive type for the object,
the number of elements for that object.
GL_UNSIGNED_BYTE tells you that the elements
are just represented by an unsigned character,
which is the indices specifying the object.
Thereafter you unbind.
In the display routine you have this command glClear,
GL_COLOR_BUFFER_BIT, which clears all of the pixels.
Thereafter you draw the object FLOOR,
draw the object FLOOR2,
and you flush and you start processing
the buffered OpenGL commands.
Finally, I just briefly want to talk about initialization,
including initialization of the shaders.
We'll talk more about shaders in the next video segment.
Notice that you have integer values for the vertex shader,
the fragment shader and the shader program.
This is the initialization you do in init for drawing,
we already talked about glGenVertexArrays and glGenBuffers
to generate unique IDs for the vertex arrays in the buffers.
You initialize the object.
We looked at the initobject command.
You initobject for the floor,
you initobject for floor2.
Then you need to do initialization for the shaders.
the vertex shader lies in this file
shader/nop.vert
Right now it's not doing anything particularly interesting.
The initialization for the fragment shader lies in this file
shaders/nop.frag
These do not actually have to be in files.
In fact they are just strings to the OpenGL program
that are compiled on the fly.
In practice, we read them in from the vertex and frag files
that we have created.
The next part is creating the shader program,
which initializes a program based on the vertex
and fragment shader.
I will now demonstrate the program.
Here I'm showing you the actual text for the program.
You can download this of course,
it is the mytest sequence of demos,
and this is mytest1.cpp.
I can even run the program here.
Let me show it to you.
This is what you saw earlier, you have these two planes,
I can zoom in, I can zoom out,
and I can hit escape or quit to quit.
What I'm going to do now is try to change the color
of the floor.
So I first comment out these lines.
I'm showing you writing of the actual code
so you get a sense that this is simple code
that can be written,
and modified.
Let me now un-comment these lines,
and I will change the colors to have
the lower floor be all white.
I changed all of the colors to be white.
There are a number of different compilation options.
I am working on a Mac, so I'm just going to
compile from the command line.
We are giving you the make files,
so don't worry too much about all of the arguments here,
it should just work.
Now I'm going to run the program.
Notice that it's compiled the shader files for the vertex
and the fragment shader and successfully attached
and linked the shaders.
You notice that now the floor at the bottom is white.
The floor on top is still red,
I didn't change the colors,
but instead of being multi-colored,
the floor at the bottom is still white.
Go ahead, download these programs,
and explore playing with them.