H5打造3d場景不徹底攻略(二): Amazing CSS3D

前言

對的,本文就是着重介紹如何使用CSS3中的3D變換打造出H5中的3D效果。靈感來源於造物節團隊的3d引擎,由於使用方法比較複雜,也沒有開源的API文檔,因而想本身另外造個輪子,便開始了相關內容的學習和實踐。
衆所周知,目前市面上的H5 3D類庫(如Three)、引擎(Egret)、構建工具(kpano、720雲)都或存在體積太大、不開源、非免費、學習成本高等問題。對於咱們較爲熟悉的CSS3,爲何就不對它好好利用一把呢?誠然,CSS3存在咱們比較清楚的短板,CSS對平面的渲染能力高,可是對3D建模方面便力不從心了。css

咱們知道3D的表現形式即讓咱們經過平面可從不一樣角度看到真實物體的展現效果。html

在計算機世界裏,3D世界是由點組成,兩個點可以組成一條直線,三個不在一條直線上的點就可以組成一個三角形面,無數三角形面就可以組成各類形狀的物體,以下圖。css3

clipboard.png

咱們一般把這種網格模型叫作Mesh模型。給物體貼上皮膚,或者專業點就叫作紋理,那麼這個物體就活靈活現了。最後無數的物體就組成了咱們的3D世界。web

Three中模型解析器的原理是將頂點數組將模型的頂點用數組儲存起來,再利用three中的face函數取得定點數組中的三個或四個頂點的索引構成空間平面。如此反覆,模型就被完整構造出來了。數組

因而,越複雜的物體就須要越多的網面拼接。而css中是不存在根據座標創建空間平面的能力的。函數

