WebGL With Three.js – Lesson 1

WebGL With Three.js – Lesson 1

0 62845
WebGL With Three.js - Lesson 1
WebGL With Three.js - Lesson 1

WebGL With Three.js – Lesson 1

As we have promised – it is time to start our new series of articles devoted to the WebGL. This is our first lesson, where we consider the main basic functions: creating a scene, camera, renderer, controls (OrbitControls). We will also create the simplest directional light, add a dozen objects (of different geometry) with shadows. In order to make things went faster, we decided to take one of the most popular webgl frameworks – three.js. Why use Three.js? Besides the fact that it is open source JavaScript framework, it is also the most rapidly growing and discussed engine. Here already implemented really many possibilities, from low-level work with points and vectors, to work with ready scenes, shaders and even stereoscopic effects.

Live Demo

HTML

We could omit this step, but, habitually, we do it at every lesson. So here’s the html structure of our lesson:

<!DOCTYPE html>
<html lang="en" >
    <head>
        <meta charset="utf-8" />
        <meta name="author" content="Script Tutorials" />
        <title>WebGL With Three.js - Lesson 1 | Script Tutorials</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <link href="css/main.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <script src="js/three.min.js"></script>
        <script src="js/THREEx.WindowResize.js"></script>
        <script src="js/OrbitControls.js"></script>
        <script src="js/stats.min.js"></script>
        <script src="js/script.js"></script>
    </body>
</html>

In this code, we just connect all the libraries that we are going to use today.

Javascript

I hope that you have already seen our demo and imagine what the basic elements it is composed, we will consider the creation of each item step-by-step.

Skeleton

Abstractly, our scene looks like this:

var lesson1 = {
    scene: null,
    camera: null,
    renderer: null,
    container: null,
    controls: null,
    clock: null,
    stats: null,
    init: function() { // Initialization
    }
};
// Animate the scene
function animate() {
    requestAnimationFrame(animate);
    render();
    update();
}
// Update controls and stats
function update() {
    lesson1.controls.update(lesson1.clock.getDelta());
    lesson1.stats.update();
}
// Render the scene
function render() {
    if (lesson1.renderer) {
        lesson1.renderer.render(lesson1.scene, lesson1.camera);
    }
}
// Initialize lesson on page load
function initializeLesson() {
    lesson1.init();
    animate();
}
if (window.addEventListener)
    window.addEventListener('load', initializeLesson, false);
else if (window.attachEvent)
    window.attachEvent('onload', initializeLesson);
else window.onload = initializeLesson;

This is common structure of application built with three.js. Almost everything will be created in the ‘init’ function.

Creation of scene, camera and renderer

They are the main elements of our scene, they following code creates an empty scene with perspective camera and renderer with enabled shadow map:

    // create main scene
    this.scene = new THREE.Scene();
    var SCREEN_WIDTH = window.innerWidth,
        SCREEN_HEIGHT = window.innerHeight;
    // prepare camera
    var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 1, FAR = 10000;
    this.camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
    this.scene.add(this.camera);
    this.camera.position.set(-1000, 1000, 0);
    this.camera.lookAt(new THREE.Vector3(0,0,0));
    // prepare renderer
    this.renderer = new THREE.WebGLRenderer({antialias:true, alpha: false});
    this.renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    this.renderer.setClearColor(0xffffff);
    this.renderer.shadowMapEnabled = true;
    this.renderer.shadowMapSoft = true;
    // prepare container
    this.container = document.createElement('div');
    document.body.appendChild(this.container);
    this.container.appendChild(this.renderer.domElement);
    // events
    THREEx.WindowResize(this.renderer, this.camera);

We set the camera at an angle of 45 degrees, set the full screen size and white cleanup color for WebGLRenderer, added our scene in the HTML document, and also connected THREEx.WindowResize to control the renderer and the camera when resizing the window (of browser).

OrbitControls and Stats

In order to be able to somehow control the camera – three.js gives us the possibility to use ready-made controls. One of these is OrbitControls, which provides the ability to rotate the scene around its axis. Also it will be helpful to see the statistics (FPS) of our scene – a very small class Stats will help us (stats.min.js).

    // prepare controls (OrbitControls)
    this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
    this.controls.target = new THREE.Vector3(0, 0, 0);
    // prepare clock
    this.clock = new THREE.Clock();
    // prepare stats
    this.stats = new Stats();
    this.stats.domElement.style.position = 'absolute';
    this.stats.domElement.style.bottom = '0px';
    this.stats.domElement.style.zIndex = 10;
    this.container.appendChild( this.stats.domElement );

Thus we have prepared four materials

Creation of light and ground

