Skip to main content

Computer Graphics and Multimedia: Assignment 2

Computer Graphics and Multimedia
Assignment 2
    • Notifications
    • Privacy
  • Project HomeComputer Graphics and Multimedia
  • Projects
  • Learn more about Manifold

Notes

Show the following:

  • Annotations
  • Resources
Search within:

Adjust appearance:

  • font
    Font style
  • color scheme
  • Margins
table of contents
  1. Module 1
    1. Introduction to Computer Graphics with WebGL
    2. Assignment 1
    3. Assignment 2
  2. Module 2
    1. Working with WebGL and JavaScript
    2. Assignment 1
    3. Assignment 2
  3. Module 3
    1. Animation and Geometric Transformations
    2. Assignment 1
    3. Assignment 2
  4. Module 4
    1. Viewing and Projections
    2. Assignment 1
    3. Assignment 2
  5. Module 5
    1. Lighting and Shading
    2. Assignment 1
    3. Assignment 2
  6. Module 6
    1. Texture Mapping and Matrix Stacks
    2. Assignment 1
    3. Assignment 2
  7. Module 7
    1. Skyboxes and Shadow Maps
    2. Assignment 1
    3. Assignment 2
  8. Module 8
    1. Modeling and Hierarchy - Building Scenes
    2. Assignment 1
    3. Assignment 2

CS 4722 - Computer Graphics and Multimedia

Module #6, Assignment #2


Exercise #1

Modify the given TextureCube application and call it BatmanCube so that instead of a checkered cube, as it becomes now:

It becomes an animated cube of Batman and other characters walking and running:

Along with the Rotate X, Rotate Y and Rotate Z buttons, you must also add a button to stop and start the rotation, and a button to stop and start the animation. Also, you must remove the colors from the cube, and give it an outline.

You will need to use the following six sprite sheets of walking and running images.

Note: To download the sprite images, right-click on the image and choose "Save Image As" or a similar option, and then save the images to a folder where you will find them and load them into Visual Studio later:


character1sprites.png:


character2sprites.png:


character3sprites.png:


character4sprites.png:


character5sprites.png:


character6sprites.png:


The partial HTML code is given here: TextureCube.html.

The partial JavaScript code is given here: TextureCube.js.


To animate the characters you will need to store your images and then load them with code in your HTML file like so:

    <img id="animImage1" src="images/character1sprites.png" hidden />
    <img id="animImage2" src="images/character2sprites.png" hidden />
    <img id="animImage3" src="images/character3sprites.png" hidden />
    <img id="animImage4" src="images/character4sprites.png" hidden />
    <img id="animImage5" src="images/character5sprites.png" hidden />
    <img id="animImage6" src="images/character6sprites.png" hidden />

Then in your JavaScript file, you should create a global variable called "imgIndex" and set it to 1. And then add the following method that makes use of the setTimeout() JavaScript event handler to repeatedly load different sprite images into the texture map every 100 milliseconds:


function flipImage() {
    setTimeout(function () {
        ++imgIndex;
        if (imgIndex > 6)
            imgIndex = 1;

        configureTexture(document.getElementById("animImage" + imgIndex));

        flipImage();
    }, 100);
}


Then add a call to "flipImage()" at the bottom of your init() method.

In addition to that, you need to create a border on the edges of the cube, and to do that you should create something like a "drawBlack" uniform field to determine when you are drawing the black border versus when you are drawing one of the faces of the cube. The code for this in your Fragment Shader would look something like the following:


<script id="fragment-shader" type="x-shader/x-fragment">
    precision mediump float;

    varying vec4 fColor;
    varying vec2 fTexCoord;

    uniform sampler2D texture;
    uniform bool drawBlack;

    void main()
    {
        if (drawBlack)
        {
            gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
        }
        else
        {
            gl_FragColor = fColor * texture2D( texture, fTexCoord );
        }
    }
</script>


Then, in your JavaScript file you will need to create your "drawBlackLoc" global variable, then set it in your init() method like so:

    drawBlackLoc = gl.getUniformLocation(program, "drawBlack");
    gl.uniform1i(drawBlackLoc, false);

