The 3rd Dimension

With WebGL and Three.js

Sorry, these slides require a browser that supports WebGL

Peter O'Shaughnessy, Future Technologies, Pearson

@poshaughnessy

These slides are at: http://bit.ly/third-dimension

Best viewed in Chrome  Press up & down arrow keys to navigate

What's WebGL?

  • JavaScript API (no plugins!)
  • Hardware accelerated 3D (or 2D) graphics
  • Open standard based on OpenGL ES
  • Not HTML5 (but uses HTML5 canvas)

What can you use it for?

  • Games
  • 3D modelling & simulations
  • Data visualisation
  • Interactive music videos
  • New ways of presenting your content

Great examples

Top 3 Coolest Examples

3. Still on the podium

Photobooth

webcamtoy.com/

2. A close second!

WebGL Nyan Cat

bit.ly/webglnyancat

1. The best example ever...

Let's wait til the end!

Desktop Browsers

  • Chrome 8+
  • Firefox 4+
  • Safari 5.1+ (switch on in menu)
  • Opera 12+ (switch on in config)
  • Internet Explorer - boo!

Desktop Systems

  • Windows XP onwards
  • Mac OS 10.6+
  • Linux
  • Chrome OS
  • Can also depend on graphics card & drivers

Mobile

  • Android Browser from some vendors
  • Firefox Mobile (Android)
  • Opera Mobile (Android)
  • Blackberry Playbook OS 2.0
  • iOS - boo! - except iAds

What about those without WebGL?

  • You can detect support & add a nice message
  • Provide an alternative - for accessibility too
  • You could try a different renderer (more later)
  • Fallback example: EVE Online Spaceships
  • You can use it in production now - if you're leet!

WebGL Pipeline (simplified)

WebGL workflow

Coordinate system

Coordinate system

Hello WebGL

  • We're going to draw a 2D triangle
  • As simple as raw WebGL gets...
  • ...But still not simple at all!

Hello WebGL (1/6)

var canvas = document.getElementById('triangleCanvas');

// Get WebGL canvas context
var gl = canvas.getContext('webgl') ||
         canvas.getContext('experimental-webgl');
                

Hello WebGL (2/6)

var vertexPosBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);

var vertices = [
    -0.5, -0.5,
    0.5, -0.5,
    0, 0.5
];

gl.bufferData( gl.ARRAY_BUFFER,
        new Float32Array(vertices), gl.STATIC_DRAW );
                

Hello WebGL (3/6)

// Shader code (GLSL)

var vertexShaderSource =
        'attribute vec2 pos;' +
        'void main() {' +
        '  gl_Position = vec4( pos, 0.0, 1.0 );' + // xyzw
        '}';

var fragmentShaderSource =
        'precision mediump float;' +
        'void main() {' +
        '  gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );' + // rgba
        '}';
                

Hello WebGL (4/6)

// Create shaders

var vertexShader =
        gl.createShader( gl.VERTEX_SHADER );

gl.shaderSource( vertexShader, vertexShaderSource );
gl.compileShader( vertexShader );

var fragmentShader =
        gl.createShader( gl.FRAGMENT_SHADER );

gl.shaderSource( fragmentShader, fragmentShaderSource );
gl.compileShader( fragmentShader );
                

Hello WebGL (5/6)

// Create program

var program = gl.createProgram();

gl.attachShader( program, vertexShader );
gl.attachShader( program, fragmentShader );

gl.linkProgram( program );

gl.useProgram( program );
                

Hello WebGL (6/6)

// Hook up program input and buffer
program.vertexPosAttrib =
        gl.getAttribLocation( program, 'pos' );

// Tell it to read buffer when we start drawing
gl.enableVertexAttribArray( program.vertexPosAttrib );

// Specify how program should read data from buffer
gl.vertexAttribPointer( program.vertexPosAttrib,
        2, gl.FLOAT, false, 0, 0 );

// Draw!
gl.drawArrays( gl.TRIANGLES, 0, 3 );
                

Here it is!

All that for a triangle!

  • WebGL is low-level - best left to the experts!
  • Thankfully there are libraries...

