Unity中UI曲面化

在VR下面,曲面UI能够提高用户在场景中的沉浸感,得到更好的视觉体验html

方案选择

  1. 作一套基于曲面的UI
    咱们项目中基本只用到Image和Text两种,Image是比较好处理的,直接将Texture贴到一个曲面的mesh上就能够了,可是Text相对比较麻烦。咱们没法简单的取到某一段文字的Texture,必须本身从字体文件里面裁剪每一个文字的Texture,而后拼接到一个Texture,而后再将最终的纹理贴到一个曲面mesh上。缓存

  2. 单独将平面UI渲染到一个纹理,而后贴到曲面mesh上
    这个方案在实现曲面化上是没有问题的,可是为了知足VR下面的立体效果,咱们两只眼睛看到的东西是有必定差别的,经过这些差别才有立体感,若是是同一个曲面的UI Texture,UI上的一些立体效果会损失(好比UI向前浮动)。另外,若是使用这种方法,咱们眼睛看到的UI和真实的UI有很大的差别,须要从新设计凝视输入。markdown

  3. 让UI沿着曲面分布,且每一个UI元素有曲面效果
    让UI沿着曲面分布是比较好实现的,只须要计算合理的位置和角度,让UI总体上呈现出曲面的效果。目前有不少VR的曲面效果都是采用这种简单的方法实现的,可是这种方案实现的曲面效果并非很好,是一种假曲面化的效果。以下图所示,在上下边缘能够看到很明显的直线。
    ide

    UI元素沿着曲面分布
    UI元素沿着曲面分布

    因此在沿着曲面分布的前提下,若是每一个”UI元素都有曲面效果“,那么总体上才会看出曲面效果。以下图所示
    UI元素都有曲面效果
    UI元素都有曲面效果

结合实现难度和效果,咱们选择了方案 3函数

曲面化中的数学原理

不管是让UI沿着曲面分布,仍是实现每一个UI自身的曲面效果,实质上都是作同一种数学运算——计算平面上的点映射到曲面上的坐标。
因为咱们曲面化是一个圆柱面,圆柱轴心线与Y轴平行,变化先后Y轴坐标是同样的,下面是原理:
oop

掘金不支持数学公式
掘金不支持数学公式

几何示意图
几何示意图

其中的关键点是 变换先后的长度对应关系弧长与半径的比是角度。上面给出的过程是一种特殊状况,实际过程当中会有些变化,好比圆心不在原点,可是都是能够经过以上的方法推导出来结果。

曲面化

让UI沿着曲面分布

对每一个UI计算曲面化以后的坐标,上面已经给出了计算方法,须要注意的是,计算玩坐标以后还须要调整UI的角度,让UI的前方是圆心到变换以后坐标的方向。好比上面变换以后位于B点的UItransform.forward = transform.position.normalized
到了这一步,总体的UI就有了上面所说的假曲面化的效果。字体

每一个UI元素的曲面效果

Unity提供了BaseMeshEffect对UI元素生成的mesh作一些修改来实现一些效果,不一样Unity版本这个API有些差别,这里用到的是Unity5.3.4,主要是重写ModifyMesh(VertexHelper vh)方法。
ModifyMesh方法主要内容:ui

public override void ModifyMesh(VertexHelper vh) {
        base.ModifyMesh(vh);
        if (!this.IsActive() || !bendEnable)
            return;

        /* 检查是否须要从新生成或修改顶点坐标,若是不须要,则使用已经缓存的顶点坐标 */

        if (cachedVertices == null || cachedTriangles == null || verticesDirty)
        {
            // 须要修改顶点,首先将Unity生成的顶点取出来
            List<UIVertex> originUIVertices = new List<UIVertex>();
            vh.GetUIVertexStream(originUIVertices);
            /* 对顶点作一些变换,包括增长顶点以及从新计算顶点坐标,对于Image和Text有不一样的处理方式 */
        }

        // 若是材质改变,从新给定点着色
        if (materialDirty)
        {
            UpdateVertiecsColor(cachedVertices);
            materialDirty = false;
        }
        // 清除Unity生成的顶点,将咱们从新计算的顶点设置到mesh上
        vh.Clear();
        vh.AddUIVertexStream(cachedVertices, cachedTriangles);
        // 根据生成顶点的类型也可使用vh.AddUIVertexTriangleStream(cachedVertices)设置顶点
}复制代码

1. 检查顶点是否须要更新

当Unity发现UI须要更新的时候会调用ModifyMesh(VertexHelper vh),Unity本身触发UI更新的条件有尺寸改变和材质改变,咱们也可使用Graphic.SetAllDirty() Graphic.SetVerticesDirty() 触发。可是并不是全部的状况下都须要从新计算顶点坐标,当咱们计算出一个UI的曲面状态下的顶点以后,不多须要从新计算,咱们只在UI尺寸改变的状况下才触发从新计算顶点,固然能够根据实际使用状况调整策略。顶点计算比较耗时,建议先判断在计算。this

2. 取出顶点

取出来的顶点是一个UIVertex的列表,通常状况下,列表中每3个构成一个三角形,若是改变列表中元素的位置,会致使UI显示异常,因此最后输出给VertexHelper的顶点也是有顺序的。spa

