对的,本文就是着重介绍如何使用CSS3中的3D变换打造出H5中的3D效果。灵感来源于造物节团队的3d引擎,由于使用方法比较复杂,也没有开源的API文档,因而想本身另外造个轮子,便开始了相关内容的学习和实践。
众所周知,目前市面上的H5 3D类库(如Three)、引擎(Egret)、构建工具(kpano、720云)都或存在体积太大、不开源、非免费、学习成本高等问题。对于咱们较为熟悉的CSS3,为何就不对它好好利用一把呢?诚然,CSS3存在咱们比较清楚的短板,CSS对平面的渲染能力高,可是对3D建模方面便力不从心了。css
咱们知道3D的表现形式即让咱们经过平面可从不一样角度看到真实物体的展现效果。html
在计算机世界里,3D世界是由点组成,两个点可以组成一条直线,三个不在一条直线上的点就可以组成一个三角形面,无数三角形面就可以组成各类形状的物体,以下图。css3
咱们一般把这种网格模型叫作Mesh模型。给物体贴上皮肤,或者专业点就叫作纹理,那么这个物体就活灵活现了。最后无数的物体就组成了咱们的3D世界。web
Three中模型解析器的原理是将顶点数组将模型的顶点用数组储存起来,再利用three中的face函数取得定点数组中的三个或四个顶点的索引构成空间平面。如此反复,模型就被完整构造出来了。数组
因而,越复杂的物体就须要越多的网面拼接。而css中是不存在根据坐标创建空间平面的能力的。函数
(插个题外话,其实css有一个属性与坐标有关,那就是clip-path。这个属性的特性赋予了css3必定的建模能力。实现方法可参考这篇文章 纯clip-path打造的3D模型渲染器)工具
。上篇文章介绍了Web3D的一些表现形式,这里着重谈谈怎么以CSS3实现3D全景。下面会探索Three实现全景的方案,由于WebGL门槛和学习成本仍是比较高的,不适于用于快速开发。造物节的CSS3d全景已有文章对其进行了技术探秘,但都未深刻谈及具体实现方式。学习
要清晰理解实现方式,必须对CSS3的transform、perspective有必定的认识。
原理方面的东西我就不深刻讲了,你们能够先看看这篇文章,对CSS3D有一个大体的概念。
玩轉 CSS 3D - 原理篇ui
CSS全景可经过创建柱形或者立方体再经过贴图方式实现。也许会有人问,球体行不行?其实是不行的,球体模型由无数个极小的平面拼接构成连贯曲面,而CSS缺少使平面扭曲的属性。球体模型咱们可使用上文提过的Clip-3d建造出,可是,贴图问题就解决不了了。this
相信不少打造过或有了解过3d全景的同行们都知道这个概念。实际上Skybox就是一个立方体,经过给六个面贴上不一样的,边缘能够无缝贴合的图片,再将视角伸入盒子内部。能够想象成咱们本身站入了一个巨型立方体盒子内部,移动视角便能看到不一样的场景。
一、贴图
来看一张天空盒子的贴图,剪头指向的边缘表明须要无缝贴合的边。
从上图能够看出只要相互贴合的两个面上的图像可以无缝拼接,那么再经过对各个面进行必定的旋转变换,天空盒子就能被打造出来了。
那么问题来了,怎么去拍摄制做这样的图片呢?这就须要经过一些专业软件了,好比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轴逆时针旋转是正数)
此时会获得下图这样的效果:
可是因为每一个面的旋转中心都在其正中位置,所以还不能造成正方体。因而咱们须要让每一个面产生必定的位移。
贴一张坐标系图以助于你们理解。
如今首先让front位移到应该到的位置,因为全景图的镜头在立方体内部,所以,能够想象一下,咱们须要将图片日后移动。移动距离很明显为立方体边长的一半。在这里是65px。获得下图结果。
.cube img:nth-child(1) { transform: translateZ(-65px); }
照这样看,是否是back位移为translateZ(65px)
,left为translateX(-65px)
,top translateY(-65px)
呢?但结果并非咱们想要的。
从新看回上文空间坐标系的那张贴图,咱们会发现,平面旋转后,其对应的三个轴的位置也改变了。如图片绕Y旋转后,Z轴指向为屏幕的水平方向。绕X旋转后,Z轴指向垂直方向。所以咱们很容易发现,其实要将贴面移动到正确的位置,都只须要让他们translateZ(-width/2px)
就能够了。
为了让你们容易理解,我这里设置了一个较大的perspective。要想获得全景的效果,咱们将镜头拉近让它进入到box里面就能够了。
接下来绑定手势,就可让它动起来啦。
部分代码:
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
看一张图,上面的lenZ
即translateZ
值,为负值。
perspective为镜头到屏幕的距离,由于此时镜头在柱体内部,所以不能看到柱体后面的图像。
当perspective值为-lenZ值时,正好柱体back面能与镜头在同一平面上,为了不它有必定的机率遮挡镜头,咱们能够将镜头拉近一些。便设成了-lenZ-5
。这个时候就能保证镜头处于柱体内部,同时也能更广角度地观察到柱体全景。
你们能够复制代码体验一下。这里的背景图我选用的是本身拼合成的造物节背景图。
相信你们也有体会,天空盒制造起来会相对的简单,而且天空和地面都能被考虑进去。可是因为面面间的贴合角度太大,若物体正好处于相互贴合的两个面,会给人一种被拦腰折断的感受。而柱形图对这种状况有了比较好的解决,可是天空和地面的贴图就比较困难了,通常状况下只能经过给scene添加背景图片模拟。
未完待续…