For the past few years there is a boom in new kind of websites that incorporate 3D elements right into your browser with flawless rendering frame rates. No, I'm not talking about CSS 3D transformations, I'm talking about 3D graphics created in software like Blender or Maya embedded in your website. This is possible due to WebGL, a Javascript API to OpenGL which lets us run website elements directly on the client's Graphics Processing Unit. I'm not specifically refering to graphic cards because this will also run on the Graphics embedding on a CPU.
But coding directly in WebGL is nothing less than doing self-harm. That's where Three JS comes into play. This Javascript library takes care of all the painful elements of WebGL and provides an abstracted syntactic sugar for us to work with. We will see in later posts, how we can make custom functions in WebGL and append that to Three js to extend its functionalities.
Before we begin, take a look at some of these amazing websites built using Three.js
You can see a lot more of these real world usage of Three js over at Three JS Website
Initial Setup
There are three ways to embed Three.js to your website. (Coincidence)
Option 1 - Cloning Three js Github Repository
Before doing this, I recommend you to create a folder structure for all your projects. Otherwise, it will be hard for you to find something in the future. Once that is done. Simply clone the repository to your desired folder.
git clone https://github.com/mrdoob/three.js.git three
Option 2 - Three js CDN
This would be the easiest way to start, just embed this script to your webpage
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r126/three.min.js" integrity="sha512-n8IpKWzDnBOcBhRlHirMZOUvEq2bLRMuJGjuVqbzUJwtTsgwOgK5aS0c1JA647XWYfqvXve8k3PtZdzpipFjgg==" crossorigin="anonymous"></script>
But as you will see later, the core Three js library only include simple shapes, textures and materials. If you want to add an external model or do something that is not included in this js file then you need to import those specific files too. And in a real world project, by using this method you will end up importing way too much files that you won't even fully utilize.
Option 3 - Three js Project Starter Kit
My Three js sensei, Bruno Simon, provided us a starter pack that uses webpack to assemble all packages. I took that as base and created a three js starter kit for myself so that I don't have to write the boilerplate code everytime. You can use this kit by using the following git command.
git clone https://github.com/Idiomatic-Programmers/threejs_starter_kit.git <YOUR_PROJECT_NAME>
Don't forget to replace <YOUR_PROJECT_NAME> with something meaningful to your project.
If you haven't already, you need to install Node.js and NPM for this to work. Checkout Node JS Documentation for more information.
Setting up your first project
For demonstration purpose, I am going to name our project "Hello World"
git clone https://github.com/Idiomatic-Programmers/threejs_starter_kit.git hello_world
This will create a folder called hello_world inside which you will find all the starter code already written.
FYI - If you want to learn and master Three js and can afford to buy a $95 course, checkout Bruno Simon's Three JS Journey, it pretty great, very in-depth, and easy to follow along. Plus, you get access to a private Discord server where you can meet thousands of amazing developers and you can clear your doubts directly from Bruno. But if you can't afford it, then follow along. I will try my best to share the knowledge I gained from this course.
Open the hello_world folder in your favourite code editor and navigate to script.js file located in src folder. Now select everything and just delete it because we will be coding just that.
Before we continue, don't forget to install the packages we need by doing.
npm install
The beauty of webpack is that we can do most of our things using Javascript without ever touching the HTML file. I'm assuming you have deleted everything from script.js file.
So in the very first line, import our css file
import './style.css'
Then obviously we need to import our three js library.
import * as THREE from 'three'
Before we write our next line of JS, let's understand the HTML structure of our project. If you open index.html in the src folder, you will find there's standard HTML boilerplate with a canvas element in the body. This canvas will render everything we do in three js, which means we must have a reference to this canvas element in out js file.
const webGLCanvas = document.getElementById('webgl')
With these three lines in our js file, we can officially start writing our three js code. But first, let's learn some theory.
Fundamental Elements in Three JS
Every three js project will contain these three elements.
a. Scene
In the genre of Film Making, a scene is a place or setting where the action takes place. A scene holds everything a movie needs. If you take an example from this amazing Steven Spielberg movie Catch me if you can.
This particular scene contains everything that is required to move the story forward.
Much like that, a scene in Three js will hold everything that is required to tell our story, cars, trains, builings, or even wierd 3D figures, everything that you need to show will be included in a scene.
b. Camera
Again taking the example of the aforementioned movie, a camera will only show a particular section of a scene that is required to tell the story.
In Three JS, there are two main types of camera,
-
A Perspective camera in three js is a Frustum Shape (3D Trapezoid) and the user will only be able to see what's inside this frustum. You can adjust the size of this Frustum to get the best results. And everything in the shape is Perspective Projected meaning farther objects appear smaller than nearer objects.
This camera simulates real world so we will be using this camera a lot in coming posts.
-
An Orthographic camera in Three js is a Box instead of a Frusturm like in Perspective Camera and it uses an Orthogonal Projection which means the size of the object remains same regardless of their distance from the camera. These type of cameras are useful to design maps of the environment.
There are other type of cameras in three js but we won't be using them. But if you would still like to learn about them here's a list of all cameras linked to their documentation.
c. Renderer
A renderer is an element that displays whatever the camera sees onto a canvas. It is a screen on which the 3D scene is projected. And that is the only thing we need to know about this.
Create your first 3D environment
Coming back to script.js file, we have three lines which imports everything we need.
Next we will proceed to define the three fundamental elements in three js.
const scene = new THREE.Scene()
This will initiate an empty scene on our project, next we need a camera to see what's in a scene. We will be using Perspective Camera which takes in four main parameters.
- Field of View (fov): This specifies how wide your camera can see, in degrees.
- Aspect Ratio (aspect): As the name suggests, this specifies the aspect ratio of your scene. is it 16:9, 16:10, 4:3 etc. If you would like to find out more about aspect ratios, check this article
- Near Distance (near) and Far Distance (far): These two parameters will specify the boundaries of the Frustum. You can only see those objects that are between the near and far values of the camera.
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
We will define an object that will contain the height and width of the renderer. Everything the camera sees in 3D will be projected to this 2D plane. In our case, the renderer will take up our entire screen.
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000)
Notice for aspect ratio, we have set the ratio between renderer width and height. This is because we have no control over the aspect ratio of user device. If you are 100% sure that only people with devices that has a 16 by 9 ratio, then you simply write 16 / 9 instead of sizes.width / sizes.height and you can experiment with the near and far values that work best for you. You can copy what I have written it doesn't matter, atleast for what we are doing.
Finally, we need a renderer. Three js provides many renderers, but we are only interested in one of them called WebGLRenderer which takes an object as a parameter that has many attributes that you can look at in the documentation
For now, we will be passing one attribute called canvas which you guessed it, takes the reference to our canvas.
const renderer = new THREE.WebGLRenderer({
canvas: webGLCanvas
})
We also need to set the size of this renderer.
renderer.setSize(sizes.width, sizes.height)
Alright, that's it. Let run our code. Run this code in your terminal
npm run dev
Wait, what? We only see a blank black screen? Let me check it there is any error in Inspect Element Console.
And my right click isn't working? What's happening?
I lied, we need to do a bit more work.
We need to add our camera into the scene.
scene.add(camera)
and actually render the scene. You can do this by calling the render method of renderer and pass the scene and camera into it.
renderer.render(scene, camera)
Now save and refresh the page. Still a black screen? It's completely normal. Remember the scene currently contains a camera and we can only see what the camera see and there's nothing else to see.
If you don't like this black screen then you can change the background colour of the scene like this.
scene.background = new THREE.Color("#f7f7f7")
You cannot directly pass the hex code, you need to wrap it around a THREE.Color object.
If you refresh your page now, you can see the background has been changed to nice light greyish colour.
Now let's create a Sphere.
You need three things to create any object in Three js.
- Geometry: This holds a general shape of the object
- Material: This hold information about the texture, color, opacity, etc.
- Mesh: This combines the Geometry and Material into an object.
Three js provides Geometry for almost all basic shapes like sphere, box, torus, etc. For making a sphere, there is something called SphereBufferGeometry that takes three parameters.
- Radius: The radius of the sphere
- Width Segments
- Height Segments
Don't worry about Width and Height segments now, I will show you once we built our Sphere.
const sphereGeometry = new THREE.SphereBufferGeometry(1, 32, 32)
We have a radius of 1 units and 32 Width and Height segments.
In three JS, it'd better to decide what a unit is. For me 1 unit is 1 meter.
Then we need a Material, we will see different type of materials provide by Three JS and also you can create your own material using a laguage called GLSL (OpenGL Shading Language). But for now, we'll use MeshStandardMaterial
const sphereMaterial = new THREE.MeshStandardMaterial()
sphereMaterial.color = new THREE.Color("#ffffff")
We can set the colour of this material by updating the color attribute. Also, remember we need to pass color as a THREE.Color object.
Finally, we will create a mesh using THREE.Mesh and pass in our geometry and material.
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
And don't forget to add the sphere to scene.
scene.add(sphere)
One last thing, you should always render the scene at the end of the script, so move this line to the end.
renderer.render(scene, camera)
Nothing? Don't worry. Right now your camera is inside the Sphere.
Three js calulates the distance between a vertex and the camera, if that distance is less that near or far parameters of the camera then that object will not be shown. In this case, the distance between Sphere and Camera is 0 and since it is smaller than the near parameter which is 0.1, the sphere will not be shown.
We can move back our camera by moving it few units towards positive z direction.
camera.position.z = 5
Now you can see a Black circle. But why is it black? We did set the color as White (#ffffff).
Some of you might have guessed it. We have no light
Three js actually has this amazing method called letThereBeLight(), which automatically adds all the light we want.
Just Kidding, life isn't that simple, let us actually add some light to our scene.
We will see all kind of light throughout this series, but for now we will look at a light called Directional Light, which gives parallel rays of light just like our Sun.
The DirectionalLight takes two parameters, the color of the light, which will be a Hex code and the intensity of the light.
const directionalLight = new THREE.DirectionalLight("#ffffff", 0.5)
scene.add(directionalLight)
Always remember to add your objects to scene.
We can see some light at the top, but actually the light is inside the sphere. Let's move it using position method of the object.
For any object in three js, you can move it using one of two ways
- Changing the x, y, z positions one by one
directionalLight.position.x = 30.25
directionalLight.position.y = 30
directionalLight.position.z = 30
- Using set() method:
directionalLight.position.set(30.25, 30, 30)
You can use whatever method, which you feel is right.
This is better. But there is very harsh shadow. Let's introduce a new kind of light to fix this issue called Ambient Light.
Ambient Light adds the same amount of light everywhere in the scene, and it does not cast any shadow. Using AmbientLight is similar to Directional light, the only difference is that you don't have to move it around.
const ambientLight = new THREE.AmbientLight("#ffffff", 0.5)
scene.add(ambientLight)
And there it is, a beautiful Sphere suspended in our scene.
Resizing
If we resize the window, you'll notice that the canvas is not changing it's size. We need to do bit of work to fix that.
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
})
We have created a resize event listener and just updated the aspect ratio and renderer size. We also need to tell the camera to recalculate its projections, which is done by the updateProjectionMatrix() method.
Animation
It wouldn't be fun if we don't animate our sphere. Let's create a function called animate() and inside that call the renderer.render method. Make sure that you are writing this code at the end of your script.
const animate = () => {
renderer.render(scene, camera)
}
animate()
This is not enough, We need to tell our browser that we wish to animate our scene. Which can be done by calling the window.requestAnimationFrame method and pass it reference to our animate function.
const animate = () => {
window.requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()
Animating our scene isn't that simple as calling requestAnimationFrame. We need to figure our some math for that.
For our scene, I'm thinking to animate Sphere up and down in a smooth motion. This can be done by using the sine function in trigonometry. But before that, we need some way to tell the passage of time. Conveniently, Three js provides a class for that called Clock.
const clock = new THREE.Clock()
We can get the elapsed time by calling the getElapsedTime method of the clock object.
Write this code in our animate function.
const elapsedTime = clock.getElapsedTime()
sphere.position.y = Math.sin(elapsedTime * 150 * Math.PI / 180) * 0.125
Save this and view the page, your sphere will be moving Up and Down smoothly.
You can play with the math and change the axis, and see what happens.
Before we leave, take a look at what we will be building throughout this series.
You can check out the live demo website
That is all for this post, in the next post we will explore some few more shapes, materials, cameras and lights. If you have any query be sure to comment below, we will get back at you as soon as possible,