Skip to main content

Command Palette

Search for a command to run...

Skybox in Three.js

Create a simple Three.js skybox

Updated
7 min read
J

I am a software Craftsman at RoleModel Software.

I started exploring programming in middle school, following some books with guidance from family friends who did web development for a living. In High School, I joined a robotics team and was taught by another developer how to take a project from nothing to competing against other robotics teams. Near the end of High School, I learned about RoleModel software and the Software Craftsmanship Academy they ran https://rolemodelsoftware.com/academy. It felt like the perfect opportunity for me to pursue a career in software. After graduating the Academy, I joined RoleModel full time and have been delivering quality software to clients, leading teams of developers, and mentoring junior developers. I am continuing to learn new things and push my abilities while helping others do the same.

Note: I'm sure most of this is outdated at this point, but wanted to move the article from my previous blog for archive purposes.

Three.js is a javascript 3D library that makes WebGL very simple. Today we will see how to set up a scene with a skybox in Three.js.

The first thing we need is an HTML file.

<body></body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r58/three.min.js"></script>
<script>
  // Everything will happen in here.
</script>

As you can see we are loading in Three.js via http://www.cdnjs.com/ Next, we will set up some variables to be used later.

var scene, camera, renderer;

The three main things we need are a scene to render on, a camera to show us the scene, and a renderer to draw on the scene. Now we will call two functions to get everything started.

init();
animate();

function init() {

}

function animate() {

}

The init function is what we use to initialize the scene, camera, and renderer as well as any other objects we want to show. Animate handles the runtime loop and rendering.

Let's start to fill in our init function. We will start with the main components.

////////////
// scene  //
////////////
scene = new THREE.Scene();

////////////
// camera //
////////////
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 20000);
camera.position.set(0, 100, 200);
camera.lookAt(scene.position);

//////////////
// renderer //
//////////////
renderer = new THREE.WebGLRenderer();
renderer.setSize ( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

Creating the scene is probably the easiest part of setting up a Three.js project.

The camera is a bit more complicated. We make a Perspective Camera which is one of a few options Three.js provides. The arguments we pass in are as follows ( field_of_view, aspect_ratio, near_clip, far_clip ). Field_of_view is how wide your vision is, aspect_ratio is the size of the viewing window, near_clip is how close you can get to an object before it stops rendering, and far_clip is how far away an object can be before it stops rendering. Before we finish here, we set the position of the camera and tell it to look at the scene's position which is (0, 0).

For the renderer, we create a webGL renderer, sets the size to the size of your screen and append its dom Element(the HTML canvas) to the page body.

This is what your file should look like inside the script tag at this point.

var scene, camera, renderer;

init();
animate();

function init() {
  ////////////
  // scene  //
  ////////////
  scene = new THREE.Scene();

  ////////////
  // camera //
  ////////////
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 20000);
  camera.position.set(0, 100, 200);
  camera.lookAt(scene.position);

  //////////////
  // renderer //
  //////////////
  renderer = new THREE.WebGLRenderer();
  renderer.setSize ( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

}

function animate() {

}

Now that we have the main pieces, let’s fill in our animate function.

requestAnimationFrame( animate );
render();

All this does is tell the renderer to render the scene to the camera(canvas view).

We have a scene, a camera, a renderer that is being updated and everything is finally set for us to show something. The first thing we will add is an x, y, z axes. This will help us visualize the 3D space. Inside of our init function, after make the renderer, add this.

////////////
//  axes  //
////////////
var axes = new THREE.AxisHelper(100);
scene.add(axes);

Using Three.js built in Helper, adding an axes is as easy as telling it what size we want, in this case 100. Once we make the axes, all we need to do is add it to the scene and viola, we are done.

////////////
// floor  //
////////////
var floorTexture = new THREE.ImageUtils.loadTexture( 'images/checkerboard.jpg' );
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set( 10, 10 );
var floorMaterial = new THREE.MeshBasicMaterial({ map: floorTexture, side: THREE.DoubleSide });
var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.y = -0.5;
floor.rotation.x = Math.PI / 2;
scene.add(floor);

What is a floor without a texture? That's why we load in a texture on the first line. Now that we have a texture, we tell it to wrap ten times(repeat.set( 10, 10 )) in both directions(wrapS and wrapT).

All 3D objects in Three.js are meshes. Meshes are composed of geometry(vertices) and a material(how they reflect light/texture). For our floor, we will use a basic material. This allows us to see it even without a light source. Upon creation, we map the texture on its sides and set it to double-sided since it’s just a plane. For the geometry, we use Plane Geometry passing in a few variables (width, depth, x_position, y_position).
Finally, we create the mesh, passing in our geometry and material, set its position and add it to the scene.

That white background is somewhat boring, let’s add a skybox to fill in that empty space.

////////////
// skybox //
////////////
var materialArray = [];
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-xpos.png' ) }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-xneg.png' ) }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-ypos.png' ) }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-yneg.png' ) }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-zpos.png' ) }));
materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-zneg.png' ) }));
for (var i = 0; i < 6; i++)
   materialArray[i].side = THREE.BackSide;
var skyboxMaterial = new THREE.MeshFaceMaterial( materialArray );
var skyboxGeom = new THREE.CubeGeometry( 5000, 5000, 5000, 1, 1, 1 );
var skybox = new THREE.Mesh( skyboxGeom, skyboxMaterial );
scene.add( skybox );

Just like before, we need to create a material, but this time we make one for each side of our skybox. This allows us to map a different image to each side. By setting the side to BackSide, Three.js will render the images on the inside of the cube instead of the default outside. We have all our materials in an array, but to map them properly, we give the array to another type of material, a face material. Next we create the geometry, this time we use CubeGeometry and pass in (width, height, depth, x_position, y_position, z_position). After creating the mesh we can add it to the scene and now we have a background!

Last, but not least, we will add a simple cube to the scene

////////////
//  cube  //
////////////
var material = new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture('images/crate.jpg') });
var geometry = new THREE.CubeGeometry(50, 50, 50);
var cube = new THREE.Mesh(geometry, material);
cube.position.set(0, 25, 0);
scene.add(cube);

As before, we create a basic material and map an image to it. Next we create the CubeGeometry with a size of 50 on each dimension. Finally we create the mesh, set its position, and add it to the scene.

Here is what everything inside the script tags should look like.

var scene, camera, renderer;

init();
animate();

function init() {
  ////////////
  // scene  //
  ////////////
  scene = new THREE.Scene();

  ////////////
  // camera //
  ////////////
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 20000);
  camera.position.set(0, 100, 200);
  camera.lookAt(scene.position);

  //////////////
  // renderer //
  //////////////
  renderer = new THREE.WebGLRenderer();
  renderer.setSize ( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  ////////////
  //  axes  //
  ////////////
  var axes = new THREE.AxisHelper(100);
  scene.add(axes);

  ////////////
  // floor  //
  ////////////
  var floorTexture = new THREE.ImageUtils.loadTexture( 'images/checkerboard.jpg' );
  floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
  floorTexture.repeat.set( 10, 10 );
  var floorMaterial = new THREE.MeshBasicMaterial({ map: floorTexture, side: THREE.DoubleSide });
  var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
  var floor = new THREE.Mesh(floorGeometry, floorMaterial);
  floor.position.y = -0.5;
  floor.rotation.x = Math.PI / 2;
  scene.add(floor);

  ////////////
  // skybox //
  ////////////
  var materialArray = [];
  materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-xpos.png' ) }));
  materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-xneg.png' ) }));
  materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-ypos.png' ) }));
  materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-yneg.png' ) }));
  materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-zpos.png' ) }));
  materialArray.push(new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/dawnmountain-zneg.png' ) }));
  for (var i = 0; i < 6; i++)
     materialArray[i].side = THREE.BackSide;
  var skyboxMaterial = new THREE.MeshFaceMaterial( materialArray );
  var skyboxGeom = new THREE.CubeGeometry( 5000, 5000, 5000, 1, 1, 1 );
  var skybox = new THREE.Mesh( skyboxGeom, skyboxMaterial );
  scene.add( skybox );

  ////////////
  //  cube  //
  ////////////
  var material = new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture('images/crate.jpg') });
  var geometry = new THREE.CubeGeometry(50, 50, 50);
  var cube = new THREE.Mesh(geometry, material);
  cube.position.set(0, 25, 0);
  scene.add(cube);

}

function animate() {
  requestAnimationFrame( animate );
  render();
}

function update() {

}

function render() {
  renderer.render(scene, camera);
}

There you have it. We have created a simple scene with a floor, axes, a skybox, and a cube as well. The next step is to add some controls to view the rest of the scene. I hope you learned something from this brief tutorial. Feel free to leave a comment and share what you have done with Three.js

Here are the pictures used in this tutorial.

crate

dawnmountain-xneg

dawnmountain-xpos

dawnmountain-yneg

dawnmountain-ypos

dawnmountain-zneg

dawnmountain-zpos

More from this blog

Code for Thought

2 posts

Software Craftsman at RoleModel Software.

I love writing Ruby, CSS, building web applications, and design systems.