Quick Introduction to Open GL (with C++) - Part I


                             

OpenGL Tutorial - A Very Brief Introduction and Overview - Part I


What Is OpenGL?

OpenGL is a software interface to graphics hardware. This interface consists of about 150 distinct commands that you use to specify the objects and operations needed to produce interactive three-dimensional applications.
OpenGL is designed as a streamlined, hardware-independent interface to be implemented on many different hardware platforms. To achieve these qualities, no commands for performing windowing tasks or obtaining user input are included in OpenGL; instead, you must work through whatever windowing system controls the particular hardware you're using. Similarly, OpenGL doesn't provide high-level commands for describing models of three-dimensional objects. Such commands might allow you to specify relatively complicated shapes such as automobiles, parts of the body, airplanes, or molecules. With OpenGL, you must build up your desired model from a small set of geometric primitives - points, lines, and polygons.
A sophisticated library that provides these features could certainly be built on top of OpenGL. The OpenGL Utility Library (GLU) provides many of the modeling features, such as quadric surfaces and NURBS curves and surfaces. GLU is a standard part of every OpenGL implementation. Also, there is a higher-level, object-oriented toolkit, Open Inventor, which is built atop OpenGL, and is available separately for many implementations of OpenGL.
#include<GL/glut.h>
#include<gl/GL.h>

These two header files are must to compile our code.

Understanding openGL:

Let us understand how each function in openGL helps us in achieving visualization we desire to make. Let us go through a code which was written to make a visualization of a solid cube, cone and a sphere with some added features like color and lighting. Go through the code and try getting an idea of what each function does and how commands are used. The code is followed by the output obtained after compiling and running the code. The output is then followed by complete description of each function, library and commands. I have also explained the mathematics behind the graphics.


CODE:
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <GL/glut.h>
#include <gl/GL.h>
#include <malloc.h>
 
//#define FREE_ARG1 char*
int g_rot_abt_x = 0, g_rot_abt_y=0, g_rot_abt_z=0;// variables that take input from            keyboard(in glutKeyboardFunc)

void first_initiations()
{
  // Following 12 lines shows us different values we pass to a corresponding function  
  glClearColor (0.0, 0.0, 0.0, 0.0);// to establish what color the window should be cleared to(here it is black)
  glShadeModel (GL_SMOOTH);// to provide a shading technique
  GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };//specular color of material
  GLfloat light_ambient[] = { 1.5, 0.0, 0.0, 0.0 };//ambient RGBA intensity of light
  GLfloat mat_shininess[] = { 50.0 };//specular exponent of the material
  GLfloat light_position[] = { 1.0, -1.0, 0.0, 0.0 };// (x,y,z,w) positional value of light
  GLfloat light1_position[] = { 1.0, 1.0, 0.0, 1.0 };// (x,y,z,w) positional value of light1
  GLfloat light2_position[] = { 1.0, -1.0, 0.0, 1.0 };// (x,y,z,w) positional value of light2(we fixed three lighting arrangements)
  GLfloat light1_diffuse[] = { 1.0, 0.0, 0.0, 1.0 };//diffuse RGBA intensity of light1
  GLfloat light2_diffuse[] = { 0.0, 0.5, 0.0, 1.0 };//diffuse RGBA intensity of light2
  GLfloat spot_direction[] = { 0.0, -1.0, 1.0 };//(x,y,z) direction of spot light
  GLfloat spot_direction1[] = { 1.0, -1.0, 0.0 };//(x,y,z) direction of spot light1(we fixed two spot lights)

  // Following 11 lines shows different functions that help us in coloring the visualization
  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);//command that applies specified property to the material on a specified face
  glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);//command that applies specified property to the material on a specified face
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);//command that specifies and applies position of light0
  glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);//command that specifies and applies ambient property to  light0
  glLightfv(GL_LIGHT1, GL_POSITION, light1_position);//command that specifies and applies position of light1
  glLightfv(GL_LIGHT1, GL_POSITION, light2_position);//command that specifies and applies position of light2
  glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);//command that specifies and applies diffuse property to light1
  glLightfv(GL_LIGHT2, GL_DIFFUSE, light2_diffuse);//command that specifies and applies diffuse property to light2
  glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 45.0);// to specify the spot cut off(the scattering angle)
  glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, spot_direction);// to specify the spotlight direction(from where the spot light starts)
  glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 2.0);// to specify the spot exponent(the intensity of light)

  /*glLightf(GL_LIGHT2, GL_SPOT_CUTOFF, 45.0);
  glLightfv(GL_LIGHT2, GL_SPOT_DIRECTION, spot_direction1);
  glLightf(GL_LIGHT2, GL_SPOT_EXPONENT, 2.0);*/


  glEnable(GL_LIGHTING);// enable openGL to perform lighting calculations specified
  glEnable(GL_LIGHT0);// enables light0
  glEnable(GL_LIGHT1);// enables light1
  glEnable(GL_LIGHT2);// enables light2
  glEnable(GL_DEPTH_TEST);// to update depth comparisions and depth buffers
}

