WebGL With Three.js – Lesson 8

Image Tutorials

Our lessons on webgl are continuing. Today we start another topic where we will be working with sprites and texture animation. If you do not know, sprites are simply images, that could be attached to objects. These sprite images are always orthogonal to our camera. Three.js provides a special material for the sprites – THREE.SpriteMaterial, as well as a special object – THREE.Sprite. Also in this tutorial we will learn how to play the animation using sprites.

Live Demo

Preparation

As usual, we have to prepare a small index.html file with necessary html markup to work on:

index.html

01 <!DOCTYPE html>
02 <html lang="en" >
03   <head>
04     <meta charset="utf-8" />
05     <meta name="author" content="Script Tutorials" />
06     <title>WebGL With Three.js - Lesson 8 - Sprites and Texture Animation | Script Tutorials</title>
07     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
08     <link href="css/main.css" rel="stylesheet" type="text/css" />
09   </head>
10   <body>
11     <script src="js/three.min.js"></script>
12     <script src="js/THREEx.WindowResize.js"></script>
13     <script src="js/OrbitControls.js"></script>
14     <script src="js/stats.min.js"></script>
15     <script src="js/script.js"></script>
16     <div style="position: absolute; top: 10px; left: 20px; text-align: center;"><a href="https://www.script-tutorials.com/webgl-with-three-js-lesson-8/" target="_blank">"WebGL With Three.js - Lesson 8"</a> is prepared by <a href="https://www.script-tutorials.com/" target="_blank">Script Tutorials</a> team.<br>Drag to spin</div>
17   </body>
18 </html>

In this code, we connect the main Three.js library and few additional utilites: WindowResize event handler, Orbit controls and Stats

Preparation of the main webgl scene

Now let’s create the main ‘script.js’ file and place the code shown below:

script.js

001 var lesson8 = {
002   scene: null,
003   camera: null,
004   renderer: null,
005   container: null,
006   controls: null,
007   clock: null,
008   stats: null,
009   anim1: null, anim2: null// animations
010   animReady1: false, animReady2: false,
011   init: function() { // initialization
012     // create main scene
013     this.scene = new THREE.Scene();
014     this.scene.fog = new THREE.FogExp2(0xcce0ff, 0.0003);
015     var SCREEN_WIDTH = window.innerWidth,
016         SCREEN_HEIGHT = window.innerHeight;
017     // prepare perspective camera
018     var VIEW_ANGLE = 60, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 1, FAR = 1000;
019     this.camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
020     this.scene.add(this.camera);
021     this.camera.position.set(100, 0, 0);
022     this.camera.lookAt(new THREE.Vector3(0,0,0));
023     // prepare webgl renderer
024     this.renderer = new THREE.WebGLRenderer({ antialias:true });
025     this.renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
026     this.renderer.setClearColor(this.scene.fog.color);
027     this.renderer.shadowMapEnabled = true;
028     this.renderer.shadowMapSoft = true;
029     // prepare container
030     this.container = document.createElement('div');
031     document.body.appendChild(this.container);
032     this.container.appendChild(this.renderer.domElement);
033     // events
034     THREEx.WindowResize(this.renderer, this.camera);
035     // prepare controls (OrbitControls)
036     this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
037     this.controls.target = new THREE.Vector3(0, 0, 0);
038     this.controls.maxDistance = 3000;
039     // prepare clock
040     this.clock = new THREE.Clock();
041     // prepare stats
042     this.stats = new Stats();
043     this.stats.domElement.style.position = 'absolute';
044     this.stats.domElement.style.left = '50px';
045     this.stats.domElement.style.bottom = '50px';
046     this.stats.domElement.style.zIndex = 1;
047     this.container.appendChild( this.stats.domElement );
048     // add lights
049     this.scene.add( new THREE.AmbientLight(0x606060) );
050     var dirLight = new THREE.DirectionalLight(0xffffff);
051     dirLight.position.set(200, 200, 1000).normalize();
052     this.camera.add(dirLight);
053     this.camera.add(dirLight.target);
054     // display skybox
055     this.addSkybox();
056     // display animated objects
057     this.addAnimatedObjects();
058   },
059   addSkybox: function() {
060       // define path and box sides images
061       var path = 'skybox/';
062       var sides = [ path + 'sbox_px.jpg', path + 'sbox_nx.jpg', path + 'sbox_py.jpg', path + 'sbox_ny.jpg', path + 'sbox_pz.jpg', path + 'sbox_nz.jpg' ];
063       // load images
064       var scCube = THREE.ImageUtils.loadTextureCube(sides);
065       scCube.format = THREE.RGBFormat;
066       // prepare skybox material (shader)
067       var skyShader = THREE.ShaderLib["cube"];
068       skyShader.uniforms["tCube"].value = scCube;
069       var skyMaterial = new THREE.ShaderMaterial( {
070         fragmentShader: skyShader.fragmentShader, vertexShader: skyShader.vertexShader,
071         uniforms: skyShader.uniforms, depthWrite: false, side: THREE.BackSide
072       });
073       // create Mesh with cube geometry and add to the scene
074       var skyBox = new THREE.Mesh(new THREE.BoxGeometry(500, 500, 500), skyMaterial);
075       skyMaterial.needsUpdate = true;
076       this.scene.add(skyBox);
077   }
078 };
079 // animate the scene
080 function animate() {
081   requestAnimationFrame(animate);
082   render();
083   update();
084 }
085 // update controls and stats
086 function update() {
087   var delta = lesson8.clock.getDelta();
088   lesson8.controls.update(delta);
089   lesson8.stats.update();
090 }
091 // Render the scene
092 function render() {
093   if (lesson8.renderer) {
094     lesson8.renderer.render(lesson8.scene, lesson8.camera);
095   }
096 }
097 // Initialize lesson on page load
098 function initializeLesson() {
099   lesson8.init();
100   animate();
101 }
102 if (window.addEventListener)
103   window.addEventListener('load', initializeLesson, false);
104 else if (window.attachEvent)
105   window.attachEvent('onload', initializeLesson);
106 else window.onload = initializeLesson;