Light is one of the important elements of the scene, in our first tutorial we will create the most simple – directional light, since we are going to add basic shadows:

    // add directional light
    var dLight = new THREE.DirectionalLight(0xffffff);
    dLight.position.set(1, 1000, 1);
    dLight.castShadow = true;
    dLight.shadowCameraVisible = true;
    dLight.shadowDarkness = 0.2;
    dLight.shadowMapWidth = dLight.shadowMapHeight = 1000;
    this.scene.add(dLight);
    // add particle of light
    particleLight = new THREE.Mesh( new THREE.SphereGeometry(10, 10, 10), new THREE.MeshBasicMaterial({ color: 0x44ff44 }));
    particleLight.position = dLight.position;
    this.scene.add(particleLight);
    // add simple ground
    var groundGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);
    ground = new THREE.Mesh(groundGeometry, new THREE.MeshLambertMaterial({
        color: this.getRandColor()
    }));
    ground.position.y = 0;
    ground.rotation.x = - Math.PI / 2;
    ground.receiveShadow = true;
    this.scene.add(ground);

When we create the light, we enabled two parameters (castShadow and shadowCameraVisible). This will allow us to visually see where is the light and we can understand the process of creating (and boundaries) of shadows.

You may also notice that immediately after the light, we have added a spherical object – it is for you, to be able to visually know in what position is our directional light source. In order to create the earth – we used the flat element (PlaneGeometry), to prepare it to receiveshadows – we set the ‘receiveShadow’ param to ‘true’.

Colors

Soon we will add additional objects in the scene. But, I prepared one additional function to generate a different color for our items. This function will randomly return a color from a predefined list of pastel colors.

var colors = [
    0xFF62B0,
    0x9A03FE,
    0x62D0FF,
    0x48FB0D,
    0xDFA800,
    0xC27E3A,
    0x990099,
    0x9669FE,
    0x23819C,
    0x01F33E,
    0xB6BA18,
    0xFF800D,
    0xB96F6F,
    0x4A9586
];
getRandColor: function() {
    return colors[Math.floor(Math.random() * colors.length)];
}

CircleGeometry / CubeGeometry / CylinderGeometry and ExtrudeGeometry

