This lab is an introduction to WebGL programming.
Assignment
After the lab seminar, you have one week to:- Read the remaining contents of this lab material.
- Complete your first WebGL program.
- Submit your code and question answers to URCourses.
Seminar Notes
A. General OpenGL Architecture
Block Diagram of Typical Application / OpenGL / OS interactions.
OpenGL is the definitive cross-platform 3D library. It has a long history. It inspired Microsoft's Direct3D (D3D) API, and both evolved together from supporting fixed function graphics accelerators to supporting modern programmable hardware. The modern OpenGL style, and the one most like recent versions of D3D, is known as Core Profile. A similar set of features is available for mobile devices as OpenGL ES. OpenGL ES 3.0 is used as the basis for WebGL 2, which we will be learning in this class.
By learning WebGL2, you will learn a low level graphics programming style that is common to all modern OpenGL flavours. Especially if you stick to the same programming language, switching between versions is not too hard, and porting code can be easy if you modularize properly.
Getting to the point where you can begin writing OpenGL code is a bit trickier as the process differs from operating system to operating system. Fortunately there are cross platform libraries that can make your code extremely simple to port. The above diagram shows the general case for getting a modern "Core" OpenGL environment. Regardless of how you choose to set up :
- The Application module is developed by you, the programmer
- Windowing API functions specifically developed to support OpenGL are used to set up, shut down, and handle event signals for an OpenGL drawing area called a Rendering Context (RC) in a window or component . These functions may be defined by the operating system, a third party widget set, or by a cross platform OpenGL library.
- OS Defined Libraries:
- Windows: WGL functions
- XWindows (Linux, most UNIXs): GLX functions
- Mac OS X: NSOpenGL classes (usually)
- Full Featured Cross Platform APIs and Environments with OpenGL support
- wxWidgets
- fltk
- tcl/tk
- Qt
- HTML 5 (through WebGL canvas)
- This is the API for this class. Everything you need is built in, including automatic selection of the best drivers for your platform. More on this later.
- Cross Platform OpenGL Libraries
- SDL
- GLFW
- GLUT (pronounced like the glut in gluttony)
- For years, GLUT was the API used in most OpenGL courses, including this one. Old lessons used classic fixed function OpenGL with GLUT 3.7, while newer lessons used freeglut or Apple GLUT to get access to Core Profile functionality. You can find the GLUT based version of these labs here.
- OpenGL API functions defined in gl.h and provided by the active OpenGL driver (specified by the RC) are used to draw.
- GLEW is a popular utility used to easily expose new OpenGL features in C and C++. This is very important for Windows programs, since Microsoft stopped caring about OpenGL at version 1.1 (current version is 4.5) and does not provide Windows API definitions for new OpenGL functions. It is also useful for checking for OpenGL capabilities and extensions on other OSes.
- GLbinding is a modern XML based C++ alternative to GLEW that provides detailed autocomplete headers, type-safe parameters, and more C++ quality of life improvements.
- OpenGL Drivers implement the details of OpenGL functions either by passing data directly to 3D hardware, performing computations on the main CPU or some combination of the two.
When you write a WebGL program, many of these details are hidden from you. In fact, your WebGL calls may not even be executed by an OpenGL driver. On Macs, where OpenGL support is built-in to the OS, WebGL calls are translated directly into their OpenGL equivalents. On Windows, DirectX/D3D 9 or better has been built-in to the OS since Vista, so a special layer called ANGLE — developed by Google — specifically for WebGL and used by Chrome, Firefox, IE11 and Edge — is used to translate WebGL API commands into equivalent D3D9 or D3D11 commands. On Linux, driver support is EXTREMELY important — most browsers only support nVidia's official drivers.
"Ideal" WebGL Application / OpenGL / OS interactions. |
Windows WebGL Application / "OpenGL" / OS interactions. |
ANGLE's translation from WebGL to D3D is good, but not perfect. If you want a "pure" OpenGL experience on Windows, and you think your graphics drivers are up to it, then:
- Firefox: type
about:configinto your address bar, search for angle and switch webgl.disable-angle to true. - Chrome: type
chrome://flags#use-angleinto your address bar to show an option to select back end.
Unfortunately, the Chrome/Chromium team hid ANGLE's OpenGL backend on Windows in 2025 due to lack of developer support. You can still try it with the--use-angle=glflag. For example:
"c:\Program Files\Google\Chrome\Application\chrome.exe" --use-angle=gl
Try one of the textbook's samples, or one of my experiments, to see if WebGL is working for you. If not, the lab has excellent Windows computers with very capable graphics cards installed. You may work there whenever a lab is not scheduled..
We will not go into detail on exactly how everything works in this week's lab. Instead we will focus on getting to the point where you can begin drawing, then use HTML UI elements to interact with the drawing.
B. Overview of an Interactive Program.
Model-View-Controller Architecture
Interactive program design entails breaking your program into three parts:- The Model: all the data that are unique to your program reside there. This might be game state, the contents of a text file, or tables in a database.
- The View: it is a way of displaying some or all of the model's data to user. Objects in the game state might be drawn in 3D, text in the file might be drawn to the screen with formatting, or queries on the database might be wrapped in HTML and sent to a web browser.
- The Controller: the methods for users to manipulate the model or the view. Mouse actions and keystrokes might change the game state, select text, or fill in and submit a form for a new query.
Object-oriented programming was developed, in part, to aid with modularising the components of Model-View-Architecture programs. Most user interface APIs are written in an Object Oriented language.
You will be structuring your programs to handle these three aspects of an interactive program. A 3D graphics:- Model consists of descriptions of the geometry, positioning, apperance and lighting in your scene. Ideally these could be saved to and loaded from a file, but we won't do that much in this lab.
- View consists of the OpenGL rendering calls that set up your rendering context, interpret the model, and send things to be drawn.
- Controller is usually a set of functions you write that respond to event signals sent to your program via the Windowing API.
The Main Events
All OpenGL programs should respond to at least two events:- Setup - There are at least two things to do here
- Acquire Rendering Context: how you perform this step is determined by your Windowing API.
- Set
initial OpenGL scene settings:
OpenGL's default settings are useless. An important part of the drawing pipeline, the shader program
is
missing. You will need to load, compile and activate one. It is also desirable to preload some objects
you
want to draw into data buffers and connect them to your shader programs. You may also need to enable,
disable or configure various OpenGL settings to suit your rendering needs.
- Rendering
- the function that does this is Windowing API specific, but the OpenGL
code will be completely generic and should be copy/paste portable.
C. Setting up Your Lab Environment
With WebGL2 as the API of choice for this course, you will want to set up an environment to easily edit web pages. We will edit and view our code from Langs2 using Vite as a light web server. It will provide us with reload on save features. We will also use JavaScript modules based on the textbook's code and to make certain parts of the program easier to write. Follow these instructions to learn how to set up a textbook style WebGL program.
Before starting these instructions, make sure your browser is WebGL capable. I prefer Chrome for this lab, but Firefox will do. See the end of Section A for links to tests and instructions.
1. Download and Configure VS Code
You should be familiar with this process from CS 115 and CS 285. If not your instructor will guide you through these steps in lab:
- Install or start VS Code.
It's already installed on the lab computers.
At home Download Visual Studio Code and install it if necessary, then run it. - Install the Remote - SSH Extension if necessary.
- Click the Remote - SSH icon that appears, then click the gear icon that appears next to Remotes > SSH
- Select the .ssh/config option to open your SSH config file
- Go to CS Services System Information and log in with CAS
- Click the account tab
- Reset your CS password if necessary - use the provided button, then the link to set your own password.
- Scroll to the bottom of the account tab and copy your SSH Config File information
- Paste that information into your .ssh/config file in VS Code and Save
- Select langs2 from the SSH Remotes list, and login. Follow the instructor if you're not sure how to proceed from here.
- OPTIONAL: follow the instructor as he creates and installs a SSH Identity Key for passwordless login from trusted computers
2. Install and Run Vite
Vite is a web build tool that focuses on making your code lean. You develop in a custom server, then build to publish a highly optimised page with only the JavaScript you actually need for that page. For now, we'll be using it to provide a way to navigate your project tree and to reload your work on save.
Vite requires Node.js, so we'll start there.
- Get a prebuilt Node.js® Linux x64 standalone from here: https://nodejs.org/en/download
And place the downloaded file in your CS homefolder on Langs2 - OR: copy it there directly with this command:
curl https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.xz -O -s - In a terminal unzip it with:
tar -Jvxf *.tar.xz rm *.tar.xz - Rename the extracted directory to
nodejsmv node-v* nodejs - Add the nodejs executable directory to your environment's PATH in your
.profileecho 'PATH=~/nodejs/bin:$PATH' >> ~/.profile
The .profile file runs once when your VS Code Server starts. - VS Code servers persist after logout so the server down manually, not just disconnect.
Shut down your VS Code Server by either:- Typing
pkill -f codein the terminal
This option will prompt you to reload right away, but will disconnect from other computers you left logged in on your account. - Typing
CTRL + SHIFT + Pthen searching forremote ssh killand selecting Kill Current VS Code Server
This option will give you a notification, but you will have to wait to reload and reconnect. Other computers should stay logged in.
- Typing
npm init -y
npm install vite
index.html file in your CS315 folderpackage.json file that npm created and replace the scripts section
with this:
"scripts": {
"dev": "vite",
"build": "vite build"
},
npm run dev
index.html with whatever text you like and save. The page should automatically
reload and show your changes.3. Install WebGL Folders
The textbook provides many WebGL2 samples and a set of library files to make writing 3D programs easier for
you. Most importantly, the textbook provides simple shader loaders and a Vector/Matrix math library. The CS
315 team has modernized and added to these by modularizing the textbook's libraries and providing the
uofrGraphics.mjs module that can draw various simple shapes. We will now download these adapted
files, plus a new Vite homepage, and explore a bit.
- In your terminal press CTRL+C to stop vite.
- Get and unzip
CS315StarterPack.ziplike this:curl https://www.labs.cs.uregina.ca/315/WebGL2/CS315StarterPack.zip -O -s unzip CS315StarterPack.zip - To prevent some error codes, open your
package.jsonfile and change"type"from"commonjs"to"module". - Restart Vite - you'll see no changes. We'll fix those in a moment.
- Delete your
index.html- the Starter Pack provides a file explorer invite.config.jsif noindex.htmlorindex.phpis detected. It should appear automagically! - What are these files? Take a moment to check them out, there's a question about this later!
- Try a simple change - perhaps change the point colour or size in
8E_Textbook_Code/02/gasket1.html. Your lab instructor will probably play for a bit here. - When you're done exploring, create a Lab1 folder and move to the section.
D. Your First WebGL Program
1. Create the HTML File
The HTML file for a WebGL application will link in necessary javascript files and resources. Some resources, such as shaders, can be defined in-line. The following is a commented minimal HTML file for a textbook style WebGL application:
<!DOCTYPE html>
<html>
<head>
<title>WebGL Template</title>
<!-- This in-line script is a vertex shader resource
Shaders can be linked from an external file as well.
First line must be shader language version, no spaces before.
(Actually textbook's shader loader strips leading spaces...)
-->
<script id="vertex-shader" type="x-shader/x-vertex">
#version 300 es
// All vertex shaders require a position input.
// The name can be changed.
// Other inputs can be added as desired for colors and other features.
in vec4 vPosition;
void main()
{
// gl_Position is a built-in vertex shader output.
// Its value should always be set by the vertex shader.
// The simplest shaders just copy the position attribute straight to
// gl_Position
gl_Position = vPosition;
}
</script>
<!-- This in-line script is a vertex shader resource
Shaders can be linked from an external file as well.
First line must be shader language version, no spaces before.
(Actually textbook's shader loader strips the spaces...) -->
<script id="fragment-shader" type="x-shader/x-fragment">
#version 300 es
// Sets default precision for floats.
// Since fragment shaders have no default precision, you must either:
// - set a default before declaring types that use floating point OR
// - specify the precision before each floating point declaration
// Choices are lowp, mediump, and highp.
precision mediump float;
// The output of a fragment shader is sent to draw buffers,
// which might be an array or the screen. The default is
out vec4 fragColor;
void main()
{
// In general, the fragment shader output should be set,
// but this is not required.
// If an output is not set,
// there will be no output to the corresponding draw buffer.
fragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
</script>
<!-- These are external javascript files.
The first three are the textbook libraries.
The last one is your own javascript code. Make sure to change the name
to match your javascript file. -->
<script type="module" src="yourWebGLJavascript.mjs"></script>
</head>
<body>
<!-- This is the canvas - the only HTML element that can render WebGL
graphics. You can have more than one WebGL canvas on a web page, but
that gets tricky. Stick to one per page for now. -->
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
</html>
Using your favorite editor, create a new HTML file called Lab1Demo.html in your Lab1
folder and
paste the above code into it.
2. Create the Javascript Module File
The JavaScript module file will breathe life into your WebGL application. It
sets up the WebGL rendering context, does the drawing and defines
responses to various events. The simplest WebGL javascript program will
set up the rendering context after the HTML has been loaded. This type of code has been placed in the
init() function which is called at the end of the file. It can also draw, but we'll separate
that into a render() function that can be called once when initialization is done for simple drawings or repeatedly with
requestAnimationFrame() if animation is desired.
The following JavaScript defines a very minimalistic template WebGL program:
import * as MV from '/Common/MV.mjs';
import { uofrGraphics, flatten4x4 } from "/Common/uofrGraphics.mjs";
import { initShaders } from "/Common/initShadersHTML.mjs";
// This variable will store the WebGL rendering context
let gl;
// This variable will store UofR Graphics object
let urgl;
function init() {
// Set up a WebGL Rendering Context in an HTML5 Canvas
let canvas = document.getElementById("gl-canvas");
gl = canvas.getContext('webgl2');
if (!gl) {
throw new Error("WebGL 2.0 isn't available");;
}
// Configure WebGL
// eg. - set a clear color
// - turn on depth testing
// Load shaders and initialize attribute buffers
let program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
// Set up data to draw
// Load the data into GPU data buffers
// Associate shader attributes with corresponding data buffers
// Get addresses of shader uniforms
// Either draw as part of initialization
//render();
// Or draw just before the next repaint event
//requestAnimationFrame(render);
};
function render() {
// clear the screen
// draw
}
init();
Using your favorite editor, create a new javascript file called Lab1Demo.mjs in the Lab1 folder and paste the above code into it. Don't forget to edit the .html file so it knows about your new .mjs file!
4. Add More Code to Draw a Coloured Triangle
Load the HTML file in your WebGL capable web browser. You will see nothing. This is the correct behaviour. Let's add the code to draw a blended colour triangle, starting with the necessary javascript, then moving to the shaders.
In this part, add or change the highlighted code in the template code.
import * as MV from '/Common/MV.mjs';
import { uofrGraphics, flatten4x4 } from "/Common/uofrGraphics.mjs";
import { initShaders } from "/Common/initShadersHTML.mjs";
// This variable will store the WebGL rendering context
let gl;
// This variable will store UofR Graphics object
let urgl;
window.onload = function init() {
// Set up a WebGL Rendering Context in an HTML5 Canvas
let canvas = document.getElementById("gl-canvas");
gl = canvas.getContext('webgl2');
if (!gl) alert("WebGL 2.0 isn't available");
// Configure WebGL
// eg. - set a clear color
// - turn on depth testing
// This light gray clear colour will help you see your canvas
gl.clearColor(0.9, 0.9, 0.9, 1.0);
// Load shaders and initialize attribute buffers
let program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
// Set up data to draw
// Here, 2D vertex positions and RGB colours are loaded into arrays.
let positions = [
-0.5, -0.5, // point 1
0.5, -0.5, // point 2
0.0, 0.5 // point 2
];
let colors = [
1, 0, 0, // red
0, 1, 0, // green
0, 0, 1 // blue
];
// Load the data into GPU data buffers
// The vertex positions are copied into one buffer
let vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, MV.flatten(positions), gl.STATIC_DRAW);
// The colours are copied into another buffer
let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, MV.flatten(colors), gl.STATIC_DRAW);
// Associate shader attributes with corresponding data buffers
// Create a connection manager for the data, a Vertex Array Object
// These are typically made global so you can swap what you draw in the
// render function.
let triangleVAO = gl.createVertexArray();
gl.bindVertexArray(triangleVAO);
//Here we prepare the "vPosition" shader attribute entry point to
//receive 2D float vertex positions from the vertex buffer
let vPosition = gl.getAttribLocation(program, "vPosition");
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
//Here we prepare the "vColor" shader attribute entry point to
//receive RGB float colours from the colour buffer
let vColor = gl.getAttribLocation(program, "vColor");
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.vertexAttribPointer(vColor, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vColor);
// Get addresses of shader uniforms
// None in this program...
//Either draw once as part of initialization
render();
//Or schedule a draw just before the next repaint event
//requestAnimationFrame(render);
};
function render() {
// clear the screen
// Actually, the WebGL automatically clears the screen before drawing.
// This command will clear the screen to the clear color instead of white.
gl.clear(gl.COLOR_BUFFER_BIT);
// draw
// Draw the data from the buffers currently associated with shader variables
// Our triangle has three vertices that start at the beginning of the buffer.
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
Reload the HTML page. The result should look like this:
What your project should look like. If you see nothing and you are sure you copy/pasted everything correctly, make sure your HTML is referencing your javascript file instead of the template's dummy one.
Now we're getting somewhere. The reason the triangle is black is that the shaders in the HTML file are hardcoded to paint everything black. Check your web browser's error console. Your WebGL object may have reported errors when you called vertexAttribPointer() and enableVertexAttribArray() with the invalid vColor returned by getAttribLocation(). We need to modify the shaders to make them aware of the colour buffer. Make the following changes to the vertex and fragment shaders:
#version 300 es
// All vertex shaders require a position input.
// The name can be changed.
// Other inputs can be added as desired for colors and other features.
in vec4 vPosition;
in vec4 vColor;
// This varying output is interpolated between the vertices in the
// primitive we are drawing before being sent to an input with
// matching name and type in the fragment shader
out vec4 varColor;
void main()
{
// gl_Position is a built-in vertex shader output.
// Its value should always be set by the vertex shader.
// The simplest shaders just copy the position attribute straight to
// gl_Position
gl_Position = vPosition;
varColor = vColor;
}
#version 300 es
// Sets default precision for floats.
// Since fragment shaders have no default precision, you must either:
// - set a default before declaring types that use floating point OR
// - specify the precision before each floating point declaration
// Choices are lowp, mediump, and highp.
precision mediump float;
in vec4 varColor;
// The output of a fragment shader is sent to draw buffers,
// which might be an array or the screen. The default is
out vec4 fragColor;
void main()
{
// In general, the fragment shader output should be set,
// but this is not required.
// If an output is not set,
// there will be no output to the corresponding draw buffer.
fragColor = varColor;
}
Much more colourful. Yay!
Click here to see a page with the working WebGL result instead of a picture.
E. A More Complex Scene
The triangle is OK, but you probably want Real 3D Graphcs. Let's do that. I do not expect you to understand everything that's going on here, but you should be able to understand enough to use this as the basis for your exercise this week.
- Create new files and call them Lab1Exercise.html and Lab1Exercise.mjs
- Add the template code. Click to jump: HTML, Javascript)
- Replace the vertex shader in the HTML file with this one:
New Code: Basic Transformation and Illumination Vertex Shader
#version 300 es in vec4 vPosition; in vec4 vNormal; in vec3 vColor; uniform mat4 p; uniform mat4 mv; uniform vec4 lightPosition; out vec4 varColor; float shininess; vec4 ambientProduct; vec4 diffuseProduct; vec4 specularProduct; vec4 mvPosition; mat4 t_mv, t_p; void main() { //initialize variables shininess = 5.0; ambientProduct = vec4(0.2 * vColor, 1); diffuseProduct = vec4(0.8 * vColor,1); specularProduct = vec4(0.3); //Transform the point t_mv = transpose(mv); t_p = transpose(p); mvPosition = t_mv*vPosition; gl_Position = t_p*mvPosition; //Set up Normal, Light, Eye and Half vectors vec3 N = normalize((t_mv*vNormal).xyz); vec3 L = normalize(lightPosition.xyz - mvPosition.xyz); if (lightPosition.w == 0.0) L = normalize(lightPosition.xyz); vec3 E = -normalize(mvPosition.xyz); vec3 H = normalize(L+E); //Calculate diffuse coefficient float Kd = max(dot(L,N), 0.0); //Calculate Blinn-Phong specular coefficient float Ks = pow(max(dot(N,H), 0.0), shininess); //Calculate lit colour for this pixel varColor = Kd * diffuseProduct + Ks * specularProduct + ambientProduct; } - Use the color fragment shader from the triangle demo (jump) program.
The one
in the template will make everything black.
- Add these global variable to the top of your javascript file, right after let urgl;
- Replace all the code in init with the following:
New Code: init
// Set up a WebGL Rendering Context in an HTML5 Canvas let canvas = document.getElementById("gl-canvas"); gl = canvas.getContext('webgl2'); if (!gl) { throw new Error("WebGL 2.0 isn't available");; } // Configure WebGL // eg. - set a clear color // - turn on depth testing // This light gray clear colour will help you see your canvas gl.clearColor(0.9, 0.9, 0.9, 1.0); gl.enable(gl.DEPTH_TEST); // Load shaders and initialize attribute buffers let program = initShaders(gl, "vertex-shader", "fragment-shader"); gl.useProgram(program); // Get locations of transformation matrices from shader mvIndex = gl.getUniformLocation(program, "mv"); projIndex = gl.getUniformLocation(program, "p"); // Send a perspective transformation to the shader let p = MV.perspective(50.0, canvas.width/canvas.height, 0.5, 50.0); gl.uniformMatrix4fv(projIndex, gl.FALSE, flatten4x4(p)); // Get locations of lighting uniforms from shader let uLightPosition = gl.getUniformLocation(program, "lightPosition"); // Set default light direction in shader. gl.uniform4f(uLightPosition, 0.0, 0.0, 1.0, 0.0); // Configure uofrGraphics object urgl = new uofrGraphics(gl); urgl.connectShader(program, "vPosition", "vNormal", "vColor"); // Begin an animation sequence requestAnimationFrame(render); - Replace all the code in render with the following:
New Code: render
// Clear the canvas with the clear color instead of plain white, // and also clear the depth buffer gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Draw previous model state // Notice we modularized the work a bit... PreRenderScene(); RenderStockScene(); RenderScene(); // Update the model and request a new animation frame rotAngle += rotChange; requestAnimationFrame(render); - Notice we divided up the drawing between three new functions. Add those functions to the end of your
Javascript file.
New Code: Render helpers
// Use this to perform view transforms or other tasks // that will affect both stock scene and detail scene function PreRenderScene() { // select a default viewing transformation // of a 20 degree rotation about the X axis // then a -5 unit transformation along Z mv = MV.mat4(); mv = MV.mult(mv, MV.translate(0.0, 0.0, -5.0)); mv = MV.mult(mv, MV.rotate(-20.0, MV.vec3(1, 0, 0))); //Allow variable controlled rotation around local y axis. mv = MV.mult(mv, MV.rotate(rotAngle, MV.vec3(0, 1, 0))); } // Function: RenderStockScene // Purpose: // Draw a stock scene that looks like a // black and white checkerboard function RenderStockScene() { let width = 10.0; let nCells = 20.0; let delta = width / nCells; // define four vertices that make up a square. const v1 = MV.vec4(0.0, 0.0, 0.0, 1.0); const v2 = MV.vec4(0.0, 0.0, delta, 1.0); const v3 = MV.vec4(delta, 0.0, delta, 1.0); const v4 = MV.vec4(delta, 0.0, 0.0, 1.0); // define the two colors const color1 = MV.vec4(0.9, 0.9, 0.9, 1); const color2 = MV.vec4(0.05, 0.05, 0.05, 1); //Make a checkerboard let placementX = mv; let placementZ const xTrans = MV.translate(delta, 0.0, 0.0); const zTrans = MV.translate(0.0, 0.0, delta); placementX = MV.mult(placementX, MV.translate(-width / 2.0, 0.0, -width / 2.0)); let color = 0; for (let x = 0; x <= nCells; x++) { placementZ = placementX; for (let z = 0; z <= nCells; z++) { urgl.setDrawColour((color++) % 2 ? color1 : color2); gl.uniformMatrix4fv(mvIndex, gl.FALSE, flatten4x4(placementZ)); urgl.drawQuad(v1, v2, v3, v4); placementZ = MV.mult(placementZ, zTrans); } placementX = MV.mult(placementX, xTrans); } } // Function: RenderScene // Purpose: // Your playground. Code additional scene details here. function RenderScene() { ////// //Draw a cube "base" // TODO 1: give the cube a side length of .5 // and put it exactly on the checkerboard // Create the a translation transformation. // Translate's 3 arguments are x, y and z. // At this point, positive y points up from checkerboard // TODO 1b: adjust the translation so the cube is on top of the checkerboard // HINT: what's half the cube's side length? mv = MV.mult(mv, MV.translate(0.0, 0.0, 0.0)); //Send the transformation matrix to the shader gl.uniformMatrix4fv(mvIndex, gl.FALSE, flatten4x4(mv)); // set the drawing color to light blue // arguments to vec4 are red, green, blue and alpha (transparancy) urgl.setDrawColour(MV.vec4(0.5, 0.5, 1.0, 1.0)); // Draw the cube with urgl.drawSolidCube // Argument "size" refers to the side length of the cube // Cube is centered around current origin // TODO 1a: change the cube's side length to .5 urgl.drawSolidCube(1.0); /////// // Draw a Sphere: // TODO 2: place sphere exactly on top of the box // change the sphere to a wire sphere to see the poles // uncomment the rotate to see the effect // Set the drawing color to the one from the menu urgl.setDrawColour(objectColor); // Move the "drawing space" up by the sphere's radius // so the sphere is on top of the checkerboard // mv is a transformation matrix. It accumulates transformations through // right side matrix multiplication. // TODO 2a: use the translation to put the sphere on top of the box // HINT: radius of sphere + half side length of the cube... mv = MV.mult(mv, MV.translate(0.0, 0.5, 0.0)); // Rotate drawing space by 90 degrees around X so the sphere's poles // are vertical. Arguments are angle in degrees, // and a three part rotation axis with x, y and z components. // after this rotation the Z axis is up instead of the Y axis // TODO 2c: uncomment this rotate //mv = MV.mult(mv, MV.rotate(90.0, MV.vec3(1, 0, 0))); //Send the transformation matrix to the shader gl.uniformMatrix4fv(mvIndex, gl.FALSE, flatten4x4(mv)); // Draw a sphere with urgl.drawSolidSphere // Arguments are Radius, Slices, Stacks // Sphere is centered around current origin. // TODO 2b: Change to drawWireSphere so you can see the sphere's poles urgl.drawSolidSphere(0.5, 40, 40); /////// // Draw a Torus // TODO 3: tilt the torus by 10 degrees and make it fit the sphere snugly // Change the draw color to cyan (see the colors object at start of code) urgl.setDrawColour(colors.cyan); // TODO 3a: add a rotation here like the one for the sphere // and change the angle to 10 degrees // TODO 3b: send the transformation matrix to the shader // Draw a torus with urgl.drawSolidTorus // // Arguments are: // - iradius: Inner Radius (radius of the torus "tube") // - oradius: Outer Radius (radius of the center of torus "ring") // - nsides: sides around one segment or ring of the torus // - nrings: number of rings around the torus // TODO 3c: adjust the oradius so the ring fits the sphere exactly // HINT: how much bigger than the sphere does oradius need to be // so that iradius will just touch it? urgl.drawSolidTorus(0.1, 1.0, 40, 40); } - You can now test your program. It will look like this but without the menu to change colours.
//WebGL State Management
////////////////////////
let mvIndex; //Shader Positioning Input
let projIndex; //Shader Projection Input
let mv; //Local Positioning Matrix
let p; //Local Projection Matrix
let colors = {
'red': new MV.vec4(1, 0, 0, 1),
'blue': new MV.vec4(0, 0, 1, 1),
'green': new MV.vec4(0, 1, 0, 1),
'yellow': new MV.vec4(1, 1, 0, 1),
'cyan': new MV.vec4(0, 1, 1, 1),
'magenta': new MV.vec4(1, 0, 1, 1),
};
//Model Control Variables
/////////////////////////
let objectColor = colors['red']; //current color of sphere
let rotAngle = 0; //current rotation angle of scene
let rotChange = -0.5; //speed and direction of scene rotation
F. A UI Event
Let's make the program a little more interactive.- Add this select element (menu) to your HTML page, right after the canvas tags.
HTML: A menu
<br /> <select id="colorMenu" > <option value="red">red</option> <option value="blue">blue</option> <option value="green">green</option> <option value="yellow">yellow</option> <option value="cyan">cyan</option> <option value="magenta">magenta</option> </select> - You should see a little drop down menu below your canvas. You can interact with it, but it doesn't do anything yet...
- The values in this menu happen to correspond to the values in an associative array called colors that we
added
to our javascript program earlier. Let's connect the two and use the menu to change the value of the
sphere's
color variable.
Javascript: add this to the end of your init function
// You can't work with HTML elements until the page is loaded, // So init is a good place to set up event listeners setupEventListeners();Javascript: New Functionfunction setupEventListeners() { //Request an HTML element let m = document.getElementById("colorMenu"); //Setup a listener - notice we are defining a function from inside a function //The textbook recommends click, but I find that change works better m.addEventListener("change", function(event) { //acquire the menu entry number let index = m.selectedIndex; //learn the value of the selected option. This need not match the text... let colorName = m.options[index].value; //change the object color by looking up the value in our associate array objectColor = colors[colorName]; }); } - Attempt to run your project. If something went wrong, reread the notes. Or berate the lab instructor. That might work too.
Good luck!
If your program runs correctly, you might be wondering how exactly the picture is drawn. That is, you might want to understand how each function works. Well, you do not have to worry too much about this in the first lab. Over the rest of the semester we will go through these subjects one-by-one in detail. Of course, we will learn some even more advanced functions and features too.
G. Assignment
To be completed and submitted to URCourses by the start of your next lab seminar.
Play with WebGL
Try to make to the RenderScene function by following the TODOs in the comments. This is a taste of things to come. For help with the function calls read the comments carefully and consult with your lab instructor.
- TODO 1: Make the blue cube be half as big (1a) and put it exactly on the checkerboard (1b).
- TODO 2: Make the sphere sit exactly on top of the cube (2a), switch it to wire frame (2b) and rotate it 90° so its poles point up and down (2c). The rotation will also affect the torus (ring).
- TODO 3:: Tilt the torus by 10° (3a & 3b) and make it fit the sphere snugly (3c).
Your result should look like this... you may add other objects to the scene if you wish, but please keep the blue cube and red sphere visible.
Think About Event Programming
- What is an event? Please explain in general programming terms, not language specific.
- What kinds of things trigger events - are they always the direct result of a user interaction?
- Add interactivity to your program by registering your own event callback function. Sections 3.6 to 3.8 of your textbook contains many examples of how to set up event listeners for various user interactions. You can also find a list of HTML Input types and how to use them from the Mozilla Developer Network. Choose one of them and make it affect the rotation of your scene. Or you can check out the source code for my HTML Input Zoo. You might want to try one of the following (in order of increasing difficulty):
- Change the direction of rotation with a mouse click or button press.
- Change the speed of rotation with a slider.
- Remove the slow rotation completely and use mouse motion or drag to spin the scene
- Extra: Try more than one.
Learn About the CS315 Libraries
You have seen a lot of stuff so far and you may be confused about all the libraries used in this lab's program. Take a moment to learn about them.
- What is HTML5 and why is it exciting?
- What is WebGL2 based on?
- Name one classic OpenGL Windowing API and provide a link to some official documentation.
- What type of JavaScript files are in the lab's Common folder? How is using functions in this type of file different than a normal JavaScript file?
- Briefly describe each file in the Common folder: Is it based on a textbook file or original to this lab? What features do its functions provide (be brief, at most one short sentence per file)?
Deliverables
Package all your exercise files into one zip file. Submit the .zip file to URCourses. Include the following:- Working WebGL folder structure including Lab1 and Common folders with all code with modified box/sphere position and your new event handler/UI element.
- Shown on Lab1Exercise.html page: answers to the "Think About Event Programming" questions.
- Shown on Lab1Exercise.html page: answers to "Learn About the CS315 Libraries"