How to Create a Skybox With Three.js
Tags: threejs, codepen, javascript, webgl
Published: 2020-03-27 23:58:26

*This was originally published on [codinhood.com](https://codinhood.com/post/create-skybox-with-threejs)*


Skyboxes are typically used in video games to create the illusion of far off three-dimensional backgrounds. A skybox is essentially a cube with textures on each side of the cube. The player or camera is then placed within the cube so that all six textures surround them giving them the illusion that they're within a much larger environment. [reactnativeinfinity.com](https://www.reactnativeinfinity.com/) makes use of this technique to create the illusion of spinning in space. The Codepen below has a few different skyboxes you can try out. Click and drag the screen to move around.


{% codepen https://codepen.io/codypearce/pen/oNXQyOb %}


## Three.js setup

First, set up a `scene`, `camera`, and `renderer` within an `init` function we will call to initialize Three.js. We'll use a [PerspectiveCamera](https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera) with a position that is zoomed out pretty far so we can see the box before jumping in. We'll also use the `THREE.WEbGLRenderer` and append it to the body of the page. Finally, the `animate` function will handle rerendering the scene with any updates we add.

```js
let scene, camera, renderer, skyboxGeo, skybox;

function init() {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(
    55,
    window.innerWidth / window.innerHeight,
    45,
    30000
  );
  camera.position.set(1200, -250, 20000);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.domElement.id = "canvas";
  document.body.appendChild(renderer.domElement);
  animate();
}
function animate() {
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

init();
```

Import the core Three.js library.

```html
<script src="https://threejs.org/build/three.min.js"></script>
```

Set the body height to the viewport height and add a grey background to the body so we can see the cube.

```css
body {
  margin: 0;
  height: 100vh;
  background: #bdc3c7;
}
```

Since we haven't added any objects we'll only see a grey background right now.

## Add Three.js box

We can add a box with `THREE.BoxGeometry` with `width`, `height`, and `depth` set to `10000`. Then use `THREE.Mesh` to apply a texture to it, in this case, it will default to a pure `white` texture. Finally, add the object to the scene before calling the `animate` function within the `init` function.

```js
function init() {
  ...
  skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
  skybox = new THREE.Mesh(skyboxGeo);
  scene.add(skybox);

  animate();
```

![Simple Box](https://codinhood.com/static/945562a371bd58322ea4c397355cd965/14e05/square.png)

Even though it's a cube, it looks like a square because we're viewing it straight on. To verify it is a cube we can add a rotation animation within the `animate` function:

```js
function animate() {
  skybox.rotation.x += 0.005;
  skybox.rotation.y += 0.005;
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
```

![Simple cube](https://codinhood.com/static/f3a99bd7b7ee264dfb6bc4d8a0cf62e9/14e05/cube.png)

## Skybox Mesh Material

You can find free skybox images on [opengameart.org](https://opengameart.org/art-search-advanced?keys=skybox&title=&field_art_tags_tid_op=or&field_art_tags_tid=&name=&sort_by=count&sort_order=DESC&items_per_page=24&Collection=) or you can search Google for "free skybox images". There should be six images that correspond to each side of the cube that seamlessly mesh together. For example, for React Native Infinity, the six space images correspond to different sides as shown below.

![Skybox Names](https://codinhood.com/static/78e5f34c1ceb1e205cc8cc00b01a7d53/14e05/skybox2.png)

Each image should be named according to the side which they correspond to, for example, `purplenebula_ft.png` is the front image, `purplenebula_rt.png` is the right image, and `purplenebula_dn.png` is the bottom image. There are three steps we need to follow to add these images to our cube:

1. Load each image as a Texture
2. Map each Texture to a Material array
3. Add Material array to the Skybox cube

### 1. Load Images as Textures

Textures can be loaded in Three.js using the `TextureLoader().load()` function. The `load()` method takes the path of the image as a parameter. We could load each image by creating six `TextureLoader()` functions, like this:

```js
const ft = new THREE.TextureLoader().load("purplenebula_ft.jpg");
const bk = new THREE.TextureLoader().load("purplenebula_bk.jpg");
const up = new THREE.TextureLoader().load("purplenebula_up.jpg");
const dn = new THREE.TextureLoader().load("purplenebula_dn.jpg");
const rt = new THREE.TextureLoader().load("purplenebula_rt.jpg");
const lf = new THREE.TextureLoader().load("purplenebula_lf.jpg");
```

But it would be better to create a reusable function that loops through all of our images for us. Create a function `createPathStrings()` that will create an array of path strings from a file image name, `filename`.

```js
function createPathStrings(filename) {
  const basePath = "./static/skybox/";
  const baseFilename = basePath + filename;
  const fileType = ".png";
  const sides = ["ft", "bk", "up", "dn", "rt", "lf"];
  const pathStings = sides.map(side => {
    return baseFilename + "_" + side + fileType;
  });

  return pathStings;
}
```

This should create an array of strings that represent the path to each image:

```js
['./static/skybox/purplenebula_ft.jpg', './static/skybox/purplenebula_bk.jpg', ...]
```

Next, load each texture using `TextureLoader().load()` by mapping over the array above. Let's create another function, `createMaterialArray()`, to generate a new array of loaded textures. We will also pass in the `filename` parameter into the `createPathStrings` function.

```js
let skyboxImage = "purplenebula";
function createMaterialArray(filename) {
  const skyboxImagepaths = createPathStrings(filename);
  const materialArray = skyboxImagepaths.map(image => {
    let texture = new THREE.TextureLoader().load(image);

    return texture;
  });
  return materialArray;
}
```

### 2. Map each Texture to a Mesh array

The `MeshBasicMaterial()` Three.js method will allow us to map our textures above to a Three.js material. Instead of creating another function to do this, we can simply modify the `createMaterialArray()` function to return a Three.js material instead of the loaded texture.

```js
function createMaterialArray(filename) {
  const skyboxImagepaths = createPathStrings(filename);
  const materialArray = skyboxImagepaths.map(image => {
    let texture = new THREE.TextureLoader().load(image);

    return new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide }); // <---
  });
  return materialArray;
}
```

### 3. Add Mesh array to the Skybox cube

We're finally ready to add our mesh array to the cube we created above. First, create a variable, `skyboxImage`, with the base file name. Pass that variable into the `createMaterialArray` to generate our mesh array. Finally, pass that array into the second parameter of the `new Three.Mesh()` function.

```js
const skyboxImage = 'purplenebula';

function init() {
...

  const materialArray = createMaterialArray(skyboxImage);
  skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
  skybox = new THREE.Mesh(skyboxGeo, materialArray);
  scene.add(skybox);

  animate();
}
```

Our cube should now have the mesh array, click the "Outside Box" button to see how this should look.

{% codepen https://codepen.io/codypearce/pen/oNXQyOb %}

## Place camera inside the cube

We can change the `camera`'s `z` position from `20000` to `2000` to put the camera inside the cube.

```js
function init()
  ...
  camera.position.set(1200, -250, 2000);
  ...
}
```

![Inside the cube](https://codinhood.com/static/f636e81e6a6e6f584e1c47edad968b09/14e05/inside-cube.png)

## Orbit Controls

While the above works to put us inside the cube, it would be better if could control the camera with the mouse and look around the environment. The Orbit Controls package for Three.js allows us to do that add a `<script>` import:

```html
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
```

First, add another variable named `controls` to the initialization at the top. Then assign that variable to the `OrbitControls` method while passing in the `camera` and `domElement`. Enable the controls by setting `controls.enabled` to `true`. Finally, set a `minDistance` and `maxDistance` so that the user cannot zoom outside of the cube.

```js
let scene, camera, renderer, skyboxGeo, skybox, controls;

function init() {
  ...

  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.enabled = true;
  controls.minDistance = 700;
  controls.maxDistance = 1500;

  ...
  animate();
}
```

Next, remove the rotation in the `animate()` function and add `controls.update()`;

```js
function animate() {
  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
```

You should now be able to click and drag the environment around to see whatever part you want. If you want the environment to rotate again, like you're in space rotating, you can use the `autoRotate` property:

```js
function init() {
  ...

  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.enabled = true;
  controls.minDistance = 700;
  controls.maxDistance = 1500;
  controls.autoRotate = true;
  controls.autoRotateSpeed = 1.0;
  ...
  animate();
}
```

## Resize window

If you resize your browser window after initialization the canvas will not resize to fit the new window size. To fix this create a function to redefine the `camera.aspect` and `renderer` size to the height and width of the `window`:

```js
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;

  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}
```

Then add an event listener on the `window` `resize` event and pass this new function. Add this event listener to the `init()` function right above the `animate()` call.

```js
function init() {
  ...
  window.addEventListener('resize', onWindowResize, false);
  animate();
}
```

The canvas will now resize with the window.

## Conclusion

Skyboxes are a neat way to create the illusion of a 3d environment quickly. Although generally used for video games, there might be some creative ways you could implement them in a web project.