Before becoming a Web developer, I worked in The visual design industry, creating award-winning, high-end 3D effects for movies and TV shows such as Tron, The Thing, Resident Evil, and Vikings. To be able to create these effects, we need to use highly sophisticated animation software such as Maya, 3Ds Max or Houdini, including hundreds of machine rendering machines to do long offline renderings. I’ve been working with these tools for so long that I’m amazed by the current state of web technology. We can create and display high-quality 3D content in the browser, in effect, using webGL and three.js.

This project example was created using these techniques. You can find more thress.js projects on their website. Web site.

Some projects using three.js

These sites, built by Three.js, show that 3D visualization has huge potential in commerce, retail, entertainment and advertising.

WebGL is a low-level JavaScript API that can create and display 3D content in a browser using a GPU. Unfortunately, since WebGL is a lower API, that means it’s harder to use. You need to write hundreds of lines of code to run even simple tasks. Three.js, on the other hand, is an open JavaScript library that abstracts the complexity of WebGL while allowing you to create real-time 3D content in a simpler way.

In this tutorial, I’ll introduce the basic three.js library. When introducing a new library, I’ll start with a simple example to better illustrate the fundamentals, but I’d like to expand on the point. At the same time, I will also build a scene that is more pleasant and lifelike.

We start with a simple plane and sphere but eventually it will look something like this:

See the Pen learning-threejs-final by Engin Arslan (@enginarslan) on CodePen.

Copying is the pinnacle of computer graphics, but, in your position, implementation is not necessarily an essential element of the process, but rather an intelligent deployment from your toolbox. Here are some techniques that you will learn in this tutorial that will help achieve photo realism.

  • Color, bump and roughness maps.

  • Physical materials

  • Shadow lighting

Realistic 3D images by Ian Spriggs

The basic 3D principles and techniques you’ll learn here are relevant in any other 3D content creation environment, whether it’s Blender, Unity, Maya, or 3DS Max.

This is going to be a long tutorial. If you’re a video watcher, or if you’d like to learn more about three.js, you should check out my training on this issue. From Lynda.com.

requirements

When using three.js, if you are working locally, it helps to serve HTML files through a local server in order to load application scenarios like external 3D geometry, images, etc. If you’re looking for a service that’s easy to set up, you can use Python to run a simple HTTP server. Python is installed on many operating systems.

You don’t have to worry about setting up a local development server to perform this tutorial. Instead, you rely on data urls to load resources like images to eliminate the overhead of setting up the server. Using an online code editor like CodePen, you can easily run your three.js code scenarios.