Geometry is an object to hold all necessary data to describe any 3D model (points, vertices, faces etc). All the listed geometries are used to create such objects as: circle (plain), cube and cylinder. Extrude Geometry is used to extrude geometry from a path shape. We are going to extrude triangle:

    // add circle shape
    var circle = new THREE.Mesh(new THREE.CircleGeometry(70, 50), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    circle.rotation.x = - Math.PI / 2;
    circle.rotation.y = - Math.PI / 3;
    circle.rotation.z = Math.PI / 3;
    circle.position.x = -300;
    circle.position.y = 150;
    circle.position.z = -300;
    circle.castShadow = circle.receiveShadow = true;
    this.scene.add(circle);
    // add cube shape
    var cube = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    cube.rotation.x = cube.rotation.z = Math.PI * 0.1;
    cube.position.x = -300;
    cube.position.y = 150;
    cube.position.z = -100;
    cube.castShadow = cube.receiveShadow = true;
    this.scene.add(cube);
    // add cylinder shape
    var cube = new THREE.Mesh(new THREE.CylinderGeometry(60, 80, 90, 32), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    cube.rotation.x = cube.rotation.z = Math.PI * 0.1;
    cube.position.x = -300;
    cube.position.y = 150;
    cube.position.z = 100;
    cube.castShadow = cube.receiveShadow = true;
    this.scene.add(cube);
    // add extrude geometry shape
    var extrudeSettings = {
        amount: 10,
        steps: 10,
        bevelSegments: 10,
        bevelSize: 10,
        bevelThickness: 10
    };
    var triangleShape = new THREE.Shape();
    triangleShape.moveTo(  0, -50 );
    triangleShape.lineTo(  -50, 50 );
    triangleShape.lineTo( 50, 50 );
    triangleShape.lineTo(  0, -50 );
    var extrude = new THREE.Mesh(new THREE.ExtrudeGeometry(triangleShape, extrudeSettings), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    extrude.rotation.y = Math.PI / 2;
    extrude.position.x = -300;
    extrude.position.y = 150;
    extrude.position.z = 300;
    extrude.castShadow = extrude.receiveShadow = true;
    this.scene.add(extrude);

Once the geometry is created, we can create a Mesh on the basis of this geometry. When you create a Mesh, we specify the material (as the second argument).

IcosahedronGeometry / OctahedronGeometry / RingGeometry and ShapeGeometry

Now we will create the next four elements: icosahedron, octahedron, ring, and the object by a custom path (shape) using ShapeGeometry:

    // add icosahedron shape
    var icosahedron = new THREE.Mesh(new THREE.IcosahedronGeometry(70), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    icosahedron.position.x = -100;
    icosahedron.position.y = 150;
    icosahedron.position.z = -300;
    icosahedron.castShadow = icosahedron.receiveShadow = true;
    this.scene.add(icosahedron);
    // add octahedron shape
    var octahedron = new THREE.Mesh(new THREE.OctahedronGeometry(70), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    octahedron.position.x = -100;
    octahedron.position.y = 150;
    octahedron.position.z = -100;
    octahedron.castShadow = octahedron.receiveShadow = true;
    this.scene.add(octahedron);
    // add ring shape
    var ring = new THREE.Mesh(new THREE.RingGeometry(30, 70, 32), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    ring.rotation.y = -Math.PI / 2;
    ring.position.x = -100;
    ring.position.y = 150;
    ring.position.z = 100;
    ring.castShadow = ring.receiveShadow = true;
    this.scene.add(ring);
    // add shape geometry shape
    var shapeG = new THREE.Mesh(new THREE.ShapeGeometry(triangleShape), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    shapeG.rotation.y = -Math.PI / 2;
    shapeG.position.x = -100;
    shapeG.position.y = 150;
    shapeG.position.z = 300;
    shapeG.castShadow = shapeG.receiveShadow = true;
    this.scene.add(shapeG);

SphereGeometry / TetrahedronGeometry / TorusGeometry / TorusKnotGeometry and TubeGeometry

Finally, we create a sphere, tetrahedron, torus, torus knot and tube:

    // add sphere shape
    var sphere = new THREE.Mesh(new THREE.SphereGeometry(70, 32, 32), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    sphere.rotation.y = -Math.PI / 2;
    sphere.position.x = 100;
    sphere.position.y = 150;
    sphere.position.z = -300;
    sphere.castShadow = sphere.receiveShadow = true;
    this.scene.add(sphere);
    // add tetrahedron shape
    var tetrahedron = new THREE.Mesh(new THREE.TetrahedronGeometry(70), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    tetrahedron.position.x = 100;
    tetrahedron.position.y = 150;
    tetrahedron.position.z = -100;
    tetrahedron.castShadow = tetrahedron.receiveShadow = true;
    this.scene.add(tetrahedron);
    // add torus shape
    var torus = new THREE.Mesh(new THREE.TorusGeometry(70, 20, 16, 100), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    torus.rotation.y = -Math.PI / 2;
    torus.position.x = 100;
    torus.position.y = 150;
    torus.position.z = 100;
    torus.castShadow = torus.receiveShadow = true;
    this.scene.add(torus);
    // add torus knot shape
    var torusK = new THREE.Mesh(new THREE.TorusKnotGeometry(70, 20, 16, 100), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    torusK.rotation.y = -Math.PI / 2;
    torusK.position.x = 100;
    torusK.position.y = 150;
    torusK.position.z = 300;
    torusK.castShadow = torusK.receiveShadow = true;
    this.scene.add(torusK);
    // add tube shape
    var points = [];
    for (var i = 0; i < 10; i++) {
        var randomX = -100 + Math.round(Math.random() * 200);
        var randomY = -100 + Math.round(Math.random() * 200);
        var randomZ = -100 + Math.round(Math.random() * 200);
        points.push(new THREE.Vector3(randomX, randomY, randomZ));
    }
    var tube = new THREE.Mesh(new THREE.TubeGeometry(new THREE.SplineCurve3(points), 64, 20), new THREE.MeshLambertMaterial({ color: this.getRandColor() }));
    tube.rotation.y = -Math.PI / 2;
    tube.position.x = 0;
    tube.position.y = 500;
    tube.position.z = 0;
    tube.castShadow = tube.receiveShadow = true;
    this.scene.add(tube);

Pay attention to the process of creating the pipe. TubeGeometry allows us to build a cylindrical object by the array of points.

Smooth movement of the light

In order to smoothly move the light, we only need to add the following code in the ‘update’ function:

    // smoothly move the particleLight
    var timer = Date.now() * 0.000025;
    particleLight.position.x = Math.sin(timer * 5) * 300;
    particleLight.position.z = Math.cos(timer * 5) * 300;

Live Demo

[sociallocker]

download in package

[/sociallocker]


Conclusion

I think that we done enough for our first lesson. In addition to creating a scene, we also saw how to create a shadow, and even made a review of almost all possible geometries. See you at the next lesson!

SIMILAR ARTICLES

Understanding Closures

0 24640

NO COMMENTS

Leave a Reply