Archived

ThreeJS Screw

7 min read

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 );

light on a screw 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 });

reflections on a screw 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.

glossy screw

Scripts and Graphics by Leo Mühlfeld. Thankfully using Threejs.