Please send suggestions or corrections for this page to the author at sbuss@ucsd.edu.

This page describes some of the features of Modern OpenGL used in the SolarModern program. This is the third of a series of programs illustrating features of Modern OpenGL. The first two programs, SimpleDrawModern and SimpleAnimModern, already introduced many of the basic features of any OpenGL program, and these were described on their web pages. This webpage only discusses aspects of the software which are new to SolarModern.

The code fragments listed below are taken from the program SolarModern.cpp. It is recommended to refer to the source code as you read this web page to see how the code fits together. In some cases, the code examples given below are slightly modified to make this web page more readable.

The SolarModern program renders a large yellow sun, being orbited by a blue earth, which in turn is orbited by a smaller white moon. The GlGeomSphere class is used to render spheres. The LinearMapR3 and LinearMapR4 classes are used to handle matrices.

I. GlGeomSphere - rendering the sun, earth and moon as wireframe spheres

The SolarModern program uses the GlGeomSphere class to triangulate and render spheres. These are drawn in wireframe so as to make their spherical structure evident. These classes encapsulate all the work of allocating and loading a VAO and VBO with data triangulating a unit sphere. They also take care of issuing the glDrawElements to render the sphere.

The three spheres for the sun, the earth, and the moon are allocated with constructors:

    GlGeomSphere Moon1(6, 6);    // A sphere with 6 slices and 6 stacks
    GlGeomSphere Earth(8, 12);   // A sphere with 8 slices and 12 stacks
    GlGeomSphere Sun(10, 10);    // A sphere with 10 slices and 10 stacks

The GlGeomSphere constructors are called with two parameters: the numbers slices and the number of stacks. The number of slices means number of radial slices in the sphere. The number of stacks means the the sphere is split into that many horizontal pieces. The slices go parallel to the y-axis; the stacks are perpindicular to the y axis. The GlGeomSphere's are always rendered as spheres of radius 1 centered at the original, but the vertex shader uses a Modelview matrix to resize and reposition it as needed.

The following code is then called to set up the VAO, VBO, and EBO for the GlGeomSphere's. The value vertPos_loc is equal to 0: it is the vertex shader's location for the position of a vertex.

    // These routines take care of loading info into their VAO's, VBO's and EBO's.
    Sun.InitializeAttribLocations(vertPos_loc);
    Earth.InitializeAttribLocations(vertPos_loc);
    Moon1.InitializeAttribLocations(vertPos_loc);

When rendering the scene, myRenderScene invokes the following functions to render the sun, then later the earth, and finally the moon:

    Sun.Render();
    …
    Earth.Render();
    …
    Moon1.Render();

These routines bind the VAO associated with the GlGeomSphere, and render the sphere using glDrawElements.

For more information on the GlGeomSphere class, see the GlGeomShapes webpages.

II. Rendering with the Modelview matrix

The sun, earth and moon are rendered using Modelview matrices to place them at the correct location. These Modelview matrices are updated every time the scene is rendered so as to animate the solar system. There are two variables HourOfDay and DayOfYear that control the position of the earth as it revolves around the sun, how the earth is rotated on its axis, and the position of the moon as it revolves around the earth.

The view matrix is a LinearMapR4 object; namely a 4x4 matrix. It computed by:

    viewMatrix.Set_glTranslate(0.0, 0.0, -CameraDistance);  // Translate to be in front of the camera
    viewMatrix.Mult_glRotate(viewAzimuth, 1.0, 0.0, 0.0);   // Rotate to view from slightly above   

The first line sets viewMatrix to a translation matrix that puts the camera CameraDistance out the z-axis. The second line multiplies by a matrix that rotates the scene around the x-axis by viewAzimuth radians: the direction of the rotation is given by the righthand rule. CameraDistance is equal 30.0; viewAzimuth is initially equal to 0.25 radians. These place the solar system in front of the camera, viewed slightly from above.

The sun is rendered with the following code:

    float matEntries[16];        // Holds 16 floats (since cannot load doubles into a shader that uses floats)
    …
    LinearMapR4 SunPosMatrix = viewMatrix;    // Place Sun at center of the scene
    SunPosMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glVertexAttrib3f(vertColor_loc, 1.0f, 1.0f, 0.0f);     // Make the sun yellow
    Sun.Render();