void myReshape(int w, int h)// Function that modifies the current drawing window
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h);// to adjust pixel rectangle for drawing to be the entire new window(upper left corner is (w,h))
   glMatrixMode (GL_PROJECTION);       //  prepare for modifying the projection matrix
   glLoadIdentity ();  // initialize the projection(current) matrix
   /*if (w <= h)
     glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
        1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
  else
     glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
        1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);*/

   glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);// to multiply the current matrix with orthographic matrix
//glOrtho(g_x_coord_min-40, g_x_coord_max+40, g_y_coord_min-40, g_y_coord_max+40, g_z_coord_min-40, g_z_coord_max+40);
   glMatrixMode (GL_MODELVIEW);        // back to modelview matrix as current matrix
}

void display(void)
{
 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// clears all the buffers obtained by bitwise OR of both buffers
 glMatrixMode(GL_MODELVIEW);// set modelview matrix as the current matrix
 glPushMatrix();// puts the current matrix on top of the matrix stack
 glRotatef((GLfloat)g_rot_abt_z, 0.0, 0.0, 1.0);//rotates the visualization about z-axis by an angle given by g_rot_abt_z
 glRotatef((GLfloat)g_rot_abt_y, 0.0, 1.0, 0.0);//rotates the visualization about y-axis by an angle given by g_rot_abt_y
 glRotatef((GLfloat)g_rot_abt_x, 1.0, 0.0, 0.0);//rotates the visualization about x-axis by an angle given by g_rot_abt_x
 glTranslatef(-0.5, 0.0, 0.0);// translates the object given by (x,y,z)
 glutSolidSphere (0.2, 20, 16);// renders a solid sphere given by (radius, longitudes, latitudes)
 /*glRotatef((GLfloat)g_rot_abt_z, 0.0, 0.0, 1.0);
 glRotatef((GLfloat)g_rot_abt_y, 0.0, 1.0, 0.0);
 glRotatef((GLfloat)g_rot_abt_x, 1.0, 0.0, 0.0);*/
 glEnd(); // ends the drawing
 glTranslatef(1.0, -0.5, 0.0);// translates the visualization(new area) accordingly
 glutSolidCube (0.2);// here it renders a solid cube given by (side length)
 glEnd(); // ends the drawing
 glTranslatef(0.0, 1.0, 0.0); // translates the visualization(new area) accordingly
 glutSolidCone (0.3, 0.4, 20, 16);// Renders the solid cone (in new area) given by (base, height, longitudes, latitudes)                  
 glPopMatrix();// replaces current matrix with one below it on the stack
 //glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glFlush();// ensures drawing commands are executed rather than storing in a buffer
}
void keyboard(unsigned char key, int x, int y) // get keyboard input to change the camera view
{
 switch(key){
 case 'x' :
   g_rot_abt_x = (g_rot_abt_x + 5)%360; // to change the angle(to rotate about x-axis) with each input/type
   glutPostRedisplay(); // updates and redisplays the updated visualization
break;
 case 'X' :
   g_rot_abt_x = (g_rot_abt_x - 5)%360; // to change the angle(to rotate about x-axis) with each input/type in opposite direction
   glutPostRedisplay(); // updates and redisplays the updated visualization
break;
 case 'y' :
   g_rot_abt_y = (g_rot_abt_y + 5)%360; // to change the angle(to rotate about y-axis) with each input/type
   glutPostRedisplay(); // updates and redisplays the updated visualization
break;
 case 'Y' :
   g_rot_abt_y = (g_rot_abt_y - 5)%360; // to change the angle(to rotate about y-axis) with each input/type in opposite direction
   glutPostRedisplay(); // updates and redisplays the updated visualization
break;
 case 'z' :
   g_rot_abt_z = (g_rot_abt_z + 5)%360; // to change the angle(to rotate about z-axis) with each input/type
   glutPostRedisplay(); // updates and redisplays the updated visualization
break;
 case 'Z' :
   g_rot_abt_z = (g_rot_abt_z - 5)%360; // to change the angle(to rotate about z-axis) with each input/type in opposite direction
   glutPostRedisplay(); // updates and redisplays the updated visualization
break;
 }
}

