An Introduction

05.04.2017

Eirik Ola Aksnes | Thomas Johansen

Agenda

  • 3D on the Web
  • Hello Canvas (2D)
  • Hello WebGL (3D)
  • Draw Something Meaningful
  • Three.js to the rescue!
  • Great Examples
  • References

3D on the Web

BEFORE (PLUG-INS)

  • Adobe Flash, MS Silverlight
  • Java OpenGL, JOGL, Java 3D
  • O3D
  • VRML, X3D/X3DOM

OpenGL

  • 1992 - OpenGL 1.0
    • Direct-mode rendering
    • Fixed pipeline
  • 2004 - OpenGL 2.0
    • Programmable pipeline: vertex (position calculator) and fragment (color chooser) shading stages of the pipeline
    • OpenGL Shading Language
  • 2007 - OpenGL ES 2.0
    • Designed for mobile phones, embedded devices and video game systems
  • 2010 - OpenGL 4.0
  • 2011 - WebGL 1.0
  • 2012 - OpenGL ES 3.0
  • 2014 - OpenGL 4.5
  • 2017 - WebGL 2.0

NOW

OpenGL ES 2.0 + JavaScript + HTML5 canvas
=

WebGL

YAY!

  • + NO Plugins!
  • + Integrates nicely in website

Hello Canvas (2D)

1. Create a canvas element


<canvas id="myCanvas" width="400" height="400" style="background-color: black;">
  Your browser doesn't appear to support the HTML5 <canvas> element.
</canvas>
						
Your browser doesn't appear to support the HTML5 <canvas> element.

2. Obtain a drawing context


var canvas = document.getElementById("myCanvas");
var drawingContext = canvas.getContext("2d");
						

3. Draw something


drawingContext.strokeStyle = "#0000FF";
drawingContext.fillStyle = "#00FF00";
drawingContext.lineWidth = 4;

drawingContext.beginPath();

drawingContext.moveTo(250,0);
drawingContext.lineTo(500,500);
drawingContext.lineTo(0,500);
drawingContext.lineTo(250,0);

drawingContext.fill();
drawingContext.stroke();
drawingContext.closePath();
						

3. The result

Your browser doesn't appear to support the HTML5 <canvas> element.

Hello WebGL (3D)

Draw a blue background

  • As simple as it gets (with raw WebGL)

The Code!


<html>
  <head>          
    <script type="text/javascript">
      function draw() {
        var canvas = document.getElementById('canvas');
        var gl = canvas.getContext("webgl");
        gl.clearColor(0, 0, 0.5, 1);
        gl.clear(gl.COLOR_BUFFER_BIT); // Call the clear function to set the color
      }
    </script>
  </head>
  <body onload="draw()">
    <canvas id="canvas" width="500" height="500"></canvas>
  </body>
</html>
						
  • Create a canvas element
  • Get a reference to the canvas element
  • Obtain a WebGL context
    • Through gl (JavaScript object) we can access all WebGL functionality
  • Set background color to blue

Draw Something Meaningful

Draw A Triangle

  • Reusing the code from the previous example (blue background)

Low Level API

  • WebGL is designed
    • To work directly with your GPU
    • So that you can build higher level libraries upon it

How To Draw A Triangle?

  • The basic idea is to send a list of vertices to the GPU for drawing
  • A triangle is defined by three vertices (corner points)

Graphical Pipeline

Programmable pipeline

What Are Shaders?

  • Small "programs" that runs on the GPU
  • Have a well-defined set of input/output values, some built-in and some user-defined
  • Written in OpenGL Shading Language (GLSL)
  • GLSL syntax is kinda like C
  • Generally regarded as fairly advanced, but it is actually much simpler than it looks

Shader Effect Examples

  • Shaders can be used to create cool effects like lightning, shadowing, refraction, and more

Reference

Shaders In WebGL

  • There are two shader types (two stages of the pipeline are programmable):
    • A vertex shader (process vertices)
    • A fragment shader (process fragments/pixels)
  • You will need both shaders in order to draw anything on the screen
  • For standard usage, these shaders are quite simple
  • No default shaders
  • You need to write your own shaders or use one provided by a WebGL library (copy and paste)

Simplified WebGL Pipeline

Reference

Create A Buffer

  • That contains the three vertices (corners) of the triangle