Introducing Three.js

Materials

Hello Three.js

  • We'll make a spinning cube

Hello Three.js (1/4)

// Create a WebGL renderer
var renderer = new THREE.WebGLRenderer();

renderer.setSize( width, height );

var container = document.getElementById(
        'spinningCubeContainer');

// Add generated <canvas> to page
container.appendChild( renderer.domElement );
                

Hello Three.js (2/4)

// Make a scene
var scene = new THREE.Scene();

// Create a camera
var camera = new THREE.PerspectiveCamera(
        45,           // Field of View
        width/height, // Aspect ratio
        1,            // zNear
        10000         // zFar
    );

camera.position.z = 300;

// Add it to the scene
scene.add( camera );
                

Hello Three.js (3/4)

// Make a cube
var cube = new THREE.Mesh(
        new THREE.CubeGeometry( 50, 50, 50 ), // w,h,d
        new THREE.MeshLambertMaterial( {color: 0xFF0000} ));

// Add it to the scene
scene.add( cube );
                

Hello Three.js (4/4)

// Make it spin
function animate() {

    // Angles are in radians
    cube.rotation.y += 0.1;

    // Render the scene from the camera
    renderer.render(scene, camera);

    // Call each time browser's ready for next frame
    requestAnimationFrame( animate );

}

// Start animation going
animate();
                

Here it is!

