Professional Documents
Culture Documents
...@gmu.edu
CS551 Summer 2013 Project
WebGL on iOS devices - Insights and Experiments
WebGL Background
WebGL is effectively a Javascript API based on OpenGL ES 2.0, where the target viewing plane is an HTML5 canvas.
For developers already familiar with OpenGL, they typically think of WebGL as "OpenGL ES in a web browser". For
users, WebGL means that more advanced 3D graphics are possible from a website or web application, and without
requiring any additional plugins to extend the web browser.
WebGL is managed and maintained by the Khronos Group, which is an industry consortium that develops and
maintains standards for the authoring and acceleration of parallel computing, graphics and dynamic media on a
wide variety of platforms and devices. For application developers, WebGL is a cross-platform, royalty-free standard
API platform for 3D computer graphics.
Introducing Ejecta
So despite these limitations on iOS this paper introduces Ejecta [ref3], a application container technology that
provides a separate HTML5 canvas implementation with WebGL support. What is novel about Ejecta is that it is not a
web browser but rather a WebGL container written in Objective-C. Ejecta implements an HTML5 canvas and other
minimal functions to allow WebGL applications written purely in javascript, where Ejecta along with a developer's
own javascript WebGL code is packaged and deployed as a standard iOS application. For detailed documentation of
all the functions supported on Ejecta, see the Ejecta website for details on the current release, currently version 1.3.
Per section 2.17 of Apple's App Store Review Guidelines, "iOS applications that browse the web must use the iOS
WebKit framework and WebKit Javascript". So Ejecta specifically works around Apple's lack of WebGL support in its
iOS WebKit because Ejecta is not designed or intended for use cases of general web browsing, and as such does not
use WebKit on iOS.
Ejecta runs on all iPad models and most iPhone models starting with the iPhone 3GS or any subsequent iPhone
model. Applications on Ejecta do not require any special logic to run or behave differently on an iPad vs and iPhone.
Experiments in Ejecta
32
33 // display code that will iterate continually
34 function run()
35 {
36
camera.lookAt(scene.position);
37
renderer.render( scene, camera );
38
requestAnimationFrame(run);
39 }
40
41 run();
Notable Aspects
Even though this is a static view, note lines 34-41with the run() function and the requestAnimationFrame(run) call on
line 38. This is the block of code that gets called continually to support animation updates to the scene [ref7].
39 plane.rotation.x = -Math.PI/2;
40 //plane.position.y = -25;
41 plane.receiveShadow = true;
42 scene.add(plane);
43
44 // camera and scene are set now, OK to lookAt
45 camera.lookAt(scene.position);
46
47 var frameNum=0,startTime,runningTime,fps; // tracking vars for frames-per-sec
48
49 startTime=new Date().getTime();
50
51 // iteration block
52 function run()
53 {
54
// modify litCube position and rotation as a function of time
55
var t = new Date().getTime()
56
litCube.position.x = Math.cos(t/600)*85;
57
litCube.position.y = 60-Math.sin(t/900)*25;
58
litCube.position.z = Math.sin(t/600)*85;
59
litCube.rotation.x = t/500;
60
litCube.rotation.y = t/800;
61
62
// Render the scene
63
frameNum++;
64
runningTime=(new Date().getTime()-startTime)/1000;
65
fps=frameNum/runningTime;
66
renderer.render( scene, camera );
67
console.log("mean fps: "+fps);
68
69 // renderer.render( scene, camera );
70
71
// Ask for another frame to keep iterating
72
requestAnimationFrame(run);
73 }
74
75 run();
Here are 2 different screen captures taken at different times in the animation.
Figure 8: Snapshots during animation of 1 cube moving over another one with a plane and lighting
12
world.broadphase=new CANNON.NaiveBroadphase();//req'd despite no collisions
13
14
// define body physics for Cube
15
shape = new CANNON.Box(new CANNON.Vec3(1,1,1));
16
mass = 1;
17
cubeBody = new CANNON.RigidBody(mass,shape);
18
cubeBody.angularVelocity.set(0,10,0); //rotate around y axis
19
cubeBody.angularDamping = 0.25; // with this damping factor
20
21
// add this cubeBody to world
22
world.add(cubeBody);
23 }
24
25 function initThree() {
26
// init scene
27
scene = new THREE.Scene();
28
29
// set up camera
30
camera = new THREE.PerspectiveCamera( 75, 320/480, 1, 1000 );
31
camera.position.z = 5;
32
scene.add( camera );
33
34
// set up wireframe cube in red
35
geometry = new THREE.CubeGeometry( 2, 2, 2 );
36
material = new THREE.MeshBasicMaterial( {color: 0xff0000,wireframe: true});
37
cubeMesh = new THREE.Mesh( geometry, material );
38
scene.add( cubeMesh );
39
40
// set up WebGL renderer
41
renderer = new THREE.WebGLRenderer({antialias: true});
42 }
43
44 function updatePhysics() {
45
// Step forward in time
46
world.step(timeStep);
47
48
// update Three.js cubeMesh quat coordinates with Cannon.js physics calc's
49
cubeBody.quaternion.copy(cubeMesh.quaternion); //rotation update
50 }
51
52 function animate() {
53
// update physics model
54
updatePhysics();
55
// render the scene
56
renderer.render( scene, camera );
57
// get next frame for animation!
58
requestAnimationFrame( animate );
59 }
60
61 initThree();
62 initCannon();
63 animate();
Figure 10: Rotating cube with dampening using Three.js and cannon.js together
Notable Findings
cannon.js blended very cleanly with three.js, in part because it was designed originally in javascript rather than being
a port of a physics engine written in another language into javascript [ref14]. The key to three.js and cannon.js
working together is that they both use different key variable names (lines 5 & 6) and elements in cannon.js mapped
neatly to those in three.js (line 49).
16
world = new CANNON.World();
17
world.gravity.set(0,0,0); //zero G
18
world.broadphase=new CANNON.NaiveBroadphase();//req'd despite no collision
19
20
// init crate body
21
shape = new CANNON.Box(new CANNON.Vec3(1,1,1));
22
mass = 1;
23
crateBody = new CANNON.RigidBody(mass,shape);
24
crateBody.angularVelocity.set(targetYRotation,targetXRotation,0);
25
crateBody.angularDamping = 0.25;
26
world.add(crateBody);
27 }
28
29 function initThree() {
30
// init scene
31
scene = new THREE.Scene();
32
33
// set up camera
34
camera = new THREE.PerspectiveCamera( 45, 320/480, 1, 1000 );
35
camera.position.z = 7;
36
camera.position.y = 2;
37
camera.lookAt(scene.position);
38
scene.add( camera );
39
40
// add subtle blue ambient lighting
41
var ambientLight = new THREE.AmbientLight(0x000044);
42
scene.add(ambientLight);
43
44
// directional lighting
45
var directionalLight = new THREE.DirectionalLight(0xffffff);
46
directionalLight.position.set(1, 1, 1).normalize();
47
scene.add(directionalLight);
48
49
// set up crate Mesh
50
geometry = new THREE.CubeGeometry( 2, 2, 2 );
51
material = new THREE.MeshLambertMaterial(
52
{map:THREE.ImageUtils.loadTexture('img/crate.jpg')});
53
crateMesh = new THREE.Mesh( geometry, material );
54
crateMesh.overdraw = true;
55
scene.add( crateMesh );
56
57
// init renderer
58
renderer = new THREE.WebGLRenderer({antialias: true});
59
60
//add Event Listeners for touch events
61
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
62
document.addEventListener( 'touchmove', onDocumentTouchMove, false );
63 }
64
65 function onDocumentTouchStart( event ) {
66
if ( event.touches.length === 1 ) { //only respond to single touch
67
event.preventDefault(); //prevent the default behavior where touch
68
// shifts the whole screen around in iOS
69
crateBody.angularDamping = 1;//"catch" crate by stoping any rotation
70
71
// calculate tap start x/y vars
72
tapXstart = event.touches[ 0 ].pageX - canvasHalfX;
73
tapYstart = event.touches[ 0 ].pageY - canvasHalfY;
74
75
// capture current X/Y Rotation values
76
targetXRotationOnTap = targetXRotation;
77
targetYRotationOnTap = targetYRotation;
78
}
79 }
80
81 function onDocumentTouchMove( event ) {
82
if ( event.touches.length === 1 ) { //only respond to single touch
83
event.preventDefault(); //prevent the default behavior where touch
84
// shifts the whole screen around in iOS
85
crateBody.angularDamping = 0.25;//set back to orig Damping factor
86
87
// calculate touch move x/y vars
88
tapX = event.touches[ 0 ].pageX - canvasHalfX;
89
tapY = event.touches[ 0 ].pageY - canvasHalfY;
90
91
//update X/Y Rotation values
92
targetXRotation = targetXRotationOnTap + ( tapX - tapXstart ) * 0.05;
93
targetYRotation = targetYRotationOnTap + ( tapY - tapYstart ) * 0.05;
94
95
// update crate's Angular Velocity values X/Y
96
crateBody.angularVelocity.set(targetYRotation,targetXRotation,0);
97
}
98 }
99
100 function updatePhysics() {
101
// Step the physics world
102
world.step(timeStep);
103
// Copy quat's from Cannon.js body to Three.js mesh
104
crateBody.quaternion.copy(crateMesh.quaternion); // update rotation
105 }
106
107 function animate() {
108
updatePhysics();
109
renderer.render( scene, camera );
110
requestAnimationFrame( animate );
111 }
112
113 initThree();
114 initCannon();
115 animate();
http://caniuse.com/#feat=webgl
http://www.khronos.org/webgl/security/#Cross-Origin_Media
http://impactjs.com/ejecta
http://developer.apple.com/library/ios/#documentation/IDEs/Conceptual/iOS_Simulator_Guide/
TestingontheiOSSimulator/TestingontheiOSSimulator.html
http://www.khronos.org/registry/webgl/specs/1.0/
http://threejs.org/
http://www.w3.org/TR/animation-timing/#requestAnimationFrame
http://developer.apple.com/library/ios/#documentation/AppleApplications/Reference/SafariWebContent/
HandlingEvents/HandlingEvents.html
Spinning Crate inspiration: http://www.html5canvastutorials.com/three/html5-canvas-webgl-texture-withthree-js/
Cannon.js home - http://cannonjs.org/ ; github home - http://github.com/schteppe/cannon.js
http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
http://www.geek.com/games/box2d-creator-asks-rovio-for-angry-birds-credit-at-gdc-1321779/
http://granular.cs.umu.se/pres2/#slide16
http://granular.cs.umu.se/pres2/#slide18
http://impactjs.com/ejecta/supported-apis-methods
https://github.com/mrdoob/three.js/wiki/Features
http://processingjs.org/articles/RenderingModes.html
http://developer.apple.com/library/safari/#documentation/SafariDOMAdditions/Reference/
DeviceMotionEventClassRef/DeviceMotionEvent/DeviceMotionEvent.html
http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/
TouchEventClassReference/TouchEvent/TouchEvent.html
http://stemkoski.github.io/Three.js/