
Support for WebGL is fairly widespread but there are some exceptions to the rule. The chief two exceptions come from mobile and Internet Explorer. With mobile usage neither iOS nor Android support WebGL and this is largely down to the security threats that could potentially come from WebGL given the access to low level rendering of graphics.
Surprisingly the Blackberry Playbook does allow it (although there is a hefty warning screen), but it runs quite fast. IE can support WebGL through Chrome Frame (www.google.com/chromeframe) even though there is no native support. These drawbacks cause problems for us as we prepare our content. ThreeJS gives the option of rendering using Canvas or WebGL, so we’ll use WebGL if it’s supported and if not a cut-down version will be provided and rendered through Canvas. The Canvas renderer is not as fast as WebGL so we’ll lose the particles when rendering to Canvas. Canvas also requires the particles to be programmed slightly differently so it makes sense on two levels to cut this out.
Starting out
Downoad the tutorial files and drag the ‘start’ folder onto your desktop and open up the file ‘index.html’ in Dreamweaver or a similar code editor of your choice. Take a look at the body content, and you’ll see that there is a div tag named ‘container’ – the WebGL will go here. Scroll down to the third script tag and add the following variables before the ‘createParticles’ function.
001 var renderer, scene, camera; 002 var sphere, uniforms, attributes, group; 003 var WIDTH = window.innerWidth; 004 var HEIGHT = window.innerHeight/2;
Remaining variables
You will have noticed two previous script tags – these are special scripts that create the shaders for the particles in the project, hence why we need to add the variables in the third script tag. We’ve already added the code that makes the particles; we will add the rest of the project in this tutorial, though.
001 var mouseX = 0, mouseY = 0; 002 var lastUpdate = new Date().getTime(); 003 var windowHalfX = window.innerWidth / 2; 004 var windowHalfY = window.innerHeight / 2;
Call the functions
The variables are holders that will hold values within our code that we need to access at various times. Now add the code as shown here, which calls two functions. The first will initialize our 3D scene, while the second function will animate that scene on the web page.
001 init(); 002 animate();
Create the init function
The init function sets the scene up, so we’ll go ahead and create that function right under the last code. Here we define a new camera to view the scene. This takes the arguments for field of view, aspect ratio, how near and how far we can see in the scene. We position this camera to look down the Z axis and set up a new Scene.
001 function init() {
002 camera = new THREE.PerspectiveCamera( 40, WIDTH / HEIGHT, 1, 10000 );
003 camera.position.z = 300;
004 scene = new THREE.Scene();
Add a light
We will not be able to see anything in our scene unless we have a light shining so the next block of code sets up a new light with white as its colour. We position the light to be shining from the Z axis so it shines from the point of view of the camera. We then add the light to the scene.
001 var light = new THREE.DirectionalLight( 0xffffff ); 002 light.position.set( 0, 0, 1 ); 003 scene.add( light );
Group objects together
We are setting up a group that we can add many elements into. This is useful if you want to move many elements in one go, you can just move the group and everything will move with it. Once the group is set up we add the group into the scene as we did with the light in the last step.
001 group = new THREE.Object3D(); 002 scene.add( group );
Check the renderer
We are now going to check the type of renderer we have available to us. If we can render through WebGL then we are going to call the createParticles function code. Particles can be rendered through the Canvas renderer but with different code. Canvas is a much slower renderer so we are keeping the page content lower.
001 if(Detector.webgl) {
002 createParticles();
003 }
Creating the logo
The next line of code calls a function that we have yet to write named ‘createLogo’. This does exactly what it says. We are going to create the logo from a Canvas-drawn shape, extruding this and texturing in our code. This is because the canvas renderer isn’t great at bringing in loaded models, so we’ll create one with our code!
001 createLogo();
Choosing how to display
Use the detector code we used earlier to check to see if the browser has WebGL available. If it has then we use this much faster renderer and set the anti-aliasing (edge smoothing) to true. If there is no WebGL renderer then the Canvas version is used. You could easily add an else statement here that uses an image if Canvas isn’t supported.
001 if(Detector.webgl) {
002 renderer = new THREE.WebGLRenderer( { antialias: true } );
003 } else if(Detector.canvas) {
004 renderer = new THREE.CanvasRenderer();
005 }
Display the 3D content
The next block of code sets the size of the renderer to the width and half the height of the browser, as that’s the size we want to allow for the view. We store the div tag with the id ‘container’ in the variable of the same name, then finally we add in the renderer to the container so it is in the DOM.
001 renderer.setSize( WIDTH, HEIGHT ); 002 var container = document.getElementById( ‘container’ ); 003 container.appendChild( renderer.domElement );
Listening in
The code here sets up three listeners. The first is for the mouse moving, the second is if the content is viewed on a touch screen device and a finger is moved anywhere. The last line is the window resize which will redraw the WebGL/Canvas display accordingly to display for the new size.
001 document.addEventListener(‘mousemove’, onDocumentMouseMove, false); 002 document.addEventListener( ‘touchmove’, onDocumentTouchMove, false ); 003 window.addEventListener( ‘resize’, onWindowResize, false ); 004 }
Dynamic 3D shape
When we’ve drawn the path for our 3D shape we will call the function started in this step. This will extrude the path into 3D and add a material to the mesh. We are passing into the function the shape of the path, the extrude settings, color, position, rotation, scaling and opacity of the new model.
001 function addShape( shape, extrudeSettings, color, x, y, z, rx, ry, rz, s, o ) {
002 var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
003 var mesh = THREE.SceneUtils.createMultiMaterialObject( geometry, [ new
THREE.MeshLambertMaterial( { color: color,
opacity: o } ) ] );
Position the mesh
In the next block of code we use the settings that have been passed into the function to set the position, rotation and scaling of the mesh. Once this is done we add the mesh to the group that we set up in step 6 of the code.
001 mesh.position.set( x, y, z ); 002 mesh.rotation.set( rx, ry, rz ); 003 mesh.scale.set( s, s, s ); 004 group.add( mesh ); 005 }
Creating the logo
We set up our function that will create the logo. At present this just has a new variable that is holding a shape, so it is expecting some points of a shape to be put in here. In the start folder, open the text file step14.txt in a text editor. Copy and paste the shape data just after the code shown here. We explain how we created this in the In Detail box.
001 function createLogo(){
002 var logo = new THREE.Shape();
Extrusion settings
Now we set the extrude settings of the shape, which define how much to extrude the shape by and the size of the bevel with the thickness as well. We’re adding a very subtle bevelled edge to the shape. Now we have the shape and the amount to extrude it, we call the addShape function from steps 12 and 13.
001 var extrudeSettings = { amount: 8,
002 size: 10, height: 4, curveSegments: 6,
003 bevelThickness: 0.4, bevelSize: 0.4, bevelEnabled: true };
004 addShape( logo, extrudeSettings, 0xFFAA33,
-80, 100, 0, 3.14, 0, 0, 2, 0.9 );
005 }
Update the view
If the window is resized the camera is updated to redraw the display to the new width and height of the document. The camera settings have changed and so the updateProjectionMatrix is called. This will make the display redraw. The renderer is also updated to reflect the new resized browser window.
001 function onWindowResize() {
002 camera.aspect = window.innerWidth / (window.innerHeight / 2);
003 camera.updateProjectionMatrix();
004 renderer.setSize( window.innerWidth, window.innerHeight/2 );
005 }
Responding to the mouse
If the mouse moves around, we want the logo to spin in 3D and follow it, so we need to store the position of the mouse from the halfway point of the document on both axes. This will allow the model to spin around the centre position, so when the mouse is above half the document we can make the logo face upwards.
001 function onDocumentMouseMove( event ) {
002 mouseX = ( event.clientX - windowHalfX );
003 mouseY = ( event.clientY - windowHalfY )
004 }
Responding to touch
Obviously, in this day and age your website visitor is quite likely to be looking at your page on a device with a touch screen (such as a smartphone or tablet), so here we need to register the touch movement and take the position of this exactly as we did with the mouse and store the position so that we can rotate it later.
001 function onDocumentTouchMove( event ) {
002 if ( event.touches.length == 1 ) {
003 mouseX = event.touches[ 0 ].pageX - windowHalfX;
004 mouseY = event.touches[ 0 ].pageY - windowHalfY;
005 }
006 }
Animating the scene
The requestAnimationFrame is a W3C specification for allowing browser vendors to take the strain of running a loop. This used to be done with setInterval but now can be done by calling this function. All this does is call itself, so we update the display by calling the render function.
001 function animate() {
002 requestAnimationFrame( animate );
003 render();
004 }
Checking before we render
Now we set up the render function as mentioned in the last step. The first thing we need to do here is check whether we have WebGL enabled. If so, then we are going to animate the particles; if not, we’ll just move on to animating the 3D model’s position in the scene.
001 function render() {
002 if(Detector.webgl) {
Animate the particles
We want the particles to be continually animating so here we rotate all of the particles on
the X axis which gives a swirling up and down motion, then we check the size of the particle and make them pulse up and down in size, which are then updated in the display.
001 var time = Date.now() * 0.005;
002 sphere.rotation.x = -0.01 * time;
003 for( var i = 0; i < attributes.size.value.
length; i++ )
004 {
005 attributes.size.value[ i ] = 24 + 13 * Math.sin( 0.1 * i + time );
006 }
007 attributes.size.needsUpdate = true;
008 }
Rotate the model
Our final step of code takes the mouse X and Mouse Y position and then uses it to rotate the model. We then divide the mouse position by such a high number because we rotate in radians, 3.14 (pi) is 180 degrees. Any high numbers would cause the model to spin erratically. We then tell the renderer to render the scene and the camera. Test this in a browser to finally see it in action.
001 group.rotation.y += ( (mouseX/250) - group.rotation.y ) * .05; 002 group.rotation.x += ( (mouseY/250) - group.rotation.x ) * .05; 003 camera.lookAt( scene.position ); 004 renderer.render( scene, camera ); 005 }
