A journey towards Web3D

Getting started with WebGL for Web 3D using threejs library.

A journey towards Web3D

Let's dive deep into WebGL, the engine running on browser Javascript VM exuding all these beautiful works of art.

The Setup

It's painfully simple to setup threejs. Just import the library and you will have the global THREE object available for you to play with.

Add the CDN link of threejs to your project, at the <head> section of your document.

After working on this project for a week, I understand how close design and engineering are.

<!DOCTYPE html>
<html>
	<head>
		<meta charset=utf-8>
		<title>My first three.js app</title>
		<style>
			body { margin: 0; }
			canvas { width: 100%; height: 100% }
		</style>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script>
	</head>
	<body>
		
		<script>
			// Our Javascript will go here or link to your local .js file!
		</script>
	</body>
</html>

Abstraction before creation

Before getting on with creating an object, abstract the code into easy to test blocks till you learn. As you get better at this, organize your project in any way you see fit.

Remember, abstraction and organization is one of the most important aspect of a project; makes it easy to debug and scale.

/* Our JS section or file containing the animation */

// Define variables that we need globally. Not a good practice for large scale projects. Will become clear as we go into our functions.
var scene, renderer, camera, render, d_light, mouse_light, mouse;

// The main functions that contains everything we need
init();
animate();

function init() {
    /* Contains the scene, meshes, materials and light setup section */
}

function animate() {
    /* Contains the animation loop where the scene is rendered and stuff animated */

}

Creating the world stage

In order to see something, we need to render a scene. Let's create these two using the THREEjs toolkit.

/* Let's create our scene and renderer within init() */

function init() {
 
    // Create a renderer which will contain all scenes. The renderer is a place which will be added to your HTML. Usually lives within <canvas> HTML element. There are many kinds of renderers, we use WebGLRenderer.
    renderer = new THREE.WebGLRenderer();
    renderer.physicallyCorrectLights = true; // This will make things difficult, but beautiful nonetheless. Lights need high power in this mode.
    renderer.setSize(window.innerWidth, window.innerHeight); // Set size if you like it to take full window
    renderer.setPixelRatio( window.devicePixelRatio ); // Helps with rendering on mobile and desktop
    document.body.appendChild(renderer.domElement); // Append this to HTML. You can .appendChild to any parent div or canvas! In our case, it's full screen. 
    
    // Create a scene in our renderer.
    scene = new THREE.Scene();

}

At this stage you should be able to see a pitch black canvas. If not, follow the instructions from the official documentation.

Lights, Camera, Action!

It's pitch black. Several things to be done before we see anything.

Because, we need a camera to observe our scene. We still wouldn't see anything.

Because, we need lights to light up the scene and things in it.

We need a cube so the light reflects it, and we can see it. We still wouldn't see anything unless we add "renderer.render(scene, camera);" in our init() function. this will show one rendered static frame.

Creating a light

Read more about Spotlights here. THREE provides many kinds of lights.

// Create a function for your light. The actual method is pretty simple but keeping our common elements in a functions keeps the code clean.

function createSpotLight(color, x, y, z, power) {
    _light = new THREE.SpotLight(color);
    _light.intensity = 2;
    _light.power = power;
    _light.penumbra = 0.8;
    _light.decay = 2;
    _light.position.set(x, y, z);
    _light.castShadow = true;
    return _light;
}

// Within your init() function, just after your scene add a light.
function init() {
    // ... after other code ... 
    
    // Add your spotlight using the function we just created.
    var s_light = createSpotLight(0xffffff, -15, 15, -15, 800);
    scene.add( s_light );
    
    // Why not add an ambient light. Just to make sure we can see things.
    var light = new THREE.AmbientLight( 0x404040, 2 ); // soft white light
  	scene.add( light );
    
}

Creating your cube

Read more about creating your first mesh here.

// Within your init() function, just after your scene add a light.
function init() {
    // ... after lights
    var geometry = new THREE.BoxGeometry( 5, 5, 5 );
    var material = new THREE.MeshStandardMaterial( { color: 0x00ff00 } );
    var cube = new THREE.Mesh( geometry, material );
    scene.add( cube );
}

Creating the camera

To see things, we need a camera. Let's add it and place it just slightly away from the center point.

// Within your init() function, just after your scene add a light.
function init() {
    // ... after the cube
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 500);
  camera.position.set(0, 5, 20); // Place it slightly away on the y and z axis.
  camera.lookAt(scene.position); // Make it look at the center of the scene :)
}

This is what it will look like

The final code, along with the completed animate() function. All it does is, call the renderer function to render the scene frame, and then repeats it on every frame because of requestAnimationFrame();

var scene, renderer, camera, render, d_light, material, mouse_light, mouse, raycaster;

init();
animate();

function init() {
  // Create a renderer which will contain all scenes. The renderer is a place which will be added to your HTML. Usually lives within <canvas> HTML element. There are many kinds of renderers, we use WebGLRenderer.
    renderer = new THREE.WebGLRenderer();
    renderer.physicallyCorrectLights = true; // This will make things difficult, but beautiful nonetheless. Lights need high power in this mode.
    renderer.setSize(window.innerWidth, window.innerHeight); // Set size if you like it to take full window
    renderer.setPixelRatio( window.devicePixelRatio ); // Helps with rendering on mobile and desktop
    document.body.appendChild(renderer.domElement); // Append this to HTML. You can .appendChild to any parent div or canvas! In our case, it's full screen. 
    
    // Create a scene in our renderer.
    scene = new THREE.Scene();

// Add your spotlight using the function we just created.
    var s_light = createSpotLight(0xffffff, -15, 15, -15, 800);
    scene.add( s_light );
    
    // Why not add an ambient light. Just to make sure we can see things.
    var light = new THREE.AmbientLight( 0x404040, 2 ); // soft white light
  	scene.add( light );

 var geometry = new THREE.BoxGeometry( 5, 5, 5 );
    var material = new THREE.MeshStandardMaterial( { color: 0x00ff00 } );
    var cube = new THREE.Mesh( geometry, material );
    scene.add( cube );
  
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 500);
  camera.position.set(0, 5, 20); // Place it slightly away on the y and z axis.
  camera.lookAt(scene.position); // Make it look at the center of the scene :)

}

function animate() {
    // Reccursive function to request a new frame, the magic actually happens within the render function!
    requestAnimationFrame(animate);
    // Render the scene again, as each frame is animated.
    renderer.render(scene, camera);
};

function createSpotLight(color, x, y, z, power) {
    _light = new THREE.SpotLight(color);
    _light.intensity = 2;
    _light.power = power;
    _light.penumbra = 0.8;
    _light.decay = 2;
    _light.position.set(x, y, z);
    _light.castShadow = true;
    return _light;
}

In the below sample, I have added one more light just to light the scene up and enlarged the cube.

See the Pen ThreeJS Simple View by Varun D (@varun_acn) on CodePen.

Feel free to copy the code and experiment!