This tutorial assumes some knowledge of front-end JavaScript and front-end Web development. If you are not comfortable with JavaScript but want to get started easily, you may want to check out the courses/books. [” Coding for Visual Learners, Learning JavaScript with p5. Js “(www.codingforvisuallearners.com/). (disclaimer: I am the author)

Let’s start building 3D graphics on the Web!

An introduction to

I have prepared Prepared a Pen which you can follow in this tutorial.

The HTML code you will use will be super simple. All it needs is a div element to hold the canvas on which the 3D graphics are being displayed. It loads the three.js library (version 86) from the CDN.

<div id="webgl"></div>
``<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js">``</script>
Copy the code

CodeOpen hides the HTML structure for your convenience. If you build this scenario on another online editor or local site, your HTML will need something similar to the code below, where main.js will be the file that holds the JavaScript code.

<! DOCTYPE html> <html> <head> <title>Three.js</title> <style type="text/css"> html, body { margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <div id="webgl"></div> ``<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js">``</script> ``<script src="./main.js">``</script> </body> </html>Copy the code

Notice the simple CSS declarations in HTML. Here is your CSS tag in CodePen:

html, body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}
Copy the code

This is to ensure that there are no margin or padding values that can be applied through your browser, and that there are no scrollbars, so that graphics can fill the entire screen. That’s all we need to start building 3D graphics.

Part ONE – Three. Js basic scenario

Here are a few objects you need to master when working with three.js and 3D. They are the scene, the camera, and the rendering.

First, you need to create a scene. You can think of a scene object as a container for every 3D object you work with. It represents the 3D world you will create. You can create scene objects like this:

`var scene = new THREE.Scene(); `Copy the code

The other thing is when you work in 3D, you need a camera. Imagine the camera is like an eye, and you see this 3D world. When using 2D visualization, the concept of a camera usually doesn’t exist. What you see is what you see. But in the 3D world, you need a camera to define your point of view, because you can see so many positions and angles from a scene. The camera not only defines position, but also other information, such as field of view or aspect ratio.

var camera = new THREE.PerspectiveCamera(
    45, // field of view
    window.innerWidth / window.innerHeight, // aspect ratio
    1, // near clipping plane (beyond which nothing is visible)
    1000 // far clipping plane (beyond which nothing is visible)
);
Copy the code

The camera captures the scene for display purposes, but we can actually see anything, and the 3D data needs to be converted into 2D images. This process is called rendering, and you need the renderer to render the scene in three.js. You can initialize render like this:

`var renderer = new THREE.WebGLRenderer(); `Copy the code

Then set the renderer size. This will determine the size of the output image.

`renderer.setSize(window.innerWidth, window.innerHeight); `Copy the code

To be able to display your rendered results, you need to append the renderer’s DomElement property to the HTML content. You’ll use an empty div element to create an element with the id WebGL.

`document.getElementById('webgl').appendChild(renderer.domElement); `Copy the code

When all this is done, the way you can render on the renderer is by providing the scene and camera as parameter objects.

renderer.render(
    scene,
    camera
);
Copy the code

To keep the code simple, call and execute functions inside the init function.

`init(); `Copy the code

Maybe you can’t see anything right now… Except for the black screen. Don’t worry, it’s normal. The scene works fine, but you don’t include any objects in the scene, so what you see is basically empty space. Next, you fill the scene with 3D objects.

Watch the Pen Learning – Threejs-01 by Engin Arslan (@Enginarslan) on CodePen.

Add objects to the scene

The geometry object in three.js is composed of two parts. A geometric object determines the shape of an object, a material determines its surface quality, and an object’s shape. The combination of the two forms the grid in Three. js to form 3D objects.

Three.js allows you to create some simple shapes such as cubes and spheres in a simple way. You can do this by creating a simple radius value for the sphere.

`var geo = new THREE.SphereGeometry(1); `Copy the code

There are all kinds of materials that can be used in geometry. The material determines how an object responds to the light scene. We can use any material to make things reflective, rough, transparent, etc. The default materials, like the three.js object created by MeshBasicMaterial. MeshBasicMaterial are not affected by the light scene at all. This means that your geometry will be visible even if there are no lights in your scene. You can pass an object with a color property that sets the color you want for the object with the meshBasicMaterial HEX value. You will use this material now, but updating it later will make your object subject to the scene lighting. You don’t have any lighting for the scene, now meshbasicMaterial should be a good choice.

var material = new THREE.MeshBasicMaterial({
        color: 0x00ff00
});
Copy the code

You can combine geometry and materials to create a grid, which will form three-dimensional objects.

`var mesh = new THREE.Mesh(geometry, material); `Copy the code

Create a function to encapsulate the code that creates the sphere. You won’t be creating multiple spheres in this tutorial, but it’s good to keep the whole thing clean.

function getSphere(radius) {
    var geometry = new THREE.SphereGeometry(radius);
    var material = new THREE.MeshBasicMaterial({
        color: 0x00ff00
    });
    var sphere = new THREE.Mesh(geometry, material);
    return sphere;
}

var sphere = getSphere(1);
Copy the code

You then need to add this newly created object to the scene to make it visible.

`scene.add(sphere); `Copy the code

Let’s check the scene again, and you’ll see this black screen.

Learning-threejs-02 by Engin Arslan (@enginarslan) on CodePen

Whenever you add a scene object to three.js, the object is placed in the center of the scene with x, y, and Z coordinates 0, 0, 0, which is why you can’t see anything, which means that your camera and the sphere are in the same position. You should change the position of either of them to be able to start seeing things.

3 d coordinate

Let’s move the camera 20 units onto the Z axis. This is done by setting the * position property on the camera. 3D objects have position, rotation, and scale properties that allow you to rotate them around in 3D space.

`camera.position.z = 20; `Copy the code

You can move the camera and other axes.

camera.position.x = 0;
camera.position.y = 5;
camera.position.z = 20;
Copy the code

The camera is now higher up, but the sphere is no longer centered on the frame. You need to point the camera at it. To do this, you can call a method of the camera called lookAt. The lookAt method on the camera determines which points the camera is pointing at. Points in three dimensions represent vectors. So, you can look through a new Vector3 object, and this lookAt method can be viewed at 0, 0, 0 camera coordinates.

`camera.lookAt(new THREE.Vector3(0, 0, 0)); `Copy the code

The ball object now doesn’t look very round. The reason is that the Heregeometry function actually accepts two additional parameters, width and height subdivision, affecting the resolution surface. Increase these values and smooth surfaces appear. I set this value to 24 for width and height subdivision.

`var geo = new THREE.SphereGeometry(radius, 24, 24); `Copy the code

See the Pen learning-threejs-03 by Engin Arslan (@enginarslan) on CodePen.

Now you will create a simple flat geometry of the sphere. The PlaneGeometry function requires width and height parameters. In 3D, 2D objects do not have two sides by default, so you need to pass a side property to the material to render both sides of the flat geometry.

function getPlane(w, h) {
  var geo = new THREE.PlaneGeometry(w, h);
  var material = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    side: THREE.DoubleSide,
  });
  var mesh = new THREE.Mesh(geo, material);

  return mesh;
}
Copy the code

