Note

Access to this page requires authorization. You can try signing in or .

Access to this page requires authorization. You can try .

WebGL demos, references, and resources

Here's a list of WebGL resources that will help you learn and create great 2D and 3D graphics.

Microsoft websites

Demos

  • Lookaround A cool Three.js based demo where you can view a waterfront boardwalk in 360 degrees.
  • Flight to Everest Find your base camp, check ice flows, and scale heights with this 3D demo.
  • Warp The full version of the trimmed down demo presented here.

Reference content

  • WebGL API reference Reference pages for the WebGL API supported by Internet Explorer 11.
  • GLSL errors pages for the WebGL and GLSL features supported by IE11.

Third-party websites

Learning sites

Libraries

  • Three.js One of the most widely used general purpose WebGL libraries.
  • GLMatrix A commonly used library to simplify using matrices.
  • PhiloGL A framework for game development, data visualization, and creative coding.
  • GLGE: WebGL for the lazy A general purpose library that abstracts graphics and 3D concepts.
  • Babylon.js A general purpose library with an emphasis on gaming.

The full example

For this WebGL discussion, we've used snippets of code from a larger example. Here's the complete code listing. There are two files, demo.js and default.html. We've combined the Cascading Style Sheets (CSS) portion with the HTML file, while it's a separate file in the Warp sample.

This is the default.html file

<!DOCTYPE html>
<html>
<head>
 <!-- Copyright � Microsoft Corporation. All Rights Reserved. -->
 <!-- Demo Author: Frank Olivier, Microsoft Corporation --> 
 <!-- Updates by Jay Munro -->
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <title>Photo warping with WebGL</title>
 <style>
 /* Prevent text selection from being displayed when the user drags out of the canvas */
*::selection {
 background:transparent;
}

#DemoContent {
 margin-top:10px;
}

#DemoContent {
 width:600px;
 margin-left:auto;
 margin-right:auto;
 margin-top:10px;
}

#Details {
 margin-top:10px;
 margin-bottom:10px;
 padding:20px;
 border-radius:4px;
}

body {
 -ms-user-select: none; /* turn off user selection */
 font-size: 12pt;
}

canvas {
 -ms-touch-action: none; /* turn off panning and zooming */
}

.heading
{
 font-size: 20pt;
 font-weight: normal;
}

#ErrorMessage {
 position:absolute;
 top:100px;
 left:20%;
 right:20%;
 background-color:lightcoral;
 padding:20px;
 border-radius:4px;
}

 </style>


