實現一個 360 全景的 N 種方案

本文來自飛豬前端同窗@甜蝦,教你用 N 種方式用 JS 實現一個 360 全景效果。javascript

概述

360 全景瀏覽是一種性價比很高的虛擬現實解決方案,給人一種全新的瀏覽體驗,讓你足不出戶就能身臨其境地感覺到現場的環境。該技術被普遍地應用在房產、酒店、家居等領域。在互聯網訂房已經普及的時代,在網站上用全景展現酒店賓館的各類餐飲和住宿設施,是吸引顧客的好辦法。利用網絡,遠程虛擬瀏覽賓館的外型、大廳、客房、會議廳等各項服務場所,展示賓館溫馨的環境,給客戶以實在感覺,促進客戶預約客房。在酒店大堂提供客房的全景展現,不再用麻煩客戶在各個房間會場穿梭,就能觀看各房間的真實場景,更方便客戶確認和挑選客房,進而提升效率,用戶體驗更勝一籌。css

下面咱們使用三種方法討論一個 360 全景的實現。html

1、CSS3

利用 CSS 中的變換、旋轉等屬性就能夠實現一個 360 全景。實現的基本思路以下:前端

  • 利用 CSS3 作出一個 3D 立方體。
  • 在立方體的 6 個面設置目標圖片(利用全景工具導出的圖片,一共有 6 張)。
  • 使用 perspective、translateZ、transform-style: preserve-3d 等屬性改變視圖的大小。
  • 添加觸摸事件改變 translateX、translateY 的角度數便可實現一個基本的全景圖效果。

下面咱們嘗試使用 div 作出一個 3D 立方體:html5

  1. 寫出 6 個面:java

    <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,即實際咱們看到的元素高度。css3

    image.png

    關於這個屬性的詳細講解可查看css3 系列之詳解 perspectivegit

  2. 寫出基本定位github

    .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

  3. 旋轉各面,使「三對」面互相垂直,達到以下效果web

    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

  4. 貌似看不出怎麼變換的?咱們將以上重合的兩個面的旋轉角度稍微調整下,就能夠更直觀地看出效果:

    image.png

  5. 各面繼續向兩側位移

    .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

    整個過程可用下圖表示:

    1.gif

    最終效果:

    3.gif

  6. 經過調整容器樣式的 perspective 屬性值,將視角放置在立方體中。將每一個面的尺寸放大,添加上全景圖切出的 6 面圖,添加上鼠標事件,即可實現 360 全景效果。

    4.gif

    掃碼看效果:

    image.png

2、Three.js

Three.js 是一款運行在瀏覽器中的 webGL 引擎,咱們能夠用它建立各類三維場景,包括了攝影機、光影、材質等各類對象,利用 Three.js 咱們能夠輕鬆地實現各類想要的幾何體。 上面的方案中,咱們用 CSS3 「拼」出了一個立方體,而 webGL 中立方體等各類幾何體是最多見的。咱們能夠利用 Threejs 中的立方體實現全景圖功能,把立方體當整天空盒子,將無縫銜接的圖片貼上,看起來就像在一個場景中,相機通常放置在中央,只要離邊緣足夠遠就看不出是立方體,但若是超出邊界就能看到他們。 下面是簡單的實現過程:

  1. 初始化一個立方體幾何,給材料上色,便獲得了一個立方體:

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

    444.gif

  2. 將立方體的紋理顏色換成圖片紋理:

    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);
    複製代碼

    555.gif

  3. 將 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);
    複製代碼

    666.gif

  4. 由於相機在 skyBox 的內部,而內部的面不會顯示,因此要將 X 軸或者 Z 軸的放大倍數變爲負數,這樣才能看到內部,scale.z=-1 時至關於將 Z 軸正向的面移到 Z 軸負方向上。

    skyBox.geometry.scale(1, 1, -1);
    複製代碼

    3333.gif

  5. 進一步優化體驗:

    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

3、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

基於 pannellum 咱們封裝了一個 360 全景容器組件 cube,支持了 pannellum 的全部配置,支持了立方體投影和球型投影兩種方式、場景切換、陀螺儀效果等。目前該組件已應用在飛豬酒店詳情中。 使用:

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: '客廳',
            // 或 panorama: 'https://img.alicdn.com/imgextra/i4/6000000007276/O1CN01Hp5gIf23cSOvzzA9k_!!6000000007276-0-hotel.jpg', //type: 'equirectangular' 時須要
            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

球型投影

以上的實現所有是利用立方體實現的,除此以外,咱們還可使用圓柱體投影的方式,three.js 和 pannellum 都支持這種方式。 O1CN01djW9bE1h1QprTMP5d_!!6000000004217-0-hotel.jpeg 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',
});
複製代碼

5.gif

頁面和代碼可參考 webgl_panorama_equirectangular 球型全景和立方體全景兩種方式比較起來各有本身的特色,好比:

  • 球型全景圖更貼近人眼的構建模式,只用一張圖,但圖片的尺寸可能會很大
  • 球型全景圖,從赤道到兩極,橫向拉伸不斷加重,致使南北極存在扭曲的狀況
  • 立方體兼容性更好,還能夠用 css3 實現
  • 從模型上來講,球型是由很是多的三角面拼接出來的,比立方體更復雜,因此立方體有更高的性能

飛豬酒店詳情中,服務端對這兩種方式都提供了圖片資源,但在使用球型全景的方式時少數酒店上傳的圖片過大,致使部分手機上組件加載資源出錯,因此最終決定採用立方體的方式()。

4、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 種不一樣的方案,包括:CSS三、Three.js、pannellum 和 krpano,在基於 webGL 的方案中,介紹了兩種主要的投影方式:立方體投影和球型投影,並給出了 demo 代碼和頁面,推薦了基於 pannellum 開發的 360 全景容器組件, 若有錯誤,歡迎指正。

參考

  1. Front-End Challenge Accepted: CSS 3D Cube
  2. Intro to CSS 3D transforms
  3. css3 系列之詳解 perspective
  4. threejs
  5. pannellum
相關文章
相關標籤/搜索