And then set and unset the drawBlack uniform variable with the following code in your render method:

    gl.uniform1i(drawBlackLoc, false);
    gl.drawArrays(gl.TRIANGLES, 0, numVertices);

    gl.uniform1i(drawBlackLoc, true);
    for (var i = 0; i < 6; ++i) {
        gl.drawArrays(gl.LINES, i * 6, 2);
        gl.drawArrays(gl.LINES, i * 6 + 1, 2);
        gl.drawArrays(gl.LINES, i * 6 + 4, 2);
    }

Next in your init() method, you need to remove the following line because you won't be showing the checker image bitmap anymore:

    configureTexture(checkerImage);

And then in your configureTexture() method, you will need to change the following line, that is only valid for a bitmap image:

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize, texSize, 0,

On that line, remove the "texSize, texSize, 0," so you end up with just:

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,

This change makes the code work for non-bitmap images.

And finally, you will need to change the textCoord array so that it identifies the coordinates of the different images for the different sides. Right now the array is:

var texCoord = [
    vec2(0, 0),
    vec2(0, 1),
    vec2(1, 1),
    vec2(1, 0)
];

These 4 coordinates are the coordinates of the 4 corners of the entire texture image. Instead of justing using those 4 coordinates, you need the 4 coordinates of the first image, then the 4 coordinates of the second image, and so on up to the 4 coordinates of the sixth image. Each of those 4 coordinates will be in their own separate sub-array. So you will need to change the textCoord array to the following:

var texCoord = [
    [ vec2(0, 0), vec2(0, 0.5), vec2(0.25, 0.5), vec2(0.25, 0) ],
    [ vec2(0, 0.5), vec2(0, 1), vec2(0.25, 1), vec2(0.25, 0.5) ],
    [ vec2(0.25, 0), vec2(0.25, 0.5), vec2(0.5, 0.5), vec2(0.5, 0) ],
    [ vec2(0.25, 0.5), vec2(0.25, 1), vec2(0.5, 1), vec2(0.5, 0.5) ],
    [ vec2(0.5, 0), vec2(0.5, 0.5), vec2(0.75, 0.5), vec2(0.75, 0) ],
    [ vec2(0.5, 0.5), vec2(0.5, 1), vec2(0.75, 1), vec2(0.75, 0.5) ]
];

Next, in the colorCube() method we will need to identify which face texture image goes with which face in our call to the quad() method. To do that add the face image index as a fifth argument. To do that just replace the colorCube() method with the following code:

function colorCube() {
    quad(1, 0, 3, 2, 0);
    quad(2, 3, 7, 6, 1);
    quad(3, 0, 4, 7, 2);
    quad(5, 1, 2, 6, 3);
    quad(4, 5, 6, 7, 4);
    quad(5, 4, 0, 1, 5);
}

Now we need to change the quad() method so that it uses the right set of coordinates for each face by using that fifth argument. The first thing to do is to change the header of the quad() method to:

function quad(a, b, c, d, faceIndex) {

Next, we have to use faceIndex inside the quad() method. Right now the first texture coordinate is identified in the line:

    texCoordsArray.push(texCoord[0]);

Since the texCoord array has an added dimension now, this line needs to be changed to the following:

    texCoordsArray.push(texCoord[faceIndex][0]);

Make a similar change to the other texCoordsArray lines, and then you should be able to see your animated images. However, they will be showing up merged with the color for that side of the cube, and to fix that you need to make sure that the color being stored on the colorsArray is always white. White is at index 5 in the vertexColors array, so to fix the problem, all you have to do is change each of the following lines:

    colorsArray.push(vertexColors[c]);

Change each of these lines to:

    colorsArray.push(vertexColors[5]);


Now all you need to do is figure out how to get your "Toggle Rotation" button (which starts and stops the rotation) and your "Toggle Animation" button (which starts and stops the animation) to work. After that you should be all done -- unless you are experiencing the CORS error.



If you are experiencing the CORS error, you should be able to fix it by making these changes:



First, add the following six <script> tags below your other <script> tags in your HTML file:

    <script type="text/javascript" 
     src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/character1sprites.js"></script>
    <script type="text/javascript" 
     src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/character2sprites.js"></script>
    <script type="text/javascript" 
     src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/character3sprites.js"></script>
    <script type="text/javascript" 
     src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/character4sprites.js"></script>
    <script type="text/javascript" 
     src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/character5sprites.js"></script>
    <script type="text/javascript" 
     src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/character6sprites.js"></script>



Then, in your JavaScript file above your init() method, add the following declarations:

var imagesLoaded = 0;
var image1, image2, image3, image4, image5, image6;
var imageMap = [null, null, null, null, null, null];
var texture1, texture2, texture3, texture4, texture5, texture6;
var textureMap = [null, null, null, null, null, null];



Then, in your JavaScript file inside your init() method, above your line calling "flipImage();", add the following lines:

    image1 = new Image();
    image1.onload = function () {
        configureTextureMap(image1, 1);
    }
    image1.src = filecharacter1sprites;

    image2 = new Image();
    image2.onload = function () {
        configureTextureMap(image2, 2);
    }
    image2.src = filecharacter2sprites;

    image3 = new Image();
    image3.onload = function () {
        configureTextureMap(image3, 3);
    }
    image3.src = filecharacter3sprites;

    image4 = new Image();
    image4.onload = function () {
        configureTextureMap(image4, 4);
    }
    image4.src = filecharacter4sprites;

    image5 = new Image();
    image5.onload = function () {
        configureTextureMap(image5, 5);
    }
    image5.src = filecharacter5sprites;

    image6 = new Image();
    image6.onload = function () {
        configureTextureMap(image6, 6);
    }
    image6.src = filecharacter6sprites;



Then, in your JavaScript file inside your flipImage() method, replace the following line:

    configureTexture(document.getElementById("animImage" + imgIndex));

with the following line:

    gl.bindTexture(gl.TEXTURE_2D, textureMap[imgIndex-1]);



And lastly, in your JavaScript file below your flipImage() method, add the following method:

function configureTextureMap(img, imgNum) {
    imageMap[imgNum - 1] = img;
    if (++imagesLoaded < 6) {
        return;
    }
    for (var i = 1; i <= 6; ++i) {
        switch (i) {
            case 1:
                texture1 = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, texture1);
                break;
            case 2:
                texture2 = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, texture2);
                break;
            case 3:
                texture3 = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, texture3);
                break;
            case 4:
                texture4 = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, texture4);
                break;
            case 5:
                texture5 = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, texture5);
                break;
            default:
                texture6 = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, texture6);
                break;
        }
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
            gl.RGBA, gl.UNSIGNED_BYTE, imageMap[i - 1]);
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
            gl.NEAREST_MIPMAP_LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    }

    textureMap = [texture1, texture2, texture3, texture4, texture5, texture6];
}



If you were experiencing the CORS error, after the above changes your program should work.



Exercise #2

Create a program called MoonOrbit that uses a SphereMap of the Earth and the Moon to create an animation of the Moon orbiting around the Earth, while the Sun rotates around it in the opposite direction from the orthographic viewpoint of an eye looking at a fixed position on the Earth.

The shadow from the Moon will be traveling in one direction, while the shadow from the Sun will be going in the other direction. The scene will have a light source from the Sun that is off the screen and cannot be seen, and when the Moon comes in between the Sun and the Earth, it will cast its shadow on the Earth. Use a ShadowMap to create this effect. The dimensions of the Earth and the Moon and the radius of the Moon's orbit are not realistic, but they guarantee an eclipse shadow somewhere on the Earth during every complete orbit of the Moon, and provides an example of how a non-trivial ShadowMap works.

The partial HTML code is given here: SphereMap.html.

The partial Javascript code is given here: SphereMap.js.


Use the following images for your planetary bodies:

  • earth.png
  • moon.png


The first step you need to take, after changing the name of the program, to create the required features is to transform the SphereMap code so that you get the Earth and the Moon to show up togther next to each other and in the right dimensions. To do this we will remove the List Box from the front page, and then modify the loadImage() method so that it loads both images, instead of just one.


To remove the List Box from the program, in your HTML code, remove the following lines:

    <br />

    <select id="imageVal" size="2">
        <option value="moonImage" selected="selected">Moon</option>
        <option value="earthImage">Earth</option>
    </select>

Then go into your JavaScript code and remove the following lines:

    document.getElementById("imageVal").onchange =
      function (event) {
          loadImage(document.getElementById(event.target.value));
          createSphereMap();
      };


Next, in your JavaScript code, find the following declaration:

var radius = 2;

And change the 2 to a 1:

var radius = 1;


Next, add the following declarations:

var earthTexture, moonTexture;

var eScale = 1.3;
var mScale = 0.12;

var eTrans = [0.0, 1.0, 0];
var mTrans = [-2.6, 1.0, 0];

var earthTheta = 135;
var earthPhi = 10;

var moonTheta = 0;
var moonPhi = 0;

var orbitRad = 2.6;
var orbitAng = 3 * Math.PI / 4;

var modelViewStack = [];


Then in your init() method, replace the following line:

    loadImage(document.getElementById(document.getElementById('imageVal').value));

Replace it with the following 2 lines:

    loadImages(document.getElementById("earthImage"),
                     document.getElementById("moonImage"));


Then find your loadImage() method and replace it with the following new loadImages() methd:

function loadImages(earthImage, moonImage) {
    earthTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, earthTexture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, earthImage);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, earthTexture);

    moonTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, moonTexture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, moonImage);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, moonTexture);
}


Next, since we are sticking with an orthographic projection, the projectionMatrix won't change in this application, so we will move the code where it is being set from the render() method (because it is being called there repeatedly) to the init() method where it will be called only once. So go into the render() method and find the line:

    projectionMatrix = ortho(left, right, bottom, ytop, near, far);

And also find the line:

    gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix));

And move those two lines to the end of the init() method right above the "render();" line, so that when they are there, you will have the following three lines at the end of your init() method:

    projectionMatrix = ortho(left, right, bottom, ytop, near, far);
    gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix));

    render();


Then go back into your render() method, and find the following lines:

    gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);

Replace these two lines with the following lines:

    //////////// Render the Earth /////////////

    modelViewStack.push(modelViewMatrix);

    modelViewMatrix = mult(modelViewMatrix, translate(eTrans));
    modelViewMatrix = mult(modelViewMatrix, rotateX(earthPhi));
    modelViewMatrix = mult(modelViewMatrix, rotateY(earthTheta));
    modelViewMatrix = mult(modelViewMatrix, scalem(eScale, eScale, eScale));

    gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, earthTexture);
    gl.uniform1i(textureLoc, 0);

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);

    //////////// Render the Moon /////////////

    modelViewMatrix = modelViewStack.pop();

    modelViewMatrix = mult(modelViewMatrix, translate(mTrans));
    modelViewMatrix = mult(modelViewMatrix, rotateX(moonPhi));
    modelViewMatrix = mult(modelViewMatrix, rotateY(moonTheta));
    modelViewMatrix = mult(modelViewMatrix, scalem(mScale, mScale, mScale));
    gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));

    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, moonTexture);
    gl.uniform1i(textureLoc, 1);

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);



At this point you should be able to run your program and see the Moon next to the Earth, without special lighting, in the dimensions shown in the illustration above for this assignment. The next step is to add the Phong lighting and the Shadow Map.

To add the Phong lighting you need to follow the steps below:



Replace the Vertex Shader with the following code:

    attribute vec4 vPosition;
    attribute vec3 vNormal;
    attribute vec2 vTexCoord;

    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;

    uniform vec4 ambientProduct, diffuseProduct, specularProduct;
    uniform vec4 lightPosition;
    uniform float shininess;

    varying vec4 fColor;
    varying vec2 fTexCoord;

    void main()
    {
        vec3 pos = -(modelViewMatrix * vPosition).xyz;

        //fixed light postion
        vec3 light = lightPosition.xyz;
        vec3 L = normalize( light - pos );

        vec3 E = normalize( -pos );
        vec3 H = normalize( L + E );

        vec4 NN = vec4(vNormal,0);

        // Transform vertex normal into eye coordinates

        vec3 N = normalize( (modelViewMatrix*NN).xyz);

        // Compute terms in the illumination equation
        vec4 ambient = ambientProduct;

        float Kd = max( dot(L, N), 0.0 );
        vec4  diffuse = Kd*diffuseProduct;

        float Ks = pow( max(dot(N, H), 0.0), shininess );
        vec4  specular = Ks * specularProduct;

        if( dot(L, N) < 0.0 ) {
            specular = vec4(0.0, 0.0, 0.0, 1.0);
        }
        fColor = ambient + diffuse +specular;
        fColor.a = 1.0;

        gl_Position = projectionMatrix * modelViewMatrix * vPosition;
        fColor = ambient + diffuse +specular;
        fColor.a = 1.0;
        fTexCoord = vTexCoord;
    }


