CS 4722 - Computer Graphics and Multimedia
Module #6, Assignment #1Exercise #1The ObjManager application shown below uses a standard file picker dialog to load OBJ model files, and it allows the user to change their color and observe them as a light source rotates around them: Unfortunately, when you run this program from a local folder on your hard drive instead of on a server, most browsers produce a Cross Origin Request Security (CORS) error because browsers are not supposed to have access to non-library files (like ".obj" files) on the browser's local machine. When you run the program in Visual Studio in Windows (but not on the Mac) a virtual server is created, and that resolves this problem. Another way to resolve the problem is to load the OBJ files as ".js" library files using the <script> tag. To learn how to do this, you are to modify the given ObjManager application so that it loads the set of OBJ files shown below as script files, and then allows the user to choose which of those files to load using a dropdown menu. Also, you are to add buttons to rotate the object through the X, Y and Z axes, and to toggle the rotation on and off. And you must also make the applilcation zoom in and out of the view using an Orthographic projection. This zoom in and out feature must be accomplished using Arrow Keys and the Mouse Wheel. Be sure to come no closer than a minimum of 0.25 units on the Right, Left, Top and Bottom when you zoom in. And the maximum distance you zoom out to should have no limit. Implement this using the Up and Down Arrow keys, and also produce the same functionality using the foward and backward scrolling of the Mouse Wheel. When you are all done, your application should look like the following: The partial HTML code is given here: ObjManager.html. The partial JavaScript code is given here: ObjManager.js. Your first OBJ .js file (which goes in the "Objs" folder) is given here: Polyhedron.js. You need to add the buttons and handlers for rotation and the "Toggle Rotation" function, which stops and starts the rotation of the object. And you will need to add the dropdown for each of the following OBJ model files which you need to pre-load using the <script> tags. Polyhedron: Polyhedron.obj The first file is pre-loaded for you. It's <script> tags are already in place in the ObjManager.html file, and the Polyhedron.js has already been edited and placed in the "Objs" folder. The following is the resulting <script> tag: What you need to do to edit the other OBJ files is first change their file extension to ".js", and then open them up and add at the top of the file the following text that includes a backtick (`) character: var filedata# = `
Where you see the # symbol, replace that with a '1' for the first OBJ file (which is Polyhedron.obj), and a '2' for the second one, and a '3' for the third one, and so on. Then at the end of the file add the following with another backtick (`) character: `;
The `(backtick) character begins and ends a template literal in JavaScript, making anything between them a text block of whatever we want, which in this case is the source code for the OBJ file. The above additions load the OBJ file's content into a JavaScript variable that is accessible to our program after being loaded. Each variable must have a separate name, which is why the # sign is there and the variable names will actually be filedata1, filedata2, filedata3, and so on. Once the OBJ files have been changed to ".js" files and the new first and last lines are added, you need to add a dropdown control that sets a value for each OBJ files name like so: <select id="selObject" size="1">
<option value="" selected="selected">None</option>
<option value="1">Polyhedron</option>
<option value="2">Battledrone</option>
<option value="3">Duck</option>
<option value="4">Female</option>
<option value="5">Male</option>
<option value="6">Teapot</option>
<option value="7">Tree</option>
</select>
Then inside your JavaScipt handler, you will need to identify which filedata variable goes with the selection, then assign it to the "objfile" variable and call the loadobj() function like so: function (event) {
var fileselected = event.target.value;
if (fileselected == "") {
objfile = "";
}
else if (fileselected == "1") {
objfile = filedata1;
}
else if (fileselected == "2") {
objfile = filedata2;
}
else if (fileselected == "3") {
objfile = filedata3;
}
...
loadobj();
};
Next you have to implement functionality for zooming in and out of the view with Arrow Keys and the Mouse Wheel. One way to do this is to create a zoomFactor() function like so: function zoomfactor(zfactor) {
left *= zfactor;
right *= zfactor;
ytop *= zfactor;
bottom *= zfactor;
projectionMatrix = ortho(left, right, bottom, ytop, near, far);
gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix));
}
And then you would call this function in your "keydown" handler and "wheel" handler like so: document.addEventListener("keydown",
function (event) {
if (event.keyCode == 38) { // Up
if (right > 0.25) {
zoomfactor(0.9);
};
}
if (event.keyCode == 40) { // Down
zoomfactor(1.1);
}
}, false);
document.addEventListener("wheel",
function (event) {
var delta = Math.sign(event.deltaY);
if (delta > 0) {
zoomfactor(1.1);
}
else if (right > 0.25 && delta < 0) {
zoomfactor(0.9);
}
});
The last change to make involves the rotation of the object through the X, Y and Z axes, and the toggling of the rotation on and off. We have done this multiple times in the past, so this should not be too hard, but it is important to note that it involves moving the object this time, not just moving the camera eye around the object. That means, we won't calculate a modelViewMatrix for the eye this time. Instead, we will just do the rotation in the following simple way: modelViewMatrix = mat4();
modelViewMatrix = mult(modelViewMatrix, rotateX(theta[xAxis]));
modelViewMatrix = mult(modelViewMatrix, rotateY(theta[yAxis]));
modelViewMatrix = mult(modelViewMatrix, rotateZ(theta[zAxis]));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix)); After all of these changes and other small additions (like creating a variable to determine when the rotation is on or off), your program should be all finished. By the way, it is possible to modify FireFox's settings on a Mac or a PC so that it does not generate the "Cross Origin Request Security (CORS) error", which would mean you could use the standard file picker dialog to load OBJ model files even in a local folder without an error. You can read about how to do this in the following file: Cross Origin Fix For Firefox Exercise #2Modify the given SphereMap application and call it PlanetShader. Your new version of this application will give the user 4 optional objects to choose from (Earth, the Moon, Mars or Jupitor), and it will simulate the Sun's light shining on a side of the object as it rotates on its axis. Our camera eye will rotate with the object, so from our perspective, the object's position will be stationary as the Sun rotates around it: Your program must give the user the ability to choose which planetary object to show, whether or not to use lighting, whether or not to rotate the lighting, and it must use the Arrow keys and the WASD keys to rotate the camera eye around the planetary object. 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 (meaning place these files into your "images" folder): However, if the program gives you a CORS error when you run the code as it is now, you can use <script> tags to load the data in a text format that will bypass the CORS error (and the steps for this are explained below). The first change to the code you need to make is to add the lighting effects by replacing the Vertex Shader with the following: 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);
}
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
fColor = ambient + diffuse +specular;
fColor.a = 1.0;
fTexCoord = vTexCoord;
}
Next, replace your Fragment Shader with the following: precision mediump float;
varying vec4 fColor;
varying vec2 fTexCoord;
uniform sampler2D texture;
uniform bool useLight;
void main()
{
if (useLight) {
gl_FragColor = fColor * texture2D(texture, fTexCoord);
}
else {
gl_FragColor = texture2D(texture, fTexCoord);
}
}
Replace the current block of HTML code under your <canvas> tag with the following: <br />
<select id="imageVal" size="4">
<option value="earthImage" selected="selected">Earth</option>
<option value="moonImage">Moon</option>
<option value="marsImage">Mars</option>
<option value="jupiterImage">Jupiter</option>
</select>
<img id="earthImage" src="images/earth.png" hidden />
<img id="moonImage" src="images/moon.png" hidden />
<img id="marsImage" src="images/mars.png" hidden />
<img id="jupiterImage" src="images/jupiter.png" hidden />
<br />
Use lighting:
<input id="lighting" type="checkbox" checked="checked" />
<br />
Rotate lighting:
<input id="rotateLighting" type="checkbox" checked="checked" />
If you are experiencing the CORS error, then add the following <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/jupiter.js"></script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/mars.js"></script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/moon.js"></script>
Now, whether or not you are experiencing the CORS error (in other words, everyone must do the next step), go into your JavaScript code and add the following declarations: 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(1.0, 0.0, 1.0, 1.0);
var moonMaterialDiffuse = vec4(1.0, 0.8, 0.0, 1.0);
var moonMaterialSpecular = vec4(1.0, 0.8, 0.0, 1.0);
var otherMaterialAmbient = vec4(1.0, 0.0, 1.0, 1.0);
var otherMaterialDiffuse = vec4(0.8, 0.8, 1.0, 1.0);
var otherMaterialSpecular = vec4(0.8, 0.8, 1.0, 1.0);
var moonShininess = 100.0;
var otherShininess = 50.0;
var shininessLoc;
var lightPosition = [0.0, 1.0, 4.0, 0.0];
var lightPosition2;
var lightPositionLoc;
var lightTheta = 0;
var lightRad = 4;
var rotMat;
var ambientProduct, ambientProductLoc;
var diffuseProduct, diffuseProductLoc;
var specularProduct, specularProductLoc;
var lightingLoc;
var rotateLighting = true;
var theta = 0.0;
var phi = 0.0;
Next, find the following lines in your init() method: loadImage(document.getElementById(document.getElementById('imageVal').value));
document.getElementById("imageVal").onchange =
function (event) {
loadImage(document.getElementById(event.target.value));
createSphereMap();
};
And replace the above lines with the following lines: // 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, flatten(lightPosition));
shininessLoc = gl.getUniformLocation(program, "shininess");
loadImage(document.getElementById("imageVal").value);
if (document.getElementById("imageVal").value == "moonImage") {
ambientProduct = mult(lightAmbient, moonMaterialAmbient);
diffuseProduct = mult(lightDiffuse, moonMaterialDiffuse);
specularProduct = mult(lightSpecular, moonMaterialSpecular);
gl.uniform1f(shininessLoc, moonShininess);
}
else
{
ambientProduct = mult(lightAmbient, otherMaterialAmbient);
diffuseProduct = mult(lightDiffuse, otherMaterialDiffuse);
specularProduct = mult(lightSpecular, otherMaterialSpecular);
gl.uniform1f(shininessLoc, otherShininess);
}
ambientProductLoc = gl.getUniformLocation(program, "ambientProduct");
gl.uniform4fv(ambientProductLoc, flatten(ambientProduct));
diffuseProductLoc = gl.getUniformLocation(program, "diffuseProduct");
gl.uniform4fv(diffuseProductLoc, flatten(diffuseProduct));
specularProductLoc = gl.getUniformLocation(program, "specularProduct");
gl.uniform4fv(specularProductLoc,flatten(specularProduct));
document.getElementById("imageVal").onchange =
function (event) {
loadImage(event.target.value);
if (event.target.value == "moonImage") {
ambientProduct = mult(lightAmbient, moonMaterialAmbient);
diffuseProduct = mult(lightDiffuse, moonMaterialDiffuse);
specularProduct = mult(lightSpecular, moonMaterialSpecular);
gl.uniform1f(shininessLoc, moonShininess);
}
else {
ambientProduct = mult(lightAmbient, otherMaterialAmbient);
diffuseProduct = mult(lightDiffuse, otherMaterialDiffuse);
specularProduct = mult(lightSpecular, otherMaterialSpecular);
gl.uniform1f(shininessLoc, otherShininess);
}
gl.uniform4fv(ambientProductLoc,flatten(ambientProduct));
gl.uniform4fv(diffuseProductLoc,flatten(diffuseProduct));
gl.uniform4fv(specularProductLoc,flatten(specularProduct));
createSphereMap();
};
lightingLoc = gl.getUniformLocation(program, "useLight");
gl.uniform1i(lightingLoc, document.getElementById("lighting").checked);
document.getElementById("lighting").onchange =
function (event) {
gl.uniform1i(lightingLoc, event.target.checked);
};
document.getElementById("rotateLighting").onchange =
function (event) {
rotateLighting = event.target.checked;
};
rotateLighting = document.getElementById("rotateLighting").checked;
document.addEventListener("keydown",
function (event) {
if (event.keyCode == 65 || event.keyCode == 37) { // A or Left
// You should know what goes here
}
if (event.keyCode == 68 || event.keyCode == 39) { // D or Right
// You should know what goes here
}
if (event.keyCode == 87 || event.keyCode == 38) { // W or Up
// You should know what goes here
}
if (event.keyCode == 83 || event.keyCode == 40) { // S or Down
// You should know what goes here
}
}, false);
Then, if you DO NOT HAVE the CORS error replace the header line of your loadImage() method. Right now the header line is: function loadImage(image) {
Replace that one line with the following two lines: function loadImage(imageTag) {
var image = document.getElementById(imageTag);
On the other hand, if you DO HAVE the CORS error then replace the entire loadImage() method with the following two methods: function loadImage(imageTag) {
var imageStr = "";
if (imageTag == "earthImage") {
imageStr = fileearthdata;
} else if (imageTag == "moonImage") {
imageStr = filemoondata;
} else if (imageTag == "marsImage") {
imageStr = filemarsdata;
} else {
imageStr = filejupiterdata;
}
var imageData = new Image();
imageData.onload = function() {
loadImage2(imageData);
}
imageData.src = imageStr;
}
function loadImage2(image) {
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
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.uniform1i(textureLoc, 0);
}
Then, whether or not you have the CORS error, inside of 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));
Add then add the following code to your render() method right ABOVE your "modelViewMatrix = lookAt(eye, at, up);" line: if (phi < 0)
phi += 2 * Math.PI;
if (phi > 2 * Math.PI)
phi -= 2 * Math.PI;
if (phi > Math.PI / 2 && phi < 3 * Math.PI / 2)
up = vec3(0.0, -1.0, 0.0);
else
up = vec3(0.0, 1.0, 0.0);
eye = vec3(Math.sin(theta) * Math.cos(phi),
Math.sin(phi),
Math.cos(theta) * Math.cos(phi));
Next, right BELOW your "modelViewMatrix = lookAt(eye, at, up);" line add the following: if (rotateLighting) {
lightTheta -= 0.01;
lightPosition[0] = lightRad * Math.sin(lightTheta);
lightPosition[2] = lightRad * Math.cos(lightTheta);
}
// Offsetting the light to compensate for rotation of eye
var lightPosition2 = mult(modelViewMatrix, lightPosition);
gl.uniform4fv(lightPositionLoc, flatten(lightPosition2));
Now if you added the right code at the "// You should know what goes here" comments in the instructions for the init() method, you should be all done. 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. DeliverablesTurn in the files:
Do this by uploading the file as an attachment to this Module's assignment drop box in D2L Brightspace. |