(But it's not red!)

Let there be lights

Ambient
Ambient
Directional
Directional
Point
Point
Spotlight
Spotlight

Let there be lights

// Ambient light

var ambientLight = new THREE.AmbientLight( 0x333333 );
scene.add( ambientLight );

// Spotlight

// color, intensity, distance...
var spotLight = new THREE.SpotLight( 0xFFFFFF, 0.8, 300 );

spotLight.position.set( 50, 50, 300 ); // x, y, z
spotLight.target.position.set( 0, 0, 0 );

scene.add( spotLight );
                

Let there be lights

Shadows

  • Work with spotlights & directional lights
  • Enable globally, per-light and per-object

Shadows

// Enable globally
renderer.shadowMapEnabled = true;

// Enable on the spotlight
spotLight.castShadow = true;

// Enable on objects
cube.castShadow = true;
anotherCube.receiveShadow = true;
                

Shadows

Adding models

  • Built-in loaders for Three.js JSON & binary formats
  • Loaders for Collada (.DAE), Wavefront (.OBJ) & more
  • Converter from .OBJ to Three.js
  • Blender export plugin

Adding a dinosaur (1/2)

var loader = new THREE.JSONLoader();

var mesh;

loader.load('trex.js', function( geometry ) {

    mesh = new THREE.Mesh( geometry,
                new THREE.MeshFaceMaterial() );

    scene.add( mesh );

    animate();

});
                

Adding a dinosaur (2/2)

var animate = function() {

    mesh.rotation.y += 0.05;

    renderer.render( scene, camera );

    requestAnimationFrame( animate );
};
                

Adding a dinosaur

Dinosaur used with kind permission from DK

Where's the interaction?

  • Three.js is a graphics engine, not a game engine
  • It does have common controls ready to use
  • Add libraries or roll your own for the rest

FlyControls example (1/2)

var controls = new THREE.FlyControls( camera );

controls.movementSpeed = 30;
controls.rollSpeed = 0.1;
controls.dragToLook = true;
                

FlyControls example (2/2)

var clock = new THREE.Clock();

var animate = function() {

    var delta = clock.getDelta();

    controls.update(delta);

    ...

}
                

Fly-By Dinosaur (WASD keys + mouse drag)

Dinosaur used with kind permission from DK

So how do you click on stuff?

  • Click events in 3D space?
  • Registering clicks on objects doesn't come for free
  • Example: Clickable Objects
Ray 1

Thanks to yomotsu for the image

Ray 2

Thanks to yomotsu for the image

Click ‘n’ roar (1/3)

var projector = new THREE.Projector();

function onMouseDown( event ) {

    var clickX = event.clientX - canvasOffsetLeft;
    var clickY = event.clientY - canvasOffsetTop;

    // Viewport coordinates range from -1 to +1
    var vector = new THREE.Vector3( ( clickX / width ) *2 -1,
                                    -( clickY / height ) *2 +1,
                                    0.5 );

    // 'Unproject' from 2D to 3D
    projector.unprojectVector( vector, camera );
    ...
                

Click ‘n’ roar (2/3)

    ...
    var ray = new THREE.Ray( camera.position,
            vector.subSelf( camera.position ).normalize() );

    var intersects = ray.intersectObjects( [mesh] );

    if ( intersects.length > 0 ) {
        // We only have 1 object - must've clicked dinosaur
        roar();
    }

} // End of onMouseDown
                

Click ‘n’ roar (3/3)

function roar() {

    roarSound.play();

    speechBubble.className = ''; // show
    window.setTimeout(function() {
        speechBubble.className = 'hidden';
    }, 1500);
}
                

Click the dinosaur

Dinosaur used with kind permission from DK

So now we can fly past dinosaurs,
spin them & make them roar...

...But how do we animate a robot?

Please contact me if you're the rights holder of this model

Tween.js (1/4)

loader.load( 'models/robot.dae', function( collada ) {

    var model = collada.scene;

    ...

    /*
     * IDs correspond to values in the XML-based Collada file,
     * e.g. <node id="ID93" name="leftLeg">
     */
    robot.key = model.getChildByName('ID65', true);
    robot.head = model.getChildByName('ID139', true);
    robot.leftLeg = model.getChildByName('ID93', true);
    robot.rightLeg = model.getChildByName('ID75', true);
                

Tween.js (2/4)

var keyTurn = new TWEEN.Tween( robot.key.rotation )
    .to( { x: robot.key.rotation.x - (Math.PI * 2) }, 3000)
    .onComplete(function() {
        robot.key.rotation.x = 0;
    });

keyTurn.chain( keyTurn );

keyTurn.start();
                

Tween.js (3/4)

var rightLegForwards = new TWEEN.Tween( robot.rightLeg.rotation )
    .to( { z: robot.rightLeg.rotation.z - (Math.PI / 9) }, 1000 )
    .easing( TWEEN.Easing.Quadratic.InOut );

var rightLegBackwards = new TWEEN.Tween( robot.rightLeg.rotation )
    .to( { z: robot.rightLeg.rotation.z + (Math.PI / 9) }, 1000 )
    .easing( TWEEN.Easing.Quadratic.InOut );

rightLegForwards.chain( rightLegBackwards );
rightLegBackwards.chain( rightLegForwards );

rightLegForwards.start();
                

Tween.js (4/4)

var animate = function() {

    TWEEN.update();

    ...

}
                

What about physics & collision detection

  • Libraries are out there
  • E.g. You could try Physijs...
  • ...which is built on top of ammo.js

Quick quiz question...

How did they build this?

www.creativesandbox.com/guidebook

CSS3D

Talking of other renderers...

CSS Filters

  • Shaders for 'normal' HTML content, through CSS
  • Powerful effects: blur, warp, Instagram-style filters...
  • In-built filter effects
  • In Chrome, enable in about:flags

Example

<style>
    #shaderDemo:hover {
        -webkit-filter: custom( url(shaders/sphere.vs)
                                mix( url(shaders/sphere.fs)
                                normal
                                source-atop),
                        16 32,
                        amount 1,
                        sphereRadius 0.35,
                        lightPosition 0.0 0.0 1.0 );
    }
</style>
                

In Chrome with flag enabled,
hover over the image

Debugging

Be aware...

  • Performance can depend on canvas size!
  • Shadow maps with no spotlights make WebGL mad
  • Restarting browser sometimes fixes things!

Finally... are you ready?

The number 1 best WebGL example ever

apps.playcanvas.com/will/doom3/gangnamstyle

Wrapping up

  • The Web is becoming more immersive
  • Use WebGL to add a 3rd dimension to your content
  • Can use in production now - with fallbacks
  • Use a lib like Three.js (unless you're hardcore!)
  • What can you come up with? :-)

Find out more

Questions

?