一个将页面映射到立方体进行旋转浏览的方法|征稿活动V5

cocos进入3.x时代,我也努力跟进,实现了一个将页面映射到立方体上进行旋转查看的效果。效果虽然简单,但是也耗了我不少时日。

效果如下:

8

要点

为了这个效果,我主要实现了3个要点:

  • 将页面准确映射到立方体上面,适配不同尺寸的手机屏幕。
  • 对页面内容进行正常滑动、点击操作。
  • 不同页面数量能够在立方体上滑动屏幕循环查看。

页面映射到立方体

创建立方体和页面

因为需要在4个面上显示不同的页面内容,引擎自带的立方体并不能简单地满足这个功能。这里我使用了4个quad拼凑成一个四面体,放在一个节点下,方便后面进行整体旋转。

四个quad节点的坐标分别是:前(0,0,0.5),右(0.5,0,0),后(0,0,-0.5),左(-0.5,0,0)

image
(图一:立方体拼凑示意)

页面就是普通的渲染节点。不用scrollview做页面滚动,之后统一用屏幕操作来处理。

页面没有铺满屏幕,在上方空出了可能会增加的导航栏的空间。
(白色线框使canvas画布的边框)


(图二:页面布局示意)

canvas的宽度尺寸是750,写有内容的页面的宽度是600(后面用pageWidth表示),居中。

屏幕宽适配,页面距离canvas顶部始终是250(后面用top表示) ,距离底部始终是60(后面用bottom表示) 。

页面节点加了mask组件所以只显示了一部分内容。

透视摄像机参数调整

做3d项目,我觉得首先不同的是透视摄像机的引入。需要先了解它的各项参数的意思,才好完成一些3d功能。

我想实现的是:调整主摄像机的参数,可以让立方体的前quad模型在页面上呈现和“图二”一样的尺寸效果。

主要是调整主摄像机的位置坐标和fov值。(我想保持立方体不动)

x坐标 影响quad左右显示,因为页面左右对称,所以x坐标为0 。

y坐标 影响quad上下显示,值越大,页面越靠下。

y的值可以直接由:(top-bottom)/(2pageWidth)得到。这里是:(250-60)/(2600)=0.1583333

z坐标 影响quad两边的空间,值越大,离quad越远,页面显示越小。

摄像机距离立方体距离和fov的大概关系如下:

(因为用了宽适配,不想fov影响宽度,所以fov axis使用了horizontal)

image

(图三:透视摄像机距离和fov关系,俯视图)

红色实线代表了前quad模型。宽度是1
绿色实线是近裁剪面。

BC比上AD的大小是:600/750 . 这里用ratio表示

AD尺寸:
2*near*tan(fov/2)

可以算出DisZ和Fov的关系是:

DisZ=1/(2*tan(Fov/2)*ratio)

当Fov取60度时,DisZ是:1.0825 。那么主摄像机距离原点就是:1.5825(前quad距离立方体节点0.5)

调整z坐标后的效果如下。

(图四:调整主摄像机z坐标后的效果)

可以看到调整主摄像机的z值后,透视摄像机的前quad模型和正交摄像机的页面宽度保持了一致。

因为设备屏幕宽高比(aspect)不一致,所以quad的高度需要在启动后调节。

适配需求: 页面距离canvas顶部始终是250 ,距离底部始终是60 。

quad高度和透视摄像机的关系如下图:

image

(图五:透视摄像机和立方体前quad的关系,左视图)

其中绿色实线为近裁剪面
红色实线为前quad模型
黄色虚线是xz平面,经过立方体中心。

近裁剪面的宽度除以高度等于aspect,这个可以通过屏幕尺寸得到。

在cocos里可以通过camera.aspect得到

通过之前的公式,计算得到红色实线即前quad的高度为:

1/(ratio*aspect)-(top+bottom)/pageWidth

如果基于iphoneSE的屏幕,该值为:1.706666

调整好x,y,z和fov值后的效果:

(图六:调整完摄像机参数后的效果)

可以看到,2d页面和quad已经完全重合了!
:v:

立方体旋转

我直接监听了input的touchmove事件,然后根据delta来旋转立方体。

旋转的时候,使用到了四元数,四元数又是一个新词,不好理解,

但是cocos做了很好的封装,使用起来很方便。

旋转api用到了Quat.fromAxisAngle函数,代表了基于旋转轴和旋转角度的旋转。

旋转轴很容易确定,因为只是水平方向的旋转,那么就是绕着y轴旋转。即Vec3.UP