Replace your Fragment Shader with the following code:

    precision mediump float;
    
    varying vec4 fColor;
    varying vec2 fTexCoord;

    uniform sampler2D texture;

    void main()
    {
        gl_FragColor = fColor * texture2D(texture, fTexCoord);
    }


Add the following declarations to your JavaScript code:

var normalsArray = [];
var nBuffer;
var vNormal;

var lightAmbient = vec4(0.2, 0.2, 0.2, 1.0);
var lightDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
var lightSpecular = vec4(1.0, 1.0, 1.0, 1.0);

var moonMaterialAmbient = vec4(0.7, 0.7, 0.7, 1.0);
var moonMaterialDiffuse = vec4(0.7, 0.7, 0.7, 1.0);
var moonMaterialSpecular = vec4(0.0, 0.0, 0.0, 1.0);

var earthMaterialAmbient = vec4(0.8, 0.0, .8, 1.0);
var earthMaterialDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
var earthMaterialSpecular = vec4(0.0, 0.0, 0.0, 1.0);

var moonShininess = 60.0;
var earthShininess = 40.0;
var shininessLoc;

var lightTheta = 2.8;
var lightRad = 20;
var lightPosition = [0.0, 1.0, lightRad, 0.0];
var lightPositionLoc;

var ambientProduct, ambientProductLoc;
var diffuseProduct, diffuseProductLoc;
var specularProduct, specularProductLoc;

var lightingLoc;
var rotateLighting = true;


Add the following initializations to your init() method above the "render();" line:

    // Create normal buffer and vNormal attribute
    nBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW);
    vNormal = gl.getAttribLocation(program, "vNormal");
    gl.vertexAttribPointer(vNormal, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vNormal);

    // Setting Lighting variables and their Uniform Locations
    lightPositionLoc = gl.getUniformLocation(program, "lightPosition");
    gl.uniform4fv(lightPositionLoc, lightPosition);

    shininessLoc = gl.getUniformLocation(program, "shininess");
    ambientProductLoc = gl.getUniformLocation(program, "ambientProduct");
    diffuseProductLoc = gl.getUniformLocation(program, "diffuseProduct");
    specularProductLoc = gl.getUniformLocation(program, "specularProduct");


Then, to your createSphereMap() method add the following line after the "texCoordsArray = [];" line:

    normalsArray = [];


And while still in your createSphereMap() method, after the "texCoordsArray.push(uv3);" line, add the following lines:

            normalsArray.push(vec3(p1));
            normalsArray.push(vec3(p2));
            normalsArray.push(vec3(p3));
            normalsArray.push(vec3(p2));
            normalsArray.push(vec3(p4));
            normalsArray.push(vec3(p3));


And next, add the following method below your createSphereMap() method:

function setIllumination(isEarth) {
    if (isEarth) {
        ambientProduct = mult(lightAmbient, earthMaterialAmbient);
        diffuseProduct = mult(lightDiffuse, earthMaterialDiffuse);
        specularProduct = mult(lightSpecular, earthMaterialSpecular);
        gl.uniform1f(shininessLoc, earthShininess);
    }
    else {
        ambientProduct = mult(lightAmbient, moonMaterialAmbient);
        diffuseProduct = mult(lightDiffuse, moonMaterialDiffuse);
        specularProduct = mult(lightSpecular, moonMaterialSpecular);
        gl.uniform1f(shininessLoc, moonShininess);
    }

    gl.uniform4fv(ambientProductLoc, flatten(ambientProduct));
    gl.uniform4fv(diffuseProductLoc, flatten(diffuseProduct));
    gl.uniform4fv(specularProductLoc, flatten(specularProduct));
}


Then add the following code to your render() method right ABOVE your "modelViewMatrix = lookAt(eye, at, up);" line:

    lightTheta -= 0.01;
    if (lightTheta < 0)
        lightTheta += 2 * Math.PI;

    lightPosition[0] = lightRad * Math.sin(lightTheta);
    lightPosition[2] = lightRad * Math.cos(lightTheta);

    gl.uniform4fv(lightPositionLoc, lightPosition);


Then, while still in your render() method, you need to add the following line BELOW your "gl.uniform1i(textureLoc,0);" line, and be sure you add it BELOW the version of this line with a "0" and not a "1":

    setIllumination(true);


