предисловие: Эта статья будет посвящена:Понять, что такое панорама --> как формировать панораму --> принцип взаимодействия с панорамойПриходите объяснить, научит как реализовать крутую веб-панораму с нуля и объяснит принцип. Xiaobai тоже можно узнать.Рекомендуется собирать и учиться.Если есть вопросы,прошу обсудить в комментариях.Автор часто проверяет и отвечает.
1. Понять, что такое панорама
1.1 Определение панорамы
Определение: Панорама – это пространствовсеПосмотреть.
С точки зрения непрофессионала: Все фотографировали, поэтому давайте подумаем о процессе фотографирования: стоя в определенном месте, держа камеру и снимая под определенным углом, вы можете получить изображение пейзажа с этого ракурса, и панорама? Это интерактивная фотография, которая стоит в определенном пространстве, держит камеру, снимает под углом 360 градусов, получает фотографии пейзажей со всех ракурсов, объединяет их, а затем с помощью специальной технологии показывает всем.
Пример панорамы:
Испытайте QR-код (поддержите код сканирования WeChat):
1.2 Метод панорамного отображения
Существует множество способов отображения панорамы, например: цилиндрическая панорама, кубическая панорама, сферическая панорама и т. д.
Самое популярное понимание: с большой картонной коробкой, установленной на голове, увидеть сцену (такой способ отображения - панорамный вид куба)
Цилиндры и кубы имеют области пересечения, и взаимодействие интерфейсов в области пересечения будет тупиковым. Итак, лучший способ представить панорамуПанорама сферы, 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 (панорама будет использоваться позже)
ссылка на скачивание: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>
Эффект страницы браузера (не забудьте включить отладку моделирования мобильного телефона):
2.2 Базовые очки знаний
2.2.1 Долгота и широта
В данной статье для работы с панорамой используются долгота и широта, и необходимо популяризировать знания о долготе и широте
Широта и долгота — это комбинация долготы и широты, образующая систему координат. Называемая географической системой координат, это сферическая система координат, которая использует сферу трехмерного пространства для определения пространства на земле и может указывать любое положение на поверхности земли.
Как показано на рисунке, долгота: долгота, диапазон значений: [0,360], широта: широта, диапазон значений: [-90,90];
2.2.2 Преобразование 3D-координат из широты и долготы
Сферическая точка {lon, lat}, где R — радиус сферы, а положение трех JS-координат сферической точки:
развязать:
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>
Эффект:
На данный момент изготовление нашей панорамы завершено, (учитывается только код js: всего28 строкКод, разве я не главная партия? 😁).
3. Принцип панорамного взаимодействия
3.1 взаимодействие жестов вращения
Вращение взаимодействия жестов относится к операции скольжения одним пальцем, что согласуется с взаимодействием скольжения земного шара.
Система координат экрана, левый верхний угол — начало координат, ось X: слева направо, ось Y: сверху вниз, Скольжение пальца по экрану вызовет последовательно три события: touchstart, touchmove и touchend; положение пальца на экране записывается в объект события
Процесс скольжения пальца по экрану:
- touchstart: записать позицию, с которой начинается скольжение (startX, startY)
- touchmove: записывает текущую позицию (curX, curY) минус значение предыдущей позиции, вычисляет длину дуги, деленную на радиус, умноженный на коэффициент, и вычисляет (lon, lat)
- touchend: временно бесполезен
Среди них: значение длины дуги R - расстояние скольжения экрана
Затем один палец скользит по экрану от 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
})
Эффект управления одним пальцем:
В приведенном выше коде добавлено взаимодействие с панорамой одним пальцем, однако отсутствует инерция вращения. Далее добавим инерционную анимацию:
Реализована инерция скольжения, и процесс скольжения пальца по экрану:
-
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 был представлен ранее, и упоминалась камера.Панорамный зум также основан на принципе масштабирования и съемки содержимого фотографии, когда камера делает снимок.
Код камеры, созданный с помощью ThreeJS, выглядит следующим образом:
const camera = new THREE.PerspectiveCamera( fov , aspect , near , fear )
Описание параметра:
в,
- рядом: принять значение по умолчанию: 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°]
Когда телефон стоит вертикально, а передняя часть (90 градусов) обращена к вам.
Наблюдая за приведенным выше рисунком в сочетании с системой координат ThreeJS, можно сделать ключевые выводы:
- лат соответствует (beta - 90) * (Math.PI / 180) [радиан преобразования угла]
- lon соответствует гамме * (Math.PI / 180) [радиан преобразования угла]
Этого альфа-ракурса больше нет в этом панорамном взаимодействии.
Держите телефон вертикально лицевой стороной (90 градусов) к себе, поверните телефон, чтобы продемонстрировать
Браузер Chrome может включить симуляцию гироскопа, операция выглядит следующим образом:
Тогда код прост:
// 角度换算弧度公式
const L = Math.PI / 180
// 陀螺仪交互
window.addEventListener('deviceorientation', function (evt) {
lon = evt.alpha * L
lat = (evt.beta - 90) * L
})
Эффект следующий:
Следует отметить, что значение направления мобильного телефона, полученное H5, имеет явный джиттер в некоторых мобильных телефонах Android.Даже если мобильный телефон все еще находится на рабочем столе, данные, выдаваемые гироскопом, также будут дрожать (эта проблема не не относится к принципу, только в панорамном приложении Студенты, не интересующиеся проблемами, возникающими в процессе, могут его пропустить 😄)
Нам нужно обработать цифровой выход гироскопа, который используется при передаче сигнала.алгоритм фильтрации нижних частот
Формула выглядит следующим образом:
Когда K=1, это реальные данные, а если больше 1, значение изменения может быть разбавлено.
Но появились новые проблемы:Парадокс чувствительности и стационарности
- Чем меньше коэффициент фильтра, тем более гладкий результат фильтрации, но ниже чувствительность.
- Чем больше коэффициент фильтра, тем выше чувствительность, но тем нестабильнее результат фильтра.
Согласно выводу, сделанному из статистических данных, значение К равно 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, добро пожаловать, чтобы прочитать и обсудить.