Менее 30 строк кода для создания крутой панорамы H5

внешний интерфейс three.js
Менее 30 строк кода для создания крутой панорамы H5

предисловие: Эта статья будет посвящена:Понять, что такое панорама --> как формировать панораму --> принцип взаимодействия с панорамойПриходите объяснить, научит как реализовать крутую веб-панораму с нуля и объяснит принцип. Xiaobai тоже можно узнать.Рекомендуется собирать и учиться.Если есть вопросы,прошу обсудить в комментариях.Автор часто проверяет и отвечает.

1. Понять, что такое панорама

1.1 Определение панорамы

Определение: Панорама – это пространствовсеПосмотреть.

С точки зрения непрофессионала: Все фотографировали, поэтому давайте подумаем о процессе фотографирования: стоя в определенном месте, держа камеру и снимая под определенным углом, вы можете получить изображение пейзажа с этого ракурса, и панорама? Это интерактивная фотография, которая стоит в определенном пространстве, держит камеру, снимает под углом 360 градусов, получает фотографии пейзажей со всех ракурсов, объединяет их, а затем с помощью специальной технологии показывает всем.

Пример панорамы:

Jietu20210527-113413-HD.gif

Испытайте QR-код (поддержите код сканирования WeChat):

image.png

1.2 Метод панорамного отображения

Существует множество способов отображения панорамы, например: цилиндрическая панорама, кубическая панорама, сферическая панорама и т. д.Jietu20210527-152042-HD.gif

Самое популярное понимание: с большой картонной коробкой, установленной на голове, увидеть сцену (такой способ отображения - панорамный вид куба)

image.png

Цилиндры и кубы имеют области пересечения, и взаимодействие интерфейсов в области пересечения будет тупиковым. Итак, лучший способ представить панорамуПанорама сферы, 360 градусов без мертвого угла, эта статья объяснит сферическую панораму.

2. Как сформировать панораму

2.1 Знакомство с ThreeJS

Текущие основные методы реализации внешнего интерфейса панорамы:

Метод реализации Стоимость Это с открытым исходным кодом стоимость обучения Сложность разработки совместимость расширять представление
CSSS 3D бесплатно да середина трудный Браузеры, поддерживающие CSS3D легкий Низкий
ThreeJS бесплатно да высоко середина Некоторые браузеры, поддерживающие WebGL легкий высоко
Инструмент панорамы (Krpano) потери нет легкий без Браузеры, поддерживающие flash и canvas трудный середина

Как фронтенд-разработчику с преследованием (метанием), конечно, приходится выбиратьThreeJS! ! !

ThreeJS — это Three (3D) + JS (JavaScript), который инкапсулирует базовый интерфейс WebGL, так что мы можем визуализировать 3D-сцены с помощью простого кода, не зная графики.

Общая идея для отображения 3D-изображений на экране такова:

  • Первый шаг: построение пространства в декартовой системе координат: Три называется сценой (Scene)
  • Шаг 2: В системе координат нарисуйте геометрию: в Three есть много видов геометрии, включая BoxGeometry (куб), SphereGeometry (сфера) и т. д.
  • Шаг 3: Выберите точку наблюдения и определите направление наблюдения и т. д.: Три называется Камера (Камера)
  • Шаг 4: Рендеринг наблюдаемой сцены в указанную область на экране: Используйте Renderer in Three для завершения этой работы (эквивалентно съемке изображений)

Выше приведен фиксированный способ рендеринга объектов в ThreeJS, если вы его не понимаете, то можете запомнить😄

Материал изображения, необходимый для сферической панорамы (внизу): ширина в два раза больше высоты, а значение является целым числом, кратным 2. Рекомендуется, чтобы ширина и высота изображения были 2048px*1024px (панорама будет использоваться позже)

image.png

ссылка на скачивание:GitHub.com/Ah Be a/O Disk…

Конкретная реализация кода:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <title>手把手教你制作酷炫Web全景</title>
 <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
</head>

