В прошлом я писал о создании простых адаптивных галерей изображений, простых лайтбоксов для вашего веб-сайта или даже простого инструмента для создания слайд-шоу для вашего сайта. Чтобы продолжить то, что я думал, я бы попробовал что-то немного другое, поэтому сегодня я собираюсь создать простое слайд-шоу для вашего веб-сайта, НО мы собираемся сделать его 3D.

Чтобы следовать этому, вам в идеале потребуется некоторое понимание JavaScript, а также вам понадобится веб-сервер, на котором можно будет запускать этот код. Если вы не уверены в веб-сервере, вы можете посмотреть здесь для некоторых легких и простых в использовании предложений.

В этом проекте мы будем использовать Three.js, отличную библиотеку для создания приложений и игр WebGL, которые запускаются в веб-браузере. Так что вам нужно будет получить копию этого или использовать three.min.js из CDN по вашему выбору.

Создайте каталог для кода и создайте новый файл HTML: index.html.

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>3d photo gallery</title>
  <style>
   body { margin: 0; }
  </style>
 </head>
 <body>
    
    
  <script src="three.min.js"></script>
  <script src="project.js"></script>
 </body>
</html>

Как видите, ничего особенного. Это базовая структура HTML с библиотекой three.min.js, а также файлом project.js, который будет кодом, который мы сейчас создадим.

Создайте новый файл с именем project.js в том же каталоге и откройте его в любом редакторе.

Мы начнем с определения некоторых переменных, которые нам понадобятся для этого проекта.

var sceneWidth;
var sceneHeight;
var camera;
var scene;
var renderer;
var clock;
var speed = 2;
var delta = 0;
var forward = false;
var backward = false;
var left = false;
var right = false;
var loader;
var prevMouseX;
var prevMouseY;
var billboard;
var billboardMat;
var changeClock;
var currentImageIndex = 0;
var images = [
'images/1.jpg',
'images/2.jpg',
'images/3.jpg',
'images/4.jpg',
];

Здесь у нас есть переменные, которые помогут нам настроить сцену, 3D-рендерер, несколько часов, чтобы следить за происходящим. Логические значения, которые будут сохранены при нажатии клавиш вперед, назад, влево или вправо. Позиция мыши и, самое главное, массив изображений, которые мы хотим отобразить.

Затем мы вызовем функцию: init()

Поскольку мы вызываем это, нам нужно определить его. Когда наша HTML-страница загружается в браузере, она вызывает эту функцию, которую мы будем использовать для настройки остальных функций.

function init() {
// Call a method to create the initial scene
createScene();
// Call the game loop
update();
}

Это было просто. За исключением того, что теперь эти функции должны быть определены. Начнем с функции createScene.

function createScene() {
    clock = new THREE.Clock();
 clock.start();
    
    changeClock = new THREE.Clock();
    changeClock.start();
    
    sceneWidth = window.innerWidth;
    sceneHeight = window.innerHeight;
    
    scene = new THREE.Scene(); //the 3d scene
    
    camera = new THREE.PerspectiveCamera( 75, sceneWidth / sceneHeight, 0.1, 1000 );//perspective camera
    
    renderer = new THREE.WebGLRenderer({
        antialias:true,
    });
    renderer.setClearColor(0x000000, 1); 
    renderer.shadowMap.enabled = true;//enable shadow
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.setSize( sceneWidth, sceneHeight );
    
    document.body.appendChild(renderer.domElement);
    
    camera.position.z = 2.0;
 camera.position.y = 2.0;
        
    // Set event callbacks
    window.addEventListener('resize', onWindowResize, false);
 document.onkeydown = handleKeyDown;
    document.onkeyup = handleKeyUp;
    document.onmousemove = handleMouseMove;
    
    loader = new THREE.TextureLoader();
    
    // Call methods to add geometry and images
    addWorld();
    addLight();
}

Мы начинаем с создания часов, которые мы можем использовать для deltaTime. Это позволит нам сгладить движение, которое мы реализуем в ближайшее время.

Мы создадим вторые часы, которые будут нашим таймером того, когда фотография должна измениться. Мы вернемся к этому позже, но мы заставим его менять изображения каждые 10 секунд.

Высота и ширина визуализируемой сцены будут равны высоте и ширине окна браузера.

Затем мы создаем сцену и камеру. Для камеры мы устанавливаем FOV 75, соотношение сторон основано на размерах сцены и расстоянии отсечения от 0,1 до 1000.

Многое из этой стороны больше связано с тем, как работают OpenGL и WebGL, а это совсем другая тема.

Затем мы настраиваем наш рендерер. Мы говорим, что хотим, чтобы сглаживание было включено. Установка чистого цвета на черный и включение теней. Затем мы добавляем элемент рендеринга как элемент DOM в браузер.

Устанавливаем начальное положение камеры.

Затем мы настраиваем некоторые обратные вызовы для обработки событий. Мы собираемся обрабатывать события keydown и keyup, чтобы мы могли отслеживать, удерживается ли клавиша нажатой. Нам также нужно событие перемещения мыши, чтобы мы могли добавить движение к изображениям.

Мы также перехватываем событие изменения размера, чтобы исправить рендерер и сцену в случае, если пользователь изменит размер окна браузера.

После этого мы хотим создать загрузчик текстур. Это позволит нам загружать изображения для отображения.

Наконец, для этой функции мы вызываем еще две функции, которые нам еще нужно создать. Эти функции добавят некоторую геометрию в 3D-сцену для отображения изображений, а также некоторое освещение, чтобы все выглядело круто.

function addWorld() {
       
    // Add an image plane
    const sign_geometry = new THREE.PlaneGeometry( 3, 2 );
    billboardMat = new THREE.MeshStandardMaterial({
        color: 0x808080, 
        side: THREE.DoubleSide,
        map: loader.load(images[currentImageIndex]),
    });
    billboard = new THREE.Mesh( sign_geometry, billboardMat );
    scene.add(billboard);
    billboard.position.y = 2;
    
}

Эта функция имеет одну работу. Он создает простую плоскую сетку, которая представляет собой плоскую поверхность для отображения наших изображений.

Затем нам нужно установить материал. Это определяет, как выглядит сетка, включая базовый цвет и начальное изображение, отображаемое спереди. Здесь вы можете видеть, что мы используем созданный ранее загрузчик изображений для загрузки первого изображения в нашем массиве.

Затем мы создаем сетку, используя геометрию и материал, и добавляем ее в 3D-сцену. После того, как он был добавлен, мы переместим его по оси Y на 2.

Вот и все, на свет, чтобы мы могли видеть.

function addLight(){
    
    const spot1 = new THREE.SpotLight(0xffffff, 5.0);
    spot1.position.set(billboard.position.x + ((3/2)+0.5), billboard.position.y+1, billboard.position.z+0.5);
    spot1.castShadow = true;
spot1.shadow.mapSize.width = 1024;
    spot1.shadow.mapSize.height = 1024;
spot1.shadow.camera.near = 500;
    spot1.shadow.camera.far = 4000;
    spot1.shadow.camera.fov = 30;
scene.add( spot1 );
    spot1.target = billboard;
    
    const spot2 = new THREE.SpotLight(0xffffff, 5.0);
    spot2.position.set(billboard.position.x - ((3/2)+0.5), billboard.position.y+1, billboard.position.z+0.5);
    spot2.castShadow = true;
spot2.shadow.mapSize.width = 1024;
    spot2.shadow.mapSize.height = 1024;
spot2.shadow.camera.near = 500;
    spot2.shadow.camera.far = 4000;
    spot2.shadow.camera.fov = 30;
scene.add( spot2 );
    spot2.target = billboard;
    
}

В этой функции мы добавляем 2 прожектора. По одному в каждом верхнем углу плоскости рекламного щита, которую мы добавили ранее. Эти огни должны светить на рекламном щите. Здесь мы также настроили некоторые свойства отбрасывания теней. На данный момент это не очень актуально, так как в сцене нет ничего, на что можно было бы отбрасывать тени. Однако, если вы позже измените это самостоятельно, вам может понадобиться это.

Давайте теперь посмотрим на некоторые обратные вызовы обработки событий.

Для нашей клавиатуры мы хотим фиксировать, нажимает ли пользователь клавишу «движение» или отпускает ее. Мы будем следить за клавишами WADS, а также клавишами со стрелками. Если они нажаты, мы установим соответствующее логическое значение в true. После выпуска мы установим его обратно в false.

function handleKeyUp(keyEvent) {
    //65 = a     37 = left
    //68 = d     39 = right
    //87 = w     38 = up
    //83 = s     40 = down
    if( keyEvent.keyCode == 65 || keyEvent.keyCode == 37 ) {
        left = false;
    } else if( keyEvent.keyCode == 68 || keyEvent.keyCode == 39) {
        right = false;
    } else if( keyEvent.keyCode == 87 || keyEvent.keyCode == 38) {
        forward = false;
    } else if( keyEvent.keyCode == 83 || keyEvent.keyCode == 40) {
        backward = false;
    }
}
function handleKeyDown(keyEvent){
 
    //65 = a     37 = left
    //68 = d     39 = right
    //87 = w     38 = up
    //83 = s     40 = down
    if( keyEvent.keyCode == 65 || keyEvent.keyCode == 37 ) {
        left = true;
    } else if( keyEvent.keyCode == 68 || keyEvent.keyCode == 39) {
        right = true;
    } else if( keyEvent.keyCode == 87 || keyEvent.keyCode == 38) {
        forward = true;
    } else if( keyEvent.keyCode == 83 || keyEvent.keyCode == 40) {
        backward = true;
    }
}

Мы скоро увидим, почему мы сделали это в нашей функции обновления. Сначала займемся мышью.

Мы хотим, чтобы, если пользователь нажмет левую кнопку и переместит мышь вокруг нее, изображение наклонится в трехмерном пространстве.

function handleMouseMove(event) {
    if(event.buttons == 1) {
        var horiz = event.clientX;
        var vert = event.clientY;
        
        if(horiz > prevMouseX) {
            billboard.rotateY(THREE.Math.degToRad(1));
        }
        if(horiz < prevMouseX) {
            billboard.rotateY(THREE.Math.degToRad(-1));
        }
        
        if(vert > prevMouseY) {
            billboard.rotateX(THREE.Math.degToRad(1));
        }
        if(vert < prevMouseY) {
            billboard.rotateX(THREE.Math.degToRad(-1));
        }
        
        billboard.rotateZ(THREE.Math.degToRad(0));
        
        prevMouseX = horiz;
        prevMouseY = vert;
    }
}

Сначала мы проверяем, нажата ли левая кнопка. Если да, то мы сравниваем текущую позицию мыши с предыдущей позицией мыши и поворачиваем изображение по соответствующей оси.

Это не идеально, и, без сомнения, есть лучшие способы справиться с этим, но на данный момент это подойдет.

Последний обратный вызов события, который мы хотим обработать, — это изменение размера.

function onWindowResize() {
 //resize & align
 sceneHeight = window.innerHeight;
 sceneWidth = window.innerWidth;
 renderer.setSize(sceneWidth, sceneHeight);
 camera.aspect = sceneWidth/sceneHeight;
 camera.updateProjectionMatrix();
}

Это просто настраивает сцену и камеру в соответствии с новыми размерами окна браузера.

Почти готово. Нам нужно реализовать метод обновления. Это наш цикл событий, в котором объекты перемещаются при необходимости и отображается экран.

// Main loop, move stuff and do whatever here
function update(){
 requestAnimationFrame(update); //request next update
    
    delta = clock.getDelta();
    
    if( left ) {
        camera.position.x -= speed * delta;
    }
    if( right ) {
        camera.position.x += speed * delta;
    }
    
    if( forward ) {
        camera.position.z -= speed * delta;
    }
    if( backward ) {
        camera.position.z += speed * delta;
    }
    
    if( changeClock.getElapsedTime() > 10.0 ) {
        changeClock.stop();
        changeImage();
        changeClock.start();
    }
    
    render(); 
}

Здесь мы получаем дельта-время, то есть время, прошедшее с момента рендеринга последнего кадра. Затем, используя это, мы можем обрабатывать движение в зависимости от того, нажаты клавиши или нет.

Если нажата левая клавиша, мы перемещаем камеру по отрицательной оси x (влево) на коэффициент скорости, который мы определили в начале, умноженный на время, прошедшее с момента последнего кадра. То же самое относится и к другим клавишам ввода, если ось и направление будут изменены соответствующим образом. Это даст нам плавное движение, когда пользователь нажимал WADS или клавиши со стрелками.

Мы уже работали с мышью, поэтому нам не нужно делать это здесь.

Теперь мы проверяем часы changeClock, чтобы определить, прошло ли 10 секунд. Если это так, мы останавливаем часы, вызываем функцию для изменения изображения на материале рекламного щита, а затем перезапускаем часы. Это сбрасывает прошедшее время.

Наконец, после выполнения движения мы вызываем функцию рендеринга. Это будет вызываться до 60 раз в секунду, если компьютер может отображать сцену достаточно быстро. Это простая сцена, поэтому проблем со скоростью рендеринга быть не должно.

Давайте создадим функцию рендеринга.

function render(){
    renderer.render(scene, camera);
}

Здесь мы просто вызываем методы рендеринга Three.js для обновления экрана.

Наконец, нам нужно создать функцию, которая меняет изображение так, чтобы каждые 10 секунд изображение обновлялось на новое.

function changeImage() {
    var indx = currentImageIndex;
    while(indx == currentImageIndex) {
        indx = Math.floor(Math.random() * images.length);
    }
    currentImageIndex = indx;
    billboardMat.map = loader.load(images[indx]);
}

Здесь вы можете видеть, что мы отслеживаем индекс изображения, чтобы не повторять его сразу. Затем мы генерируем случайное число в диапазоне длины массива изображений. Это означает, что мы можем добавить в массив столько изображений, сколько захотим, и он просто выберет их случайным образом.

Затем, как только мы получим индекс нового изображения в массиве, мы обновим материал рекламных щитов новым изображением.

Вот оно. Простое слайд-шоу 3D-фотографий, которое можно использовать для галереи изображений на веб-сайте. Если вы используете клавиши перемещения, вы можете приблизиться или отдалиться, двигаться влево или вправо. Нажатие кнопки мыши и перемещение мыши будут наклонять изображение, и вы сможете увидеть эффект освещения на 3D-холсте.

Это отличная отправная точка, чтобы улучшить это и сделать что-то еще более впечатляющим. Вы можете разбросать изображения по всему месту, чтобы пользователи перемещались по пространству, чтобы посмотреть на них все. Вы можете заставить их скользить внутрь и наружу. Вы можете добавить эффекты частиц, чтобы это выглядело круто. Частицы делают все прохладным.

Вы также поверхностно познакомились с библиотекой Three.js, и я уверен, что вы могли бы придумать какие-то другие интересные вещи, которые можно было бы сделать с ее помощью для интерактивных веб-приложений. Наслаждаться.