This code creates a basic scene with renderer, camera, controls, lights, stats and skybox. Similar code you already saw earlier in previous lessons. There is nothing new.

Sprites

As mentioned earlier, sprites are (two-dimensional) images, which are orthogonal (perpendicular) to our camera. Now let’s add the sprites to our scene with the following function:

01 addAnimatedObjects: function() {
02   var texture1 = new THREE.ImageUtils.loadTexture('images/sprite1.png', undefined, function() {
03     var material1 = new THREE.SpriteMaterial( { map: texture1, useScreenCoordinates: false, side:THREE.DoubleSide, transparent: true } );
04     var mesh1 = new THREE.Sprite(material1);
05     mesh1.position.set(0, 0, -40);
06     mesh1.scale.set(64, 64, 1.0);
07     lesson8.scene.add(mesh1);
08   });
09   var texture2 = new THREE.ImageUtils.loadTexture('images/sprite2.png', undefined, function() {
10     var material2 = new THREE.SpriteMaterial( { map: texture2, useScreenCoordinates: false, transparent: true } );
11     var mesh2 = new THREE.Sprite(material2);
12     mesh2.position.set(0, 0, 40);
13     mesh2.scale.set(24, 46, 1.0);
14     lesson8.scene.add(mesh2);
15   });
16 }

This code loads two textures (sprite1.png and sprite2.png). After both images are loaded, we create two sprite materials and the Sprite object, and add them to our scene. If you run the code now, you will see two two-dimensional images on our scene. As you may have noticed, the images are drawn as is – we see a lot of small images (tiles) – these image files were taken due to the fact that we will use these tiles to do the animation.

Texture Animation

Now we need to add a new function to our script:

01 function TileTextureAnimator(texture, hTiles, vTiles, durationTile) {
02   // current tile number
03   this.currentTile = 0;
04   // duration of every tile
05   this.durationTile = durationTile;
06   // internal time counter
07   this.currentTime = 0;
08   // amount of horizontal and vertical tiles, and total count of tiles
09   this.hTiles = hTiles;
10   this.vTiles = vTiles;
11   this.cntTiles = this.hTiles * this.vTiles;
12   texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
13   texture.repeat.set(1 / this.hTiles, 1 / this.vTiles);
14   this.update = function(time) {
15     this.currentTime += time;
16     while (this.currentTime > this.durationTile) {
17       this.currentTime -= this.durationTile;
18       this.currentTile++;
19       if (this.currentTile == this.cntTiles) {
20         this.currentTile = 0;
21       }
22       var iColumn = this.currentTile % this.hTiles;
23       texture.offset.x = iColumn / this.hTiles;
24       var iRow = Math.floor(this.currentTile / this.hTiles);
25       texture.offset.y = iRow / this.vTiles;
26     }
27   };
28 }

The ‘TileTextureAnimator’ function adjusts the original images to display animation. It turns between tiles of the image from first to last tile. This does at a specified interval of time. Every tile is visible within the certain duration time, after it turns to another tile. Now let’s update the ‘addAnimatedObjects’ function that we added before:

01 addAnimatedObjects: function() {
02   var texture1 = new THREE.ImageUtils.loadTexture('images/sprite1.png', undefined, function() {
03     lesson8.anim1 = new TileTextureAnimator(texture1, 8, 8, 100);
04     var material1 = new THREE.SpriteMaterial( { map: texture1, useScreenCoordinates: false, side:THREE.DoubleSide, transparent: true } );
05     var mesh1 = new THREE.Sprite(material1);
06     mesh1.position.set(0, 0, -40);
07     mesh1.scale.set(64, 64, 1.0);
08     lesson8.scene.add(mesh1);
09     lesson8.animReady1 = true;
10   });
11   var texture2 = new THREE.ImageUtils.loadTexture('images/sprite2.png', undefined, function() {
12     lesson8.anim2 = new TileTextureAnimator(texture2, 9, 8, 100);
13     var material2 = new THREE.SpriteMaterial( { map: texture2, useScreenCoordinates: false, transparent: true } );
14     var mesh2 = new THREE.Sprite(material2);
15     mesh2.position.set(0, 0, 40);
16     mesh2.scale.set(24, 46, 1.0);
17     lesson8.scene.add(mesh2);
18     lesson8.animReady2 = true;
19   });
20 }

The first sprite image contains 8 tiles in row, 8 rows total, the second image contains 9 tiles in row. Every tile will be visible for 100ms. Finally, in the main ‘update’ function, we need to put the following code:

1 if (lesson8.animReady1) {
2   lesson8.anim1.update(1000 * delta);
3 }
4 if (lesson8.animReady2) {
5   lesson8.anim2.update(1000 * delta);
6 }

This code invokes the ‘update’ function of ‘TileTextureAnimator’ class objects.

As a result we got that only one tile is visible at a time. And every tile is visible within 100ms. So, the animation works pretty fast, as we needed to make.


Live Demo

[sociallocker]

download in package

[/sociallocker]

Rate article