带有Three.js的WebGL –第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.

我们在webgl上的课程仍在继续。 今天,我们开始另一个主题,在这里我们将处理精灵和纹理动画。 如果您不知道,精灵只是图像,可以附加到对象上。 这些子画面图像始终与我们的相机正交。 Three.js为精灵提供了一种特殊的材质THREE.SpriteMaterial,以及一种特殊的对象THREE.Sprite。 同样在本教程中,我们将学习如何使用精灵播放动画。

现场演示

制备 (Preparation)

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

和往常一样,我们必须准备一个小的index.html文件,其中包含必要的html标记才能进行处理:

index.html (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>

<!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

在此代码中,我们连接了Three.js主库和一些其他实用程序:WindowResize事件处理程序,Orbit控件和Stats

Webgl主要场景的准备 (Preparation of the main webgl scene)

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

现在,让我们创建主“ script.js”文件并放置以下代码:

script.js (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;

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);
    });
  }

  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.

此代码加载两个纹理(sprite1.png和sprite2.png)。 加载两个图像之后,我们创建两个Sprite材质和Sprite对象,并将它们添加到场景中。 如果现在运行代码,您将在我们的场景中看到两个二维图像。 您可能已经注意到,图像是按原样绘制的-我们看到了很多小图像(平铺)-这些图像文件是由于我们将使用这些图块来进行动画处理而拍摄的。

纹理动画 (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;
    }
  };
}

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:

“ TileTextureAnimator”功能调整原始图像以显示动画。 它在图像的第一个图块和最后一个图块之间切换。 这是在指定的时间间隔内进行的。 在转到另一个图块之后,每个图块在一定的持续时间内都可见。 现在,让我们更新之前添加的“ addAnimatedObjects”函数:


  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;
    });
  }

  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:

第一张子画面图片包含8行,共8行,第二张图片包含9行。 每个图块将在100毫秒内可见。 最后,在主要的“更新”功能中,我们需要输入以下代码:


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

  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.

此代码调用“ TileTextureAnimator”类对象的“更新”功能。

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.

结果,我们一次只能看到一个图块。 并且每个图块在100毫秒内可见。 因此,动画的制作速度非常快,就像我们需要制作的那样。

现场演示

[sociallocker]

[社交储物柜]

打包下载

[/sociallocker]

[/ sociallocker]

翻译自: https://www.script-tutorials.com/webgl-with-three-js-lesson-8/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对整threeJS体系进行全面剖析。整理出全面的教学大纲,涵盖内容面非常广。此教学版本为threeJS107版本。关于版本不建议大家使用低于90的版本学习。以下是程目录1-ThreeJS概览(基本图形简介,什么是点线面如何绘制点线面,什么是材质,什么是几何体,什么是相机,什么是渲染器,什么是场景)2-相机和渲染器(详解相机类型,渲染器如何使用,针对不同场景怎么用,怎么调效果,怎么渲染,怎么输出画布,如何解决透明问题等等)3-创建平面几何(常见的几何体如何使用,如何使用简单的几何体绘制出自定义自己想要的几何体,关于几何体的性能剖析,如何解决性能,几何体的渲染原理)4-高级图形算法常见库(求直线的斜率  计算线段与圆的交点 计算线段的长度 判断折线是否在多边形内 等等)5-sprite精灵(怎么让一个图标永远朝向屏幕,精灵的属性,精灵材质原理等,广告提示框必用)6-骨骼游戏动画(什么是模型动画,常见游戏案例,如何让人头进行各种攻击动作)7-3d模型加载(常见模型格式,如何渲染不同格式,不同格式的特点,什么格式性能优越,模型渲染异常,贴图不显示等问题详解)8-高阶动态纹理(你所不知道的纹理用法,我说你不知道,你肯定不知道)9-漫游轨迹以及其动画路径(怎么绘制贝塞尔曲线,如何使用曲线上的路径,跟随路径移动的原理,相机如何运动,物体如何运动)10-着色器(什么是着色器。初识着色器基础,着色器材质怎么用,怎么使用着色器库)11-常见渲染以及透明度问题12-对象拾取以及拖拽(3d世界里面如何拖拽物体,拖拽的原理,mousemove mouseon等的事件效果)13-世界坐标以及组的问题(什么是相对坐标,什么是世界坐标,什么是当前坐标,怎么转化父子坐标系,组的优化,为什么用组,组的优势)14-指定对象旋转中心(什么是物体的几何体中心,如何改变中心,如何绕轴转动)15-层级对象渲染(多个场景一键切换,切换的优势,针对大项目的用法)16-拓展了解系列(不定期不断更新案例,各种酷炫效果bloom,halo等,以及各种3d图表,粒子案例等,不断构建你的3d实践能力)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值