</head>
<body>
 <h1 id="DemoTitle">
 Photo warping with WebGL
 </h1>
 <h2>click and drag</h2>
 
 <div id="DemoContent">
 <div style='width:600px; height:600px'>
 <div style='position: relative'> 
 <!-- WebGL canvas -->
 <canvas style='position: absolute' id='webglcanvas' width='600' height='600'></canvas>
 
 <!-- notation cavas --> 
 <canvas style='position: absolute' id='2dcanvas' width='600' height='600'></canvas>
 </div>
 </div>
 <div style="text-align:center">
 
 <button id="openphoto1" onclick="OpenPhoto1()">Open a photo</button> 
 <span id="openphoto2" style="display:none">Pick a photo: <input type="file" id="files" /></span> <br />
 <button onclick="undo()">Undo</button> 
 <button onclick="reset()">Start over</button> 
 <button onclick="save()">Save</button> 
 </div>
 </div>
 
 <div id="ErrorMessage" style="display:none">
 <div class="heading">Sorry!</div>
 This demonstation requires a browser with WebGL support.
 </div>
 <div id="log"></div>
 
 <div id="Details">
 <div class="heading">WebGL 101</div> 
 To render the warped photo above, a mesh of 400 triangle coordinates, a photo, a vertex & 
 fragment shader program and uniform points are uploaded to the GPU using WebGL.<br /> 
 <br /> 
 <!-- The show uniform points checkbox -->
 <label><input type="checkbox" name="showUniforms" id="showUniforms" onchange="renderer.changeMode();renderer.render()" />Show uniform points</label> 
 <div>
 <label><input type="radio" name="rendertype" id="renderLines" onclick="renderer.render()" />Show triangle mesh</label>
 <label><input type="radio" name="rendertype" id="renderTriangles" onclick="renderer.render()" checked />Show rendered photo</label>
 </div>
 <br />
 When you click and drag on the photo, new uniform points are set on the GPU...<br /><br />
 <div class="heading">Vertex shader</div>
 <img src="vertex.png"/><br />
 ...The GPU runs the vertex shader below to distort the mesh using the uniform points...<br /><br />
 <pre id="vertexshadersource"></pre>
 <br />
 <div class="heading">Fragment shader</div>
 <img src="fragment.png"/><br />
 ...and the fragment shader paints photo pixels using the distorted mesh.<br /><br /> 
 <pre id="fragmentshadersource"></pre> 
 <br />
 <!-- For more information on WebGL, see <a href="http://docs.webplatform.org/wiki/webgl">webplatform.org</a>.</div> -->
 </div>
 
 <script type="text/javascript" src="demo.js"></script>


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

 // outgoing coordinate
 varying vec2 v_texCoord;

 // incoming coordinate (point)
 attribute vec2 a_texCoord; 
 
 // maximum number of changes to grid
 #define MAXPOINTS 10 
 
 uniform vec2 p1[MAXPOINTS]; // Where the drag started
 uniform vec2 p2[MAXPOINTS]; // Where the drag ended

 void main() { 
 
 v_texCoord = a_texCoord; 
 // Set up position variable with current coordinate normalized from 0 - 1 to -1 to 1 range 
 vec2 position = a_texCoord * 2.0 - 1.0; 

 for (int i = 0; i < MAXPOINTS; i++) // loop through 
 {
 float dragdistance = distance(p1[i], p2[i]); // Calculate the distance between two start and end of mouse drag for each of the drags
 float mydistance = distance(p1[i], position); // Calculate the distance between the start of the mouse drag and the last position 
 if (mydistance < dragdistance) 
 {
 vec2 maxdistort = (p2[i] - p1[i]) / 4.0; // only affect vertices within 4 x the drag distance ( 
 float normalizeddistance = mydistance / dragdistance; 
 float normalizedimpact = (cos(normalizeddistance*3.14159265359)+1.0)/2.0;
 position += (maxdistort * normalizedimpact); 
 }
 }
 // gl_Position always specifies where to render this vector 
 gl_Position = vec4(position, 0.0, 1.0); // x,y,z,
 }
 </script>

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

 // uniform to use for texture 
 uniform sampler2D u_image;

 // Output of the vertex shader 
 varying vec2 v_texCoord;

 void main() {
 // gl_FragColor always specifies the color to make the current pixel 
 gl_FragColor = texture2D(u_image, v_texCoord); 
 }
</script>
<!-- fragment shader -->
<script id="red" type="x-shader/x-fragment">
 precision mediump float;

 varying vec2 v_texCoord;

 // Set a solid color for the grid 
 void main() {
 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
 } 
</script>

</body>
</html>

This is the demo.js file

'use strict';

window.onload = main; // Startup

// Point object - converts incoming values to a -1 to 1 range).
function Point(x, y) {
 if (x<-1) x = -1;
 if (y<-1) y = -1;
 if (x>1) x = 1;
 if (y>1) y = 1;
 
 this.x = x;
 this.y = y;
}

// A new mouse manipulation.
function Move(point) { 
 this.point1 = new Point(point.x, point.y);
 this.point2 = new Point(point.x, point.y);
 
 this.move = function (point) {
 this.point2.x = point.x;
 this.point2.y = point.y;
 }
 
}

