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.
Preparation
As usual, we have to prepare a small index.html file with necessary html markup to work on:
index.html
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" /> |
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> |
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
009 | anim1: null, anim2: null, |
010 | animReady1: false, animReady2: false, |
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; |
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)); |
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; |
030 | this.container = document.createElement('div'); |
031 | document.body.appendChild(this.container); |
032 | this.container.appendChild(this.renderer.domElement); |
034 | THREEx.WindowResize(this.renderer, this.camera); |
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; |
040 | this.clock = new THREE.Clock(); |
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 ); |
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); |
057 | this.addAnimatedObjects(); |
059 | addSkybox: function() { |
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' ]; |
064 | var scCube = THREE.ImageUtils.loadTextureCube(sides); |
065 | scCube.format = THREE.RGBFormat; |
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 |
074 | var skyBox = new THREE.Mesh(new THREE.BoxGeometry(500, 500, 500), skyMaterial); |
075 | skyMaterial.needsUpdate = true; |
076 | this.scene.add(skyBox); |
081 | requestAnimationFrame(animate); |
087 | var delta = lesson8.clock.getDelta(); |
088 | lesson8.controls.update(delta); |
089 | lesson8.stats.update(); |
093 | if (lesson8.renderer) { |
094 | lesson8.renderer.render(lesson8.scene, lesson8.camera); |
098 | function initializeLesson() { |
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); |
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); |
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) { |
05 | this.durationTile = durationTile; |
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; |
19 | if (this.currentTile == this.cntTiles) { |
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; |
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; |
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; |
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); |
4 | if (lesson8.animReady2) { |
5 | 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.
[sociallocker]
[/sociallocker]