diff --git a/.vscode/launch.json b/.vscode/launch.json index 908785a..2f969a4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,6 +29,13 @@ "request": "launch", "reAttach": true, "file": "${workspaceFolder}/RotatingCube/RotatingCube.html" + }, + { + "name": "BivariateFunction", + "type": "firefox", + "request": "launch", + "reAttach": true, + "file": "${workspaceFolder}/BivariateFunction/BivariateFunction.html" } ] } \ No newline at end of file diff --git a/BivariateFunction/BivariateFunction.html b/BivariateFunction/BivariateFunction.html new file mode 100644 index 0000000..8daab74 --- /dev/null +++ b/BivariateFunction/BivariateFunction.html @@ -0,0 +1,20 @@ + + + + + Point lighted graph of bivariate function (Per fragment) + + + + + Please use a browser that supports "canvas" + + + + + + + + + + diff --git a/BivariateFunction/BivariateFunction.js b/BivariateFunction/BivariateFunction.js new file mode 100644 index 0000000..f43f538 --- /dev/null +++ b/BivariateFunction/BivariateFunction.js @@ -0,0 +1,280 @@ +// PointLightedCube_perFragment.js (c) 2012 matsuda, 2022 Jonathon Doran + +const vertex_shader = ` + attribute vec4 a_Position; + attribute vec4 a_Color; + attribute vec4 a_Normal; + uniform mat4 u_MvpMatrix; + uniform mat4 u_ModelMatrix; // Model matrix + uniform mat4 u_NormalMatrix; // Transformation matrix of the normal + varying vec4 v_Color; + varying vec3 v_Normal; + varying vec3 v_Position; + + void main() + { + gl_Position = u_MvpMatrix * a_Position; + + // Calculate the vertex position in the world coordinate + v_Position = vec3(u_ModelMatrix * a_Position); + v_Normal = normalize(vec3(u_NormalMatrix * a_Normal)); + v_Color = a_Color; + } `; + + +const fragment_shader = ` + #ifdef GL_ES + precision mediump float; + #endif + + uniform vec3 u_LightColor; // Light color + uniform vec3 u_LightPosition; // Position of the light source + uniform vec3 u_AmbientLight; // Ambient light color + varying vec3 v_Normal; + varying vec3 v_Position; + varying vec4 v_Color; + void main() + { + // Normalize the normal because it is interpolated and not 1.0 in length any more + vec3 normal = normalize(v_Normal); + + // Calculate the light direction and make its length 1. + vec3 lightDirection = normalize(u_LightPosition - v_Position); + + // The dot product of the light direction and the orientation of a surface (the normal) + float nDotL = max(dot(lightDirection, normal), 0.0); + + // Calculate the final color from diffuse reflection and ambient reflection + vec3 diffuse = u_LightColor * v_Color.rgb * nDotL; + + vec3 ambient = u_AmbientLight * v_Color.rgb; + gl_FragColor = vec4(diffuse + ambient, v_Color.a); + } `; + +var scale = 192 +function f(x, y) { + var u = 80 * x - 40, v = 90 * y - 45; + var norm = Math.sqrt(u * u + v * v); + return (1 / 2) * Math.pow(Math.E, -0.04 * norm) * Math.cos(0.15 * norm) +} + +function main() { + // Retrieve element + var canvas = document.getElementById('webgl'); + + // Get the rendering context for WebGL + var gl = getWebGLContext(canvas); + if (!gl) { + console.log('Failed to get the rendering context for WebGL'); + return; + } + + // Initialize shaders + if (!initShaders(gl, vertex_shader, fragment_shader)) { + console.log('Failed to intialize shaders.'); + return; + } + + // + var n = initVertexBuffers(gl); + if (n < 0) { + console.log('Failed to set the vertex information'); + return; + } + + // Set the clear color and enable the depth test + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.enable(gl.DEPTH_TEST); + + // Get the storage locations of uniform variables + var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix'); + var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix'); + var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix'); + var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor'); + var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition'); + var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight'); + + if (!u_ModelMatrix || !u_MvpMatrix || !u_NormalMatrix || !u_LightColor || !u_LightPosition || !u_AmbientLight) { + console.log('Failed to get the storage location'); + return; + } + + // Set the light color (white) + gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0); + + // Set the light direction (in the world coordinate) + gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5); + + // Set the ambient light + gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2); + + var modelMatrix = new Matrix4(); // Model matrix + var mvpMatrix = new Matrix4(); // Model view projection matrix + var normalMatrix = new Matrix4(); // Transformation matrix for normals + + // Calculate the model matrix + modelMatrix.setRotate(-90, 1, 0, 0); // Rotate around the *X* axis + + // Calculate the view projection matrix + mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100); + mvpMatrix.translate(0, 0, -2) + var theta = Math.PI / 3 + mvpMatrix.lookAt( + Math.tan(theta) / 4, Math.sin(theta) / 4, Math.cos(theta) / 4, + 0, 0, 0, + 0, 1, 0 + ); + mvpMatrix.multiply(modelMatrix); + + // Calculate the matrix to transform the normal based on the model matrix + normalMatrix.setInverseOf(modelMatrix); + normalMatrix.transpose(); + + // Pass the model matrix to u_ModelMatrix + gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements); + + // Pass the model view projection matrix to u_mvpMatrix + gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); + + // Pass the transformation matrix for normals to u_NormalMatrix + gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements); + + // Clear color and depth buffer + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Draw the cube + gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0); +} + + +//!!! RIGHT HERE + +class BivariateFunction { + vertices = []; + vertex_colors = []; + vertex_normals = []; + indices = []; + + func = (x, y) => { return 0; } + + constructor({ width = 50, height = 50, func = this.func } = {}) { + this.width = width; + this.height = height; + this.func = func; + } + + generate_quad(width, _height, index) { + var upper = [index + 1, index, index + width + 1] + var lower = [index + 1, index + width + 1, index + width + 2] + return [upper, lower] + } + + generate_plane() { + var width = this.width, height = this.height; + var vertices = [], vertex_colors = [], vertex_normals = []; + var triangles = []; + for (var i = 0, y = 0; y <= width; y++) { + for (var x = 0; x <= height; x++) { + // Create a new vertex + vertices.push([x / width - 0.5, y / height - 0.5, this.func(x / width, y / width)]); + vertex_colors.push([0.625, 0.025, 0.075]) + //vertex_colors.push([x / width, y / width, 1]); + vertex_normals.push([0, 0, 0]); + + // If this point is the top-left corner of a quad, + // then create a new quad + if (x < width && y < height) { + triangles.push(...this.generate_quad(width, height, i)); + } + i++ + } + } + + for (var triangle of triangles) { + // Turn verts into vec3's + var v = [new vec3(...vertices[triangle[0]]), new vec3(...vertices[triangle[1]]), new vec3(...vertices[triangle[2]])]; + // Calculate alpha and beta vectors + var alpha = v[2].sub(v[0]); + var beta = v[1].sub(v[0]); + // Take the cross product & normalize to get the normal + var normal = alpha.cross(beta).normalize(); + // Add the normal to each vertex + vertex_normals[triangle[0]] = normal.add(new vec3(...vertex_normals[triangle[0]])).a(); + vertex_normals[triangle[1]] = normal.add(new vec3(...vertex_normals[triangle[1]])).a(); + vertex_normals[triangle[2]] = normal.add(new vec3(...vertex_normals[triangle[2]])).a(); + } + // Average the normals by normalizing the sums of the normals + // this assumes the sum of normals around a vertex is not 0 + for (var i = 0; i < vertex_normals.length; i++) { + // taking full advantage of the garbage collector here + vertex_normals[i] = new vec3(...vertex_normals[i]).normalize().a(); + } + // Save the vertices and indices + this.vertices = vertices; + this.vertex_colors = vertex_colors; + this.vertex_normals = vertex_normals; + this.indices = triangles; + return + } +} + + +function initVertexBuffers(gl) { + // Create a new BivariateFunction + var bf = new BivariateFunction({ + width: scale, height: scale, func: f + }); + + bf.generate_plane(); + + var vertices = new Float32Array(bf.vertices.flat()); + var colors = new Float32Array(bf.vertex_colors.flat()); + var normals = new Float32Array(bf.vertex_normals.flat()); + var indices = new Uint16Array(bf.indices.flat()); + + // Write the vertex property to buffers (coordinates, colors and normals) + if (!initArrayBuffer(gl, 'a_Position', vertices, 3)) return -1; + if (!initArrayBuffer(gl, 'a_Color', colors, 3)) return -1; + if (!initArrayBuffer(gl, 'a_Normal', normals, 3)) return -1; + + // Unbind the buffer object + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + // Write the indices to the buffer object + var indexBuffer = gl.createBuffer(); + if (!indexBuffer) { + console.log('Failed to create the buffer object'); + return false; + } + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); + + return indices.length; +} + +function initArrayBuffer(gl, attribute, data, num) { + // Create a buffer object + var buffer = gl.createBuffer(); + if (!buffer) { + console.log('Failed to create the buffer object'); + return false; + } + + // Write date into the buffer object + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); + + // Assign the buffer object to the attribute variable + var a_attribute = gl.getAttribLocation(gl.program, attribute); + if (a_attribute < 0) { + console.log('Failed to get the storage location of ' + attribute); + return false; + } + + gl.vertexAttribPointer(a_attribute, num, gl.FLOAT, false, 0, 0); + + // Enable the assignment of the buffer object to the attribute variable + gl.enableVertexAttribArray(a_attribute); + + return true; +} diff --git a/BivariateFunction/vec3.js b/BivariateFunction/vec3.js new file mode 100644 index 0000000..0a15673 --- /dev/null +++ b/BivariateFunction/vec3.js @@ -0,0 +1,66 @@ +// BetterVector3.js +// John Breaux 2022-07-28 +// 3D Vector library + +"use strict"; + + +class vec3 { + constructor(x = 0, y = 0, z = 0) { + this.x = x; + this.y = y; + this.z = z; + } + + add(rhs) { + return new vec3(this.x + rhs.x, this.y + rhs.y, this.z + rhs.z); + } + sub(rhs) { + return new vec3(this.x - rhs.x, this.y - rhs.y, this.z - rhs.z); + } + dot(rhs) { + return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z; + } + cross(rhs) { + return new vec3( + this.y * rhs.z - this.z * rhs.y, + this.z * rhs.x - this.x * rhs.z, + this.x * rhs.y - this.y * rhs.x + ); + } + + magnitude() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + + normal() { + var m = this.magnitude(); + if (m === 0) {return null}; + return new vec3( + this.x / m, + this.y / m, + this.z / m + ); + } + normalize() { + var m = this.magnitude(); + if (m === 0) { return null }; + this.x /= m; + this.y /= m; + this.z /= m; + return this; + } + + // copy + copy() { + return new vec3(this.x, this.y, this.z); + } + // to array + a() { + return [this.x, this.y, this.z]; + } + // to String + toString() { + return `{${this.x}, ${this.y}, ${this.z}}` + } +} \ No newline at end of file diff --git a/README.md b/README.md index e1b0f43..bade468 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,28 @@ Orthographic rotating cube Write a WebGL program that displays a cube with colored faces using an orthographic projection. Allow an interactive user to rotate the cube 15 degrees about the x and y axis. The viewing volume defined by setOrtho should be chosen so that the cube occupies most of the volume but no clipping occurs. + +# Program 5 + +Graph a bivariate function + +### Problem Statement + +Write a WebGL program that displays the graph of a bivariate function: +$z=f\left(x,y\right)$ for $(x,y)$ in $D=[0,1]*[0,1]$. + +$$f(x,y) = \frac{1}{2} e^{[-0.04 \sqrt{(80x-40)^2 + (90y-45)^2)}]}cos(0.15\sqrt{(80x-40)^2+(90y-45)^2})$$ + +#### The following procedure creates the polygonal (triangle) mesh surface: + +* partition D into a k+1 by k+1 uniform rectangular grid, and partition each of the k*k squares into a pair of triangles. A reasonable value of k is 50. + +* Call f to obtain a z value at each of the grid points + +* Use filled triangles with Gouraud shading and lighting. Note that each vertex normal must be computed by averaging the normals of the triangles which share the vertex. + +* Use a depth buffer for hidden surface removal. + +A good template for this program is LightedCube_animation in Matsuda Chapter 8 (one of our texts). It is sufficient to replace initVertexBuffers to create typed arrays of vertex positions, colors, normals, and indices for the triangle mesh surface rather than a cube. + +Note, however, that the indices cannot be stored as 8-bit unsigned integers, and the third artument in function gl.drawElements must be changed from gl.UNSIGNED_BYTE to short or int.