21 min read

(For more resources related to this topic, see here.)

Creating a scene

We know that for a scene to show anything, we need three types of components:

Component

Description

Camera

It determines what is rendered on the screen

Lights

They have an effect on how materials are shown and used when creating shadow effects

Objects

These are the main objects that are rendered from the perspective of the camera: cubes, spheres, and so on

The THREE.Scene() object serves as the container for all these different objects. This object itself doesn’t have too many options and functions.

Basic functionality of the scene

The best way to explore the functionality of the scene is by looking at an example. I’ll use this example to explain the various functions and options that a scene has. When we open this example in the browser, the output will look something like the following screenshot:

Even though the scene looks somewhat empty, it already contains a couple of objects. By looking at the following source code, we can see that we’ve used the Scene.add(object) function from the THREE.Scene() object to add a THREE.Mesh (the ground plane that you see), a THREE.SpotLight. and a THREE.AmbientLight object. The THREE.Camera object is added automatically by the Three.js library when you render the scene, but can also be added manually if you prefer.

var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); ... var planeGeometry = new THREE.PlaneGeometry(60,40,1,1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry,planeMaterial); ... scene.add(plane); var ambientLight = new THREE.AmbientLight(0x0c0c0c); scene.add(ambientLight); ... var spotLight = new THREE.SpotLight( 0xffffff ); ... scene.add( spotLight );

Before we look deeper into the THREE.Scene() object, I’ll first explain what you can do in the demonstration, and after that we’ll look at some code. Open this example in your browser and look at the controls at the upper-right corner as you can see in the following screenshot:

With these controls you can add a cube to the scene, remove the last added cube from the scene, and show all the current objects that the scene contains. The last entry in the control section shows the current number of objects in the scene. What you’ll probably notice when you start up the scene is that there are already four objects in the scene. These are the ground plane, the ambient light, the spot light, and the camera that we had mentioned earlier. In the following code fragment, we’ll look at each of the functions in the control section and start with the easiest one: the addCube() function:

this.addCube = function() { var cubeSize = Math.ceil((Math.random() * 3)); var cubeGeometry = new THREE.CubeGeometry(cubeSize,cubeSize,cubeSize); var cubeMaterial = new THREE.MeshLambertMaterial( {color: Math.random() * 0xffffff }); var cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.castShadow = true; cube.name = "cube-" + scene.children.length; cube.position.x=-30 + Math.round( (Math.random() * planeGeometry.width)); cube.position.y= Math.round((Math.random() * 5)); cube.position.z=-20 + Math.round((Math.random() * planeGeometry.height)); scene.add(cube); this.numberOfObjects = scene.children.length; };

This piece of code should be pretty easy to read by now. Not many new concepts are introduced here. When you click on the addCube button, a new THREE.CubeGeometry instance is created with a random size between zero and three. Besides a random size, the cube also gets a random color and position in the scene.

A new thing in this piece of code is that we also give the cube a name by using the name attribute. Its name is set to cube-appended with the number of objects currently in the scene (shown by the scene.children.length property). So you’ll get names like cube-1, cube-2, cube-3, and so on. A name can be useful for debugging purposes, but can also be used to directly find an object in your scene. If you use the Scene.getChildByName(name) function, you can directly retrieve a specific object and, for instance, change its location. You might wonder what the last line in the previous code snippet does. The numberOfObjects variable is used by our control GUI to list the number of objects in the scene. So whenever we add or remove an object, we set this variable to the updated count.

The next function that we can call from the control GUI is removeCube and, as the name implies, clicking on this button removes the last added cube from the scene. The following code snippet shows how this function is defined:

this.removeCube = function() { var allChildren = scene.children; var lastObject = allChildren[allChildren.length-1]; if (lastObject instanceof THREE.Mesh) { scene.remove(lastObject); this.numberOfObjects = scene.children.length; } }

To add an object to the scene we will use the add() function. To remove an object from the scene we use the not very surprising remove() function. In the given code fragment we have used the children property from the THREE.Scene() object to get the last object that was added. We also need to check whether that object is a Mesh object in order to avoid removing the camera and the lights. After we’ve removed the object, we will once again update the GUI property that holds the number of objects in the scene.

