您的位置:首页 > Web前端 > JavaScript

带有Three.js的WebGL –第8课

2020-08-06 09:18 411 查看

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>
[/code]

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;
[/code]

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);
});
}
[/code]

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;
}
};
}
[/code]

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;
});
}
[/code]

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);
}
[/code]

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/

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: