实现一个360全景的N种方案

简介: 手把手教你实现360全景浏览效果。
作者 | 甜虾

image.png
360全景浏览是一种性价比很高的虚拟现实解决方案,给人一种全新的浏览体验,让你足不出户就能身临其境地感受到现场的环境。该技术被广泛地应用在房产、酒店、家居等领域。在互联网订房已经普及的时代,在网站上用全景展示酒店宾馆的各种餐饮和住宿设施,是吸引顾客的好办法。利用网络,远程虚拟浏览宾馆的外型、大厅、客房、会议厅等各项服务场所,展现宾馆舒适的环境,给客户以实在感受,促进客户预定客房。在酒店大堂提供客房的全景展示,再也不用麻烦客户在各个房间会场穿梭,就能观看各房间的真实场景,更方便客户确认和挑选客房,进而提高效率,用户体验更胜一筹。

下面我们使用三种方法讨论一个360全景的实现。

CSS3

利用 CSS 中的变换、旋转等属性就可以实现一个360全景。实现的基本思路如下:

  • 利用 CSS3 做出一个 3D 立方体。
  • 在立方体的6个面设置目标图片(利用全景工具导出的图片,一共有6张)。
  • 使用perspective、translateZ、transform-style: preserve-3d 等属性改变视图的大小。
  • 添加触摸事件改变translateX、translateY的角度数即可实现一个基本的全景图效果。

下面我们尝试使用 div 做出一个 3D 立方体:

写出6个面:

<div class="scene">
  <div class="cube">
    <div class="cube__face cube__face--front">front</div>
    <div class="cube__face cube__face--back">back</div>
    <div class="cube__face cube__face--right">right</div>
    <div class="cube__face cube__face--left">left</div>
    <div class="cube__face cube__face--top">top</div>
    <div class="cube__face cube__face--bottom">bottom</div>
  </div>
</div>

image.png
perspective 指定观察者与 z=0 平面的距离,使具有三维位置变换的元素产生透视效果(近大远小原理)。transform-style 指定其为子元素提供2D还是3D的场景。

如下图,高度为 600px 的元素,距离 z=0 的屏幕 300px,视角与屏幕距离1000px,根据相似三角形的原理,可以计算出该元素在屏幕上的投影高度为 857px,即实际我们看到的元素高度。
image.png
关于这个属性的详细讲解可查看css3系列之详解perspective。

写出基本定位

.scene {
  width: 200px;
  height: 200px;
  perspective: 600px;
}

.cube {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
}

.cube__face {
  position: absolute;
  width: 200px;
  height: 200px;
}

image.png

旋转各面,使“三对”面互相垂直,达到如下效果

image.png

.cube__face--front  { transform: rotateY(  0deg); }
.cube__face--right  { transform: rotateY( 90deg); }
.cube__face--back   { transform: rotateY(180deg); }
.cube__face--left   { transform: rotateY(-90deg); }
.cube__face--top    { transform: rotateX( 90deg); }
.cube__face--bottom { transform: rotateX(-90deg); }

结果如下图:

image.png

貌似看不出怎么变换的?我们将以上重合的两个面的旋转角度稍微调整下,就可以更直观地看出效果:

image.png

各面继续向两侧位移

.cube__face--front  { transform: rotateY(  0deg) translateZ(100px); }
.cube__face--right  { transform: rotateY( 90deg) translateZ(100px); }
.cube__face--back   { transform: rotateY(180deg) translateZ(100px); }
.cube__face--left   { transform: rotateY(-90deg) translateZ(100px); }
.cube__face--top    { transform: rotateX( 90deg) translateZ(100px); }
.cube__face--bottom { transform: rotateX(-90deg) translateZ(100px); }

image.png

整个过程可用下图表示:

640 (2).gif

最终效果:

640 (3).gif

通过调整容器样式的 perspective 属性值,将视角放置在立方体中。将每个面的尺寸放大,添加上全景图切出的6面图,添加上鼠标事件,便可实现360全景效果。

640 (4).gif

扫码看效果:

image.png

Three.js

Three.js 是一款运行在浏览器中的 webGL 引擎,我们可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,利用 Three.js 我们可以轻松地实现各种想要的几何体。

上面的方案中,我们用 CSS3 “拼”出了一个立方体,而webGL中立方体等各种几何体是最常见的。我们可以利用 Threejs 中的立方体实现全景图功能,把立方体当成天空盒子,将无缝衔接的图片贴上,看起来就像在一个场景中,相机一般放置在中央,只要离边缘足够远就看不出是立方体,但如果超出边界就能看到他们。