The final button on our GUI is labeled as outputObjects. You’ve probably already clicked on it and nothing seemed to happen. What this button does is print out all the objects that are currently in our scene and will output them to the web browser Console as shown in the following screenshot:

The code to output information to the Console log makes use of the built-in console object as shown:

this.outputObjects = function() { console.log(scene.children); }

This is great for debugging purposes; especially when you name your objects, it’s very useful for finding issues and problems with a specific object in your scene. For instance, the properties of the cube-17 object will look like the following code snippet:

__webglActive: true __webglInit: true _modelViewMatrix: THREE.Matrix4 _normalMatrix: THREE.Matrix3 _vector: THREE.Vector3 castShadow: true children: Array[0] eulerOrder: "XYZ" frustumCulled: true geometry: THREE.CubeGeometry id: 20 material: THREE.MeshLambertMaterial matrix: THREE.Matrix4 matrixAutoUpdate: true matrixRotationWorld: THREE.Matrix4 matrixWorld: THREE.Matrix4 matrixWorldNeedsUpdate: false name: "cube-17" parent: THREE.Scene position: THREE.Vector3 properties: Object quaternion: THREE.Quaternion receiveShadow: false renderDepth: null rotation: THREE.Vector3 rotationAutoUpdate: true scale: THREE.Vector3 up: THREE.Vector3 useQuaternion: false visible: true __proto__: Object

So far we’ve seen the following scene-related functionality:

  • Scene.Add(): This method adds an object to the scene
  • Scene.Remove(): This removes an object from the scene
  • Scene.children(): This method gets a list of all the children in the scene
  • Scene.getChildByName(): This gets a specific object from the scene by using the name attribute

These are the most important scene-related functions, and most often you won’t need any more. There are, however, a couple of helper functions that could come in handy, and I’d like to show them based on the code that handles the cube rotation.

We use a render loop to render the scene. Let’s look at the same code snippet for this example:

function render() { stats.update(); scene.traverse(function(e) { if (e instanceof THREE.Mesh && e != plane ) { e.rotation.x+=controls.rotationSpeed; e.rotation.y+=controls.rotationSpeed; e.rotation.z+=controls.rotationSpeed; } }); requestAnimationFrame(render); renderer.render(scene, camera); }

Here we can see that the THREE.Scene.traverse() function is being used. We can pass a function as an argument to the traverse() function. This passed in function will be called for each child of the scene. In the render() function, we will use the traverse() function to update the rotation for each of the cube instances (we will explicitly ignore the ground plane). We could also have done this by iterating over the children property array by using a for loop.

Before we dive into the Mesh and Geometry object details, I’d like to show you two interesting properties that you can set on the Scene object: fog and overrideMaterial.

Adding the fog effect to the scene

The fog property let’s you add a fog effect to the complete scene. The farther an object is, the more it will be hidden from sight. The following screenshot shows how the fog property is enabled:

Enabling the fog property is really easy to do in the Three.js library. Just add the following line of code after you’ve defined your scene:

scene.fog=new THREE.Fog( 0xffffff, 0.015, 100 );

Here we are defining a white fog (0xffffff). The last two properties can be used to tune how the mist will appear. The 0.015 value sets the near property and the 100 value sets the far property. With these properties you can determine where the mist will start and how fast it will get denser. There is also a different way to set the mist for the scene; for this you will have to use the following definition:

scene.fog=new THREE.FogExp2( 0xffffff, 0.015 );

This time we don’t specify the near and far properties, but just the color and the mist density. It’s best to experiment a bit with these properties in order to get the effect that you want.

Using the overrideMaterial property

The last property that we will discuss for the scene is the overrideMaterial property, which is used to fix the materials of all the objects. When you use this property as shown in the following code snippet, all the objects that you add to the scene will make use of the same material:

scene.overrideMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});

The scene will be rendered as shown in the following screenshot:

In the earlier screenshot, you can see that all the cube instances are rendered by using the same material and color. In this example we’ve used a MeshLambertMaterial object as the material. With this material type, you can create non-shiny looking objects which will respond to the lights that you add to the scene.

In this section we’ve looked at the first of the core concepts of the Three.js library: the scene. The most important thing to remember about the scene is that it is basically a container for all the objects, lights, and cameras that you want to use while rendering. The following table summarizes the most important functions and attributes of the Scene object:

Function/Property

Description

add(object)

Adds an object to the scene. You can also use this function, as we’ll see later, to create groups of objects.

children

Returns a list of all the objects that have been added to the scene, including the camera and lights.

getChildByName(name)

When you create an object, you can give it a distinct name by using the name attribute. The Scene object has a function that you can use to directly return an object with a specific name.

remove(object)

 If you’ve got a reference to an object in the scene, you can also remove it from the scene by using this function.

traverse(function)

The children attribute returns a list of all the children in the scene. With the traverse() function we can also access these children by passing in a callback function.

fog

This property allows you to set the fog for the scene. It will render a haze that hides the objects that are far away.

overrideMaterial

With this property you can force all the objects in the scene to use the same material.

In the next section we’ll look closely at the objects that you can add to the scene.

Working with the Geometry and Mesh objects

In each of the examples so far you’ve already seen the geometries and meshes that are being used. For instance, to add a sphere object to the scene we did the following:

var sphereGeometry = new THREE.SphereGeometry(4,20,20); var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff); var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);

We have defined the shape of the object, its geometry, what this object looks like, its material, and combined all of these in a mesh that can be added to a scene. In this section we’ll look a bit closely at what the Geometry and Mesh objects are. We’ll start with the geometry.

The properties and functions of a geometry

The Three.js library comes with a large set of out-of-the-box geometries that you can use in your 3D scene. Just add a material, create a mesh variable, and you’re pretty much done. The following screenshot, from example 04-geometries.html, shows a couple of the standard geometries available in the Three.js library:

In we’ll explore all the basic and advanced geometries that the Three.js library has to offer. For now, we’ll go into more detail on what the geometry variable actually is.

A geometry in Three.js, and in most other 3D libraries, is basically a collection of points in a 3D space and a number of faces connecting all those points together. Take, for example, a cube:

  • A cube has eight corners. Each of these corners can be defined as a combination of x, y, and z coordinates. So, each cube has eight points in a 3D space. In the Three.js library, these points are called vertices.
  • A cube has six sides, with one vertex at each corner. In the Three.js library, each of these sides is called a face.

When you use one of the Three.js library-provided geometries, you don’t have to define all the vertices and faces yourself. For a cube you only need to define the width, height, and depth. The Three.js library uses that information and creates a geometry with eight vertices at the correct position and the correct face. Even though you’d normally use the Three.js library-provided geometries, or generate them automatically, you can still create geometries completely by hand by defining the vertices and faces. This is shown in the following code snippet:

var vertices = [ new THREE.Vector3(1,3,1), new THREE.Vector3(1,3,-1), new THREE.Vector3(1,-1,1), new THREE.Vector3(1,-1,-1), new THREE.Vector3(-1,3,-1), new THREE.Vector3(-1,3,1), new THREE.Vector3(-1,-1,-1), new THREE.Vector3(-1,-1,1) ]; var faces = [ new THREE.Face3(0,2,1), new THREE.Face3(2,3,1), new THREE.Face3(4,6,5), new THREE.Face3(6,7,5), new THREE.Face3(4,5,1), new THREE.Face3(5,0,1), new THREE.Face3(7,6,2), new THREE.Face3(6,3,2), new THREE.Face3(5,7,0), new THREE.Face3(7,2,0), new THREE.Face3(1,3,4), new THREE.Face3(3,6,4), ]; var geom = new THREE.Geometry(); geom.vertices = vertices; geom.faces = faces; geom.computeCentroids(); geom.mergeVertices();