And next, just a little further DOWN, add the following line BELOW your "gl.uniform1i(textureLoc,1);" line, and be sure you add it BELOW the version of this line with a "1" and not a "0":

    setIllumination(false);


And next, just a little further UP, find the following line which you will replace:

    modelViewMatrix = mult(modelViewMatrix, translate(mTrans));

Replace this line with the following lines:

    orbitAng -= 0.005;
    var moonX = orbitRad * Math.cos(orbitAng);
    var moonZ = orbitRad * Math.sin(orbitAng)

    modelViewMatrix = mult(modelViewMatrix, translate(moonX, mTrans[1], moonZ));


At this point you should be able to run your program and see the Moon rotating around the Earth in one direction while a shadow from the light of the Sun is going around the Earth in the other direction. You do not see a shadow from the Moon yet because you have not added the Shadow Map code yet.

To add the Shadow Map you need to add an additional vertex shader and fragment shader. To accomplish this, you will need to take the following steps:


In your HTML code, change the Vertex Shader ID from id="vertex-shader", to id="vertex-shader2". And change your Fragment Shader ID from id="fragment-shader" to id="fragment-shader2".


Then ABOVE these two shaders, add the following two new shaders:

    <script id="vertex-shader1" type="x-shader/x-vertex">
        attribute vec4 vPosition;

        uniform mat4 pmvMatrixFromLight;

        void main()
        {
            gl_Position = pmvMatrixFromLight * vPosition;
        }
    </script>

    <script id="fragment-shader1" type="x-shader/x-fragment">
        precision mediump float;

        void main() {
            // Write the z-value in R
            gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);
        }
    </script>


Next, go into your second Vertex Shader method, "vertex-shader2", and add the following three declarations:

        uniform mat4 pmvMatrixFromLight;
        varying vec4 fPositionFromLight;
        varying float normShadowVal;


Next, add the following two lines as the last two lines of your second Vertex Shader method:

            fPositionFromLight = pmvMatrixFromLight * vPosition;
            normShadowVal = specular.a;


Next, go into your second Fragment Shader, "fragment-shader2", and replace all the code with the following:

        precision mediump float;

        uniform sampler2D shadowTexture;
        uniform sampler2D texture;
        uniform bool drawShadow;

        varying vec4 fColor;
        varying vec2 fTexCoord;
        varying vec4 fPositionFromLight;
        varying float normShadowVal;

        void main()
        {
            vec3 shadowCoord = (fPositionFromLight.xyz/fPositionFromLight.w)/2.0 + 0.5;

            vec4 rgbaDepth = texture2D(shadowTexture, shadowCoord.xy);

            float depth = rgbaDepth.r; // Retrieve the z-value from R

            float visibility = (drawShadow && shadowCoord.z > depth + 0.005 &&
                                             normShadowVal < 0.01) ? 0.3 : 1.0;

            gl_FragColor = vec4(fColor.rgb * visibility, fColor.a) * texture2D(texture, fTexCoord);
        }


Next, go into your JavaScript code and add the following declarations:

var OFFSCREEN_WIDTH = 2048, OFFSCREEN_HEIGHT = 2048;

var shadowProgram;

var shadowVBuffer;
var shadowVPosition;
var shadowTexture;
var shadowTextureLoc;

var framebuffer, depthBuffer;

var pmvMatrixFromLightLoc1, pmvMatrixFromLightLoc2;
var pmvMatrixFromLight1, pmvMatrixFromLight2;

var drawShadowLoc;


Next, in your previous JavaScript declarations find the following declaration:

var far = 10;

And change the 10 to 30:

var far = 30;


Next, In your init() method, find and replace the following lines:

    program = initShaders(gl, "vertex-shader", "fragment-shader");
    gl.useProgram(program);

    createSphereMap();