(插個題外話,其實css有一個屬性與座標有關,那就是clip-path。這個屬性的特性賦予了css3必定的建模能力。實現方法可參考這篇文章 純clip-path打造的3D模型渲染器工具

CSS3實現3D全景

。上篇文章介紹了Web3D的一些表現形式,這裏着重談談怎麼以CSS3實現3D全景。下面會探索Three實現全景的方案,由於WebGL門檻和學習成本仍是比較高的,不適於用於快速開發。造物節的CSS3d全景已有文章對其進行了技術探祕,但都未深刻談及具體實現方式。學習

要清晰理解實現方式,必須對CSS3的transform、perspective有必定的認識。
原理方面的東西我就不深刻講了,你們能夠先看看這篇文章,對CSS3D有一個大體的概念。
玩轉 CSS 3D - 原理篇ui

CSS全景可經過創建柱形或者立方體再經過貼圖方式實現。也許會有人問,球體行不行?其實是不行的,球體模型由無數個極小的平面拼接構成連貫曲面,而CSS缺少使平面扭曲的屬性。球體模型咱們可使用上文提過的Clip-3d建造出,可是,貼圖問題就解決不了了。this

天空盒子

相信不少打造過或有了解過3d全景的同行們都知道這個概念。實際上Skybox就是一個立方體,經過給六個面貼上不一樣的,邊緣能夠無縫貼合的圖片,再將視角伸入盒子內部。能夠想象成咱們本身站入了一個巨型立方體盒子內部,移動視角便能看到不一樣的場景。

圖片描述

一、貼圖
來看一張天空盒子的貼圖,剪頭指向的邊緣表明須要無縫貼合的邊。
clipboard.png
從上圖能夠看出只要相互貼合的兩個面上的圖像可以無縫拼接,那麼再經過對各個面進行必定的旋轉變換,天空盒子就能被打造出來了。

那麼問題來了,怎麼去拍攝製做這樣的圖片呢?這就須要經過一些專業軟件了,好比pano2vr,max等。其實,須要用到這些專業工具打造的全景對畫質和拼合度的要求都很是高了,而單純依靠CSS3中的變化給不了它們很好的體驗。

但咱們今天討論的是某些運營活動H5打造的全景,此全景不必定真實存在,或者是和真實場景有必定的比例差距。例如星空、海底。對於這類貼合度可人爲改變的全景圖的打造,咱們能夠採用現有的高清圖片,再經由PS轉換成六面全景圖。
貼一篇文章 Create a Skybox From Photos
其實主要思想是
在一張大圖上勾畫出六個面的選取 >
選擇大圖中某個面的相鄰面將其旋轉到須要拼合的盒子的某個面上,使他們完美貼合 >
獲得最合理的六面貼圖後,觀察有無創造出新的邊緣,經過蒙版等工具使他們天然融合。

二、構造
貼圖完成就能夠建立立方體了。首先將建立好的六個面切割出來,以front、back、left、right…命名標記位置。

.sence {
      -webkit-perspective: 1000px;
    }
    .cube {
      width: 500px;
      height: 500px;
      margin: 100px auto;
      transform-style: preserve-3d;
    }
    .cube img {
      width: 130px;
      height: 130px;
      position: absolute;
    }
    .cube img:nth-child(1) {

    }
    .cube img:nth-child(2) {
      transform:  rotateY(180deg);
    }
    .cube img:nth-child(3) {
      transform:  rotateY(90deg);
    }
    .cube img:nth-child(4) {
      transform:  rotateY(-90deg);
    }
    .cube img:nth-child(5) {
      transform:  rotateX(90deg);
    }
    .cube img:nth-child(6) {
      transform:  rotateX(-90deg);
    }
<div class="sence">
    <div class="cube">
      <img src="img/skybox/front.jpg" alt="" />
      <img src="img/skybox/back.jpg" alt="" />
      <img src="img/skybox/left.jpg" alt="" />
      <img src="img/skybox/right.jpg" alt="" />
      <img src="img/skybox/top.jpg" alt="" />
      <img src="img/skybox/bottom.jpg" alt="" />
    </div>
  </div>

準備好6個面,載入貼圖。經過旋轉,使得每一個面旋轉到相印的位置。如左邊的面由本來面朝咱們的圖片繞Y軸逆時針旋轉90°獲得。(注意Y軸逆時針旋轉是正數)

此時會獲得下圖這樣的效果:
clipboard.png

可是因爲每一個面的旋轉中心都在其正中位置,所以還不能造成正方體。因而咱們須要讓每一個面產生必定的位移。

貼一張座標系圖以助於你們理解。
clipboard.png

如今首先讓front位移到應該到的位置,因爲全景圖的鏡頭在立方體內部,所以,能夠想象一下,咱們須要將圖片日後移動。移動距離很明顯爲立方體邊長的一半。在這裏是65px。獲得下圖結果。

.cube img:nth-child(1) {
      transform: translateZ(-65px);
    }

clipboard.png

照這樣看,是否是back位移爲translateZ(65px),left爲translateX(-65px),top translateY(-65px)呢?但結果並非咱們想要的。
clipboard.png

從新看回上文空間座標系的那張貼圖,咱們會發現,平面旋轉後,其對應的三個軸的位置也改變了。如圖片繞Y旋轉後,Z軸指向爲屏幕的水平方向。繞X旋轉後,Z軸指向垂直方向。所以咱們很容易發現,其實要將貼面移動到正確的位置,都只須要讓他們translateZ(-width/2px)就能夠了。

clipboard.png

爲了讓你們容易理解,我這裏設置了一個較大的perspective。要想獲得全景的效果,咱們將鏡頭拉近讓它進入到box裏面就能夠了。

clipboard.png

接下來綁定手勢,就可讓它動起來啦。

部分代碼:

viewer.on('touchstart', function(e) {
    x1 = e.targetTouches[0].pageX; - $(this).offset().left;
    y1 = e.targetTouches[0].pageY; - $(this).offset().top;
});

viewer.on('touchmove',function(){
    var dist_x = x2 - x1,
        dist_y = y2 - y1,
        deg_x = Math.atan2(dist_y, perspective) / Math.PI * 180,
        deg_y = -Math.atan2(dist_x, perspective) / Math.PI * 180,
        i,
        c_x_deg += deg_x;
        c_y_deg += deg_y;
        
    cube.css('transform', 'rotateX(' + deg_x + 'deg) rotateY(' + deg_y + 'deg)');
})

Math.atan2(y,x) 方法:獲得從 x 軸到點 (x,y) 之間的角度。對於空間左邊系比較難理解,你們能夠想象成一張以空間Z軸爲Y軸的平面繞X軸正方向旋轉的角度即爲cube繞空間Y軸旋轉的角度。

柱形

柱形全景也不算複雜。關於圓柱形的打造方法,你們能夠參考下這篇文章CSS3 3D transforms系列教程-3D旋轉木馬
有了這個基礎,咱們能夠寫一段函數快速構造柱形全景。

先來看下頁面結構

<style>
  body {
    height: 100%;
    overflow: hidden;
  }
    .scene {
      width: 100%;
      height: 1170px;
      transform: translateX(-50%) translateY(-50%);
      top: 50%;
      left: 50%;
      position: absolute;
    }
    .cube {
      transform-style: preserve-3d;
      height: 100%;
      width: 100%;
      margin: 0px auto;
    }
    .cube_bg {
      transform-style: preserve-3d;
      height: 100%;
      width: 128px;
      margin: 0px auto;
    }
    .cube_bg div {
      height: 100%;
      
      /* 這裏爲圓柱形的每一個面都設定了一樣的背景圖 那麼在建造柱形時再也不須要手動切圖 */
      background-image: url("img/zao/zao.png");
      
      background-repeat: no-repeat;
      position: absolute;
      top: 0;
    }
</style>

<body>
  <div class="scene">
    <div class="cube">
      <div class="cube_bg">
        <!--
        這裏是柱形全景背景貼圖
        -->
      </div>
      <div class="cube_item">
        <!--
        這裏是柱形全景中的小元件
        -->
      </div>
    </div>
  </div>
</body>
function creCylinder(lenZ,pieceWid,angle,slice){

    /* 
    pieceWid 表示單個柱形塊狀寬度
    angle表示柱形內角
    slice表示有多少個面拼接 
    slice越多,拼合的面越接近曲面
    */
    
  var l = pieceWid*slice; // 畫布全長
  var ag = angle/slice // 旋轉角度

  var html = '';

  /*
    設置每一個面的旋轉角度和位移 由於要分割成多個面,因此應該爲每一個面的背景圖設置不一樣的`background-position`
    */

  for(var i=0,len=slice;i<len;i++){
    html+='<div style="transform: rotateY(-'+ag*i+'deg) '+
          'translateZ('+lenZ+'px);'+
          'width:'+(pieceWid)+'px;'+
          'background-position: -'+(i*pieceWid)+'px 0;'+
          'background-size: '+(l)+'px 100%;"></div>';
  }

    return html;
}

function renderPano(pieceWid,angle,slice){

    var vw = $(window).width();

    var RADIAN = 0.017453293; // 弧度制 將角度轉成弧度

    var innerAngle = angle/(2*slice); //內角,用來計算translateZ

    // 這裏的原理和上文旋轉木馬連接一致
    var lenZ = -(pieceWid/2)*Math.tan((90-innerAngle)*RADIAN);

    /*  
        由於默認是由畫布的最左端開始旋轉 因此處於咱們面前的是畫布的最左端和最右端及其鏈接處
        要想畫布中央顯示再咱們面前,這裏須要給cube_bg加上必定的繞Y旋轉角度
    */
    var rotate = ((angle/slice)*(slice-1))/2,
        perspective = -lenZ-5;

    var cube_bg = $('.cube_bg'),
        scene = $('.scene');

    var cylinder = creCylinder(lenZ,pieceWid,angle,slice);

    cube_bg.html(cylinder).css('transform','rotateY('+rotate+'deg)');
    scence.css('-webkit-perspective',perspective+'px');
    
    //最後調用一下
    renderPano(128,360,20);

這裏解釋一下perspective爲何要設成 -lenZ-5
看一張圖,上面的lenZtranslateZ值,爲負值。
perspective爲鏡頭到屏幕的距離,由於此時鏡頭在柱體內部,所以不能看到柱體後面的圖像。
當perspective值爲-lenZ值時,正好柱體back面能與鏡頭在同一平面上,爲了不它有必定的機率遮擋鏡頭,咱們能夠將鏡頭拉近一些。便設成了-lenZ-5。這個時候就能保證鏡頭處於柱體內部,同時也能更廣角度地觀察到柱體全景。

clipboard.png

你們能夠複製代碼體驗一下。這裏的背景圖我選用的是本身拼合成的造物節背景圖。

優劣勢對比

相信你們也有體會,天空盒製造起來會相對的簡單,而且天空和地面都能被考慮進去。可是因爲面面間的貼合角度太大,若物體正好處於相互貼合的兩個面,會給人一種被攔腰折斷的感受。而柱形圖對這種狀況有了比較好的解決,可是天空和地面的貼圖就比較困難了,通常狀況下只能經過給scene添加背景圖片模擬。

未完待續…

相關文章
相關標籤/搜索