int main(int argc, char **argv) // main function
{

 glutInit(&argc,argv);// to initialize the GLUT library
 glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);// to set the initial display mode, by    ORing the bit masks of two different modes
 glutInitWindowSize(600,600);// to set the intial window size(the width and height of the window, in pixels)
 glutInitWindowPosition(600,0);// to set the initial window position on screen(position in pixels)
 glutCreateWindow("All in one");// to create a window with a title "All in one"
 first_initiations();// to call a function where we make some initial modifications and add some features
 glutDisplayFunc(display);// to set the display callback for the current window, when normal plane for the window needs to be redisplayed
 glutReshapeFunc(myReshape);// to set a reshape callback for current window, to update the window(new coordinate system to draw)
 glutKeyboardFunc(keyboard);// to call a keyboard function for the current window(to take input from keyboard)
 glutMainLoop();// a routine to enter GLUT event processing loop(never returns and calls all necessary functions)
 return 0;
}




OUTPUT:




After changing the camera lens ( by pressing x, y, z appropriately), output looks like this:





DETAILED DESCRIPTION OF “EXECUTION AND WORKING”:

Regarding header files, #include <GL/glut.h> and #include <gl/GL.h> are must for successful compilation. So, make sure they are present in C :/> Program files>home directory>include.
When we start compiling the code, it starts from the main function. So let us start from the main function and look at each function and their corresponding commands as they occur. Each function is explained in detail in the order they occur (complete description of commands are given if it is a user defined function).



glutInit(&argc, argv) :

glutInit will initialize the GLUT library and negotiate a session with the window system. During this process, glutInit may cause the termination of the GLUT program with an error message to the user if GLUT cannot be properly initialized.

argc is a pointer to the program's unmodified argc variable from main. Upon return, the value pointed to by argc will be updated, because glutInit extracts any command line options intended for the GLUT library. Similarly, argv.

glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB):

glutInitDisplayMode sets the initial display mode. Syntax is “glutInitDisplayMode (unsigned int mode)”.  Here, ‘mode’ is generally the display mode, obtained by bitwise OR-ing of several mode values available in GLUT library. Here we wanted to set the display mode as a single buffered RGBA mode window.
The initial display mode is used when creating top-level windows, sub windows, and overlays to determine the OpenGL display mode for the to-be-created window or overlay.

glutInitWindowSize (600,600):

glutInitWindowSize(int width, int height) is the syntax. It gives initial width and height of the window to be created or that was created. This is only a suggestion given for a rough window size but the final size and position of the window screen is based on the reshape callback. Both width and height are in pixels.

glutInitWindowPosition(600,0):

glutInitWindowPosition(int x, int y) is the syntax. It gives position of the window to be created or that was created. Here also both the coordinate values are provided in pixels (the interconversion to the actual screen coordinates will be done automatically during execution)

glutCreateWindow ("All in one"):

glutCreateWindow ("title_name”) is the syntax. This function creates a new window with the title given by title_name and the window will be of size and position already provided in above functions. Here we get a new window of name All in one as you can observe in the output image shown above.

Now that we have created a window, we should assign primitive touches like the background color, shading technique that the drawing to be drawn requires. For that we define a new function called first_initiations ( ).

first_initiations ( ):

This is a user defined function. It calls the concerned function, which contains many commands, which are listed and explained below, in the order they occur.



glClearColor (0.0, 0.0, 0.0, 0.0)

glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) is the syntax. This command establishes what color the window is to be cleared to. Red, green, blue, alpha values given, are used by the glClear() to clear the buffers. These values can range from 0.0 to 1.0
Here we want the background to be black, hence we gave all values 0.0

glShadeModel (GL_SMOOTH) :

This command renders shading technique. “glShadeModel (GL_ mode)” is the syntax. Here, the ‘mode’ refers to the value representing the shading technique. Accepted values are GL_SMOOTH and GL_FLAT. In our code, smooth shading helps us in getting smooth surfaces. Change it to flat to observe the difference.

Color and lighting:

Some objects need color and lighting to bring the closeness to the real time visualizations. Every light has 3 components i.e., ambient, specular and diffuse.
*Ambient component refers to the background lighting coming from all directions.
*Diffuse component refers to lighting coming from a direction but gets scattered equally in all directions once it hits a surface.
*Specular component refers to the light coming from a direction but scatters/bounces in a particular direction (like that of a metal, steel).
*Spot cutoff refers to the angle by which a spot light is to be emitted.

A material’s color depends on the amount of light it reflects and absorbs. So, materials also have ambient, diffuse, specular colors.

Each variable has RGBA values mentioned.
mat_specular[] is a variable which provides RGBA intensity of the specular reflectance of the material.
light_ambient[] is a variable which provides RGBA intensity of ambient property of the light.
light_position[] refers to the position from where the light originates.
Similarly other variables. Such lights with positions mentioned creates the lights with the specified(required) components and interact with material and reaches our eye.

glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular):

glMaterialfv(GL_face, GL_parameter, parameter_value) is the syntax. This command specifies the material property of an object. ‘face’ refers to the face to which a material property should be applied to. This property is given by the ‘parameter’ whose value is given by the ‘parameter_value’.
Parameters can be SPECULAR, DIFFUSE, AMBIENT, SHININESS etc which will have appropriate values.
In our code, glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular) means, the front face of the object is applied specular component of reflectance given by mat_specular[].

glLightfv(GL_LIGHT0, GL_POSITION, light_position):

glLightfv(GL_light_number, GL_parameter, parameter_value) is the syntax. This commands specifies what component of light, of what value, the given light source should emit.
light_number refers to the light source concerned, in case of multiple light arrangements.
‘parameter’ refers to the component of concerned light source, whose value is given by the ‘parameter_value’.
Parameters can be POSITION, SPECULAR, DIFFUSE, AMBIENT, SPOT_CUTOFF  etc.
glLightfv(GL_LIGHT0, GL_POSITION, light_position) means light source LIGHT0 is positioned at a given coordinate(specified by light_position[]) while glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient) means that the light emitted by LIGHT0 contains the ambient component given by light_ambient[].

glEnable( GL_parameter ):

This function enables the specified parameter. This has to be used to turn on each light that we have setup.
glEnable(GL_LIGHTING) enables openGL to perform the lighting calculations to emit the light with desired components, before we turn each light ON.
glEnable(GL_LIGHT0)  turns ON light0.
glEnable(GL_DEPTH_TEST)  does depth comparisons and updates the depth buffer. Even if the depth buffer exists, it doesn’t get updated if it is not enabled.





Mathematics behind the lighting and color:


There are two types of light: directional (where source is at infinity) and positional (where source is at a particular point).
When we specify light’s position by (x, y, z, w) values, it refers to directional light if w is zero, otherwise it refers to positional light whose position is obtained by affine transformation.

A light source emits light in all directions. To emit in form of a cone, we use spotlight. Spot_cutoff gives the angle between axis and ray edge of cone.  Spot_direction gives the direction of axis of spot light.

The way we see the objects depends on the light we receive after it reflects from an object. That light is a combination of material property and light falling on it.
So, net resultant of a light and a material color is what defines our perception.
So if light has components (R1,G1,B1,A1) and material has (R2,G2,B2,A2), we receive light of (R1*R2, G1*G2, B1*B2, A1*A2) and that is final color we perceive.

When two lights with components (R1,G1,B1,A1) and (R2,G2,B2,A2) fall on our eye, openGL makes it as NET light with components (R1+R2,G1+G2,B1+B2,A1+A2).
Generally, we calculate the lighting at the vertex in case of polygons0. We need to know the normal vectors at each vertex, to perform calculations.