下面是简单的实现过程:

初始化一个立方体几何,给材料上色,便得到了一个立方体:

const geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial({ color: 0x156289 });
const skyBox = new THREE.Mesh(geometry, material);

640 (5).gif

将立方体的纹理颜色换成图片纹理:

const texture = new THREE.TextureLoader().load('textures/crate.gif');
const geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { map: texture } );
const skyBox = new THREE.Mesh(geometry, material);

640 (6).gif

将6张全景图片替换掉上面相同的纹理:

图片加载的顺序是正X(px.jpg),负X(nx.jpg),正Y(py.jpg),负Y(ny.jpg),正Z(pz.jpg)和负Z(nz.jpg),将他们分别赋给6个材质的贴图,作为立方体skyBox的材质。

image.png

const geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
const textures = [
  'https://img.alicdn.com/imgextra/i3/O1CN01LsO1Bk20QbKpFTUQr_!!6000000006844-0-tps-1500-1500.jpg',
  'https://img.alicdn.com/imgextra/i3/O1CN01uTWCLc1XOCOuA92H0_!!6000000002913-0-tps-1500-1500.jpg',
  'https://img.alicdn.com/imgextra/i4/O1CN016lU3YJ1JdrJuFTcWt_!!6000000001052-0-tps-1500-1500.jpg',
  'https://img.alicdn.com/imgextra/i2/O1CN01nYe2Mn1ohkmBVyKpp_!!6000000005257-0-tps-1500-1500.jpg',
  'https://img.alicdn.com/imgextra/i4/O1CN014TNffn1nlaTfA98Fg_!!6000000005130-0-tps-1500-1500.jpg',
  'https://img.alicdn.com/imgextra/i1/O1CN01sS5m781ya6JgLSaVk_!!6000000006594-0-tps-1500-1500.jpg',
];
const materials = [];
const textureLoader = new THREE.TextureLoader();

for (let i = 0; i < 6; i ++ ) {
  materials.push( new THREE.MeshBasicMaterial({ map: textureLoader.load(textures[i]) }));
}
const skyBox = new THREE.Mesh(geometry, materials);

640 (7).gif

因为相机在skyBox的内部,而内部的面不会显示,所以要将X轴或者Z轴的放大倍数变为负数,这样才能看到内部,scale.z=-1时相当于将Z轴正向的面移到Z轴负方向上。

skyBox.geometry.scale( 1, 1, - 1 );

640 (8).gif

进一步优化体验:

camera.position.z = 0.01;  // 将相机放在里面
const controls = new OrbitControls( camera, renderer.domElement );
controls.enableZoom = false; // 禁用放大
controls.enablePan = false; // 禁用双指缩放
controls.enableDamping = true; // 开启阻尼效果
controls.rotateSpeed = -0.25; // 旋转方向取反,使内部拖拽旋转方向一致

扫码看效果:

image.png

pannellum

pannellum 是一个轻量级、免费、开源的基于 webGL 的全景播放器。支持多种投影方式、支持热点、支持漫游、视频等,且文档清晰明了、简单易用、API 比较丰富,易于功能扩展。

使用:

pannellum.viewer(
  'container',
  {
    type: 'cubemap',
    cubeMap: [
      'https://img.alicdn.com/imgextra/i4/O1CN014TNffn1nlaTfA98Fg_!!6000000005130-0-tps-1500-1500.jpg',
      'https://img.alicdn.com/imgextra/i3/O1CN01LsO1Bk20QbKpFTUQr_!!6000000006844-0-tps-1500-1500.jpg',
      'https://img.alicdn.com/imgextra/i1/O1CN01sS5m781ya6JgLSaVk_!!6000000006594-0-tps-1500-1500.jpg',
      'https://img.alicdn.com/imgextra/i3/O1CN01uTWCLc1XOCOuA92H0_!!6000000002913-0-tps-1500-1500.jpg',
      'https://img.alicdn.com/imgextra/i4/O1CN016lU3YJ1JdrJuFTcWt_!!6000000001052-0-tps-1500-1500.jpg',
      'https://img.alicdn.com/imgextra/i2/O1CN01nYe2Mn1ohkmBVyKpp_!!6000000005257-0-tps-1500-1500.jpg',
    ]
  }
);

扫码看效果:

image.png