var renderer = new function () {

 var gl; // Handle to the context.
 var lineprogram; // Handle to GLSL program that draws lines.
 var pictureprogram; // Handle to GLSL program that draws a picture.

 this.texCoordLocation; // Location of the texture for the picture fragment shader.
 this.texCoordLocation2; // Location of the texture for the line fragment shader.

 this.texCoordBuffer; // The buffer for the texture for the picture fragment shader.
 this.texCoordBuffer2; // The buffer for the texture for the line fragment shader.
 
 var moves = new Array; 
 var MAXMOVES = 10;
 var currentMove = 0;

 var resolution = 20; // Resolution of the mesh.


 // First init called by main().
 // Initialize the gl context variable.

 this.init = function () {
 // Get a context from our canvas object with id = "webglcanvas".
 var canvas = document.getElementById("webglcanvas"); 
 try {
 // Get the context into a local gl and and a public gl.
 // Use preserveDrawingBuffer:true to keep the drawing buffer after presentation
 var gl = this.gl = canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });
 }
 catch (e) {
 // Fail quietly 
 }

 // If we can't get a WebGL context (no WebGL support), then display an error message.
 if (!this.gl) {
 document.getElementById("ErrorMessage").style.display = "block";
 return;
 }


 try {
 // Load the GLSL source written in the HTML file.
 // Create a program with the two shaders
 this.lineprogram = loadProgram(gl, getShader(gl, "2d-vertex-shader"), getShader(gl, "red"));

 // Tell webGL to use this program
 gl.useProgram(this.lineprogram);

 // Look up where the vertex data needs to go.
 this.texCoordLocation2 = gl.getAttribLocation(this.lineprogram, "a_texCoord");

 // Provide texture coordinates for the rectangle.
 this.texCoordBuffer2 = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer2);
 
 // Create a buffer and set it use the array set up above.
 // Set it to be modified once, use many.
 // createRedGrid sets up the vector array itself. 
 gl.bufferData(gl.ARRAY_BUFFER, createRedGrid(), gl.STATIC_DRAW); // Fill buffer data

 // Turns on the vertex attributes in the GPU program. 
 gl.enableVertexAttribArray(this.texCoordLocation2);

 // Set up the data format for the vertex array - set to points (x/y). 
 // Use floats.
 gl.vertexAttribPointer(this.texCoordLocation2, 2, gl.FLOAT, false, 0, 0);
 }

 catch (e) {
 // Display the fail on the screen if the shaders/program fail.
 log('shader fail');
 return;
 }

 try {
 var vertexshader = getShader(gl, "2d-vertex-shader");
 var fragmentshader = getShader(gl, "2d-fragment-shader");
 
 this.pictureprogram = loadProgram(gl, vertexshader, fragmentshader);
 gl.useProgram(this.pictureprogram);

 // Put the shader source into the <divs>. 
 document.getElementById("vertexshadersource").innerText = gl.getShaderSource(vertexshader);
 document.getElementById("fragmentshadersource").innerText = gl.getShaderSource(fragmentshader);

 // Look up where the vertex data needs to go.
 this.texCoordLocation = gl.getAttribLocation(this.pictureprogram, "a_texCoord");

 // Provide texture coordinates for the rectangle.
 this.texCoordBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);

 // createImageGrid sets up the vector array itself
 gl.bufferData(gl.ARRAY_BUFFER, createImageGrid(), gl.STATIC_DRAW); // Fill buffer data 
 gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
 gl.enableVertexAttribArray(this.texCoordLocation);
 // Set up uniforms variables (image).
 this.pictureprogram.u_image = gl.getUniformLocation(this.pictureprogram, "u_image");

 // Set the texture to use.
 gl.uniform1i(this.pictureprogram.u_image, 0);
 }
 catch (e) {
 log('shader fail');
 return;
 }
 
 this.loadImage();
 }

 // Load a default image.
 this.loadImage = function () {
 var image = new Image();
 image.onload = function () {
 renderer.loadImage2(image);
 }
 image.src = "photos/demophoto.jpg"; // Default image 
 }