The call to SunPosMatrix.DumpByColumns(matEntries) puts into the matEntries array, the 16 entries of the 4x4 matrix SunPosMatrix as float's in column order. The call to glUniformMatrix4fv loads these 16 values into the Modelview matrix used by the shader program. The Modelview matrix is a uniform variable for the shader program: this means that both the vertex and fragments shaders have access to this variable. The value modelviewMatLocation is the shader program's location number for the Modelview matrix.

The call to Sun.Render() renders the Sun as a unit sphere, colored yellow.

The earth is then rendered with the following code:

    // EarthPosMatrix - specifies position of the earth
    // EarthMatrix - specifies the size of the earch and its rotation on its axis (on its axis).
    LinearMapR4 EarthPosMatrix = SunPosMatrix;
    double revolveAngle = (DayOfYear / 365.0)*PI2;
    EarthPosMatrix.Mult_glRotate(revolveAngle, 0.0, 1.0, 0.0);   // Revolve the earth around the sun
    EarthPosMatrix.Mult_glTranslate(0.0, 0.0, 5.0);        // Place the earth five units away from the sun

    LinearMapR4 EarthMatrix = EarthPosMatrix;
    double earthRotationAngle = (HourOfDay / 24.0)*PI2;
    EarthMatrix.Mult_glRotate(earthRotationAngle, 0.0, 1.0, 0.0);   // Rotate earth on y-axis
    EarthMatrix.Mult_glScale(0.5);                                  // Make radius 0.5.
    EarthMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glVertexAttrib3f(vertColor_loc, 0.2f, 0.4f, 1.0f);     // Make the earth bright cyan-blue
    Earth.Render();

The earth is orbiting at distance 5.0 units from the sun. The EarthPosMatrix is initialized to equal the SunPosMatrix, so it inherits the positioning of the sun. The first call to Mult_glRotate multiplies the EarthPosMatrix on the right with the rotation matrix generating a rotation of revolveAngle radians around the y-axis; the direction of rotation is given by the righthand rule. The next line calls the method Mult_glTranslate(0.0, 0.0, 5.0). This multiplies on the right by the matrix that translates distance 5.0 along the positive z. Together these two lines control the position of the earth as it revolves around the sun. The EarthPosMatrix is then copied to the EarthMatrix. The next lines control the orientation of the earth it rotates on its axis. Finally, the call to the method Mult_glScale(0.5) multiplies by the uniform scaling matrix to shrink the size of the earth by the factor 0.5.

The moon is rendered by the following code. It works similarly to the way the earth was rendered. Note that the moon's matrix is based on the EarthPosMatrix matrix, not the EarthMatrix. This is because the matrix EarthMatrix incorporates the earth's rotation on its axis and the scaling of the earth by 0.5; whereas the EarthPosMatrix does not. Thus the earth's rotation and scaling do not affect the rendering of the moon.

    LinearMapR4 MoonMatrix = EarthPosMatrix;      // Base the moon's matrix off the earth's *POS* matrix (EarthPosMatrix)
    double moonRotationAngle = (DayOfYear*12.0 / 365.0)*PI2;
    MoonMatrix.Mult_glRotate(moonRotationAngle, 0.0, 1.0, 0.0);  // Revolving around the earth twelve times per year
    MoonMatrix.Mult_glTranslate(0.0, 0.0, 1.0);        // Place the Moon one unit away from the earth
    MoonMatrix.Mult_glScale(0.2);                      // Moon has radius 0.2
    MoonMatrix.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glVertexAttrib3f(vertColor_loc, 0.9f, 0.9f, 0.9f);  // Make the moon bright gray
    Moon1.Render();

III. Animation code - fixed step and real time

SolarModern tries to animate smoothly at 60 frames per second. For the main program loop uses

    glfwWaitEventsTimeout(1.0/60.0);       // Use this to animate at 60 frames/sec (timing is NOT reliable)