@ali/rmpi-cube

@ali/rmpi-cube 是我们基于 pannellum 开发的一个360全景容器组件,支持了立方体投影和球型投影两种方式、支持 pannellum 的所有配置、支持场景切换、陀螺仪效果及添加热点(开发中)等。目前该组件已应用在飞猪酒店详情中。

import React, { useEffect, useState } from 'react';
import Cube from '@ali/rmpi-cube';

export default function CubeDemo() {
  const [scenes, setScenes] = useState([]);

  useEffect(() => {
    // 模拟异步请求
    setTimeout(() => {
      setScenes(
        [
          {
            preview: 'https://img.alicdn.com/imgextra/i1/O1CN01dVOIEe1IhEcaIPw2z_!!6000000000924-0-tps-100-100.jpg',
            title: '客厅',
            // type: 'equirectangular' 时需要
            // 或 panorama: 'https://img.alicdn.com/imgextra/i4/6000000007276/O1CN01Hp5gIf23cSOvzzA9k_!!6000000007276-0-hotel.jpg',
             // type: 'cubemap' 时需要
            cubeMap: [
              // 顺序是:前、右、后、左、上、下
              'https://gw.alicdn.com/imgextra/i3/O1CN01550SRA1JcwWgs0sIj_!!6000000001050-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i4/O1CN01e796bV1P2CRfCQkrA_!!6000000001782-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i4/O1CN01GcW84X29SHK4oJlWc_!!6000000008066-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i2/O1CN01ZHLck11GX2ZgBHA4o_!!6000000000631-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i2/O1CN019c9xKu1ig1aC7pWPk_!!6000000004441-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i4/O1CN01XfaKOu1kzNYODz7HD_!!6000000004754-0-tps-1500-1500.jpg'
            ]
          },
          {
            preview: 'https://img.alicdn.com/imgextra/i1/O1CN01KU3hrj1uJNO2OdyaC_!!6000000006016-0-tps-100-100.jpg',
            // 或 panorama: 'https://img.alicdn.com/imgextra/i4/6000000004110/O1CN01lKLSsP1gEQSNAMIsJ_!!6000000004110-0-hotel.jpg',
            title: '书房',
            cubeMap: [
              'https://img.alicdn.com/imgextra/i1/O1CN01fWDIfB1bWgC3NnVVa_!!6000000003473-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i2/O1CN01xt97cb1YMeg4BOCbI_!!6000000003045-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i1/O1CN01xKTq1u1DR8cdeMeYt_!!6000000000212-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i3/O1CN01Zko8Qy1p1uCLUYBji_!!6000000005301-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i3/O1CN01k3AVvK28W71UNWXW7_!!6000000007939-0-tps-1500-1500.jpg',
              'https://img.alicdn.com/imgextra/i1/O1CN015MBT6P1N8x3J83Fqo_!!6000000001526-0-tps-1500-1500.jpg'
            ]
          }
           ......
        ]
      );
    }, 1000);
  }, []);

  return (
    <Cube
      container="viwer"
      config={{
        type: 'cubemap', // or 'equirectangular'
        scenes,
      }} />
  );
}

扫码看效果:

image.png

640 (9).gif
640 (10).gif

球型投影

以上的实现全部是利用立方体实现的,除此之外,我们还可以使用圆柱体投影的方式,three.js 和 pannellum 都支持这种方式。
image.png
three.js 中:

const geometry = new THREE.SphereBufferGeometry( 300, 60, 40 ); // 初始化一个球体
geometry.scale( - 1, 1, 1 ); // 翻转x轴,将所有面朝向里
const texture = new THREE.TextureLoader().load( 'https://img.alicdn.com/imgextra/i2/6000000004217/O1CN01djW9bE1h1QprTMP5d_!!6000000004217-0-hotel.jpg' ); // 加载全景纹理
const material = new THREE.MeshBasicMaterial( { map: texture } );
mesh = new THREE.Mesh( geometry, material );

pannellum 中:

pannellum.viewer('panorama', {
    type: 'equirectangular',
    panorama: 'https://img.alicdn.com/imgextra/i2/6000000004217/O1CN01djW9bE1h1QprTMP5d_!!6000000004217-0-hotel.jpg'
});

640 (11).gif

页面和代码可参考 https://threejs.org/examples/#webgl_panorama_equirectangular