function createBuffer(gl) {
  // Create buffer
  var vertexPositionBuffer = gl.createBuffer();
  // Activate buffer
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
  var triangleVertices = [
    -0.5, -0.5,
     0.5, -0.5,
     0.0, 0.5
  ];
  // Copy vertices to buffer (on the GPU)
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW);
  return vertexPositionBuffer;
}
						

A Simple Vertex Shader

  • Executed 3 times, once for every vertex of the triangle
  • Its job is to set something called gl_Position, the final position of a vertex on the viewport (i.e. screen)

<script id="vertex" type="x-shader">
  attribute vec2 vertexPosition;
  void main() {
    gl_Position = vec4(vertexPosition, 0, 1); //xyzw
  }
</script>
						

A Simple Fragment Shader

  • Executed once for every pixel covered by the triangle
  • The GPU figures out which pixels on the screen that are covered by the triangle
  • Its job is to set something called gl_FragColor, the final colour of a pixel

<script id="fragment" type="x-shader">
  void main() {
    // Just apply the same color (rgba) to every pixel covered by the triangle
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
</script>
						

Create And Compile Shaders


function createShader(str, type) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, str);
  gl.compileShader(shader);
  return shader;
}
var vertexShader = createShader(document.getElementById("vertex").innerHTML, gl.VERTEX_SHADER);
var fragmentShader = createShader(document.getElementById("fragment").innerHTML, gl.FRAGMENT_SHADER);
    					

Create A Shader Program

  • The vertex shader and fragment shader are linked together into a shader program (or just program)

function createShaderProgram(gl, vertexShader, fragmentShader) {
  // A program consists of a vertex and fragment shader
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  // Set the specified program as the currently active program
  gl.useProgram(program);
  return program;
}
    					

Vertex Shader Attributes

  • Attributes is input to the vertex shader
  • Each vertex normally has a set of attributes associated with it;
    • Position
    • Color
    • Normal

function vertexShaderAttributes(gl, shaderProgram, vertexPositionBuffer) {
  // Make the vertices available to the vertex shaders
  shaderProgram.vertexPositionAttrb = gl.getAttribLocation(shaderProgram, 'vertexPosition');
  gl.enableVertexAttribArray(shaderProgram.vertexPositionAttrb);
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttrb, 2, gl.FLOAT, false, 0, 0);
}
    					

Draw The Triangle


gl.drawArrays(gl.GL_TRIANGLES, 0, 3);
						
  • Draw a triangle using GL_TRIANGLES primitive, it treats each triplet of vertices as an independent triangle.
  • Primitive is important parameter since it determines how many triangles will be generated

The Result

The code!

The bottom line

  • A lot of work (low level API)
  • Any WebGL program will have similar structure;
    • Obtain a WebGL context from the canvas element
    • Upload drawing data into buffers
    • Create and compile shaders
    • Create a shader program
    • Draw
  • Higher level libraries will be a big help!

THREE.js TO THE RESCUE!

Three.js

The HTML


<html>
  <head>
    <title>Three.js Visualization</title>
    <script src="three.js"></script>
    <script>
      // Your JavaScript will go here
    </script>
  </head>
  </body><body>
</html>
						

Need three things

Renderer (responsible to draw the scene)


var renderer = new THREE.WebGLRenderer({
  clearColor: 0x0000FF
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
						

Scene (contains what to draw)


var scene = new THREE.Scene();
						

Perspective camera


var fieldOfView = 45;
var aspectRatio = window.innerWidth / window.innerHeight;
var zNear = 0.1;
var zFar = 10000;
var camera = new THREE.PerspectiveCamera(fieldOfView, aspectRatio, zNear, zFar);
						

Now we can start draw things...

Mesh

A mesh is a complete object that can then be added to a scene.

Triangle


var geometry = new THREE.Geometry();
var vertex1 = new THREE.Vector3(-0.5 ,-0.5, 0);
var vertex2 = new THREE.Vector3(0.5, -0.5, 0);
var vertex3 = new THREE.Vector3(0.0, 0.5, 0);
geometry.vertices.push(vertex1);
geometry.vertices.push(vertex2);
geometry.vertices.push(vertex3);
geometry.faces.push(new THREE.Face3(0, 1, 2 ));

var material = new THREE.MeshBasicMaterial({
  color: 0x00FF00
});

var triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);
						

Torus

Torus


var radius = 40;
var tube = 10;
var radialSegments = 50;
var tubularSegments = 50;
var geometry = new THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments);