to request a callback for rendering 60 times per second. In "fixed-timestep" mode, the animation increments the motion of the earth and moon by 1/60th of a second. This timing is not always reliable; for instance, if there is mouse movement, there can be a callback for every pixel worth of mouse movement, and this can be much more frequent than 60 times per second. The net result can be faster animation speed when the mouse is moving!

To avoid this problem, "real-timestep" mode can be used. (Use the "t" command to toggle fixed- and real-timestep modes.) The routine glfwGetTime() is used in real-timestep mode to get the current time. The previous time and the current time are compared, and the elapsed time since the previous rendering is used in place of (1/60)-th to update the animation variables DayOfYear and HourOfDay. Here is the main part of the code:

    // Calculate the time step, either as AnimateIncrement or based on actual elapsed time
    double thisAnimateIncrement = AnimateIncrement;
    if (UseRealTime && !singleStep) {
        double curTime = glfwGetTime();
        thisAnimateIncrement *= (curTime - PreviousTime)*60.0;
        PreviousTime = curTime;
    }
    // Update the animation state
    HourOfDay += thisAnimateIncrement;
    DayOfYear += thisAnimateIncrement / 24.0;
    HourOfDay = HourOfDay - ((int)(HourOfDay / 24)) * 24;       // Wrap back to be in range [0,24)
    DayOfYear = DayOfYear - ((int)(DayOfYear / 365)) * 365;     // Wrap back to be in range [0,365)

Note how the variables DayOfYear and HourOfDay are kept in the range 0 to 365 and 0 to 24. This doesn't make an immediate functionality change, but can avoid loss of precision if the animation is run for a long time.

Two Boolean variables spinMode and singleStep are used to the control the "single step" mode. You should look at the code to see how these work.

Rotational aliasing: The earth is simulated as rotating on its axis, but when initially run, it will appear to not be rotating. This is due to the fact that the earth is modeled with eight slices; and if it rotates a multiple (1/8)-th of a full rotation, the slices will alias with the earth appearing to be in identical orientation from frame to frame. This mostly happens when using a fixed animation increment instead of using an animation increment based on the actual elapsed time.

IV. Using the Projection matrix for perspective transformations

SolarModern uses a perspective transformation for viewing the solar system. This will make the earth (and moon) larger on the screen when they are closer to the viewer, and smaller when they are farther away. It has the following features:

Here is the main code for the perspective transformation represented by the Projection matrix:

    double w = (width == 0) ? 1.0 : (double)width;
    double h = (height == 0) ? 1.0 : (double)height;
    double windowXmax, windowYmax;
    double aspectFactor = w * Ymax / (h * Xmax);   // == (w/h)/(Xmax/Ymax), ratio of aspect ratios
    if (aspectFactor>1) {
        windowXmax = Xmax * aspectFactor;
        windowYmax = Ymax;
    }
    else {
        windowYmax = Ymax / aspectFactor;
        windowXmax = Xmax;
    }

    // Using the max & min values for x & y & z that should be visible in the window,
    //        we set up the perspective projection.
    double zFar = -Zmin + CameraDistance;
    double zNear = Max(CameraDistance - Zmax, ZnearMin);
    theProjectionMatrix.Set_glFrustum(-windowXmax, windowXmax, -windowYmax, windowYmax, zNear, zFar);

    if (glIsProgram(shaderProgram1)) {
        glUseProgram(shaderProgram1);
        theProjectionMatrix.DumpByColumns(matEntries);
        glUniformMatrix4fv(projMatLocation, 1, false, matEntries);
    }

The call to Set_glFrustum sets theProjectionMatrix to be the 4x4 matrix of the legacy OpenGL glFrustum function. This maps a frustum into the 2x2 cube with x,y,z values all between -1 and 1. The first two parameters to Set_glFrustum set the front of the frustum to have x values ranging from -windowXmax to windowXmax. The next two parameters similarly bound the y extent of the front of the frustum. The last two parameters set the distances to the near and far clipping planes. The value zNear must be less than zFar. Objects at distance less than zNear or more than zFar will be clipped or culled, and thus not be rendered at all.

Note how the last two lines of code load the 16 entries of the projection matrix into the projection matrix used by the shader program. The Projection matrix is a uniform variable for the shader program; projMatLocation is the shader program's location for the Projection matrix.