ThreeJS : Getting Started


This is part of a series of posts on Project Grid.

  1. Project Grid
  2. ThreeJS : Getting Started
  3. ThreeJS : Creating a 3D World
  4. ThreeJS : Heads Up Display

What are we going to build?

In my previous Project Grid post I discussed the concept of visualizing content within a 3d grid so I am going to build a 3d world space in which I can plot points, add some temporary controls to navigate world space, and some HUD (heads up display) functionality to help me find plotted points.

You can see a working example here.

I want to use WebGL to render the scene(s) and I will be using ThreeJS to build my scene(s). ThreeJS is a JavaScript library that provides a familiar and consistent way to build  scenes using JavaScript.

Setting the Scene

We can start by creating a new html page. We need to include a <script> for the ThreeJS library. For development / PoC purposes we can just link to the latest build (http://threejs.org/build/three.js) but for a production application you’d want to reference a specific versions and would likely self-host.

We’ll also want to create an in-line <script> element for our scene with some variables, an init function and an animate function.

You should now have something like this:

var container, screenWidth, screenHeight;
var sceneMain, sceneHud;
var cameraPerspective, cameraOrthographic;
var controls, renderer, clock;
var groupItems, particleItems, hudItems;

init();
animate();

function init() {

}

function animate() {

}

We are going to need a WebGL renderer. Add the following function:

function initRenderer() {
    renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
    });

    renderer.setClearColor(0x000000, 0);
    renderer.autoClear = false; // We want to draw multiple scenes each tick
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(screenWidth, screenHeight);

    container.appendChild(renderer.domElement);
}

Next, we need to initialize our main scene and our HUD scene. Add the following functions:

function initSceneMain() {
    sceneMain = new THREE.Scene();
    sceneMain.fog = new THREE.Fog(0x000000, 1, 60);
}

function initSceneHud() {
    sceneHud = new THREE.Scene();
    sceneHud.add(new THREE.AmbientLight(0xffffff));
}

Now we have some scenes, we should create some cameras. Add the following function:

function initCameras() {
    cameraPerspective = new THREE.PerspectiveCamera(
        30,
        1600 / 900,
        0.1, 55
    );

    cameraOrthographic = new THREE.OrthographicCamera(
        1600 / -2,
        1600 / 2,
        900 / 2,
        900 / -2,
        1, 1000
    );
}

Note: Some of the parameters (1600 and 900) are arbitrary as we’ll be updating them after calculating the window dimensions anyway.

We are going to render the main scene with the perspective camera followed by the HUD scene with the orthographic camera. To do this, lets add a render function:

function renderScenes() {
    renderer.clear();
    renderer.render(sceneMain, cameraPerspective);
    renderer.clearDepth();
    renderer.render(sceneHud, cameraOrthographic);
}

Now we have a way to render our scenes we can wire it up from our existing animate function:

function animate() {
    requestAnimationFrame(animate); // This will handle the callback

    var delta = clock.getDelta();

    // Any changes to the scene will be initiated from here

    renderScenes();
}

All that remains is to wire up our existing init function:

function init() {
    container = document.createElement('div');
    document.body.appendChild(container);

    // We'll replace these values shortly
    screenWidth = 1600;
    screenHeight = 900;

    clock = new THREE.Clock();

    initRenderer();
    initCameras();
    initSceneMain();
    initSceneHud();
}

You should be able to test your scene now. It’s not very exciting but we haven’t added anything to it yet. Provided you aren’t getting any console errors, everything is working correctly (so far).

Creating a Background

For our 3d background we are going to use a skybox. A skybox is a very large cube with textures applied to the inside faces. Our camera and everything else it can see is inside this cube so those textures act as the background.

You will need 6 textures – 1 for each face of the cube. You can download my example textures from Google Drive. Add a folder for textures, and within, add another folder for skybox.

To create a skybox from the textures, add the following function:

function initSkybox(scene) {
    var skyboxPath = 'textures/skybox/';
    var skyboxFormat = 'png';

    var skyboxTextures = [
        skyboxPath + 'right.' + skyboxFormat,
        skyboxPath + 'left.' + skyboxFormat,
        skyboxPath + 'up.' + skyboxFormat,
        skyboxPath + 'down.' + skyboxFormat,
        skyboxPath + 'front.' + skyboxFormat,
        skyboxPath + 'back.' + skyboxFormat,
    ];

    var skybox = new THREE.CubeTextureLoader().load(skyboxTextures);
    skybox.format = THREE.RGBFormat;

    scene.background = skybox;
}

If you update your initSceneMain() function and add a function call for initSkybox(sceneMain) you should see a monochrome background in the browser. You should see something like this:

Grid Skybox

Note: You’ll need to serve the html page rather than just open it in the browser. If you try to use a file protocol ThreeJS will throw an CORS error when trying to load the texture(s).

Taking Control

We are going to use some ready made controls for ThreeJS to allow us to quickly navigate around in our scene. We can replace these later with our own custom controls. You’ll need to include another external script in your <head> for http://threejs.org/examples/js/controls/FlyControls.js.

Add a function for initializing the controls and binding them to our perspective camera:

function initControls(target) {
    controls = new THREE.FlyControls(target);
    controls.movementSpeed = 5;
    controls.rollSpeed = Math.PI / 12;
    controls.domElement = container;
    controls.dragToLook = true;
}

Add a function call for initControls(cameraPerspective) to your init function and update your existing animate function to update the controls:

function animate() {
    ...
    // Any changes to the scene will be initiated from here 
    controls.update(delta);
}

Now you can use mouse click / drag to look around within your scene. You can also use WASD to move but until you render something you’ll not be able to tell you’re moving yet.

Finishing Touches

We need to account for the window dimensions changing if a browser gets resized or if orientation changes on a mobile device. Add the following functions:

function initWindow() {
    screenWidth = window.innerWidth;
    screenHeight = window.innerHeight;

    renderer.setSize(screenWidth, screenHeight);
    resetCameras();
}

function resetCameras() {
    cameraPerspective.aspect = screenWidth / screenHeight;
    cameraPerspective.updateProjectionMatrix();

    cameraOrthographic.left = screenWidth / -2;
    cameraOrthographic.right = screenWidth / 2;
    cameraOrthographic.top = screenHeight / 2;
    cameraOrthographic.bottom = screenHeight / -2;
    cameraOrthographic.updateProjectionMatrix();
}

Ensure that you update the init function to call initRenderer, initCameras, initWindow, then everything else. The order is important. Lastly, you can add an event handler to call your initWindow function when the window is resized:

window.addEventListener('resize', initWindow, false);

Next Steps

In the next post we will start adding objects to our scene(s) and animating them. If you want to download an example of what we have created so far you can do so from Google Drive.

One thought on “ThreeJS : Getting Started

Leave a comment