Now you can add scenes to this flat object. You’ll notice that the initial rotation of the plane geometry is parallel to the Y-axis, but you might need it to be horizontal because it acts as a ground plane, and one important thing you need to remember is the rotation in three.js. They use radians instead of degrees. A 90 degree rotation of a radian corresponds to π/ 2 mathematics.

var plane = getPlane(50, 50);
scene.add(plane);
plane.rotation.x = Math.PI/2;
Copy the code

When you create a ball object, it is positioned with its center point. If you want to move it to the ground, then you can just increase the number of current radii of its’ position ‘Y value. But it would not be a programmatic way of doing things. If you want a sphere to stay on the plane, whatever its radius value is, you should use the radius value to locate it.

`sphere.position.y = sphere.geometry.parameters.radius; `Copy the code

Watch the Pen Learning – Threejs-04 by Engin Arslan (@Enginarslan) on CodePen.

animation

You have almost completed the first part of this tutorial. But before we put it all together, I want to show you how to animate three.js. The requestAnimationFrame method used in the three.js animation executes a given function over and over on a window object. It’s a bit like a setInterval function, but it plots browser performance.

Create an Update function with render, scene, and camera to perform render methods there, rendering within the function. You will declare the requestAnimationFrame function internally and update the function by recursively calling back to the requestAnimationFrame function to show that the code is better than writing it out.

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

  requestAnimationFrame(function() {
    update(renderer, scene, camera);
  });
}
Copy the code

Everything looks the same at this point, but the core difference is that the RequestAnimationFrame function is an update function that makes the scene render about 60 frames per second. This means that if you are executing a statement inside the update function, the statement will be executed 60 times per second. Let’s add a zoom animation ball object. To select the sphere object from the update function, you can pass it as a parameter, but we’ll use a different technique. First, set a name property on the ball object and give it a name you like.

`sphere.name = 'sphere'; `Copy the code

Inside the update function, you can use its name and use the getObjectByName method to find the object on its parent object.

var sphere = scene.getObjectByName('sphere'); Sphere. Scale. X + = 0.01; Sphere. Scale. + z = 0.01;Copy the code

With this code, the X and Z axes of the ball are being scaled. Although our intention is not to create a scaled ball. We are setting up the update function so that we can use different animations later. Now that you’ve seen how it works, you can remove the zoom animation.

Learning-threejs-05 by Engin Arslan (@enginarslan) on CodePen

Part 2 – Adding reality to the scene

Currently, we are using MeshBasicMaterial, which shows a given color even when there are no lights in the scene, resulting in a very flat appearance. Although real world materials don’t work this way. The visible surface of the real world depends on how much light is reflected back to our eyes from the surface. Three.js has several different materials that provide a better approximation to the actual surface behavior, one of which is meshStandardMaterial. Meshstandardmaterial is a physics-based rendering material that helps you achieve realistic effects. This is a material in modern game engines such as Unreal or Unity, and is the industry standard for games and visual effects.

Let’s start using MeshStandardMaterial in our object and change its color to white.

var material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
});
Copy the code

You will get black again at this point. This is normal. Objects need light to be displayed in the scene. This is not necessary for meshbasicMaterial because it is a simple material that, under all conditions, displays a given color, but other materials require interaction with light to be visible. Let’s create a SpotLight function, and you’ll use this function to create two spotlights.

function getSpotLight(color, intensity) {
  var light = new THREE.SpotLight(color, intensity);

  return light;
}

var spotLight_01 = getSpotlight(0xffffff, 1);
scene.add(spotLight_01);
Copy the code

You might see something at this point. Light and camera positions are a little different for better framing and shading. Create an auxiliary light at the same time.

var spotLight_02 = getSpotlight(0xffffff, 1);
scene.add(spotLight_02);

camera.position.x = 0;
camera.position.y = 6;
camera.position.z = 6;

spotLight_01.position.x = 6;
spotLight_01.position.y = 8;
spotLight_01.position.z = -20;

spotLight_02.position.x = -12;
spotLight_02.position.y = 6;
spotLight_02.position.z = -10;
Copy the code

