[webGL学习]基于three.js构建WebGL实例第六讲

时间:2021-12-25 04:11:34

[webGL学习]基于three.js构建WebGL实例第六讲

今天我们继续webGL的课程。 今天我们开始另一个主题,我们将使用sprites和纹理动画。 如果你不知道,sprites只是图像,可以附加到对象上。 这些sprites图像总是与我们的相机正交。 Three.js为sprites - THREE.SpriteMaterial提供了一个库,以及一个特殊的一些方法 - THREE.Sprite。 在本教程中,我们还将学习如何使用sprites播放动画。

创建主要的webGL 场景

首先我们创建一个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;

这段代码创建了渲染器,摄像头,控制装置,照明,统计数据和天空盒一个基本的场景。类似的代码你已经在前面前面的课程中看到的.

Sprites

如前所述,子画面是与我们的相机正交(垂直)的(二维)图像。 现在让我们使用以下函数将sprites添加到场景中:

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

});
}

此代码加载两个图片(sprite1.png和sprite2.png)。 在加载这两个图像之后,我们创建两个sprite材质和Sprite对象,并将它们添加到我们的场景中。 如果现在运行代码,您将在我们的场景中看到两个二维图像。 你可能已经注意到,图像是按照原样绘制 - 我们看到很多小图像(瓷砖) - 这些图像文件被拍摄,因为我们将使用这些瓷砖做动画的事实。

Texture Animation

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

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

第一个sprite图像在行中包含8个图块,总共8个行,第二个图像包含行中的9个图块。 每个图块将可见100ms。 最后,在main’update’函数中,我们需要放置以下代码:

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

结束

未完继续
源码下载请关注我的微信公众号
[webGL学习]基于three.js构建WebGL实例第六讲