The color produced by lighting a vertex is computed as follows:
vertex color =
(the material emission at that vertex)  +
(the global ambient light scaled by the material's ambient property at that vertex )  +
(the ambient, diffuse, and specular contributions from all the light sources, properly attenuated).
After lighting calculations are performed, the color values are clamped (in RGBA mode) to the range [0,1].

Material Emission

It's the RGB value assigned to the GL_EMISSION parameter.

Scaled Global Ambient Light

The second term is computed by multiplying the global ambient light (as defined by the GL_LIGHT_MODEL_AMBIENT parameter) by the material's ambient property (GL_AMBIENT value as assigned with glMaterialfv ()):
Second term for each color = ambientlight model * ambientmaterial
Each of the R, G, and B values for these two parameters are multiplied separately to compute the final RGB value for this term: (R1R2, G1G2, B1B2).

Spotlight Effect

The spotlight effect evaluates to one of three possible values, depending on whether the light is actually a spotlight and whether the vertex lies inside or outside the cone of illumination produced by the spotlight:
  • 1, if the light isn't a spotlight (GL_SPOT_CUTOFF is 180.0).
  • 0, if the light is a spotlight, but the vertex lies outside the cone of illumination produced by the spotlight.
  • (max {v · d, 0})GL_SPOT_EXPONENT where:
v = (vx, vy, vz) is the unit vector that points from the spotlight (GL_POSITION) to the vertex.
d = (dx, dy, dz) is the spotlight's direction (GL_SPOT_DIRECTION), assuming the light is a spotlight and the vertex lies inside the cone of illumination produced by the spotlight.
The dot product of the two vectors v and d varies as the cosine of the angle between them; hence, objects directly in line get maximum illumination, and objects off the axis have their illumination drop as the cosine of the angle.

Ambient Term

The ambient term is simply the ambient color of the light scaled by the ambient material property:
ambientlight  *ambientmaterial

Diffuse Term

The diffuse term needs to take into account whether light falls directly on the vertex, the diffuse color of the light, and the diffuse material property:
(max {L · n, 0}) * diffuselight * diffusematerial
where:
L = (Lx, Ly, Lz) is the unit vector that points from the vertex to the light position (GL_POSITION).
n = (nx, ny, nz) is the unit normal vector at the vertex.

Specular Term

The specular term also depends on whether light falls directly on the vertex. If L · n is less than or equal to zero, there is no specular component at the vertex. (If it's less than zero, the light is on the wrong side of the surface.) If there's a specular component, it depends on the following:
  • The unit normal vector at the vertex (nx, ny, nz).
  • The sum of the two unit vectors that point between (1) the vertex and the light position (or light direction) and (2) the vertex and the viewpoint (assuming that GL_LIGHT_MODEL_LOCAL_VIEWER is true; if it's not true, the vector (0, 0, 1) is used as the second vector in the sum). This vector sum is normalized (by dividing each component by the magnitude of the vector) to yield s = (sx, sy, sz).
  • The specular exponent (GL_SHININESS).
  • The specular color of the light (GL_SPECULARlight).
  • The specular property of the material (GL_SPECULARmaterial).


Using these definitions, here's how OpenGL calculates the specular term:
(max {s · n, 0})shininess * specularlight * specularmaterial
However, if L · n = 0, the specular term is 0.

Contributions from Light Sources

Each light source may contribute to a vertex's color, and these contributions are added together. The equation for computing each light source's contribution is as follows:
contribution = attenuation factor * spotlight effect *(ambient term + diffuse term +                    specular term)

Attenuation Factor:

where,
d = distance between the light's position and the vertex
kc = GL_CONSTANT_ATTENUATION
kl = GL_LINEAR_ATTENUATION
kq = GL_QUADRATIC_ATTENUATION
If the light is a directional one, the attenuation factor is 1.
By default, kc is 1.0 and both kl and kq are zero, but you can give these parameters different values:
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2.0);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.5);


Using the definitions of terms described in the preceding paragraphs, the following represents the entire lighting calculation in RGBA mode:
vertex color = emissionmaterial +
ambientlight model * ambientmaterial +
[ambientlight *ambientmaterial +
(max { L · n , 0} ) * diffuselight * diffusematerial +
(max { s · n , 0} )shininess * specularlight * specularmaterial ] i






Coming back to the functions used in code, display callback is next function.

glutDisplayFunc(display):

glutDisplayFunc sets the display callback for the current window. The display callback is called with no parameters. The entire normal plane region will be redisplayed in response to the callback.

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT):

This command clears buffers to preset values. glClear(GL_mask) is the syntax.
Bitwise OR-ing of masks are done and those buffers are cleared. The four masks are GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_ACCUM_BUFFER_BIT, and GL_STENCIL_BUFFER_BIT. Here in our code, it clears the color and depth buffers.

glMatrixMode(GL_MODELVIEW):

glMatrixMode(GL_matrix) is the syntax. This command tells which matrix is the current matrix, which means further matrix operations affects this current matrix. Matrix can be GL_MODELVIEW, GL_PROJECTION, and GL_TEXTURE.
In our case, modelview matrix is set as current matrix . So, further matrix transformations are performed on this current matrix.


glPushMatrix( ):

This command pushes the current matrix stack down by one. It duplicates current matrix i.e., top of stack and the one below it are same, the current matrix.

glRotate( ):

This command multiplies the current matrix by a matrix (modifying accordingly) that rotates the object by the angle provided in CCW direction about the specified point (x, y, z).
glRotatef(angle, x, y, z) is the syntax.


glTranslate( ):

This multiplies the current matrix by a matrix that translates the object by the given coordinates. glTranslatef(x, y, z) is the syntax.

Mathematical logic: Here, for such modeling transformations like glRotate() and glTranslate(), what exactly happens is that a new matrix, whose entries are according to the arguments present in the function, will get multiplied to the CURRENT matrix. This product matrix obtained after matrix multiplication will have the effect.
Let us say, C is the current matrix (say modelview natrix). When we write glRotatef( , x, y, 0.0), this is what happens:
This function generates three new matrices,
1   0   0                            cos    sin    0              1    0    0         
0   1   0                           -sin    cos    0              0    1    0   
-x  -y   1                              0           0      1              x     y    1                                        
                                                   

Call the first one as L, second as M and third as N. Let us assume C, current matrix denotes the current object configuration. (or we can assume it to be a point’s configuration matrix i.e., C = x  y  1).
This glRotate() function carries out this matrix multiplication: C = C*L*M*N, which means the C is updated by its transformed matrix and thus we could see that the object got rotated by  about a point (x, y) [assuming 2D transformation].
This is how any transformation is done, let it be scaling, stretching, shrinking, translating, rotating etc.  

glutSolidSphere (  ) renders solid sphere.
glutSolidSphere(GLdouble radius, Glint slices, Glint stacks) is syntax.
Similarly, glutSolidCube (GLdouble size) renders a solid cube.
glutSolidCone(GLdouble base, GLdouble height, Glint slices, Glint stacks) renders a solid cube.
Slices are number of subdivisions around Z axis while stacks along Z axis.

glEnd() ends the drawing (to stop the continuity between two images)
Finally, glFlush() ensures that the drawing commands are actually executed rather than stored in a buffer awaiting additional OpenGL commands.


Reshape callback:

Next, we have a user defined function myReshape(int w, int h) where we modify the current window permanently.

glViewport (0, 0, (GLsizei) w, (GLsizei) h):

The projection transformation specifies the mechanics of how the mapping should occur, and the viewport indicates the shape of the available screen area into which the scene is mapped.
glViewport (GLint x, GLint y, GLsizei width, GLsizei height) is syntax.
Defines a pixel rectangle in the window into which the final image is mapped. The (x, y) parameter specifies the lower-left corner of the viewport, (0, 0) in our example
and width and height are the size of the viewport rectangle.  The arguments to glViewport() describe the origin of the available screen space within the window and the width and height of the available screen area, all measured in pixels on the screen.


glLoadIdentity ( ):

glLoadIdentity() is used to initialize the current projection matrix so that only the specified projection transformation has an effect.
You use the glLoadIdentity() command to clear the currently modifiable matrix for future transformation commands, since these commands modify the current matrix. Typically, you always call this command before specifying projection or viewing transformations, but you might also call it before specifying a modeling transformation.
void glLoadIdentity(void);
Sets the currently modifiable matrix to the 4 x 4 identity matrix.
If you want to specify explicitly a particular matrix to be loaded as the current matrix, use glLoadMatrix*(). Similarly, use glMultMatrix*() to multiply the current matrix by the matrix passed in as an argument. The argument for both these commands is a vector of sixteen values (m1, m2, ... , m16) that specifies a matrix M as follows:


glOrtho( ):

glOrtho( ), specifies the coordinate system OpenGL assumes as it draws the final image and how the image gets mapped to the screen. In our code, current matrix is multiplied by given matrix (provided by glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0) ).


glutKeyboardFunc(keyboard):

This function takes input from keyboard. It sets the keyboard callback for the current window. When a user types into the window, each key press generating an ASCII character will generate a keyboard callback. The key callback parameter is the generated ASCII character. The state of modifier keys such as Shift cannot be determined directly; their only effect will be on the returned ASCII data.


In our code, if we press x, first case gets executed and the variable g_rot_abt_x gets modified and since this new value was used in glRotate(), enables the visualization to rotate about X axis. Similarly when you press X (i.e., shift+x), it rotates in opposite direction. Each time a variable gets modified, we are calling glutPostRedisplay() to redisplay the updated window explicitly. Thus keyboard input can be used a camera position changer.

glutMainLoop():

glutMainLoop() enters the GLUT event processing loop. This routine should be called at most once in a GLUT program. Once called, this routine will never return. It will call as necessary any callbacks that have been registered.