// load a the user's image.
 this.loadImageX = function (dataURL) {
 var image = new Image();
 
 image.onload = function () {
 renderer.loadImage2(image);
 }

 image.src = dataURL;
 }

 // This function does the heavy lifting of creating the texture from the image.
 this.loadImage2 = function (image) {
 // Convert the image to a square image via the temporary 2d canvas. 
 var canvas = document.getElementById("2dcanvas");
 var ctx = canvas.getContext("2d");
 var canvHeight = document.getElementById("2dcanvas").height;

 var x = 0;
 var y = 0;
 var xx = canvHeight;
 var yy = canvHeight;

 ctx.clearRect(0, 0, canvHeight, canvHeight);
 // If image isn't square, adjust width, height, and origin so it's centered.
 if (image.width < image.height) {
 // Change origin and dimensions if the image isn't square.
 // Change x, xx
 xx = image.width / image.height * canvHeight;
 x = (canvHeight - xx) / 2;
 }
 if (image.width > image.height) {
 // Change y, yy 
 yy = image.height / image.width * canvHeight;
 y = (canvHeight - yy) / 2;
 }

 // Put the image on the canvas, scaled using xx & yy.
 ctx.drawImage(image, 0, 0, image.width, image.height, x, y, xx, yy);
 var gl = this.gl;

 // Create a texture object that will contain the image.
 var texture = gl.createTexture();

 // Bind the texture the target (TEXTURE_2D) of the active texture unit.
 gl.bindTexture(gl.TEXTURE_2D, texture);

 // Flip the image's Y axis to match the WebGL texture coordinate space.
 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
 
 // Set the parameters so we can render any size image. 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

 // Upload the resized canvas image into the texture.
 // Note: a canvas is used here but can be replaced by an image object. 
 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
 ctx.clearRect(0, 0, canvHeight, canvHeight);

 this.reset();

 }
 // This code checks the Show uniform points checkbox and changes the mode and initializes the canvas.
 this.modeOff = 0;
 this.modeHint = 1;
 this.modeHint2 = 2;
 this.modeUniform = 3;

 // Modes tell the app to show uniforms or not
 this.canvasMode = this.modeHint;

 this.changeMode = function () {
 if (document.getElementById("showUniforms").checked) {
 this.canvasMode = this.modeUniform;
 }
 else {
 this.canvasMode = this.modeOff;
 var canvas = document.getElementById("2dcanvas");
 var ctx = canvas.getContext("2d");
 ctx.clearRect(0, 0, canvas.width, canvas.height);
 }

 }

 this.render = function () {
 
 var gl = this.gl;

 // Create two arrays to hold start and end point uniforms
 var p1 = new Float32Array(MAXMOVES * 2); //x and y
 var p2 = new Float32Array(MAXMOVES * 2); //x and y

 // Set up the arrays of points
 {
 var index = 0;
 for (var i = 0; i < MAXMOVES; i++) {
 // Working values
 var x1, y1, x2, y2;

 if (moves[i]) {
 x1 = moves[i].point1.x;
 y1 = moves[i].point1.y;
 x2 = moves[i].point2.x;
 y2 = moves[i].point2.y;
 }
 else {
 x1 = 1;
 y1 = 1;
 x2 = 0.9999999;
 y2 = 0.9999999;
 }

 p1[index] = x1;
 p1[index + 1] = y1;
 p2[index] = x2;
 p2[index + 1] = y2;
 index += 2;
 }
 }
 
 // Clear color buffer and set it to light gray
 gl.clearColor(1.0, 1.0, 1.0, 0.5);
 gl.clear(this.gl.COLOR_BUFFER_BIT);
 
 // This draws either the grid or the photo for stretching
 if (document.getElementById("renderLines").checked)
 {
 gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer2);

 gl.useProgram(this.lineprogram);

 gl.uniform2fv(gl.getUniformLocation(this.lineprogram, "p1"), p1);
 gl.uniform2fv(gl.getUniformLocation(this.lineprogram, "p2"), p2);

 gl.vertexAttribPointer(this.texCoordLocation2, 2, gl.FLOAT, false, 0, 0);

 gl.enableVertexAttribArray(this.texCoordLocation2);

 gl.drawArrays(gl.LINES, 0, resolution * resolution * 10);
 }
 else
 { 
 gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
 
 gl.useProgram(this.pictureprogram);


 gl.uniform2fv(gl.getUniformLocation(this.pictureprogram, "p1"), p1);
 gl.uniform2fv(gl.getUniformLocation(this.pictureprogram, "p2"), p2);

 gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);

 gl.enableVertexAttribArray(this.texCoordLocation);


 gl.drawArrays(gl.TRIANGLES, 0, resolution * resolution * 2 * 3);
 }
 // Draw uniform points 
 if (this.canvasMode == this.modeUniform) {
 var canvas = document.getElementById("2dcanvas");
 var ctx = canvas.getContext("2d");
 ctx.clearRect(0, 0, canvas.width, canvas.height);

 for (var i = 0; i < MAXMOVES; i++) {
 if (moves[i]) {
 var x1 = (moves[i].point1.x + 1) * canvas.width / 2;
 var y1 = (-moves[i].point1.y + 1) * canvas.height / 2;
 var x2 = (moves[i].point2.x + 1) * canvas.width / 2;
 var y2 = (-moves[i].point2.y + 1) * canvas.height / 2;

 // The raio is used here to show where the pixel started and ended
 var ratio = 0.3;
 x2 = x1 + (x2 - x1) * ratio;
 y2 = y1 + (y2 - y1) * ratio;

 var radius = 6; 
 ctx.beginPath(); // Start a fresh path 

 // Create a 2D gradient 
 var grd = ctx.createLinearGradient(x1, y1, x2, y2);
 grd.addColorStop(0, 'pink'); // Set one side to pink
 grd.addColorStop(1, 'red'); // The other side to red

 ctx.setLineDash([5, 5]); // Use a dotted line
 ctx.lineWidth = radius / 2; 
 ctx.moveTo(x1, y1); // Create a line from start to end poing
 ctx.lineTo(x2, y2);
 ctx.strokeStyle = grd;
 ctx.stroke();

 ctx.beginPath(); // Start a new path for pink dot
 ctx.arc(x1, y1, radius, 0, 2 * Math.PI, false); // full circle (2*pi)
 ctx.fillStyle = 'pink';
 ctx.fill();

 ctx.beginPath(); // Start a new path for red dot
 ctx.arc(x2, y2, radius, 0, 2 * Math.PI, false);
 ctx.fillStyle = 'red';
 ctx.fill();
 }
 }
 }
 }
 // (point) is where the mouse was clicked
 this.newMove = function(point) // Where the warp starts (-1 to 1 range)
 {
 var move = new Move(point);
 // Adds move to beginning of moves array (pushes onto array)
 moves.unshift(move);

 return move;
 }
 this.reset = function () {
 moves = [];
 this.render();
 }

 this.undo = function () {
 // Removes the first element in moves array (pops off array)
 moves.shift();
 this.render();
 }
 this.save = function () {
 // First create a dataURL string from the canvas in jpeg format.
 var dataURL = document.getElementById("webglcanvas").toDataURL("image/png");

 // Split the dataURL and decode it from ASCII to base-64 binary.
 var binArray = atob(dataURL.split(',')[1]);

 // Create an 8-bit unsigned array
 var array = [];
 // Add the unicode numeric value of each element to the new array.
 for (var i = 0; i < binArray.length; i++) {
 array.push(binArray.charCodeAt(i));
 }

 var blobObject = new Blob([new Uint8Array(array)], { type: 'image/png' }); 

 if (window.navigator.msSaveBlob) {
 window.navigator.msSaveBlob(blobObject, 'warpedphoto.png');
 }else if (window.navigator.saveBlob) {
 window.navigator.saveBlob(blobObject, 'warpedphoto.png');
 }
 else {
 dataURL = dataURL.replace("image/png", "image/octet-stream");
 window.location.href = dataURL;
 // alert("Sorry, your browser does not support navigator.saveBlob");
 }
 }


 // Grid making section 
 function createRedGrid() {
 // Make a 0,0 to 1,1 triangle mesh, using n = resolution steps.
 var q = 0.001; // A fudge factor to ensure that the wireframe lines are rendered inside the canvas boundary.
 var r = (1 - q * 2) / resolution;
 //2 numbers per coord; three coords per triangle; 2 triagles per square; resolution * resolution squares.
 var c = new Float32Array(resolution * resolution * 20);
 // Array index.
 var i = 0;
 
 // Build the mesh top to bottom, left to right. 
 for (var xs = 0; xs < resolution; xs++) {
 for (var ys = 0; ys < resolution; ys++) {
 var x = r * xs + q;
 var y = r * ys + q;
 // Top of square - first triangle.
 c[i++] = x;
 c[i++] = y;
 c[i++] = x + r;
 c[i++] = y;

 // Center line - hypotonose of triangles.
 c[i++] = x;
 c[i++] = y + r;
 c[i++] = x + r;
 c[i++] = y;

 // Bottom line of 2nd triangle.
 c[i++] = x;
 c[i++] = y + r;
 c[i++] = x + r;
 c[i++] = y + r;

 // First triangle, left side.
 c[i++] = x;
 c[i++] = y;
 c[i++] = x;
 c[i++] = y + r;

 // Right side of 2nd triangle. 
 c[i++] = x + r;
 c[i++] = y;
 c[i++] = x + r;
 c[i++] = y + r;
 }
 }
 return c;
 }
 function createImageGrid() {
 var q = 0.001;

 var r = (1 - q * 2) / resolution;
 var c = new Float32Array(resolution * resolution * 12); //2 numbers per coord; three coords per triangle; 2 triagles per square; resolution * resolution squares.

 var i = 0;

 for (var xs = 0; xs < resolution; xs++) {
 for (var ys = 0; ys < resolution; ys++) {

 var x = r * xs + q;
 var y = r * ys + q;

 c[i++] = x;
 c[i++] = y;

 c[i++] = x + r;
 c[i++] = y;

 c[i++] = x;
 c[i++] = y + r;

 c[i++] = x + r;
 c[i++] = y;

 c[i++] = x;
 c[i++] = y + r;

 c[i++] = x + r;
 c[i++] = y + r;

 }
 }
 return c;
 }
}