This code shows you how to create a simple cube. We have defined the points that make up this cube in the vertices array. These points are connected to create triangular faces and are stored in the faces array. For instance, the new THREE.Face3(0,2,1) element creates a triangular face by using the points 0, 2, and 1 from the vertices array.

In this example we have used a THREE.Face3 element to define the six sides of the cube, that is, two triangles for each face. In the previous versions of the Three.js library, you could also use a quad instead of a triangle. A quad uses four vertices instead of three to define the face. Whether using quads or triangles is better is a much-heated debate in the 3D modeling world. Basically, using quads is often preferred during modeling, since they can be more easily enhanced and smoothed much easier than triangles. For rendering and game engines, though, working with triangles is easier since every shape can be rendered as a triangle.

Using these vertices and faces, we can now create our custom geometry, and use it to create a mesh. I’ve created an example that you can use to play around with the position of the vertices. In example 05-custom-geometry.html, you can change the position of all the vertices of a cube. This is shown in the following screenshot:

This example, which uses the same setup as all our other examples, has a render loop. Whenever you change one of the properties in the drop-down control box, the cube is rendered correctly based on the changed position of one of the vertices. This isn’t something that works out-of-the-box. For performance reasons, the Three.js library assumes that the geometry of a mesh won’t change during its lifetime. To get our example to work we need to make sure that the following is added to the code in the render loop:

mesh.geometry.vertices=vertices; mesh.geometry.verticesNeedUpdate=true; mesh.geometry.computeFaceNormals();

In the first line of the given code snippet, we point the vertices of the mesh that you see on the screen to an array of the updated vertices. We don’t need to reconfigure the faces, since they are still connected to the same points as they were before. After we’ve set the updated vertices, we need to tell the geometry that the vertices need to be updated. We can do this by setting the verticesNeedUpdate property of the geometry to true. Finally we will do a recalculation of the faces to update the complete model by using the computeFaceNormals() function.

The last geometry functionality that we’ll look at is the clone() function. We had mentioned that the geometry defines the form, the shape of an object, and combined with a material we can create an object that can be added to the scene to be rendered by the Three.js library. With the clone() function, as the name implies, we can make a copy of the geometry and, for instance, use it to create a different mesh with a different material. In the same example, that is, 05-custom-geometry.html, you can see a clone button at the top of the control GUI, as seen in the following screenshot:

If you click on this button, a clone will be made of the geometry as it currently is, and a new object is created with a different material and is added to the scene. The code for this is rather trivial, but is made a bit more complex because of the materials that I have used. Let’s take a step back and first look at the code that was used to create the green material for the cube:

var materials = [ new THREE.MeshLambertMaterial( { opacity:0.6, color: 0x44ff44, transparent:true } ), new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true } ) ];

As you can see, I didn’t use a single material, but an array of two materials. The reason is that besides showing a transparent green cube, I also wanted to show you the wireframe, since that shows very clearly where the vertices and faces are located. The Three.js library, of course, supports the use of multiple materials when creating a mesh. You can use the SceneUtils.createMultiMaterialObject() function for this as shown:

var mesh = THREE.SceneUtils.createMultiMaterialObject( geom,materials);

What the Three.js library does in this function is that it doesn’t create one THREE.Mesh instance, but it creates one for each material that you have specified, and puts all of these meshes in a group. This group can be used in the same manner that you’ve used for the Scene object. You can add meshes, get meshes by name, and so on. For instance, to add shadows to all the children in this group, we will do the following:

mesh.children.forEach(function(e) {e.castShadow=true});

Now back to the clone() function that we were discussing earlier:

this.clone = function() { var cloned = mesh.children[0].geometry.clone(); var materials = [ new THREE.MeshLambertMaterial( { opacity:0.6, color: 0xff44ff, transparent:true } ), new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true } ) ]; var mesh2 = THREE.SceneUtils.createMultiMaterialObject(cloned,materials); mesh2.children.forEach(function(e) {e.castShadow=true}); mesh2.translateX(5); mesh2.translateZ(5); mesh2.name="clone"; scene.remove(scene.getChildByName("clone")); scene.add(mesh2); }