3. 顶点计算

这一步中对于Text和Image有较大差别,主要缘由在于通常的Text本身都有足够多且细分的三角形,只须要从新计算顶点的坐标就能够有很好的曲面效果,可是通常状况下,Image只有两个三角形(Sliced模式下Tiled模式会多一些,可是依然不够细分),四个顶点,对四个顶点从新计算以后依然是一个平面的效果,因此须要考虑给Image的mesh添加一些顶点,让Image上的三角形足够细分。

对于Text的处理
cachedVertices = new List<UIVertex>();
vh.GetUIVertexStream(cachedVertices);
BendMeshCylinder(cachedVertices);复制代码

其中的BendMeshCylinder(cachedVertices)函数就是将传入的顶点变换到圆柱曲面上,须要注意的是UIVertex里面的坐标是相对UI自身的局部坐标。处理过程不改变UIVertex列表的顺序,处理完的依然保持以前的三角形顺序,因此最后直接使用vh.AddUIVertexTriangleStream(cachedVertices)设置顶点。

对于Image的处理

通常状况下的Image只须要4个顶点就能够构成两个三角形,可是从VertexHelper里面取出来的顶点有6个,每三个构成一个三角形,重复使用了其中的两个顶点,以下图所示,第0,1,2和3,4,5分别构成一个三角形,0和5为同一个顶点,2和3为同一个顶点。


最初的想法是忽略Unity本身生成的顶点,直接在代码中根据原始顶点的规律生成一个足够细分的mesh,而后对全部顶点采用和Text里面相同的计算就能够有曲面效果,以下图所示,由于咱们只须要圆柱曲面效果,因此在Y轴方向不须要细分,这样能大大减小三角形的数量。

这种方案通常状况下是够用的,可是当遇到Sliced或者Tiled模式的Image的时候就会有问题:当咱们计算新增顶点坐标的时候,须要给顶点指定一个uv值,这个值将决定图片渲染在这一点的uv,普通图片的uv值是从0到1的均匀分布,因此直接根据新计算的坐标在整个Image上的位置就知道uv值,可是Sliced和Tiled的uv不是0到1的均匀分布,根据坐标是没法直接算出uv值的。好比下图是一个Sliced模式的mesh,编号为1的顶点Y轴上是整个Image高度的0.1,可是uv中v(Y轴)的值使0.3,若是咱们忽略这些值,直接生成均匀分布的点,Sliced的特性就没有了

因此须要在保持Unity计算出来的顶点,而后再在这些顶点的基础上进行线性插值计算新增的顶点。代码以下:

List<UIVertex> originUIVertices = new List<UIVertex>();
vh.GetUIVertexStream(originUIVertices);
TrisToQuads(originUIVertices);
for (int i = 0; i < originUIVertices.Count; i += 4)
{
    CreateQuads(originUIVertices, i, cachedVertices, cachedTriangles);
}
BendMeshCylinder(cachedVertices);复制代码

首先在TrisToQuads(originUIVertices)里面是将每六个顶点构成的两个三角形合并为四个顶点构成的四边形,而后在CreateQuads(originUIVertices, i, cachedVertices, cachedTriangles)里面对每一个四边形内部进行线性插值计算新增顶点,每一个四边形内部点的uv均可以根据坐标在四边形内部的位置计算出来。以下图所示,红色点为新增顶点,当四边形在X轴方向足够细分,就不须要再添加顶点,一般状况下Sliced模式的图片边缘是足够细分的。


咱们发现,四个顶点就足以描述两个三角形,可是须要知道两个三角形与四个顶点的对应关系,三角形更多的状况下能够经过顶点的复用使得顶点数量更少,好比上图中有36个三角形,可是只有28个顶点,咱们须要缓存曲面变化以后的顶点,避免后面的重复计算,因此采用顶点复用可让咱们缓存的顶点数量大大的减小,可是咱们须要缓存一个顶点与构成的三角形的对应关系的int列表,这个列表中每三个数描述一个三角形,数值对应着缓存的顶点列表的索引。顶点生成完成以后,和Text中同样对顶点的坐标进行曲面变换。最后咱们设置顶点的时候须要告诉VertexHelper顶点与三角形的对应关系, vh.AddUIVertexStream(cachedVertices, cachedTriangles)

4. 改变顶点的颜色

若是改变UI的颜色属性,会触发MaterialDirty,咱们能够经过Graphic.RegisterDirtyMaterialCallback监听这个改变,而后在ModifyMesh()改变顶点的颜色。另外不建议监听Graphic.RegisterDirtyVerticesCallback来肯定是否须要从新计算顶点,由于改变顶点颜色,这个回调也会调用。


曲面化的原理如上,若是要真正运用起来,还须要配合一个Editor。须要注意的是,最好不要在曲面化状态调整UI的坐标和角度,不只很难调整到想要的位置,并且会影响总体曲面化的效果。文章里面主要聚焦圆柱面,若是是球面,主要要修改UI坐标的计算方法,和Image三角形细化方法,若是是其余更复杂的曲面,不太建议用这种方式处理,由于涉及到曲面的数据计算,效果也很难保证。

相关文章
相关标签/搜索