chrome.exe --allow-file-access-from-files
Eric Haines
erichaines.com | erich@acm.org | @pointinpolygon
Was a software engineer working on the Autodesk Viewer; now at NVIDIA.
Also developed and answers questions for this MOOC.
Download demos & slides at http://bit.ly/eric3demos
Or follow along at http://bit.ly/basics3js
chrome.exe --allow-file-access-from-filesIt's a security thing.
Bonus info: debugger is F12 on Windows, Cmd-Shift-J on the Mac. Click on Console tab for warnings and errors.
Hit Ctrl-U to view the source code.
Create a Camera
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 ); camera.position.z = 400;
Make a Scene
scene = new THREE.Scene();
Create a Cube with a Texture
var texture = new THREE.TextureLoader().load( 'textures/crate.gif' ); var geometry = new THREE.BoxBufferGeometry( 200, 200, 200 ); var material = new THREE.MeshBasicMaterial( { map: texture } ); mesh = new THREE.Mesh( geometry, material ); scene.add( mesh );
Hook up the Renderer:
renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement );and some typical window resize code:
function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); }
Make it move and render
function animate() { requestAnimationFrame( animate ); mesh.rotation.x += 0.005; mesh.rotation.y += 0.01; renderer.render( scene, camera ); }
requestAnimationFrame(function(time){}, element)
It's like setTimeout(f, timeUntilNextFrame)
except that:
Always use this for best efficiency, especially for mobile.
Turn off the rotations; comment them out:
function animate() { requestAnimationFrame( animate ); //mesh.rotation.x += 0.005; //mesh.rotation.y += 0.01; renderer.render( scene, camera ); }
Edit in your favorite text editor, save, and refresh the browser (F5 on Windows; Cmd-R on Mac) to see the result.
Part one, add controls line at the end, since we're here:
function animate() { requestAnimationFrame( animate ); //mesh.rotation.x += 0.005; //mesh.rotation.y += 0.01; renderer.render( scene, camera ); controls.update(); // add this }
At top, include OrbitControls.js script, and declare controls:
<body> <script src="three.js/build/three.js"></script> <script src="js/controls/OrbitControls.js"></script> <script> var controls; // add this
Part three, create the controls:
renderer.setSize( window.innerWidth, window.innerHeight ); // create the controls - add this controls = new THREE.OrbitControls( camera, renderer.domElement );
OrbitControls has lots of options to customize behavior. Also works with touch controls for mobile.
Change the texture name:
// how the line used to look: //var texture = new THREE.TextureLoader().load( // 'textures/crate.gif' ); var texture = new THREE.TextureLoader().load( 'textures/UV_Grid_Sm.jpg' ); // new file name
Also, let's change the background color:
renderer = new THREE.WebGLRenderer(); // set the background color to gray: a0 is R,G,B; a0=10*16+0 hex=160 renderer.setClearColor( 0xa0a0a0 );
Get typing:
scene = new THREE.Scene(); // create a light light = new THREE.DirectionalLight(0xffffff); light.position.set(100,200,400); scene.add(light);
A one-word change, "Basic" to "Phong" or "Standard":
// material = new THREE.MeshBasicMaterial( { map: texture } ); // to have the lights affect the material, use Phong instead of Basic material = new THREE.MeshPhongMaterial( { map: texture } );
Another option is the new physically-based material (PBR):
// material = new THREE.MeshBasicMaterial( { map: texture } ); // to have the lights affect the material, use Phong instead of Basic material = new THREE.MeshStandardMaterial( { map: texture } );
Copy and paste the original light and modify:
light2 = new THREE.DirectionalLight(0xffffff); light2.position.set(-400,200,-400); scene.add(light2); light3 = new THREE.DirectionalLight(0xffffff); light3.position.set(100,-400,-400); scene.add(light3);This is bad practice, BTW: I didn't declare these variables.
Add an object:
scene.add(light3); var geometry = new THREE.PlaneGeometry( 1000, 1000 ); var material = new THREE.MeshPhongMaterial( {color: 0xffff00, side: THREE.DoubleSide} ); var plane = new THREE.Mesh( geometry, material ); scene.add( plane );
But along what axis? Let's add this:
scene.add( plane ); // add XYZ axes, in RGB colors var axisHelper = new THREE.AxisHelper( 200 ); scene.add( axisHelper );Adds an XYZ set of axes, with colors RGB
Rotations are in radians:
var plane = new THREE.Mesh( geometry, material ); plane.rotation.x = 90 * Math.PI / 180; // add this scene.add( plane );
Math.PI / 180
converts degrees to radians.
Green is the Y axis
var plane = new THREE.Mesh( geometry, material ); plane.rotation.x = 90 * Math.PI / 180; // add this plane.position.y = -100.01; scene.add( plane );The 0.01 is to put the plane just below the box to avoid z-fighting.
Add the dat.gui library to the top, and definitions:
<script src="three.js/build/three.js"></script> <script src="js/controls/OrbitControls.js"></script> <script src="js/libs/dat.gui.min.js"></script> <script> "use strict"; var controls; var light, light2, light3, params; // add this
"use strict";
at the top of the Javascript can help catch errors.
Typing time:
window.addEventListener( 'resize', onWindowResize, false ); // add all this to create a slider params = {intensity: 1}; var gui = new dat.GUI(); gui.add( params, 'intensity', 0, 2 ); gui.open();This makes a slider that changes
params.intensity
Use the slider variable params.intensity:
function animate() { requestAnimationFrame( animate ); // have our slider for intensity affect the lighting light.intensity = light2.intensity = light3.intensity = params.intensity; renderer.render( scene, camera ); controls.update(); }
Change it to a sphere:
//var geometry = new THREE.BoxBufferGeometry( 200, 200, 200 ); // let's use a sphere instead of a box var geometry = new THREE.SphereGeometry( 100, 32, 16 );100 is the radius, 32 x 16 is the mesh formed.
See the mesh by changing the material to:
material = new THREE.MeshPhongMaterial( { wireframe : true } );
Add a line to change the mesh's vertical position:
function animate() { requestAnimationFrame( animate ); // have our slider for intensity affect the lighting light.intensity = light2.intensity = light3.intensity = params.intensity; // bounce mesh.position.y = 100 * Math.abs(Math.cos(Date.now() * 0.005)); renderer.render( scene, camera ); controls.update(); }
The camera has a near and far plane to it. Move the far plane out a bit, from 1000 to 2000:
function init() { camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 2000 ); // near and far plane distances
F12 on Windows, Cmd-Shift-J on the Mac
http://jshint.com / ESLint are handy.
Website / plug-in for various text editors.
Spector.js is a WegGL tool helpful for watching things draw.
Lots more tools listed at http://bit.ly/webglhelp.
Three.js has shadow maps.
You need to enable them per-light and per-object.
The shadows work on only spot and directional lights.
Code from BasicsOfThreeJS/three/three_5.html:
// enable shadows on the renderer renderer.shadowMap.enabled = true; // enable shadows for a light light.castShadow = true; // enable shadows for an object litCube.castShadow = true; litCube.receiveShadow = true;
Shaders are small programs that tell WebGL
where to draw
and
what to draw
Shaders are written in GLSL, the GL Shading Language.
It's kinda like C for graphics.
Where to draw (code).
Projects geometry to screen coordinates.
<script id="vertex" type="x-shader/x-vertex"> varying float vZ; uniform float time; void main() { vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); mvPosition.y += 20.0*sin(time*0.5+mvPosition.x/25.0); mvPosition.x += 30.0*cos(time*0.5+mvPosition.y/25.0); vec4 p = projectionMatrix * mvPosition; vZ = p.z; gl_Position = p; } </script>
What to draw.
Computes the color of a pixel.
<script id="fragment" type="x-shader/x-fragment"> varying float vZ; uniform float time; uniform vec2 size; void main() { vec2 d = gl_FragCoord.xy - (0.5+0.02*sin(time))*size; float a = sin(time*0.3)*2.0*3.14159; d = vec2( d.x*cos(a) + d.y*sin(a), -d.x*sin(a) + d.y*cos(a)); vec2 rg = vec2(1.0)-abs(d)/(0.5*size) float b = abs(vZ) / 160.0; gl_FragColor = vec4(rg,b,1.0); } </script>
Use a ShaderMaterial
var uniforms = { time : { type: "f", value: 1.0 }, size : { type: "v2", value: new THREE.Vector2(width,height) } }; var shaderMaterial = new THREE.ShaderMaterial({ uniforms : uniforms, vertexShader: document.getElementById( 'vertex' ).textContent, fragmentShader: document.getElementById( 'fragment' ).textContent }); var meshCube = new THREE.Mesh( new THREE.CubeGeometry(50,50,50, 20,20,20), // 20 segments shaderMaterial );
Shoot a view ray and find intersecting objects (code).
raycaster.setFromCamera( mouse, camera ); var intersects = raycaster.intersectObjects( scene.children ); if ( intersects.length > 0 ) { if ( INTRSCT != intersects[ 0 ].object ) { if ( INTRSCT ) INTRSCT.material.emissive.setHex( INTRSCT.currentHex ); INTRSCT = intersects[ 0 ].object; INTRSCT.currentHex = INTRSCT.material.emissive.getHex(); INTRSCT.material.emissive.setHex( 0xff0000 ); } } else { if ( INTRSCT ) INTRSCT.material.emissive.setHex( INTRSCT.currentHex ); INTRSCT = null; }
Look into utils/exporters/
Look into examples/js/loaders/
And many more. So what should I use?
new THREE.ColladaLoader().load('models/monster.dae', function(collada) { var model = collada.scene; model.scale.set(0.1, 0.1, 0.1); model.rotation.x = -Math.PI/2; scene.add(model); });
Doesn't look too complicated.
Copy-pasted from examples/webgl_collada.html
Along with the code to make it animate (program).
var grid = /* 2D Array */ var barGraph = new THREE.Object3D(); scene.add(barGraph); var max = /* Grid max value */ var mat = new THREE.MeshLambertMaterial({color: 0xFFAA55}); for (var j=0; j<grid.length; j++) { for (var i=0; i<grid[j].length; i++) { var barHeight = grid[j][i]/max * 80; var geo = new THREE.CubeGeometry(8, barHeight, 8); var mesh = new THREE.Mesh(geo, mat); mesh.position.x = (i-grid[j].length/2) * 16; mesh.position.y = barHeight/2; mesh.position.z = -(j-grid.length/2) * 16; mesh.castShadow = mesh.receiveShadow = true; barGraph.add(mesh); } }
var scatterPlot = new THREE.Object3D(); var mat = new THREE.ParticleBasicMaterial( {vertexColors: true, size: 1.5}); var pointCount = 10000; var pointGeo = new THREE.Geometry(); for (var i=0; i<pointCount; i++) { var x = Math.random() * 100 - 50; var y = x*0.8+Math.random() * 20 - 10; var z = x*0.7+Math.random() * 30 - 15; pointGeo.vertices.push(new THREE.Vertex(new THREE.Vector3(x,y,z))); pointGeo.colors.push(new THREE.Color().setHSV( (x+50)/100, (z+50)/100, (y+50)/100)); } var points = new THREE.ParticleSystem(pointGeo, mat); scatterPlot.add(points); scene.fog = new THREE.FogExp2(0xFFFFFF, 0.0035);
Double-click to animate
It's JavaScript
They're on release 95.
They attempt to make the API perfect
This can be good, e.g. ES2015
But, they're not big on backward compatibility
Documentation is sometimes "TODO"
Pro tip: http://stemkoski.github.io/Three.js/
JavaScript library for 3D graphics
Easy to use
Efficient
Nice feature set
DAT.GUI for simple GUIs code.google.com/p/dat-gui
Eric Haines
erichaines.com | erich@acm.org | @pointinpolygon
Slides at http://bit.ly/eric3demos (github.com/erich666/BasicsOfThreeJS)
A full free course I created: http://bit.ly/intro3D
My WebGL resource page: http://bit.ly/webglhelp
Free WebGL book: http://webglinsights.com