var material = new THREE.MeshBasicMaterial({
  color: 0x0000FF
});

var torus = new THREE.Mesh(geometry, material);
scene.add(torus);
						

Spin The Camera


function animate(time) {
  // Spin the camera in a circle
  camera.position.x = Math.sin(time/1000) * 150;
  camera.position.y = 50;
  camera.position.z = Math.cos(time/1000) * 150;
  // You need to update lookAt every frame (0,0,0)
  camera.lookAt(scene.position);
  renderer.render(scene, camera);
  window.requestAnimationFrame(animate);
}
animate(new Date().getTime());
						

Reference

Color Picker

Because WebGL is programmed in JavaScript, this makes it easier to integrate WebGL applications with other JavaScript libraries such as JQuery UI and with other HTML5 technologies


<link rel="stylesheet" href="jquery-ui-1.10.2.custom/css/smoothness/jquery-ui-1.10.2.custom.css" />
<script src="jquery-ui-1.10.2.custom/js/jquery-1.9.1.js"></script>
<script src="jquery-ui-1.10.2.custom/js/jquery-ui-1.10.2.custom.js"></script>
						

Color Picker - HTML


<div id="colorPicker">
  <div id="red"></div>
  <div id="green"></div>
  <div id="blue"></div>
</div>
                        

Color Picker - CSS


#colorPicker {
  position: absolute;
  left: 10px;
  top: 10px;
}
#red, #green, #blue {
  float: left;
  clear: left;
  width: 300px;
  margin: 15px;
}
...
                        

Color Picker - Sliders


$("#red, #green, #blue").slider({
  orientation: "horizontal",
  range: "min",
  max: 255,
  value: 127,
  slide: setMaterialColor,
  change: setMaterialColor
});
$("#red").slider("value", 0);
$("#green").slider("value", 0);
$("#blue").slider("value", 255);
                        

Color Picker - Set material color


function setMaterialColor() {
  var red = $("#red").slider("value");
  var green = $("#green").slider("value");
  var blue = $("#blue").slider("value");
  material.color.r = red / 255;
  material.color.g = green / 255;
  material.color.b = blue / 255;
}
                        

Lightning

Spot light (green)


var light = new THREE.SpotLight(0x00DD00);
light.position.set(200, 0, 0);
scene.add(light);
						

Ambient light (red)


var light = new THREE.AmbientLight(0xDD0000);
scene.add(light);
						

Texture

Load textures


var canvasTexture = THREE.ImageUtils.loadTexture("html5canvas.png");
var webglTexture = THREE.ImageUtils.loadTexture("webgl.png");
						

Apply textures


var materialArray = [];
materialArray.push(new THREE.MeshLambertMaterial({map: webglTexture}));
materialArray.push(new THREE.MeshLambertMaterial({map: webglTexture}));
materialArray.push(new THREE.MeshLambertMaterial({map: webglTexture}));
materialArray.push(new THREE.MeshLambertMaterial({map: canvasTexture}));
materialArray.push(new THREE.MeshLambertMaterial({map: canvasTexture}));
materialArray.push(new THREE.MeshLambertMaterial({map: canvasTexture}));
var cubeGeometry = new THREE.CubeGeometry(50, 50, 50, 1, 1, 1);
var cubeMesh = new THREE.Mesh(cubeGeometry, new THREE.MeshFaceMaterial(materialArray));
scene.add(cubeMesh);
						

Particles

Create 100 000 particles.


var geometry = new THREE.Geometry();
for (var i = 0; i < 100000; i ++) {
  var vertex = new THREE.Vector3();
  vertex.x = 2000 * Math.random() - 1000;
  vertex.y = 2000 * Math.random() - 1000;
  vertex.z = 2000 * Math.random() - 1000;
  geometry.vertices.push(vertex);
}
material = new THREE.ParticleBasicMaterial({
  size: 3,
  sizeAttenuation: false,
  transparent: true
});
var particles = new THREE.ParticleSystem(geometry, material);
scene.add(particles);
                        

Particles

Change colors of particles


function changeColorParticles(time) {
  var h = (360 * (1.0 + time * 0.00009) % 360 ) / 360;
  material.color.setHSV(h, 1.0, 1.0);
}
                       

Great Examples

WebGL Beginner's Guide

Udacity - Online 3d graphics course!

References

Questions?

Thank you!