// getMousePoint
// input - mouse event e
function getMousePoint(e) {
 var x;
 var y;
 // The standard way to get mouse coordinates
 if (e.offsetX) {
 x = e.offsetX;
 y = e.offsetY;
 }
 // LayerX and layerY are provided for cross-browser compatibility
 else if (e.layerX) {
 x = e.layerX;
 y = e.layerY;
 }
 else {
 return undefined; //Work around Chrome
 }

 return normalizedPoint(x, y); // Converts pixels to -1 to 1
}
var inputHandler = new function() {
 var move; // Pointer to a uniform variable in the renderer object

 this.init = function () {
 var canvas = document.getElementById("2dcanvas");
 // Set up mouse events on the canvas object. 
 canvas.onmousedown = function (e) {
 console.log("onmousedown");
 this.move = renderer.newMove(getMousePoint(e)); 
 }

 canvas.onmouseup = function (e) {
 console.log("onmouseup");
 this.move = undefined;
 renderer.render();
 }

 canvas.onmouseout = function (e) {
 console.log("onmouseout");
 this.move = undefined;
 renderer.render();
 };

 canvas.onmousemove = function (e) {
 console.log("onmousemove");

 var point = getMousePoint(e);

 if (typeof this.move != 'undefined')
 {
 if (typeof point != 'undefined')
 {
 this.move.move(point);
 }
 renderer.render();
 }

 };

 canvas.ondragstart = function (e) { //Workaround for Chrome
 console.log("ondragstart");
 e.preventDefault();
 };
 }

}