Replace these lines with the following lines:

    createSphereMap();

    setFBOs();
    shadowProgram = initShaders(gl, "vertex-shader1", "fragment-shader1");
    program = initShaders(gl, "vertex-shader2", "fragment-shader2");

    gl.useProgram(shadowProgram);

    shadowVBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, shadowVBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);
    shadowVPosition = gl.getAttribLocation(shadowProgram, "vPosition");
    gl.vertexAttribPointer(shadowVPosition, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(shadowVPosition);

    pmvMatrixFromLightLoc1 = gl.getUniformLocation(shadowProgram, "pmvMatrixFromLight");

    gl.useProgram(program);


Then ABOVE the last line of your init() method, ABOVE the "render();" line, add the following four lines:

    pmvMatrixFromLightLoc2 = gl.getUniformLocation(program, "pmvMatrixFromLight");
    shadowTextureLoc = gl.getUniformLocation(program, "shadowTexture");
    drawShadowLoc = gl.getUniformLocation(program, "drawShadow");
    gl.uniform1i(drawShadowLoc, false);


Then add the following method after the init() method:

// Sets up the Frame Buffer Objects
function setFBOs() {
    shadowTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA,
            gl.UNSIGNED_BYTE, null);

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

    gl.bindTexture(gl.TEXTURE_2D, null);

    // Allocate a frame buffer object
    framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

    // Attach color buffer
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
                                        gl.TEXTURE_2D, shadowTexture, 0);

    // create a depth renderbuffer
    depthBuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);

    // make a depth buffer and the same size as the targetTexture
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

    // check for completeness
    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    if (status != gl.FRAMEBUFFER_COMPLETE)
        alert('Framebuffer Not Complete');

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
}


Then find the following two lines that are inside of your init() method:

    loadImages(document.getElementById("earthImage"),
                     document.getElementById("moonImage"));

You placed these lines into your init() method earlier, and now they must be moved so that they go ABOVE some other lines that you also added to your init() method. Move the "loadImages(...);" lines up from where they are now so that they go right ABOVE the following lines in your init() method:

    createSphereMap();

    setFBOs();
    shadowProgram = initShaders(gl, "vertex-shader1", "fragment-shader1");
    program = initShaders(gl, "vertex-shader2", "fragment-shader2");


Then go to the TOP of your render() method and ADD the following code that sets up the FrameBuffer, then renders the scene to the Shadow Buffer before the scene is next rendered to the screen:


    /////////////////// Part 1 ////////////////////
    // This First Part goes to the Shadow Buffer //
    ///////////////////////////////////////////////

    gl.useProgram(shadowProgram);
    // send data to framebuffer for off-screen render
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

    gl.disableVertexAttribArray(vPosition);
    gl.disableVertexAttribArray(vNormal);
    gl.disableVertexAttribArray(vTexCoord);

    gl.enableVertexAttribArray(shadowVPosition);

    gl.bindBuffer(gl.ARRAY_BUFFER, shadowVBuffer);
    gl.vertexAttribPointer(shadowVPosition, 4, gl.FLOAT, false, 0, 0);

    gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


    ///////////// Light Source and Camera Eye ///////////////

    lightTheta -= 0.01;
    if (lightTheta < 0)
        lightTheta += 2 * Math.PI;

    lightPosition[0] = lightRad * Math.sin(lightTheta);
    lightPosition[2] = lightRad * Math.cos(lightTheta);

    eye = vec3(lightPosition[0], lightPosition[1], lightPosition[2]);
    lightTheta += 0.01;


    ///////////// Render the Earth ///////////////

    modelViewMatrix = translate(eTrans);
    modelViewMatrix = mult(modelViewMatrix, rotateX(earthPhi));
    modelViewMatrix = mult(modelViewMatrix, rotateY(earthTheta));
    modelViewMatrix = mult(modelViewMatrix, scalem(eScale, eScale, eScale));

    pmvMatrixFromLight1 = mult(projectionMatrix, lookAt(eye, at, up));
    pmvMatrixFromLight1 = mult(pmvMatrixFromLight1, modelViewMatrix);
    gl.uniformMatrix4fv(pmvMatrixFromLightLoc1, false, flatten(pmvMatrixFromLight1));

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);


    ///////////// Render the Moon ///////////////

    orbitAng -= 0.005;
    var shadowMoonX = orbitRad * Math.cos(orbitAng);
    var shadowMoonZ = orbitRad * Math.sin(orbitAng)
    orbitAng += 0.005;

    modelViewMatrix = translate(shadowMoonX, mTrans[1], shadowMoonZ);
    modelViewMatrix = mult(modelViewMatrix, rotateX(moonPhi));
    modelViewMatrix = mult(modelViewMatrix, rotateY(moonTheta));
    modelViewMatrix = mult(modelViewMatrix, scalem(mScale, mScale, mScale));

    pmvMatrixFromLight2 = mult(projectionMatrix, lookAt(eye, at, up));
    pmvMatrixFromLight2 = mult(pmvMatrixFromLight2, modelViewMatrix);
    gl.uniformMatrix4fv(pmvMatrixFromLightLoc1, false, flatten(pmvMatrixFromLight2));

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);   


    ///////////////// Part 2 /////////////////
    // This Second Part goes to the Screen  //
    //////////////////////////////////////////

    gl.useProgram(program);
    // send data to GPU for normal render
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    gl.disableVertexAttribArray(shadowVPosition);

    gl.enableVertexAttribArray(vPosition);
    gl.enableVertexAttribArray(vNormal);
    gl.enableVertexAttribArray(vTexCoord);

    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
    gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
    gl.vertexAttribPointer(vNormal, 3, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, tBuffer);
    gl.vertexAttribPointer(vTexCoord, 2, gl.FLOAT, false, 0, 0);


    ///////////// Camera Eye ///////////////

    eye = vec3(0.0, 0.0, 1.0);

    gl.viewport(0, 0, canvas.width, canvas.height);