Having done this, you will have two lights in your scene, shining the sphere from two different locations. Lighting helps to understand the dimension of the scene. But at this point, things are still very unreal, because lighting is missing a key component: shadows!

Unfortunately, rendering shadows in three.js is not very simple. This is because shadow calculations are expensive and we need to enable shadow rendering in multiple places. First, you need to tell the renderer to start rendering shadows:

var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
Copy the code

Then, you need to tell the ray to cast a shadow. Do it inside the getSpotLight function.

`light.castShadow = true; `Copy the code

You should tell objects to cast or receive shadows. In this case, you will have the sphere cast shadows and use the plane to receive them.

mesh.castShadow = true;
mesh.receiveShadow = true;
Copy the code

With this set up, we should start looking at the shadows in the scene. First, they may not work very well. You can increase the resolution of shadows by setting the size of the shadow map.

light.shadow.mapSize.x = 4096;
light.shadow.mapSize.y = 4096;
Copy the code

MeshStandardMaterial has many properties, such as roughness and metalness, that control how surfaces interact with light. These properties range from 0 to 1, and they control the corresponding surface behavior. Increase the roughness value of the flat material to 1 to make the surface look more like rubber and blur it as a reflection.

// material adjustments
var planeMaterial = plane.material;
planeMaterial.roughness = 1;
Copy the code

However, we will not set it to 1 in this tutorial. You are free to experiment with the value, but you can set it to 0.65 for roughness and 0.75 for metals.

PlaneMaterial. Roughness = 0.65; PlaneMaterial. Metalness = 0.75;Copy the code

Although the scenario now looks more promising, it is still hard to call realistic. The truth is that it is difficult to establish phototropism in 3D without using texture mapping.

Learning-threejs-06 by Engin Arslan (@enginarslan) on CodePen

Texture mapping

A material map is a two-dimensional image that can be mapped on a material to provide surface detail. So far, you can only get solid colors on a surface, but with texture mapping, you can paint any image you want on a surface. Texture mapping is used not only to manipulate the color information of a surface, but also to manipulate other surface properties such as reflectance, brightness, roughness, etc.

Textures can be obtained from photo sources or drawn from drafts. For a texture that is useful in a 3D environment, it should be captured somehow. Images with reflections or shadows, or images that are too distorted, will not produce a good texture map. There are several websites dedicated to finding textures on the Web. One of them is Textures.com, which has an excellent archive. They have some free download options, but you need to sign up to do that. Another website for 3D materials is Megascans, with its high resolution, high quality environmental scanning and high quality product quality.

I used an example from a website called mb3d.co.uk. This site offers seamless, free textures. Seamless texture means a texture that can be repeated many times over the surface without encountering any discontinuity at the edges. This is the link to the texture file I used. I’ve reduced the image file size to 512px and used an online service called EZgif to convert the image file into a data URI to be part of the JavaScript code instead of loading it as a separate asset. (Tip: If you want to use the service, do not include tags in the data.)

Create a function that returns the data URI we generated so that we don’t have to put a large string in the middle of the code.