// Program starts here
function main() {
 renderer.init(); // Initialize WebGL shapes and image
 inputHandler.init(); // Initialize mouse and UI handler 
}

// Resets the current distortion to 0
function reset(point) {
 point1[currentPoint].x = 1;
 point1[currentPoint].y = 1;
 point2[currentPoint].x = 1.00001;
 point2[currentPoint].y = 1.00001;
 
 setVec2PointArray("p1", point1);
 setVec2PointArray("p2", point2);
}

function undo() {
 renderer.undo();
}

function reset() {
 renderer.reset();
}


function save() {
 renderer.save();
}

function normalizedPoint(x, y) 
{
 // converts screen coordinates to -1 to 1
 var canvas = document.getElementById("2dcanvas");
 x = (x / canvas.width) * 2 - 1;
 y = (1 - (y / canvas.height)) * 2 - 1;
 
 return new Point(x, y);
}


function setVec2(id, a, b) {
 gl.uniform2f(gl.getUniformLocation(program, id), a, b);
}


// Adds a string to the log in the web page
function log(result) {
 var resultDiv = document.getElementById("log");
 resultDiv.innerHTML += result + "<br />";
}

// Adds a string to the log in the web page; overwrites everything in the log with the new string
function logi(result) {
 var resultDiv = document.getElementById('log');
 resultDiv.innerHTML = result;
}

