В этой части мы улучшим визуальное качество сцены, сделаем движение самолёта гораздо более плавным и добавим эффект волн.
6К открытий6К показов
Рассказывает Карим Маалул
Почти готово!
Как вы уже убедились в прошлой части, Three.js сильно облегчает работу с WebGL. Вам не нужны обширные знания для настройки сцены и отрисовки сложных объектов. Пока что вы изучили несколько основных принципов, но пора двигаться дальше.
В этой части мы улучшим визуальное качество сцены, сделаем движение самолёта гораздо более плавным и добавим эффект волн.
Самолёт на прокачку
Наш самолёт пока что очень прост. Пока что мы умеем только создавать объекты и комбинировать их, но мы пока не умеем подгонять их под свои нужды.
Например, куб можно изменить, сдвинув его вершины. В нашем случае нужно сделать его более похожим на фюзеляж. Вот что должно получиться:
// Cockpit
var geomCockpit = new THREE.BoxGeometry(80,50,50,1,1,1);
var matCockpit = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
// we can access a specific vertex of a shape through
// the vertices array, and then move its x, y and z property:
geomCockpit.vertices[4].y-=10;
geomCockpit.vertices[4].z+=20;
geomCockpit.vertices[5].y-=10;
geomCockpit.vertices[5].z-=20;
geomCockpit.vertices[6].y+=30;
geomCockpit.vertices[6].z+=20;
geomCockpit.vertices[7].y+=30;
geomCockpit.vertices[7].z-=20;
var cockpit = new THREE.Mesh(geomCockpit, matCockpit);
cockpit.castShadow = true;
cockpit.receiveShadow = true;
this.mesh.add(cockpit);
Вот пример того, как изменять фигуры. Если вы изучите исходный код самолёта, то найдёте там ещё несколько других объектов и более приятный пропеллер. Ничего сложного.
Но кто управляет самолётом?
Посадить пилота в самолёт — как два куба добавить.
Но нам нужен не просто пилот, а крутой пилот с причёской, развевающейся на ветру. Звучит сложно, но поскольку мы работаем с низкополигональными моделями, реализация не вызовет проблем.
Давайте взглянем на код:
var Pilot = function(){
this.mesh = new THREE.Object3D();
this.mesh.name = "pilot";
// angleHairs is a property used to animate the hair later
this.angleHairs=0;
// Body of the pilot
var bodyGeom = new THREE.BoxGeometry(15,15,15);
var bodyMat = new THREE.MeshPhongMaterial({color:Colors.brown, shading:THREE.FlatShading});
var body = new THREE.Mesh(bodyGeom, bodyMat);
body.position.set(2,-12,0);
this.mesh.add(body);
// Face of the pilot
var faceGeom = new THREE.BoxGeometry(10,10,10);
var faceMat = new THREE.MeshLambertMaterial({color:Colors.pink});
var face = new THREE.Mesh(faceGeom, faceMat);
this.mesh.add(face);
// Hair element
var hairGeom = new THREE.BoxGeometry(4,4,4);
var hairMat = new THREE.MeshLambertMaterial({color:Colors.brown});
var hair = new THREE.Mesh(hairGeom, hairMat);
// Align the shape of the hair to its bottom boundary, that will make it easier to scale.
hair.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,2,0));
// create a container for the hair
var hairs = new THREE.Object3D();
// create a container for the hairs at the top
// of the head (the ones that will be animated)
this.hairsTop = new THREE.Object3D();
// create the hairs at the top of the head
// and position them on a 3 x 4 grid
for (var i=0; i<12; i++){
var h = hair.clone();
var col = i%3;
var row = Math.floor(i/3);
var startPosZ = -4;
var startPosX = -4;
h.position.set(startPosX + row*4, 0, startPosZ + col*4);
this.hairsTop.add(h);
}
hairs.add(this.hairsTop);
// create the hairs at the side of the face
var hairSideGeom = new THREE.BoxGeometry(12,4,2);
hairSideGeom.applyMatrix(new THREE.Matrix4().makeTranslation(-6,0,0));
var hairSideR = new THREE.Mesh(hairSideGeom, hairMat);
var hairSideL = hairSideR.clone();
hairSideR.position.set(8,-2,6);
hairSideL.position.set(8,-2,-6);
hairs.add(hairSideR);
hairs.add(hairSideL);
// create the hairs at the back of the head
var hairBackGeom = new THREE.BoxGeometry(2,8,10);
var hairBack = new THREE.Mesh(hairBackGeom, hairMat);
hairBack.position.set(-1,-4,0)
hairs.add(hairBack);
hairs.position.set(-5,5,0);
this.mesh.add(hairs);
var glassGeom = new THREE.BoxGeometry(5,5,5);
var glassMat = new THREE.MeshLambertMaterial({color:Colors.brown});
var glassR = new THREE.Mesh(glassGeom,glassMat);
glassR.position.set(6,0,3);
var glassL = glassR.clone();
glassL.position.z = -glassR.position.z
var glassAGeom = new THREE.BoxGeometry(11,1,11);
var glassA = new THREE.Mesh(glassAGeom, glassMat);
this.mesh.add(glassR);
this.mesh.add(glassL);
this.mesh.add(glassA);
var earGeom = new THREE.BoxGeometry(2,3,2);
var earL = new THREE.Mesh(earGeom,faceMat);
earL.position.set(0,0,-6);
var earR = earL.clone();
earR.position.set(0,0,6);
this.mesh.add(earL);
this.mesh.add(earR);
}
// move the hair
Pilot.prototype.updateHairs = function(){
// get the hair
var hairs = this.hairsTop.children;
// update them according to the angle angleHairs
var l = hairs.length;
for (var i=0; i<l; i++){
var h = hairs[i];
// each hair element will scale on cyclical basis between 75% and 100% of its original size
h.scale.y = .75 + Math.cos(this.angleHairs+i/3)*.25;
}
// increment the angle for the next frame
this.angleHairs += 0.16;
}
Чтобы волосы двигались, добавьте эту строку в цикл:
airplane.pilot.updateHairs();
Ловим волны
Вы наверняка заметили что море больше похоже не на море, а на поверхность, по которой проехались катком.
Морю нужны волны. Это можно реализовав, используя две ранее использованные техники:
перемещение вершин;
цикличное движение каждой вершиныt.
Для создания волн мы будем вращать каждую вершину цилиндра вокруг её начальной позиции, задавая её случайные угловую скорость и радиус вращения. Да, придётся вспомнить тригонометрию ?
Изменим море:
Sea = function(){
var geom = new THREE.CylinderGeometry(600,600,800,40,10);
geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
// important: by merging vertices we ensure the continuity of the waves
geom.mergeVertices();
// get the vertices
var l = geom.vertices.length;
// create an array to store new data associated to each vertex
this.waves = [];
for (var i=0; i<l; i++){
// get each vertex
var v = geom.vertices[i];
// store some data associated to it
this.waves.push({y:v.y,
x:v.x,
z:v.z,
// a random angle
ang:Math.random()*Math.PI*2,
// a random distance
amp:5 + Math.random()*15,
// a random speed between 0.016 and 0.048 radians / frame
speed:0.016 + Math.random()*0.032
});
};
var mat = new THREE.MeshPhongMaterial({
color:Colors.blue,
transparent:true,
opacity:.8,
shading:THREE.FlatShading,
});
this.mesh = new THREE.Mesh(geom, mat);
this.mesh.receiveShadow = true;
}
// now we create the function that will be called in each frame
// to update the position of the vertices to simulate the waves
Sea.prototype.moveWaves = function (){
// get the vertices
var verts = this.mesh.geometry.vertices;
var l = verts.length;
for (var i=0; i<l; i++){
var v = verts[i];
// get the data associated to it
var vprops = this.waves[i];
// update the position of the vertex
v.x = vprops.x + Math.cos(vprops.ang)*vprops.amp;
v.y = vprops.y + Math.sin(vprops.ang)*vprops.amp;
// increment the angle for the next frame
vprops.ang += vprops.speed;
}
// Tell the renderer that the geometry of the sea has changed.
// In fact, in order to maintain the best level of performance,
// three.js caches the geometries and ignores any changes
// unless we add this line
this.mesh.geometry.verticesNeedUpdate=true;
sea.mesh.rotation.z += .005;
}
Добавим эту строчку в цикл, как мы сделали с волосами:
sea.moveWaves();
Наслаждайтесь волнами!
Улучшаем освещение сцены
В первой части руководства мы уже настроили освещение. Настало время сделать его более мягким. Для этого мы используем рассеянное освещение.
В функции createLights добавим следующее:
// an ambient light modifies the global color of a scene and makes the shadows softer
ambientLight = new THREE.AmbientLight(0xdc8874, .5);
scene.add(ambientLight);
Не бойтесь экспериментировать с цветами и интенсивностью рассеянного света.
Мягкий полёт
Наш маленький самолёт уже следует за движениями мыши, но это не похоже на полёт. Нужно, чтобы он мягче изменял своё положение. В этой, последней части руководства мы займёмся именно этим.
Этого можно достичь, добавляя в каждом кадре часть расстояния между самолётом и целью. Общий вид кода таков (не добавляйте его в программу, это лишь шаблон):
Для большей реалистичности вращение самолёта также должно изменяться в зависимости от направления движения. Если самолёт быстро поднимается, то он должен быстро вращаться против часовой стрелки. Если он медленно опускается, то должен медленно вращаться по часовой стрелке.
Вот реализация всех рассуждений в функции updatePlane:
function updatePlane(){
var targetY = normalize(mousePos.y,-.75,.75,25, 175);
var targetX = normalize(mousePos.x,-.75,.75,-100, 100);
// Move the plane at each frame by adding a fraction of the remaining distance
airplane.mesh.position.y += (targetY-airplane.mesh.position.y)*0.1;
// Rotate the plane proportionally to the remaining distance
airplane.mesh.rotation.z = (targetY-airplane.mesh.position.y)*0.0128;
airplane.mesh.rotation.x = (airplane.mesh.position.y-targetY)*0.0064;
airplane.propeller.rotation.x += 0.3;
}
Теперь движение самолёта выглядит гораздо элегантней и реалистичней. Изменяя дробные величины, можно регулировать резкость движений.
Вот как выглядит наш проект: демонстрация, часть 2
Отлично!
Что дальше?
Вы изучили базовые, но универсальные приёмы Three.js, которые позволяют вам создавать простые сцены. Вы научились создавать объекты из примитивов, анимировать их и настраивать освещение сцены.
Следующим этапом, который не вошёл в этой руководство по причине использования сложных техник, будет реализация игры, в которой используются такие понятия, как коллизии, сбор предметов и управление уровнями. Изучите исходный код: там вы найдёте уже изучите приёмы и новые, более сложные, в которых вам предстоит разобраться.
Сыграть в игру вы можете, нажав на картинку ниже. Она оптимизирована для настольных компьютеров.
Поддержка Puppeteer в Firefox 23 открывает новые возможности для автоматизации и end-to-end тестирования в браузере. Разработчики могут использовать Puppeteer для сбора логов консоли, эмуляции устройств, перехвата сетевых запросов и предзагрузки скриптов.
Сравнение TypeScript и JavaScript. Показываем основные отличия друг от друга. Рассматриваем преимущества и недостатки Сравнение TypeScript и JavaScript ✔ Tproger
Марк Садыков, главный инженер по разработке в Сбере и ментор Эйч Навыки, рассказывает, что такое SSE и WebSockets и как они используются, и дает советы для прохождения собеседований.