旋转角度做了一个限制,就是希望玩家即使从屏幕一侧滑到另一侧,也不会看到背面。

所以,最大旋转角度为90度。每次滑动屏幕距离是deltaX,那么每次的旋转角度是:90*deltaX/屏幕宽度

用到的旋转代码是:
node.rotate(quat)

设置后效果如下:

(图七:立方体旋转示意)

动图中,文字页面还没有映射到立方体上,所以为各个面加了基础颜色。

图七中的效果,仅是旋转跟随滑动。当松手后,应该需要像pageview那样,根据一个阈值,要么还原,要么旋转到下一个页面。

我使用了tween的方式,在松手后,直接旋转到原角度或者下一个页面的角度。

为了方便,在旋转的时候加了锁,只有tween完成后,才可以进行下一次滑动旋转。

这里还根据需要旋转的角度,算了一下时间,最长0.5秒,最短0.1秒。做一个线性插值。

最后的旋转效果如下:

(图八:立方体旋转示意,带自动归位)

页面映射

我使用了rendertexture渲染贴图,为立方体的前,右,后,左各面片分别创建了对应的渲染贴图、材质、摄像机。材质绑定对应的渲染贴图。将材质赋值给各quad面片。为每个面设置一个layer。然后让对应的摄像机只拍摄这个layer。将渲染贴图赋值给对应的摄像机。

总之创建了:

4个渲染贴图,4个材质,4个摄像机。

都配置好后的效果如下:

(图九:配置渲染贴图后的效果)

现在会有2个问题:不同屏幕尺寸,内容会变形;内容没有铺满。

只要将各面的摄像机尺寸和主正交摄像机的orthoHeight设置成一样,

将各面的渲染贴图的尺寸设置成:
(aspect*orthoHeight,orthoHeight)

就能保证屏幕完整映射到立方体各面上。如下:

(图十:页面完整映射到立方体各面)

现在内容还是处于拉伸或者压缩的状态,因为页面的宽高比和屏幕的不一致。

只需要将渲染贴图的材质的tiling offset参数进行调整,

去掉黑色区域就可以还原内容的尺寸比例。

tilingOffset的x,y对应uv的xy缩放,tilingOffset的zw对应uv的xy偏移。

宽度是600/750=0.8;

所以tilingOffset的x设置为0.8 .

x方向应该采样0.1~0.9这个范围。所以将tilingOffset的z设置为0.1。

tilingOffset的y需要用公式:y=(screenHeight-top-bottom)/screenHeight来运算。

w需要用公式:z=top/screenHeight来运算。

最后用

material.setProperty('tilingOffset',new Vec4(0.8,(screenHeight-top-bottom)/screenHeight,0.1,top/screenHeight))

来设置最终的渲染效果,如下动图所示:

(图十一:对材质进行tilingoffset后的效果)

然后再加上一个背景图

调整一下各个摄像机的参数

主透视摄像机用depth only,priority 设置成最高

页面的正交摄像机用solid color,clear color用半透明白色, priority 设置成中等

主正交摄像机priority设置成最低,用于背景渲染。

将各页面材质的technique设置成1-transparent。

就实现了相对高级一点的效果:

(图十二:添加背景和透明度的效果)

对立方体各面内的页面进行长图滑动和点击

我的做法概要:

在滑动的第一帧,判断是想上下滑,还是想左右滑。

如果判断deltaY大于deltaX,则上下滑动,对当前页面进行content坐标处理。

如果无移动,则对当前页面进行点击处理。

页面点击是利用世界坐标进行的判断。

最后的效果如下:

(图十三:添加滑动和点击的效果)

因为不是3d内容范畴,所以写的比较简单。嘿嘿。

对不同数量的页面进行映射

我用过切换页面的layer,但是这样只能适用至少3个页面的情况。如果只有1、2个就不行了。

所以最后使用了切换摄像机的visibility来实现。

每次旋转结束,判断前,右,后,左四个页面,应该是哪个一。

然后设置摄像机的visibility来对应。

并且因为只有4个面片,所以layer也只需要用到1,2,4,8即可。

去掉一个页面后的效果如下:

7

(图十四:只有3个页面的效果)

结语

个人感悟:做3d效果讲究的事情感觉比2d会多很多。要实现一个效果,需要使挺大劲的。所以如果想要不失业,还是需要掉许多头发的。

实现本文的效果,对透视摄像机的参数的认识有了一定的加深。

由于这种效果的应用场景我认为非常的少,所以就不贴源码了。哈哈~~。有兴趣欢迎勾搭~

5赞

cool!

cool!