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 #7, Assignment #2


Exercise #1

You will complete the ShadowMap application shown below so that the program creates a Shadow Map which allows you to cast a shadow onto the object on the left from the object on the right. Right now, there is no shadow being cast:



Not only will your changes create the required shadow, but you will also change the background color, and you will add controls so that you can select different objects, and so that you can move the object up and down vertically, and so that you can rotate the object:


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

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


The OBJ files you need which have been converted to ".js" files this time are below, and they all go inside of an "Objs" folder in your Visual Studio Project:

polyhedron.js
        battledrone.js
        duck.js
        teapot.js
        tree.js


There is one more ".js" file that you need to load that really is a JavaScript file this time, and it has code in it to load in the vertices from the Obj files. This file does not need to go into a sub-folder, instead it should be saved in the same folder as your ShadowMap.html file and your ShadowMap.js file. The file you need to load is:

LoadObjNoTexture.js


Now it is time to modify your HTML file. Because Shadow Maps require double-rendering, you need to have two Vertex shaders and two Fragment shaders (which means 4 shaders in all). To understand why this is the case and how these different shaders work, you need to listen to this week's lecture on Shadow Maps. Without going into more detail about it, you will now need to copy and paste the following two Vertex shaders and the following two Fragment shaders and completely replace the Vertex shader and Fragment shader that are currently in the program:


    <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>

    <script id="vertex-shader2" type="x-shader/x-vertex">

        attribute vec4 vPosition;
        attribute vec3 vNormal;

        uniform mat4 modelViewMatrix;
        uniform mat4 projectionMatrix;

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

        varying vec4 fColor;

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

        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);
            }

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

            fPositionFromLight = pmvMatrixFromLight * vPosition;
            normShadowVal = specular.a;
        }
    </script>

    <script id="fragment-shader2" type="x-shader/x-fragment">

        precision mediump float;

        uniform sampler2D shadowTexture;

        varying vec4 fColor;
        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 = (shadowCoord.z > depth + 0.005) ? 0.3 : 1.0;

            gl_FragColor = vec4(fColor.rgb * visibility, fColor.a);
        }
    </script>


Next, while still in your HTML file add the following lines to your existing <script> tags:

    <script type="text/javascript" src="Objs/polyhedron.js"></script>
    <script type="text/javascript" src="Objs/battledrone.js"></script>
    <script type="text/javascript" src="Objs/duck.js"></script>
    <script type="text/javascript" src="Objs/teapot.js"></script>
    <script type="text/javascript" src="Objs/tree.js"></script>
    <script type="text/javascript" src="LoadObjNoTexture.js"></script>


Next, while still in your HTML file and find the following line:

        <br /><br /><br /><br /><br /><br /><br />

BELOW that line, add the following lines:

    Select an Object:
    <select id="selObject" size="1">
    <option value="0" selected="selected">Sphere</option>
    <option value="1">Polyhedron</option>
    <option value="2">Battledrone</option>
    <option value="3">Duck</option>
    <option value="4">Teapot</option>
    <option value="5">Tree</option>
    </select>

    <br />
    <br />

       Object's Vertical Position: 
       <br />-2.5
       <input id="oYPos" type="range" min="-2.5" max="2.5" step="0.1" value="0" /> 2.5

    <br />
    <br />

    <button id="rotateX">Rotate X</button>
    <button id="rotateY">Rotate Y</button>
    <button id="rotateZ">Rotate Z</button>
    <br />
    <button id="toggleRotation">Toggle Rotation</button>


Now you are finished with the HTML changs, so go into your JavaScript code, and add the following initializaitons above where your init() function begins:


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 modelViewStack = [];

var sphereScale = 0.3;
var polyhedronScale = 1.2;
var battledroneScale = 1.8;
var duckScale = 0.8;
var teapotScale = 0.8;
var treeScale = 1.2;

var theta = [0, 0, 0];
var rotateObject = true;

var axis = 1;
var xAxis = 0;
var yAxis = 1;
var zAxis = 2;

var objYOff = 0.0;


Then below there find the following line inside your init() function:

    gl.clearColor(0.0, 0.0, 0.0, 1.0);

Change the background from black to blue, by changing the third 0.0 to 1.0:

    gl.clearColor(0.0, 0.0, 1.0, 1.0);