function getTexture() { var data = 'data:image/jpeg; base64,/... '; // paste your data URI inside the quotation marks. return data }Copy the code

Next, you need to load the texture and apply it to the plane. For this you will use three.js. After loading the texture, you will load the texture into the map properties of the desired material, using it as a color map on the surface.

var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load(getTexture());
planeMaterial.map = texture;
Copy the code

Now things look ugly because the texture of the surface is pixelated. The picture is stretched too far to cover the entire surface. What you can do is make the image repeat instead of zoom so it doesn’t get pixelated. To do this, you need to set the wrapS and wrapT property to three.repeatwrapping. Repeat wrapping and specifying repeat values. Since you will also do this for other types of maps (such as bump or rough maps), it is best to create a loop for it:

var repetition = 6;
var textures = ['map']// we will add 'bumpMap' and 'roughnessMap'
textures.forEach((mapName) => {
  planeMaterial[mapName].wrapS = THREE.RepeatWrapping;
  planeMaterial[mapName].wrapT = THREE.RepeatWrapping;
  planeMaterial[mapName].repeat.set(repetition, repetition);
});
Copy the code

This should look better. Because the texture you’re using is seamless, you won’t notice any disconnections that have recurring edges.

Loading a texture is actually an asynchronous operation. This means that your 3D scene is generated before the image file is loaded. But because you’re constantly rendering the scene using the requestAnimationFrame, it doesn’t cause any problems in this case. If you do not, you need to use callbacks or other asynchronous methods to manage the load order.

Learning-threejs-07 by Engin Arslan (@enginarslan) on CodePen

Other texture maps

As mentioned in the previous chapter, texture is used to define not only the color of a surface, but also its other properties. Another way of mapping is bump mapping. When used as a bump map, the brightness value of the texture simulates a height effect.

`planeMaterial.bumpMap = texture; `Copy the code

Bump maps should also use the same repeat configuration as color maps, so include it in the Textures array.

`var textures = ['map', 'bumpMap']; `Copy the code

With bump mapping, highlight the value of one pixel and the corresponding surface will look higher. But bump mapping does not change the surface, it merely manipulates the interaction of light with the surface to create the illusion of an uneven topology. The number of collisions seems a little too high right now. Bump maps work well when used. So, let’s change the bumpScale parameter to a lower value for a more subtle effect.

` planeMaterial. BumpScale = 0.01; `Copy the code

Note that this texture makes a big difference in appearance. Reflection is no longer perfect, but perfectly segmented, just as it is in real life. Another StandardMaterial available for standard materials is the roughness map. A texture map is a rough map that you can use to control the sharpness of the reflection using the brightness value of a given image.

planeMaterial.roughnessMap = texture;
var textures = ['map', 'bumpMap', 'roughnessMap'];
Copy the code

According to the three.js document, standard materials work best when used with an Environment map. The environment diagram simulates a distant environment, reflecting the reflective surfaces in the scene. This really helps when you’re trying to model the reflectivity of an object. In three environment maps. Js appears as a cube map. A cube map is a panoramic view of a scene mapped within a cube. A cube map consists of six separate images that correspond to each face of a cube. Because it would be too much work to load six pattern images in one online editor, you won’t actually use environment mapping in this example. But to make the sphere object more interesting, you can also add a roughness diagram. You will use this texture, but with a 320px by 320px data URI.

Create a new function called getMetalTexture.

function getMetalTexture() { var data = 'data:image/jpeg; base64,/... '; // paste your data URI inside the quotation marks. return data }Copy the code

Apply it to a sphere, such as bumpMap and roughnessMap:

var sphereMaterial = sphere.material; var metalTexture = textureLoader.load(getMetalTexture()); sphereMaterial.bumpMap = metalTexture; sphereMaterial.roughnessMap = metalTexture; SphereMaterial. BumpScale = 0.01; SphereMaterial. Roughness = 0.75; SphereMaterial. Metalness = 0.25;Copy the code

Learning-threejs-08 by Engin Arslan (@enginarslan) on CodePen

encapsulated

You’re almost done! Here, you just need to make a few small adjustments. You can see the final version of the scene file in This Pen.

Give the light a non-white color. Notice how to actually use CSS color values as strings to specify colors:

var spotLight_01 = getSpotlight('rgb(145, 200, 255)', 1);
var spotLight_02 = getSpotlight('rgb(255, 220, 180)', 1);
Copy the code

Then add some subtle random animations to the lights to breathe life into the scene. First, assign the name attributes to the lights so that you can find them in the Update function using the getObjectByName method.

spotLight_01.name = 'spotLight_01';
spotLight_02.name = 'spotLight_02';
Copy the code

Then, inside the update function, use math.random () to create the animation,

var spotLight_01 = scene.getObjectByName('spotLight_01'); Spotlight_01. intensity += (math.random () -0.5) * 0.15; spotLight_01.intensity = Math.abs(spotLight_01.intensity); var spotLight_02 = scene.getObjectByName('spotLight_02'); Spotlight_02.intensity += (math.random () -0.5) * 0.05; spotLight_02.intensity = Math.abs(spotLight_02.intensity);Copy the code

As an added bonus, I’ve included the OrbitControls script in the scene file. Three. Js camera, which means you can drag your mouse over the scene to interact with the camera! I also did this so that the scene would adjust as the window size changed. For convenience, I implemented this using an external script.

Learning-threejs-final by Engin Arslan (@enginarslan) on CodePen

Now, this scenario is a little closer to realism. Still, there are many missing pieces. The sphere is too dark due to the lack of reflection and surrounding light. The ground level is too flat for viewing. The contour of the sphere is perfect — it’s CG perfect. Lighting is not really as real as it could be; It does not decay (lose strength) with distance from the source. You should also add particle effects, camera animations, and post-processing filters if you want to. But it’s still a good example of the power of three.js. And the quality of graphics you can create in your browser. For more information about what can be achieved using this amazing library, you should check out my new course Lynda.com project!

Thank you for reading this far! Hope you enjoyed this article, and if you have any questions, feel free to contact me on Twitter @Inspiratory or on my website.