<body>
 <div id="wrap" style="position: absolute;z-index: 0;top: 0;bottom: 0;left: 0;right: 0;width: 100%;height: 100%;overflow: hidden;"></div>
 <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.js"></script>
 <script>
    const width = window.innerWidth
    const height = window.innerHeight
    const radius = 50 // 球体半径
    // 第一步:创建场景
    const scene = new THREE.Scene()
    // 第二步:绘制一个球体
    const geometry = new THREE.SphereBufferGeometry(radius, 32, 32)
    const material = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('./img/1.jpg') // 上面的全景图片
    })
    const mesh = new THREE.Mesh(geometry, material)
    scene.add(mesh) // 第三步:创建相机,并确定相机位置
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    camera.position.x = 0 // 确定相机位置
    camera.position.y = 0
    camera.position.z = radius * 3 // 走远了看
    camera.target = new THREE.Vector3(0, 0, 0) // 设定对焦点


    // 第四步:拍照并绘制到canvas
    const renderer = new THREE.WebGLRenderer()
    renderer.setSize(width, height) // 设置照片大小

    document.querySelector('#wrap').appendChild(renderer.domElement) // 绘制到canvas

    function render() {
        camera.lookAt(camera.target)   // 对焦
        renderer.render(scene, camera) // 拍照

        // 不断渲染,因为图片加载和处理需要时间,不确定何时拍照合适
        requestAnimationFrame(render)
    }
    render()
</script>
</body>

</html>

Эффект страницы браузера (не забудьте включить отладку моделирования мобильного телефона):

image.png

2.2 Базовые очки знаний

2.2.1 Долгота и широта

В данной статье для работы с панорамой используются долгота и широта, и необходимо популяризировать знания о долготе и широте

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

image.png

Как показано на рисунке, долгота: долгота, диапазон значений: [0,360], широта: широта, диапазон значений: [-90,90];

2.2.2 Преобразование 3D-координат из широты и долготы

Сферическая точка {lon, lat}, где R — радиус сферы, а положение трех JS-координат сферической точки:

image.png

развязать:

X = R * cos (широта) * sin (долгота)
Y = R * sin( lat )
Z = R * cos( lat )*cos( lon )

Примечание. Система координат по умолчанию в ThreeJS — правая система координат, ось X — левая и правая, ось Y — верхняя и нижняя, а ось Z — передняя и задняя.

2.3 Шаги для создания панорамы

В главе 2.1 мы закончили рисовать сферу, а рисование панорамы заключается в том, чтобы на ее основе внести коррективы:

  • 1. Переместите камеру в центр сферы;
  • 2. Наклеить панорамное изображение на внутреннюю поверхность сферы;

Конкретные шаги заключаются в следующем:

  • Шаг 1: Создайте сцену (Scene)
  • Шаг 2: Создайте сферу и вставьте изображение панорамы в область сферы.Внутренняя поверхность, поставить на сцену
  • Шаг 4: Создайте перспективную проекционную камеру, чтобы притянуть камеру кцентр сферы, камера смотрит на внутреннюю поверхность сферы
  • Шаг 5: Измените наблюдение за точками, изменяя широту и долготу