球型全景和立方体全景两种方式比较起来各有自己的特点,比如:

  • 球型全景图更贴近人眼的构建模式,只用一张图,但图片的尺寸可能会很大
  • 球型全景图,从赤道到两极,横向拉伸不断加剧,导致南北极存在扭曲的情况
  • 立方体兼容性更好,还可以用css3 实现
  • 从模型上来说,球型是由非常多的三角面拼接出来的,比立方体更复杂,所以立方体有更高的性能

飞猪酒店详情中,服务端对这两种方式都提供了图片资源,但在使用球型全景的方式时少数酒店上传的图片过大,导致部分手机上组件加载资源出错,所以最终决定采用立方体的方式。

krpano

krpano 是一款较专业的全景引擎平台。目前市场中较大的全景平台大部分都用了 krpano 。但由于它用法较复杂,有一定的学习成本,需要掌握其使用的 XML 的各种配置、功能较多较重且是收费的,对于酒店详情的简单的业务场景来说有点重了,所以权衡之后没有选择它,而是选择了相对轻量的 pannellum。

<div id="container"></div>

embedpano({
  xml: 'https://raw.githubusercontent.com/xiaotianxia/three.js-learning/gh-pages/examples/config.xml',
  target: 'container',
  html5: 'auto',
  mobilescale: 1.0,
});

image.png

总结

本文叙述了实现360全景功能4种不同的方案,包括:CSS3、Three.js、pannellum 和 krpano,在基于 webGL 的方案中,介绍了两种主要的投影方式:立方体投影和球型投影,并给出了 demo 代码和页面,推荐了基于 pannellum 开发的360全景容器组件:@ali/rmpi-cube。如有因为,欢迎指正和交流。

参考

  • Front-End Challenge Accepted: CSS 3D Cube

https://www.smashingmagazine.com/2016/07/front-end-challenge-accepted-css-3d-cube/

  • Intro to CSS 3D transforms

https://3dtransforms.desandro.com/

  • css3系列之详解perspective

https://www.cnblogs.com/yanggeng/p/11285856.html

  • threejs

https://threejs.org/

  • pannellum

https://pannellum.org/


image.png

相关文章
|
7月前
|
传感器 数据可视化 vr&ar
三维视觉产品方案介绍
三维视觉产品方案介绍
|
2月前
|
机器学习/深度学习 API 计算机视觉
视觉智能平台常见问题之使用智能分镜功能拆分镜头丢失部分镜头如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
34 0
|
4月前
|
存储 Kubernetes 监控
K8s技术全景:架构、应用与优化
K8s技术全景:架构、应用与优化
252 0
|
监控 双11 异构计算
3D全景沉浸式体验场景未来城质量保障方案总结
未来城是淘宝探索下一代互联网电商在3D场景中的创新尝试,借助了云端强大的GPU完成高清复杂的场景渲染,实现3A影视级画面,端到端200ms时延,用户无需下载大型的安装包,就可在淘宝APP上启用自己的3D形象在虚拟世界探索,完成任务、交易、社交等互动玩法。云端渲染既有移动端发版的一些特点,也有服务端部署的特性,本文重点从测试发布流程、功能体验、性能标准、监控排查几方面阐述了云渲染的质量保障策略。
133 0
|
消息中间件 编解码 人工智能
|
JavaScript 前端开发 异构计算
大屏适配方案汇总
大屏适配方案汇总
大屏适配方案汇总
|
机器学习/深度学习 编解码 算法
|
机器学习/深度学习 编解码 算法
PixelAI移动端实时人像分割 优酷端侧弹幕穿人技术实战系列
PixelAI移动端实时人像分割 优酷端侧弹幕穿人技术实战系列
606 0
PixelAI移动端实时人像分割 优酷端侧弹幕穿人技术实战系列
|
Web App开发 移动开发 编解码
移动端法门:自适应方案和高清方案
笔者从毕业开始做前端到现在,90% 的项目是移动端打交道,所以当简历上写了“移动H5”几个字时,必会被问到自适应方案与高清方案
656 0
移动端法门:自适应方案和高清方案
|
机器学习/深度学习 存储 数据采集
视觉感知未来,高德数据采集模型部署实践
作为DAU过亿的国民出行服务平台,高德地图每天为用户提供海量的检索、定位和导航服务,实现这些服务需要有精准的道路信息,比如电子眼位置、路况信息、交通标识位置信息等。读者是否会好奇,高德是如何感知到现实世界的道路信息,并提供这些数据给用户呢?
318 0
视觉感知未来,高德数据采集模型部署实践