WebGL With Three.js – Lesson 8

WebGL With Three.js – Lesson 8

0 43140
WebGL With Three.js - Lesson 8

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

<!DOCTYPE html>
<html lang="en" >
  <head>
    <meta charset="utf-8" />
    <meta name="author" content="Script Tutorials" />
    <title>WebGL With Three.js - Lesson 8 - Sprites and Texture Animation | 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>
    <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>
  </body>
</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

var lesson8 = {
  scene: null,
  camera: null,
  renderer: null,
  container: null,
  controls: null,
  clock: null,
  stats: null,
  anim1: null, anim2: null, // animations
  animReady1: false, animReady2: false,
  init: function() { // initialization
    // create main scene
    this.scene = new THREE.Scene();
    this.scene.fog = new THREE.FogExp2(0xcce0ff, 0.0003);
    var SCREEN_WIDTH = window.innerWidth,
        SCREEN_HEIGHT = window.innerHeight;
    // prepare perspective camera
    var VIEW_ANGLE = 60, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 1, FAR = 1000;
    this.camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
    this.scene.add(this.camera);
    this.camera.position.set(100, 0, 0);
    this.camera.lookAt(new THREE.Vector3(0,0,0));
    // prepare webgl renderer
    this.renderer = new THREE.WebGLRenderer({ antialias:true });
    this.renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    this.renderer.setClearColor(this.scene.fog.color);
    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);
    // prepare controls (OrbitControls)
    this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
    this.controls.target = new THREE.Vector3(0, 0, 0);
    this.controls.maxDistance = 3000;
    // prepare clock
    this.clock = new THREE.Clock();
    // prepare stats
    this.stats = new Stats();
    this.stats.domElement.style.position = 'absolute';
    this.stats.domElement.style.left = '50px';
    this.stats.domElement.style.bottom = '50px';
    this.stats.domElement.style.zIndex = 1;
    this.container.appendChild( this.stats.domElement );
    // add lights
    this.scene.add( new THREE.AmbientLight(0x606060) );
    var dirLight = new THREE.DirectionalLight(0xffffff);
    dirLight.position.set(200, 200, 1000).normalize();
    this.camera.add(dirLight);
    this.camera.add(dirLight.target);
    // display skybox
    this.addSkybox();
    // display animated objects
    this.addAnimatedObjects();
  },
  addSkybox: function() {
      // define path and box sides images
      var path = 'skybox/';
      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' ];
      // load images
      var scCube = THREE.ImageUtils.loadTextureCube(sides);
      scCube.format = THREE.RGBFormat;
      // prepare skybox material (shader)
      var skyShader = THREE.ShaderLib["cube"];
      skyShader.uniforms["tCube"].value = scCube;
      var skyMaterial = new THREE.ShaderMaterial( {
        fragmentShader: skyShader.fragmentShader, vertexShader: skyShader.vertexShader,
        uniforms: skyShader.uniforms, depthWrite: false, side: THREE.BackSide
      });
      // create Mesh with cube geometry and add to the scene
      var skyBox = new THREE.Mesh(new THREE.BoxGeometry(500, 500, 500), skyMaterial);
      skyMaterial.needsUpdate = true;
      this.scene.add(skyBox);
  }
};
// animate the scene
function animate() {
  requestAnimationFrame(animate);
  render();
  update();
}
// update controls and stats
function update() {
  var delta = lesson8.clock.getDelta();
  lesson8.controls.update(delta);
  lesson8.stats.update();
}
// Render the scene
function render() {
  if (lesson8.renderer) {
    lesson8.renderer.render(lesson8.scene, lesson8.camera);
  }
}
// Initialize lesson on page load
function initializeLesson() {
  lesson8.init();
  animate();
}
if (window.addEventListener)
  window.addEventListener('load', initializeLesson, false);
else if (window.attachEvent)
  window.attachEvent('onload', initializeLesson);
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:

  addAnimatedObjects: function() {
    var texture1 = new THREE.ImageUtils.loadTexture('images/sprite1.png', undefined, function() {
      var material1 = new THREE.SpriteMaterial( { map: texture1, useScreenCoordinates: false, side:THREE.DoubleSide, transparent: true } );
      var mesh1 = new THREE.Sprite(material1);
      mesh1.position.set(0, 0, -40);
      mesh1.scale.set(64, 64, 1.0);
      lesson8.scene.add(mesh1);
    });
    var texture2 = new THREE.ImageUtils.loadTexture('images/sprite2.png', undefined, function() {
      var material2 = new THREE.SpriteMaterial( { map: texture2, useScreenCoordinates: false, transparent: true } );
      var mesh2 = new THREE.Sprite(material2);
      mesh2.position.set(0, 0, 40);
      mesh2.scale.set(24, 46, 1.0);
      lesson8.scene.add(mesh2);
    });
  }

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:

function TileTextureAnimator(texture, hTiles, vTiles, durationTile) {
  // current tile number
  this.currentTile = 0;
  // duration of every tile
  this.durationTile = durationTile;
  // internal time counter
  this.currentTime = 0;
  // amount of horizontal and vertical tiles, and total count of tiles
  this.hTiles = hTiles;
  this.vTiles = vTiles;
  this.cntTiles = this.hTiles * this.vTiles;
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(1 / this.hTiles, 1 / this.vTiles);
  this.update = function(time) {
    this.currentTime += time;
    while (this.currentTime > this.durationTile) {
      this.currentTime -= this.durationTile;
      this.currentTile++;
      if (this.currentTile == this.cntTiles) {
        this.currentTile = 0;
      }
      var iColumn = this.currentTile % this.hTiles;
      texture.offset.x = iColumn / this.hTiles;
      var iRow = Math.floor(this.currentTile / this.hTiles);
      texture.offset.y = iRow / this.vTiles;
    }
  };
}

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:

  addAnimatedObjects: function() {
    var texture1 = new THREE.ImageUtils.loadTexture('images/sprite1.png', undefined, function() {
      lesson8.anim1 = new TileTextureAnimator(texture1, 8, 8, 100);
      var material1 = new THREE.SpriteMaterial( { map: texture1, useScreenCoordinates: false, side:THREE.DoubleSide, transparent: true } );
      var mesh1 = new THREE.Sprite(material1);
      mesh1.position.set(0, 0, -40);
      mesh1.scale.set(64, 64, 1.0);
      lesson8.scene.add(mesh1);
      lesson8.animReady1 = true;
    });
    var texture2 = new THREE.ImageUtils.loadTexture('images/sprite2.png', undefined, function() {
      lesson8.anim2 = new TileTextureAnimator(texture2, 9, 8, 100);
      var material2 = new THREE.SpriteMaterial( { map: texture2, useScreenCoordinates: false, transparent: true } );
      var mesh2 = new THREE.Sprite(material2);
      mesh2.position.set(0, 0, 40);
      mesh2.scale.set(24, 46, 1.0);
      lesson8.scene.add(mesh2);
      lesson8.animReady2 = true;
    });
  }

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:

  if (lesson8.animReady1) {
    lesson8.anim1.update(1000 * delta);
  }
  if (lesson8.animReady2) {
    lesson8.anim2.update(1000 * delta);
  }

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]

SIMILAR ARTICLES

Understanding Closures

0 23035

NO COMMENTS

Leave a Reply