// Loads a shader from a script tag
// Parameters:
// WebGL context
// id of script element containing the shader to load
function getShader(gl, id) {
 var shaderScript = document.getElementById(id);

 // error - element with supplied id couldn't be retrieved
 if (!shaderScript) {
 return null;
 }

 // If successful, build a string representing the shader source
 var str = "";
 var k = shaderScript.firstChild;
 while (k) {
 if (k.nodeType == 3) {
 str += k.textContent;
 }
 k = k.nextSibling;
 }

 var shader;

 // Create shaders based on the type we set
 // note: these types are commonly used, but not required
 if (shaderScript.type == "x-shader/x-fragment") {
 shader = gl.createShader(gl.FRAGMENT_SHADER);
 } else if (shaderScript.type == "x-shader/x-vertex") {
 shader = gl.createShader(gl.VERTEX_SHADER);
 } else {
 return null;
 }

 gl.shaderSource(shader, str);
 gl.compileShader(shader);

 // Check the compile status, return an error if failed
 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 console.log(gl.getShaderInfoLog(shader));
 return null;
 }

 return shader;
}


function loadProgram(gl, vertexShader, fragmentShader)
{
 // create a progam object
 var program = gl.createProgram();

 // attach the two shaders 
 gl.attachShader(program, vertexShader);
 gl.attachShader(program, fragmentShader);

 // link everything 
 gl.linkProgram(program);

 // Check the link status
 var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
 if (!linked) {

 // An error occurred while linking
 var lastError = gl.getProgramInfoLog(program);
 console.warn("Error in program linking:" + lastError);

 gl.deleteProgram(program);
 return null;
 }

 // if all is well, return the program object
 return program;
};

function handleFileSelect(evt) {

 document.getElementById("openphoto1").style.display = "inline";
 document.getElementById("openphoto2").style.display = "none";
 
 var files = evt.target.files; // FileList object
 
 // files is a FileList of File objects. List some properties.
 var file = files[0];
 
 var reader = new FileReader();
 
 // Closure to capture the file information.
 reader.onload = function (e) {
 renderer.loadImageX(this.result);
 };
 
 // Read in the image file as a data URL.
 
 reader.readAsDataURL(file);
}

document.getElementById('files').addEventListener('change', handleFileSelect, false);


function OpenPhoto1() {
 document.getElementById("openphoto1").style.display = "none";
 document.getElementById("openphoto2").style.display = "inline";
}

Additional resources