ThreeJS Screw
A simple introduction to threejs. A 3D library for the web.
Threejs?
Threejs is a cross-browser JavaScript library and application programming interface used to create and display animated 3D computer graphics in a web browser using WebGL I would definately recommend their official page for a complete and deep documentation.
The Goal
As luck would have it, the rotary screw on my homepage was created with three Js. By recreating this scene, we have a compact exercise in which we can deal a little with the basics of Threejs such as scene, camera, light and more.
Setup
Let’s create a directory and add some files.
mkdir screw
cd screw
mkdir build
touch index.html main.css
We are now going to fill these files with the necessary code. Your main.css
should just contain the following:
*:focus {
outline: none;
}
body {
position: fixed;
margin: 0;
padding: 0;
overscroll-behavior: none;
}
Get three dimensional
Let’s start with index.html
. We will import additional code from the Threejs framework. For this special project we need the three.module.js
,Orbit Controls
and the FBXLoader
. You can find these on Github. Put these files in the previously created build
folder and import them.
<html lang="en">
<head>
<title>Three-Helper by Leo Mühlfeld</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="main.css"/>
</head>
<body>
<script type="module">
import * as THREE from './build/three.module.js';
import { OrbitControls } from './build/OrbitControls.js';
import { FBXLoader } from './build/FBXLoader.js';
let container, stats;
let camera, scene, renderer;
let model;
init();
animate();
As you can see, at the end of this snippet we are calling two functions. This is where the 3D stuff starts. Let’s add some functionality to index.html
.
Camera & Scene
We have to set up a PerspectiveCamera and a Scene. You can think of it as something like a canvas
for our 3D content. We will wrap the following steps in the init()
function…
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.set( 0.0, 300, 300 * 4 );
scene = new THREE.Scene();
Perfect, the scene is all set. Now we have to render it. We will set up a WebGLRenderer.
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
renderer.outputEncoding = THREE.sRGBEncoding;
Adding Lights to the scene
There are several types of lights in Threejs. I found that the Directional Light works very well for our example. We will describe two directional lightsources and add them to our scene.
let directionalLight = new THREE.DirectionalLight( 0xffffff, 1);
directionalLight.position.set( -50, -20, -30 ).normalize();
scene.add( directionalLight );
let directionalLight2 = new THREE.DirectionalLight( 0xffffff, .5);
directionalLight2.position.set( 50, -20, -30 ).normalize();
scene.add( directionalLight2 );
directional light from one side
Create a Material
Since we want a shiny metall-like reflectivity, we will make use of the CubeTexture Loader. You’ll just need to download some textures from the Threejs repo or create them yourself! Let’s load our environment map by using cubeloader.load
. After loading our images, we will create a new MeshPhongMaterial and apply our textureCube
as envMap
to the material. That’s it, we do now have a reflective and shiny material.
let cubeloader = new THREE.CubeTextureLoader();
cubeloader.setPath( './src' );
let textureCube = cubeloader.load( [
'posx.jpg', 'negx.jpg',
'posy.jpg', 'negy.jpg',
'posz.jpg', 'negz.jpg'
] );
let material = new THREE.MeshPhongMaterial( {
envMap: textureCube
});
Environment is reflected
Orbit Controls
As a little interactive touch, we want our user to be able to control the viewport of our scene. Threejs has a nice helper that enables just that. It is called OrbitControls and features a variety of settings. I chose to set the OribtControls
for this particular scene like so:
let controls = new OrbitControls( camera, container );
controls.minDistance = 1000;
controls.maxDistance = 2000;
controls.enablePan = false;
window.addEventListener( 'resize', onWindowResize, false );
Geometry (Finally)
After the setup work, we can now import a 3D model into our scene. Use the CAD / 3D software of your choice and export your geometry as fbx. We will use the FBXLoader to import the geometry into our scene. After setting position and initial scale, we will apply our previously created material to the object and add it to the scene. Note that we finally close our init()
function.
let loader = new FBXLoader();
loader.load( './src/models/screw-model.fbx', function ( object ) {
model = object.children[ 0 ];
model.position.set( 0, 0, 0);
model.scale.setScalar( 300 );
model.material = material;
scene.add( model );
} );
}
Animation & Renderer
Almost finished! To see our screw spin we need to animate and render it. Besides a function that handles the WindowResize
, we will add a function called render()
. This function will be called by another function called animate()
. Remember: we called animate()
at the beginning of our index.html
. The render()
function calls the previously created renderer – which is going to render the viewport – every time the screw model has been rotated about its y-axis.
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
if ( model ) model.rotation.y = performance.now() / 3000;
renderer.render( scene, camera );
}
Wrapping up
Our index.html
file should now look something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Three-Helper by Leo Mühlfeld</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="main.css"/>
</head>
<body>
<script type="module">
import * as THREE from './build/three.module.js';
import { OrbitControls } from './build/OrbitControls.js';
import { FBXLoader } from './build/FBXLoader.js';
let container, stats;
let camera, scene, renderer;
let model;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.set( 0.0, 300, 300 * 4 );
scene = new THREE.Scene();
// Adding Lights
let directionalLight = new THREE.DirectionalLight( 0xffffff, 1);
directionalLight.position.set( -50, -20, -30 ).normalize();
scene.add( directionalLight );
let directionalLight2 = new THREE.DirectionalLight( 0xffffff, .5);
directionalLight2.position.set( 50, -20, -30 ).normalize();
scene.add( directionalLight2 );
//Renderer
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
renderer.outputEncoding = THREE.sRGBEncoding;
// Orbit OrbitControls
let controls = new OrbitControls( camera, container );
controls.minDistance = 1000;
controls.maxDistance = 2000;
controls.enablePan = false;
window.addEventListener( 'resize', onWindowResize, false );
//CubeLoader
let cubeloader = new THREE.CubeTextureLoader();
cubeloader.setPath( './src/tex/' );
let textureCube = cubeloader.load( [
'posx.jpg', 'negx.jpg',
'posy.jpg', 'negy.jpg',
'posz.jpg', 'negz.jpg'
] );
let material = new THREE.MeshPhongMaterial( {
envMap: textureCube
});
// FBXLoader
let loader = new FBXLoader();
loader.load( './src/models/screw-model.fbx', function ( object ) {
model = object.children[ 0 ];
model.position.set( 0, 0, 0);
model.scale.setScalar( 300 );
model.material = material;
scene.add( model );
} );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
if ( model ) model.rotation.y = performance.now() / 3000;
renderer.render( scene, camera );
}
</script>
</body>
</html>
Done!
You have to start a development server to see your result in your browser. If you dont have one installed you could use Serve.
Scripts and Graphics by Leo Mühlfeld. Thankfully using Threejs.