Then below there, while still inside your init() function, find the following two lines:

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

REPLACE these two lines with the following:


    objScale = sphereScale;

    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);


Next, while still in your init() function, place the following lines above the call to your render() function:

    pmvMatrixFromLightLoc2 = gl.getUniformLocation(program, "pmvMatrixFromLight");
    shadowTextureLoc = gl.getUniformLocation(program, "shadowTexture");

    document.getElementById("rotateX").onclick =
        function () {
            axis = xAxis;
        };

    document.getElementById("rotateY").onclick =
        function () {
            axis = yAxis;
        };

    document.getElementById("rotateZ").onclick =
        function () {
            axis = zAxis;
        };

    document.getElementById("toggleRotation").onclick =
        function () {
            rotateObject = !rotateObject;
        };


    document.getElementById("oYPos").oninput =
        function (event) {
            objYOff = Number(event.target.value);
        };


    document.getElementById("selObject").onchange =
        function (event) {
            var objSelected = event.target.value;
            var objData = "";

            initPoints();
            createPlane();

            if (objSelected == "0") {
                createSphereMap();
                updateBuffers();
                objScale = sphereScale;
            }
            else {
                axis = 1;
                theta[0] = theta[1] = theta[2] = 0;
                if (objSelected == "1") {
                    objData = filedata1;
                    objScale = polyhedronScale;
                }
                else if (objSelected == "2") {
                    objData = filedata2;
                    objScale = battledroneScale ;
                }
                else if (objSelected == "3") {
                    objData = filedata3;
                    objScale = duckScale;
                }
                else if (objSelected == "4") {
                    objData = filedata6;
                    objScale = teapotScale;
                }
                else if (objSelected == "5") {
                    objData = filedata7;
                    objScale = treeScale;
                }
                loadobj(objData);
                updateBuffers();
            }
        };


Next, ABOVE your initPoints() function, add the following two functions:


// 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);
}

function updateBuffers() {
    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, shadowVBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);
}


Now you will replace the entire render() function with code that renders the scene in two passes. The first pass creates an offscreen buffer that is then used as a texture map that has shadow information in it, so it is called a shadow buffer. And this shadow buffer is used in the second pass to display the images with their shadows to the screen.

You will place this double-rendering code into the render() function by adding the code for the two passes separately. First replace the entire render() function with the following:


function render() {

}