Конкретная реализация кода:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>手把手教你制作酷炫Web全景</title>
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
</head>
<body>
    <div id="wrap" style="position: absolute;z-index: 0;top: 0;bottom: 0;left: 0;right: 0;width: 100%;height: 100%;overflow: hidden;">
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.js"></script>
    <script>
        const width = window.innerWidth, height = window.innerHeight // 屏幕宽高
        const radius = 50 // 球体半径

        // 第一步:创建场景
        const scene = new THREE.Scene()

        // 第二步:绘制一个球体
        const geometry = new THREE.SphereBufferGeometry(radius, 32, 32)
        geometry.scale(-1, 1, 1) // 球面反转,由外表面改成内表面贴图
        const material = new THREE.MeshBasicMaterial({
            map: new THREE.TextureLoader().load('./img/1.jpg') // 上面的全景图片
        })
        const mesh = new THREE.Mesh(geometry, material)
        scene.add(mesh)

        // 第三步:创建相机,并确定相机位置
        const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
        camera.position.x = 0  // 确定相机位置移到球心
        camera.position.y = 0
        camera.position.z = 0

        camera.target = new THREE.Vector3(radius, 0, 0) // 设置一个对焦点
      

        // 第四步:拍照并绘制到canvas
        const renderer = new THREE.WebGLRenderer()
        renderer.setPixelRatio(window.devicePixelRatio)
        renderer.setSize(width, height) // 设置照片大小

        document.querySelector('#wrap').appendChild(renderer.domElement) // 绘制到canvas
        renderer.render(scene, camera)

        let lat = 0, lon = 0

        function render() {
            lon += 0.003 // 每帧加一个偏移量
            // 改变相机的对焦点,计算公式参考:2.2.2章节
            camera.target.x = radius * Math.cos(lat) * Math.cos(lon);
            camera.target.y = radius * Math.sin(lat);
            camera.target.z = radius * Math.cos(lat) * Math.sin(lon)
            camera.lookAt(camera.target) // 对焦

            renderer.render(scene, camera)
            requestAnimationFrame(render)
        }
        render()
    </script>
</body>

</html>

Эффект:

Jietu20210527-172203-HD.gif

На данный момент изготовление нашей панорамы завершено, (учитывается только код js: всего28 строкКод, разве я не главная партия? 😁).

3. Принцип панорамного взаимодействия

3.1 взаимодействие жестов вращения

Вращение взаимодействия жестов относится к операции скольжения одним пальцем, что согласуется с взаимодействием скольжения земного шара.

Система координат экрана, левый верхний угол — начало координат, ось X: слева направо, ось Y: сверху вниз, Скольжение пальца по экрану вызовет последовательно три события: touchstart, touchmove и touchend; положение пальца на экране записывается в объект события

TeamTalk_IMG_2021-05-27-173217.jpg

Процесс скольжения пальца по экрану:

  • touchstart: записать позицию, с которой начинается скольжение (startX, startY)
  • touchmove: записывает текущую позицию (curX, curY) минус значение предыдущей позиции, вычисляет длину дуги, деленную на радиус, умноженный на коэффициент, и вычисляет (lon, lat)
  • touchend: временно бесполезен

Среди них: значение длины дуги R - расстояние скольжения экрана image.png

Затем один палец скользит по экрану от P1 (clientX1, clientY1) до P2 (clientX1, clientY1), длина которого соответствует изменению широты и долготы:

distanceX = clientX1 - clientX2   // X轴方向
distanceY = clientY1 - clientY2   // Y轴方向

// 其中R为球体半径,根据弧长公式:
lon = distanX / R
lat = distanY / R

Код:

// 增加touch事件监听
let lastX, lastY       // 上次屏幕位置
let curX, curY         // 当前屏幕位置
const factor = 1 / 10  // 灵敏系数

const $wrap = document.querySelector('#wrap')
// 触摸开始
$wrap.addEventListener('touchstart', function (evt) {
    const obj = evt.targetTouches[0] // 选择第一个触摸点
    startX = lastX = obj.clientX
    startY = lastY = obj.clientY
})

// 触摸中
$wrap.addEventListener('touchmove', function (evt) {
    evt.preventDefault()
    const obj = evt.targetTouches[0]
    curX = obj.clientX
    curY = obj.clientY

    // 参考:弧长公式
    lon -= ((curX - lastX) / radius) * factor // factor为了全景旋转平稳,乘以一个灵敏系数
    lat += ((curY - lastY) / radius) * factor

    lastX = curX
    lastY = curY
})

Эффект управления одним пальцем:Jietu20210527-172203-HD.gif

В приведенном выше коде добавлено взаимодействие с панорамой одним пальцем, однако отсутствует инерция вращения. Далее добавим инерционную анимацию:

Реализована инерция скольжения, и процесс скольжения пальца по экрану:

  • touchstart: записать позицию, с которой начинается скольжение (startX, startY, startTime)

  • touchmove: записать текущую позицию (curX, curY) минус значение предыдущей позиции, умножить на коэффициент, вычислить (долготу, широту), [touch follow]

  • touchend: запишите endTime, рассчитайте среднюю скорость во время этого процесса скольжения, а затем вычтите замедление d для каждого кадра, пока скорость не станет равной 0 или не будет запущено событие touchstart [конец касания запускает инерционную анимацию]

Код:

let lastX, lastY         // 上次屏幕位置
let curX, curY           // 当前屏幕位置
let startX, startY       // 开始触摸的位置,用于计算速度
let isMoving = false     // 是否停止单指操作
let speedX, speedY       // 速度
const factor = 1 / 10    // 灵敏系数,经验值
const deceleration = 0.1 // 减速度,惯性动画使用

const $wrap = document.querySelector('#wrap')
// 触摸开始
$wrap.addEventListener('touchstart', function (evt) {
    const obj = evt.targetTouches[0] // 选择第一个触摸点
    startX = lastX = obj.clientX
    startY = lastY = obj.clientY
    startTime = Date.now()
    isMoving = true
})

// 触摸中
$wrap.addEventListener('touchmove', function (evt) {
    evt.preventDefault()
    const obj = evt.targetTouches[0]
    curX = obj.clientX
    curY = obj.clientY

    // 参考:弧长公式
    lon -= ((curX - lastX) / radius) * factor // factor为了全景旋转平稳,乘以一个系数
    lat += ((curY - lastY) / radius) * factor

    lastX = curX
    lastY = curY
})

// 触摸结束
$wrap.addEventListener('touchend', function (evt) {
    isMoving = false
    var t = Date.now() - startTime
    speedX = (curX - startX) / t    // X轴方向的平均速度
    speedY = (curY - startY) / t    // Y轴方向的平均速度

    subSpeedAnimate() // 惯性动画
})

let animateInt
// 减速度动画
function subSpeedAnimate() {
    lon -= speedX * factor // X轴
    lat += speedY * factor
    
    // 减速度
    speedX = subSpeed(speedX)
    speedY = subSpeed(speedY)

    // 速度为0或者有新的触摸事件,停止动画
    if ((speedX === 0 && speedY === 0) || isMoving) {
        if (animateInt) {
            cancelAnimationFrame(animateInt)
            animateInt = undefined
        }
    } else {
        requestAnimationFrame(subSpeedAnimate)
    }
}

// 减速度
function subSpeed(speed) {
    if (speed !== 0) {
        if (speed > 0) {
            speed -= deceleration;
            speed < 0 && (speed = 0);
        } else {
            speed += deceleration;
            speed > 0 && (speed = 0);
        }
    }
    return speed;
}

Адрес предварительного просмотра:azuoge.github.io/Opanorama/

3.2 Масштабирование жестового взаимодействия

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

ThreeJS был представлен ранее, и упоминалась камера.Панорамный зум также основан на принципе масштабирования и съемки содержимого фотографии, когда камера делает снимок.

image.png

Код камеры, созданный с помощью ThreeJS, выглядит следующим образом:

const camera = new THREE.PerspectiveCamera( fov , aspect , near , fear )

Описание параметра:

image.png

в,

  • рядом: принять значение по умолчанию: 0,1
  • страх: пока он больше радиуса сферы, значение равно: радиус сферы R
  • аспект: в сцене панорамы было определено соотношение сторон фотографии: ширина экрана / высота экрана
  • fov: поле зрения, масштабирование для завершения масштабирования панорамного изображения путем изменения его значения;

На самом деле, это очень хорошо понимать, широко раскрытыми глазами, мы будем видеть видение, видя некоторые объекты [уменьшать], переворачивать, щуриться, видеть ужас, видеть, что объект большой [увеличение], можно масштабировать панорамное изображение путем изменения значения FOV правого изображения

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

Код ключа следующий:

// 其中,(clientX1,clientY1)和(clientX2,clientY2)为双指在屏幕的当前位置

// 计算距离,简化运输不用平方计算
const distance = Math.abs(clientX1 - clientX2) + Math.abc(clientY1 - clientY)
// 计算缩放比
const scale = distance / lastDiance 
// 计算新的视角
fov = camera.fov / scale

// 视角范围取值
camera.fov = Math.min(90, Math.max(fov,60)) // 90 > fov > 60 ,从参数说明中选取

// 视角需要主动更新
camera.updateProjectionMatrix()

Адрес опыта:azuoge.github.io/Opanorama/

3.3 Взаимодействие с гироскопом мобильного телефона

В событии html5 событие deviceorientation — это событие, которое обнаруживает изменение ориентации устройства.

H5 имеет две координаты:

  • Земля координирует X / Y / Z: в любом случае является постоянным направлением
  • Планарная координата мобильного телефона X / Y / Z: Направление относительно определения экрана

Диапазоны:

  • Ось X: вращайте Beta(X) вверх и вниз, диапазон значений: [-180°~180°]
  • Ось Z: поворот влево и вправо Alpha(Z), диапазон значений: [0°, 360°]
  • Ось Y: кручение может быть Гамма(Y), диапазон значений: [-90°, 90°]

image.png

Когда телефон стоит вертикально, а передняя часть (90 градусов) обращена к вам.

image.png

Наблюдая за приведенным выше рисунком в сочетании с системой координат ThreeJS, можно сделать ключевые выводы:

  • лат соответствует (beta - 90) * (Math.PI / 180) [радиан преобразования угла]
  • lon соответствует гамме * (Math.PI / 180) [радиан преобразования угла]

Этого альфа-ракурса больше нет в этом панорамном взаимодействии.

Держите телефон вертикально лицевой стороной (90 градусов) к себе, поверните телефон, чтобы продемонстрировать

Jietu20210530-104349-HD.gif

Браузер Chrome может включить симуляцию гироскопа, операция выглядит следующим образом:

image.png

Тогда код прост:

// 角度换算弧度公式
const L = Math.PI / 180

// 陀螺仪交互
window.addEventListener('deviceorientation', function (evt) {
    lon = evt.alpha * L
    lat = (evt.beta - 90) * L
})

Эффект следующий:

Jietu20210530-112850-HD.gif

Следует отметить, что значение направления мобильного телефона, полученное H5, имеет явный джиттер в некоторых мобильных телефонах Android.Даже если мобильный телефон все еще находится на рабочем столе, данные, выдаваемые гироскопом, также будут дрожать (эта проблема не не относится к принципу, только в панорамном приложении Студенты, не интересующиеся проблемами, возникающими в процессе, могут его пропустить 😄)

Jietu20210530-112405-HD.gif

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

Формула выглядит следующим образом:

image.png Когда K=1, это реальные данные, а если больше 1, значение изменения может быть разбавлено.

Но появились новые проблемы:Парадокс чувствительности и стационарности

  • Чем меньше коэффициент фильтра, тем более гладкий результат фильтрации, но ниже чувствительность.
  • Чем больше коэффициент фильтра, тем выше чувствительность, но тем нестабильнее результат фильтра.

image.png

Согласно выводу, сделанному из статистических данных, значение К равно 10, а чувствительность и плавность лучше.

Адрес опыта:azuoge.github.io/Opanorama/

3.4 Сочетание жестов и взаимодействия с гироскопом

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

Конкретные идеи заключаются в следующем:

lat = touch.lat + orienter.lat + fix.lat    // 取值范围:[-90,90]
lon = touch.lon + orienter.lon + fix.lon    // 取值范围:[0,360]

Среди них touch — это влияние жестов, orienrer — это влияние гироскопов, а fix — поправочный коэффициент, чтобы результаты преобразования долготы и широты всегда соответствовали диапазону значений.



Полный код этой статьи находится в: https://github.com/azuoge/Opanorama, добро пожаловать, чтобы прочитать и обсудить.