This piece of JavaScript is called when the clone button is clicked on. Here we clone the geometry of the first child of the cube. Remember, the mesh variable contains two children: a mesh that uses the MeshLambertMaterial and a mesh that uses the MeshBasicMaterial. Based on this cloned geometry, we will create a new mesh, aptly named mesh2. We can move this new mesh by using the translate() function remove the previous clone (if present), and add the clone to the scene.

That’s enough on geometries for now.

The functions and attributes for a mesh

We’ve already learned that, in order to create a mesh, we need a geometry and one or more materials. Once we have a mesh, we can add it to the scene, and it is rendered. There are a couple of properties that you can use to change where and how this mesh appears in the scene. In the first example, we’ll look at the following set of properties and functions:

Function/Property

Description

position

Determines the position of this object relative to the position of its parent. Most often the parent of an object is a THREE.Scene() object.

rotation

With this property you can set the rotation of an object around any of its axes.

scale

This property allows you to scale the object around its x, y, and z axes.

translateX(amount)

 Moves the object through the specified amount over the x axis.

translateY(amount)

 Moves the object through the specified amount over the y axis.

translateZ(amount)

 Moves the object through the specified amount over the z axis.

As always, we have an example ready for you that’ll allow you to play around with these properties. If you open up the 06-mesh-properties.html example in your browser, you will get a drop-down menu where you can alter all these properties and directly see the result, as shown in the following screenshot:

Let me walk you through them; I’ll start with the position property. We’ve already seen this property a couple of times, so let’s quickly address it. With this property you can set the x, y, and z coordinates of the object. The position of an object is relative to its parent object, which usually is the scene that you have added the object to. We can set an object’s position property in three different ways; each coordinate can be set directly as follows:

cube.position.x=10; cube.position.y=3; cube.position.z=1;

But we can also set all of them at once:

cube.position.set(10,3,1);

There is also a third option. The position property is a THREE.Vector3 object. This means that we can also do the following to set this object:

cube.postion=new THREE.Vector3(10,3,1)

I want to make a quick sidestep before looking at the other properties of this mesh. I had mentioned that this position is set relative to the position of its parent. In the previous section on THREE.Geometry, we made use of the THREE.SceneUtils.createMultiMaterialObject object to create a multimaterial object. I had explained that this doesn’t really return a single mesh, but a group that contains a mesh based on the same geometry for each material. In our case, it is a group that contains two meshes. If we change the position of one of the meshes that is created, you can clearly see that there really are two distinct objects. However, if we now move the created group around, the offset will remain the same. These two meshes are shown in the following screenshot:

Ok, the next one on the list is the rotation property. You’ve already seen this property being used a couple of times in this article. With this property, you can set the rotation of the object around one of its axes. You can set this value in the same manner as we did the for the position property. A complete rotation, as you might remember from math class, is two pi. The following code snippet shows how to configure this:

cube.rotation.x=0.5*Math.PI; cube.rotation.set(0.5*Math.PI,0,0); cube.rotation = new THREE.Vector3(0.5*Math.PI,0,0);

You can play around with this property by using the 06-mesh-properties.html example.

The next property on our list is one that we haven’t talked about: scale. The name pretty much sums up what you can do with this property. You can scale the object along a specific axis. If you set the scale to values smaller than one, the object will shrink as shown:

When you use values larger than one, the object will become larger as shown in the screenshot that follows:

The last part of the mesh that we’ll look at in this article is the translate functionality. With translate, you can also change the position of an object, but instead of defining the absolute position of where you want the object to be, you will define where the object should move to, relative to its current position. For instance, we’ve got a sphere object that is added to a scene and its position has been set to (1,2,3). Next, we will translate the object along its x axis by translateX(4). Its position will now be (5,2,3). If we want to restore the object to its original position we will set it to translateX(-4). In the 06-mesh-properties.html example, there is a menu tab called translate. From there you can experiment with this functionality. Just set the translate values for the x, y, and z axes, and click on the translate button. You’ll see that the object is being moved to a new position based on these three values.

LEAVE A REPLY

Please enter your comment!
Please enter your name here