Next, inside of the render() function that has no code in it right now, add the following code for the first pass:



    ////////////////// 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.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);


    ///////////// Set Light Position As Eye ///////////////

    eye = vec3(lightPosition);


    ///////////// Render the Plane ///////////////

    modelViewMatrix = translate(planeTrans);
    modelViewMatrix = mult(modelViewMatrix, scalem(planeScale, planeScale, planeScale));

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

    gl.drawArrays(gl.TRIANGLES, 0, planeVertCnt);


    ///////////// Render the Object ///////////////

    if (rotateObject)
        theta[axis] += 2.0;

    modelViewMatrix = translate(objX, objYMid + objYOff, objZ);
    modelViewMatrix = mult(modelViewMatrix, rotateX(theta[xAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(theta[yAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(theta[zAxis]));
    modelViewMatrix = mult(modelViewMatrix, scalem(objScale, objScale, objScale));

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

    gl.drawArrays(gl.TRIANGLES, planeVertCnt, pointsArray.length - planeVertCnt);
   



Next, above the closing brace } of the render() function, add the following code for the second pass:



    ///////////////// 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.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);


    ///////////// Set Camera Position As Eye ///////////////

    eye = cameraPosition;

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    modelViewMatrix = lookAt(eye, at, up);
    

    //////////// Render the Plane /////////////

    modelViewStack.push(modelViewMatrix);

    modelViewMatrix = mult(modelViewMatrix, translate(planeTrans));
    modelViewMatrix = mult(modelViewMatrix, scalem(planeScale, planeScale, planeScale));

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

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
    gl.uniform1i(shadowTextureLoc, 0);

    gl.uniformMatrix4fv(pmvMatrixFromLightLoc2, false, flatten(pmvMatrixFromLight1));

    gl.drawArrays(gl.TRIANGLES, 0, planeVertCnt);


    //////////// Render the Object /////////////

    modelViewMatrix = modelViewStack.pop();

    modelViewMatrix = mult(modelViewMatrix, translate(objX, objYMid + objYOff, objZ));
    modelViewMatrix = mult(modelViewMatrix, rotateX(theta[xAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(theta[yAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(theta[zAxis]));
    modelViewMatrix = mult(modelViewMatrix, scalem(objScale, objScale, objScale));
    gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
    gl.uniform1i(shadowTextureLoc, 0);

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


    requestAnimFrame(render);



After all of these changes to your program you should be all done adding the new functionality to include a range of objects that you can load and to show shadows for those objects as they rotate in front of a white plane.



Exercise #2

For the second part of this assignment, you will add some more features to the program and turn it into ShadowMap2. This new version will add the ability to move the main object horizontally as well as vertically, and it will add to the scene a rotating soccer ball that you can move in and out of the plane (actually embedding it), and a stationary cube that cast a shadow on the plane and that your main object can cast its shadow on:

The above is what you should be starting with using the code from Exercise #1, and below is what your project should look like after you finish adding the new features:


The first thing you need to do is change the names of your HTML file to "ShadowMap2.html" and your JavaScript file to "ShadowMap2.js". And be sure to change the <script> tag in your HTML file that it loads your JavaScript file as "ShadowMap2.js" instead of "ShadowMap.js".

Next, in order to create the new functionality, you will need to add the following image file to your project that will serve as the texture for the soccer ball in the scene:

Soccerball Texture:   soccerball.png

Create an "Images" folder in your project, and put the file into that folder.


Next, in your HTML file, inside the code for your second Vertex Shader (which is "vertex-shader2"), add the following attribute declaration below the other two attribute declarations:

        attribute vec2 vTexCoord;

And below that there already is the following varying variable declaration: "varying vec4 fColor;". Below that line, add the following additional varying variable declaration:

        varying vec2 fTexCoord;

And below that, as the last line of the main() function for your Vertex Shader code, add the following line that sends your texture coordiantes to the Fragment Shader:

            fTexCoord = vTexCoord;

Next, in your second Fragment Shader (which is "fragment-shader2"), right above your main() function add the following new declarations:

        varying vec2 fTexCoord;
        uniform bool showTexture;
        uniform sampler2D texture;

Next, replace the last line of the main() function of your Fragment Shader, which right now is:

            gl_FragColor = vec4(fColor.rgb * visibility, fColor.a);

Replace this line with the following:

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

This code adds the texture to the fragment when it is needed, which in this case is only being done during the rendering of the soccer ball.


Next we need to add the code for the new controls to your HTML file. If you look right above the "Select an Object:" line in your HTML code you will see 7 "<br />" tags. Remove one of them so there are only 6 of them. This will move the controls up one line to make room for the extra controls we need to add.

Then below the slider code that is already there (and above the button code that is already there) add the following code to create your two new sliders:

    <br />
    <br />

    Object's Horizontal Position: 
    <br />-5
    <input id="oXPos" type="range" min="-5" max="5" step="0.1" value="0" /> 5

    <br />
    <br />

    Soccer Ball's Perpendicular Position: 
    <br />0
    <input id="sphereZPos" type="range" min="0" max="1.4" step="0.1" value="0" /> 1.4

Next, as your final change to your HTML code, add the following line below your closing </table> tag, which loads the image file for your texture:

    <img id="texture1" src="Images/soccerball.png" hidden />


Next, go into your ShadowMap2.js file and find the following line:

var pmvMatrixFromLight1, pmvMatrixFromLight2;

This line sets up 2 projection model view matrices for the plane and the object rotating in front of the plane. Replace this line with the following 4 lines which declare the variables which will keep track of the 4 projection model view matrices for the 4 objects that will make up your new scene:

var pmvMatrixFromLight1;  // for rendering the plane
var pmvMatrixFromLight2;  // for rendering the cube
var pmvMatrixFromLight3;  // for rendering the embedded sphere
var pmvMatrixFromLight4;  // for rendering the object loaded


Note: in an earlier version of the ShadowMap.js file you were given in Exercise #1 of this assignment, there was a typo in which the "planeVertices" variable was spelled "planVertices", and the variable far was set to 700 instead of 110. Both of these problems were fixed in the current ShadowMap.js file, but if you got the old version of that code and you still see a variable called "planVertices" in your code, change it to "planeVertices" on every line where it appears in your code. And if the line "var far = 700;" is the way the far variable is declared in your code, change it to "var far = 110;".


Next, add the following new declarations above the init() function in your ShadowMap2.js file:

var cubeVertCnt = 36;
var cubeVertices = [
    vec4(-0.5, -0.5, 0.5, 1.0),
    vec4(-0.5, 0.5, 0.5, 1.0),
    vec4(0.5, 0.5, 0.5, 1.0),
    vec4(0.5, -0.5, 0.5, 1.0),
    vec4(-0.5, -0.5, -0.5, 1.0),
    vec4(-0.5, 0.5, -0.5, 1.0),
    vec4(0.5, 0.5, -0.5, 1.0),
    vec4(0.5, -0.5, -0.5, 1.0)
];

var soccerballVertCnt;
var objVertCnt;

var texCoordsArray = [];
var tBuffer;
var vTexCoord;

var textureLoc;
var showTextureLoc;
var stripedTexture;
var soccerballScale = 0.2;

var cubeTrans = [-0.3, 1.5, 0.4];
var cubeRot = [108.0, 15.0, 64.0];
var cubeScale = 0.4;
var cubAng = 0;

var sphereTrans = [-1.2, -0.5, 1.0];
var sphereRot = [0.0, 0.0, 0.0];
var sphereAng = 0;
var sphereXMov = 0;
var sphereYMov = 0;
var sphereZMov = 0;

var objZOff = 0.0;
var objX2Off = 0.0;
var objY2Off = 0.0;


Next, find the following lines in your init() function:

    initPoints();
    createPlane();
    createSphereMap();

    objScale = sphereScale;

And replace these lines with the following lines:

    initPoints();
    createPlane();
    createCube();
    createSphereMap();
    soccerballVertCnt = pointsArray.length - planeVertCnt - cubeVertCnt;

    createSphereMap();  // Load in the sphere as the default object
    objVertCnt = pointsArray.length - planeVertCnt - cubeVertCnt - soccerballVertCnt;
    objScale = sphereScale;


Next, while still in the init() function, go down a little further to find the following line:

    gl.enableVertexAttribArray(vNormal);

This line is the last line of the block of code that creates the normal buffer, and below this line, add the following lines which create the texture buffer:

    // Create texture buffer and vTexCoord attribute
    tBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, tBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(texCoordsArray), gl.STATIC_DRAW);
    vTexCoord = gl.getAttribLocation(program, "vTexCoord");
    gl.vertexAttribPointer(vTexCoord, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vTexCoord);


Next, while still in your init() function, find the following lines:

    document.getElementById("oYPos").oninput =
        function (event) {
            objYOff = Number(event.target.value);
        };

And replace these lines with the following lines which will manage your new slider controls:

    document.getElementById("oYPos").value = 0;
    document.getElementById("oYPos").oninput =
        function (event) {
            objYOff = Number(event.target.value);
        };

    document.getElementById("oXPos").value = 0;
    document.getElementById("oXPos").oninput =
        function (event) {
            objZOff = -Number(event.target.value);
            objX2Off = -objZOff / 3;
            objY2Off = -objZOff / 20;
        };

    document.getElementById("sphereZPos").value = 0;
    document.getElementById("sphereZPos").oninput =
        function (event) {
            sphereXMov = Number(event.target.value);
            sphereYMov = sphereXMov * 0.27;
            sphereZMov = sphereXMov * 0.8;
        };


A few lines down from there you should see the following lines:

            initPoints();
            createPlane();

Replace these lines with the following lines:

            initPoints();
            createPlane();
            createCube();
            createSphereMap();


And a few lines down from there you should see the following lines:

                loadobj(objData);
                updateBuffers();

Replace these lines with the following lines:

                var startV = pointsArray.length;
                loadobj(objData);
                for (var nextV = startV; nextV < pointsArray.length; ++nextV)
                    texCoordsArray.push(vec2(0, 0));
                updateBuffers();


And a few lines down from there you should see call to the "render()" function. Right ABOVE that line, add the following lines:

    loadImage(document.getElementById("texture1"));
    textureLoc = gl.getUniformLocation(program, "texture");
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
    gl.uniform1i(textureLoc, 1);

    showTextureLoc = gl.getUniformLocation(program, "showTexture");
    gl.uniform1i(showTextureLoc, false);


And a few lines below that you should see the code that defines the following functions: updateBuffers(), initPoints(), createPlane() and createSphereMap(). These functions do not handle the new functionality, so you must replace them with the following functions:

function updateBuffers() {
    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, tBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(texCoordsArray), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, shadowVBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);

    document.getElementById("oYPos").value = 0;
    document.getElementById("oXPos").value = 0;
    objYOff = objZOff = objX2Off = objY2Off = 0;
    document.getElementById("sphereZPos").value = 0;
    sphereXMov = sphereYMov = sphereZMov = 0;

    objVertCnt = pointsArray.length - planeVertCnt - cubeVertCnt - soccerballVertCnt;
}

function initPoints() {
    pointsArray = [];
    normalsArray = [];
    texCoordsArray = [];
}

function loadImage(texImage) {
    stripedTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texImage);
    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);
}

// Create Plane filling pointsArray and normalsArray
function createPlane() {
    pointsArray.push(planeVertices[0]);
    pointsArray.push(planeVertices[1]);
    pointsArray.push(planeVertices[2]);
    pointsArray.push(planeVertices[3]);
    pointsArray.push(planeVertices[4]);
    pointsArray.push(planeVertices[5]);

    var t1 = subtract(planeVertices[1], planeVertices[0]);
    var t2 = subtract(planeVertices[1], planeVertices[2]);
    var normal = vec3(cross(t1, t2));

    normalsArray.push(normal);
    normalsArray.push(normal);
    normalsArray.push(normal);
    normalsArray.push(normal);
    normalsArray.push(normal);
    normalsArray.push(normal);

    texCoordsArray.push(vec2(0, 0));
    texCoordsArray.push(vec2(0, 1));
    texCoordsArray.push(vec2(1, 1));
    texCoordsArray.push(vec2(1, 1));
    texCoordsArray.push(vec2(1, 0));
    texCoordsArray.push(vec2(0, 0));
}

// Create Cube filling pointsArray and normalsArray
function createCube() {
    quad(1, 0, 3, 2);
    quad(2, 3, 7, 6);
    quad(3, 0, 4, 7);
    quad(5, 1, 2, 6);
    quad(4, 5, 6, 7);
    quad(5, 4, 0, 1);
}

function quad(a, b, c, d) {
    var t1 = subtract(cubeVertices[a], cubeVertices[b]);
    var t2 = subtract(cubeVertices[a], cubeVertices[c]);
    var normal = vec3(cross(t1, t2));

    pointsArray.push(cubeVertices[a]);
    normalsArray.push(normal);
    pointsArray.push(cubeVertices[b]);
    normalsArray.push(normal);
    pointsArray.push(cubeVertices[c]);
    normalsArray.push(normal);
    pointsArray.push(cubeVertices[a]);
    normalsArray.push(normal);
    pointsArray.push(cubeVertices[c]);
    normalsArray.push(normal);
    pointsArray.push(cubeVertices[d]);
    normalsArray.push(normal);

    texCoordsArray.push(vec2(0, 0));
    texCoordsArray.push(vec2(0, 1));
    texCoordsArray.push(vec2(1, 1));
    texCoordsArray.push(vec2(1, 1));
    texCoordsArray.push(vec2(1, 0));
    texCoordsArray.push(vec2(0, 0));
}

// Create SphereMap by filling pointsArray and normalsArray
function createSphereMap() {
    var phi1, phi2, sinPhi1, sinPhi2, cosPhi1, cosPhi2;
    var theta1, theta2, sinTheta1, sinTheta2, cosTheta1, cosTheta2;
    var p1, p2, p3, p4, u1, u2, v1, v2, uv1, uv2, uv3, uv4;

    // For each latitudinal band determine phi's value
    for (var latNumber = 1; latNumber <= latitudeBands; latNumber++) {
        phi1 = Math.PI * (latNumber - 1) / latitudeBands;
        sinPhi1 = Math.sin(phi1);
        cosPhi1 = Math.cos(phi1);

        phi2 = Math.PI * latNumber / latitudeBands;
        sinPhi2 = Math.sin(phi2);
        cosPhi2 = Math.cos(phi2);

        // For each longitudinal band determine theta's value and other calculations
        for (var longNumber = 1; longNumber <= longitudeBands; longNumber++) {
            theta1 = 2 * Math.PI * (longNumber - 1) / longitudeBands;
            sinTheta1 = Math.sin(theta1);
            cosTheta1 = Math.cos(theta1);

            theta2 = 2 * Math.PI * longNumber / longitudeBands;
            sinTheta2 = Math.sin(theta2);
            cosTheta2 = Math.cos(theta2);

            p1 = vec4(cosTheta1 * sinPhi1 * radius, cosPhi1 * radius, sinTheta1 * sinPhi1 * radius, 1.0);
            p2 = vec4(cosTheta2 * sinPhi1 * radius, cosPhi1 * radius, sinTheta2 * sinPhi1 * radius, 1.0);
            p3 = vec4(cosTheta1 * sinPhi2 * radius, cosPhi2 * radius, sinTheta1 * sinPhi2 * radius, 1.0);
            p4 = vec4(cosTheta2 * sinPhi2 * radius, cosPhi2 * radius, sinTheta2 * sinPhi2 * radius, 1.0);

            pointsArray.push(p1);
            pointsArray.push(p2);
            pointsArray.push(p3);
            pointsArray.push(p2);
            pointsArray.push(p4);
            pointsArray.push(p3);

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

            u1 = 1 - ((longNumber - 1) / longitudeBands);
            u2 = 1 - (longNumber / longitudeBands);
            v1 = 1 - ((latNumber - 1) / latitudeBands);
            v2 = 1 - (latNumber / latitudeBands);

            uv1 = vec2(u1, v1);
            uv2 = vec2(u2, v1);
            uv3 = vec2(u1, v2);
            uv4 = vec2(u2, v2);

            texCoordsArray.push(uv1);
            texCoordsArray.push(uv2);
            texCoordsArray.push(uv3);
            texCoordsArray.push(uv2);
            texCoordsArray.push(uv4);
            texCoordsArray.push(uv3);
        }
    }
}


The last thing to do is to replace the current render() method with the following new render() method that handles the new functionality that involves the 2 new objects on the screen:

function render() {
    ////////////////// 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);


    ///////////// Set Light Position As Eye ///////////////

    eye = vec3(lightPosition);


    ///////////// Render the Plane ///////////////

    modelViewMatrix = translate(planeTrans);
    modelViewMatrix = mult(modelViewMatrix, scalem(planeScale, planeScale, planeScale));

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

    gl.drawArrays(gl.TRIANGLES, 0, planeVertCnt);


    ///////////// Render the Cube ///////////////

    modelViewMatrix = translate(cubeTrans);
    modelViewMatrix = mult(modelViewMatrix, rotateX(cubeRot[0]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(cubeRot[1]));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(cubeRot[2]));
    modelViewMatrix = mult(modelViewMatrix, scalem(cubeScale, cubeScale, cubeScale));

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

    gl.drawArrays(gl.TRIANGLES, planeVertCnt, cubeVertCnt);


    //////////// Render the Embedded Sphere /////////////

    sphereAng += 2.0

    var xAdd = (sphereZMov == 0) ? 0 :
        (sphereZMov < 0.5) ? 0.5 : sphereZMov;
    var yAdd = xAdd * 0.27;
    var zAdd = xAdd * 0.8;
    modelViewMatrix = translate(sphereTrans[0] + xAdd,
                                sphereTrans[1] + yAdd,
                                sphereTrans[2] + zAdd);
    modelViewMatrix = mult(modelViewMatrix, rotateX(sphereRot[0]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(sphereRot[1] + sphereAng));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(sphereRot[2]));
    modelViewMatrix = mult(modelViewMatrix, scalem(soccerballScale, soccerballScale, soccerballScale));

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

    gl.drawArrays(gl.TRIANGLES, planeVertCnt + cubeVertCnt, soccerballVertCnt);


    ///////////// Render the Loaded Object ///////////////

    if (rotateObject)
        theta[axis] += 2.0;

    modelViewMatrix = translate(objX, objYMid + objYOff, objZ + objZOff);
    modelViewMatrix = mult(modelViewMatrix, rotateX(theta[xAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(theta[yAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(theta[zAxis]));
    modelViewMatrix = mult(modelViewMatrix, scalem(objScale, objScale, objScale));

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

    gl.drawArrays(gl.TRIANGLES,
        planeVertCnt + cubeVertCnt + soccerballVertCnt, objVertCnt);


    ///////////////// 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);


    ///////////// Set Camera Position As Eye ///////////////

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    eye = cameraPosition;
    modelViewMatrix = lookAt(eye, at, up);


    //////////// Render the Plane /////////////

    modelViewStack.push(modelViewMatrix);

    modelViewMatrix = mult(modelViewMatrix, translate(planeTrans));
    modelViewMatrix = mult(modelViewMatrix, scalem(planeScale, planeScale, planeScale));

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

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
    gl.uniform1i(shadowTextureLoc, 0);

    gl.drawArrays(gl.TRIANGLES, 0, planeVertCnt);


    //////////// Render the Cube /////////////

    modelViewMatrix = modelViewStack.pop();
    modelViewStack.push(modelViewMatrix);

    modelViewMatrix = mult(modelViewMatrix, translate(cubeTrans));
    modelViewMatrix = mult(modelViewMatrix, rotateX(cubeRot[0]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(cubeRot[1]));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(cubeRot[2]));
    modelViewMatrix = mult(modelViewMatrix, scalem(cubeScale, cubeScale, cubeScale));

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

    gl.drawArrays(gl.TRIANGLES, planeVertCnt, cubeVertCnt);


    //////////// Render the Embedded Sphere /////////////

    modelViewMatrix = modelViewStack.pop();
    modelViewStack.push(modelViewMatrix);

    modelViewMatrix = mult(modelViewMatrix,
        translate(sphereTrans[0] + sphereXMov,
                  sphereTrans[1] + sphereYMov,
                  sphereTrans[2] + sphereZMov));
    modelViewMatrix = mult(modelViewMatrix, rotateX(sphereRot[0]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(sphereRot[1] + sphereAng));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(sphereRot[2]));
    modelViewMatrix = mult(modelViewMatrix, scalem(soccerballScale, soccerballScale, soccerballScale));

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

    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
    gl.uniform1i(textureLoc, 1);
    gl.uniform1i(showTextureLoc, true);

    gl.drawArrays(gl.TRIANGLES, planeVertCnt + cubeVertCnt, soccerballVertCnt);

    gl.uniform1i(showTextureLoc, false);


    //////////// Render the Object /////////////

    modelViewMatrix = modelViewStack.pop();

    modelViewMatrix = mult(modelViewMatrix, translate(objX + objX2Off, objYMid + objYOff + objY2Off, objZ));
    modelViewMatrix = mult(modelViewMatrix, rotateX(theta[xAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateY(theta[yAxis]));
    modelViewMatrix = mult(modelViewMatrix, rotateZ(theta[zAxis]));
    modelViewMatrix = mult(modelViewMatrix, scalem(objScale, objScale, objScale));

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

    gl.drawArrays(gl.TRIANGLES,
                    planeVertCnt + cubeVertCnt + soccerballVertCnt, objVertCnt);

    requestAnimFrame(render);
}


After making all fo the changes above, your program should work with the new functionalities specified for this part of the assignment, as long as you don't have the CORS error.


If you have the CORS error, the texture for your soccerball image will not be showing up yet. In order to fix this, add the following <script> tag line below your other <script> tags in your HTML file:

    <script type="text/javascript"
         src="https://ksuweb.kennesaw.edu/~ashaw8/cs4722/materials/soccerball.js">
    </script>

And then in your ShadowMap2.js file, find the following 5 lines near the end of your init() function:

    loadImage(document.getElementById("texture1"));
    textureLoc = gl.getUniformLocation(program, "texture");
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
    gl.uniform1i(textureLoc, 1);

And replace these 5 lines with the following:

    var img = new Image();
    img.onload = function () {
        loadImage(img);
        textureLoc = gl.getUniformLocation(program, "texture");
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
        gl.uniform1i(textureLoc, 1);
    };
    img.src = soccerball;

After making these changes, you should no longer have the CORS error.



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:

  • ShadowMap.zip (with your HTML and JS files)
  • ShadowMap2.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
Modeling and Hierarchy - Building Scenes
PreviousNext
Powered by Manifold Scholarship. Learn more at
Opens in new tab or windowmanifoldapp.org