Now you are almost done. BELOW the lines you have pasted is the code that will render the scene a second time, this time to the screen instead of the Shadow Buffer. You need to add some code to this second rendering in order to get this to work. All this code will go BELOW the lines you just added.


BELOW the lines you just added find the following line that draws the Earth, NOT the one that draws the Moon:

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);

REPLACE this line with the following lines:

    gl.activeTexture(gl.TEXTURE2);
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
    gl.uniform1i(shadowTextureLoc, 2);

    gl.uniformMatrix4fv(pmvMatrixFromLightLoc2, false, flatten(pmvMatrixFromLight1));
    gl.uniform1i(drawShadowLoc, true);
    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);
    gl.uniform1i(drawShadowLoc, false);


Next, BELOW the lines you just added find the following line that draws the Moon, NOT the one that draws the Earth:

    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);

REPLACE this line with the following lines:

    gl.activeTexture(gl.TEXTURE2);
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
    gl.uniform1i(shadowTextureLoc, 2);

    gl.uniformMatrix4fv(pmvMatrixFromLightLoc2, false, flatten(pmvMatrixFromLight2));
    gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);


Now you should be all done - unless you are experiencing the CORS error.



If you are experiencing the CORS error, you should be able to fix it by making these changes:



First, add the following two <script> tags below your other <script> tags in your HTML file:

    <script type="text/javascript" 
       src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/earth.js"></script>
    <script type="text/javascript" 
       src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/moon.js"></script>



Then, in your JavaScript file inside your init() method, find the following two lines:

    loadImages(document.getElementById("earthImage"),
                     document.getElementById("moonImage"));

And replace these lines with the following lines:

    var earthImg = new Image();
    earthImg.onload = function () {
        loadImageData(earthImg, 0);
    }
    earthImg.src = fileearthdata;

    var moonImg = new Image();
    moonImg.onload = function () {
        loadImageData(moonImg, 1);
    }
    moonImg.src = filemoondata;



And lastly, in your JavaScript file add the following loadImageData() method below your loadImages() method:

function loadImageData(img, actNum) {
    if (actNum == 0) {
        earthTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, earthTexture);
    }
    else {
        moonTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, moonTexture);
    }
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);
    if (actNum == 0) {
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, earthTexture);
    }
    else {
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, moonTexture);
    }
}



If you were experiencing the CORS error, after the above changes your program should work.


Add a Comment block section to the top of your Javascript program in this assignment with the following information filled in using the following format:

/*
 * Course: CS 4722
 * Section: .....
 * Name: ......
 * Professor: ......
 * Assignment #: ......
 */


Be sure your program runs without error.

Deliverables

Turn in the files:

  • BatmanCube.zip (with your HTML and JS files)
  • MoonOrbit.zip (with your HTML and JS files)

Do this by uploading the file as an attachment to this Module's assignment drop box in D2L Brightspace.

Save

Annotate

Next Chapter
Skyboxes and Shadow Maps
PreviousNext
Powered by Manifold Scholarship. Learn more at
Opens in new tab or windowmanifoldapp.org