- In the final video segment of this lecture
we'll consider shaders,
and in particular the initialization of shaders
need for the mytest1 program.
Let us first look at the entire OpenGL pipeline.
We start with the geometry specified by the user program,
that is acted upon by a vertex shader
which we will be considering in this course,
and a number of other shaders that we will not be
immediately talking about here,
including the tessellation control shader,
and tessellation evaluation shader,
to tessellate primitives like spline patches, and
a geometry shader that can generate additional geometry.
Thereafter this comes to the rasterization part of OpenGL,
which sets up the primitives, does some basic clipping,
and does rasterization, which essentially takes the
triangles and determines which pixels they should
correspond to on the screen.
In this course we cover programmable vertex
and fragment shaders.
OpenGL at this time effectively only specifies
the rasterizer, which is non-programmable;
everything else is determined by the user.
Finally, that information comes to the fragment shader,
once OpenGL decides which pixels to turn on and off;
where a triangle goes in the 2D screen.
Fragment shaders are called for those corresponding pixels,
and you can also give it input information such as textures
or image data, and you get the final color.
As I said before, besides vertex and fragment shaders,
modern OpenGL also involves tessellation of spline patches,
and geometry shaders that can generate, or remove,
geometry from the scene.
However those are not covered in this course,
and we consider exclusively programmable vertex
and fragment shaders.
To summarize the simplified pipeline,
the user specified vertices, via vertex arrays,
are processed in parallel by the vertex shader.
Semantically, OpenGL is calling the vertex shader,
the same vertex shader, separately for each of
the different vertices.
In fact, this high level of parallelism is one reason
why graphics hardware is so fast and has now been adapted
for a number of tasks that are not immediately
relevant to graphics itself.
The vertex shader transforms the vertex
by applying the ModelView or the Projection matrix,
as well as other operations.
Thereafter, for each primitive OpenGL rasterizes,
determines which pixels on the screen it corresponds to,
and generates a fragment for each pixel
that the primitive covers.
And then, again in parallel for each of the fragments
you apply the fragment shader in parallel.
This will typically do shading and lighting calculations,
and OpenGL also handles the z-buffer depth test by default,
although it can be overwritten by the user.
Let's now talk about the shader setup.
Shaders must be initialized just like any program;
effectively they are a program.
This involves a sequence of steps.
First you have to be able to create the shader,
vertex and fragment.
You have to be able to compile the shader.
You have to be able to attach the shader to the program,
link the program, and finally use the program.
Note that in OpenGL, the shader source
is just a sequence of strings.
In fact the strings can be specified in this part
of the OpenGL program.
Thereafter the strings are compiled on the fly
while the program is running, and the same steps
that you would use to compile a normal program
are used to compile a shader.
Here is the shader initialization code.
I have this command initshaders.
Type is vertex or fragment,
and filename is the file from which I'm reading the shader.
But note that this is simply my convention,
that I have the shaders in a file, I read the file
and I specify them to the OpenGL program.
You could as well, as far as OpenGL is concerned,
have the shaders specified as part of the source text
for the program. All it is doing is compiling the strings
corresponding to the shader.
So I create a shader of the appropriate type,
I read the filename. This is my own text file read command,
and I put it into a single string.
Then I have a character pointer to that string,
and I define the shader source for this given shader
with one string, and with this cstr variable.
Thereafter I compile the shader.
OpenGL allows me to check if the compilation is correct,
and actually give me a number of flags
corresponding to the shader value.
So I can check the compiled status,
which I will put in the variable compiled.
If compiled was fine, I actually print out a message
saying that the compilation was fine
and the shader can be used,
I haven't shown the cout here
in the interests of space.
But if the compilation failed,
then I will tell you what errors it encountered
and I throw an exception, which ends the program.
Here is the code for linking the shader program.
So I'm given a vertex shader and a fragment shader,
I want to link them together and use them
in the shader program.
First, I need to create a new program,
then I have this variable linked.
I attach the vertex shader to the program,
the fragment shader to the program.
Remember, both of these shaders are initialized
and created by the function we saw earlier.
I link the program,
and just as I can get the errors
for the compilation of the shader,
I can get errors in linking,
which I put in the linked variable.
If the variable is linked,
I use the program directly,
otherwise I say what errors are there
and throw an exception.
And finally, I print out that the shader program
is successfully attached and linked.
Here is my basic no op vertex shader.
This is written in GLSL, which is the high level
shading language used to write shaders.
The output values from the vertex shader
will be interpolated by the rasterizer and sent
to the fragment shader by OpenGL.
Notice that I am doing this now in OpenGL 3.1,
and GLSL version 330.
So the first line I specify is the version I'm using,
which is version 330 core.
If you modify it to something older, it will not work,
because the syntax is no longer relevant.
Here are the shader inputs.
So the layout location is 0,
input vec3 of position.
So remember that earlier we parsed the position and color
to the shader. This is how it interfaces with the shader.
That means there will be a position and a color
associated with each vertex.
The shader output is this Color,
remember the case is sensitive,
and so this is capital Color.
The shader also has uniform variables which are the same
for each vertex.
Remember that these are effectively varying variableS
that change for each vertex,
but there are also uniform variables that can be stored
only once for all the vertices,
which are simply the modelview and projection matrices.
Just like normal C code, it starts with the main routine,
and within the main routine
it's doing something very simple.
The gl_Position, which is the position of the vertex,
is just given by the position variable coming in
for the vertex information,
gets multiplied by the projection matrix.
times the modelview matrix, times the position variable
that I specified for the vertex,
made into homogeneous coordinates;
that's what the vec4 is doing,
you have the x, y, z of the position and the w
which is equal to one.
Finally, the color is just passed through from the vertex
to the fragment shader.
So this is a very basic no op vertex shader.
Note that I need to explicitly specify the multiplication
by the projection and modelview matrices.
Let us now look at the fragment shader.
Again I have specified version 330 core.
The input is just the color that came from
the vertex shader.
There are no uniform variables,
and there is an output, which is the fragment color
which will be actually displayed on the screen.
And all that I say is that this fragment color
is the input color,
and I made it from RGB to RGBA simply by setting
the alpha variable equal to one.