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有很大的差別,須要從新設計凝視輸入。bash

  3. 讓UI沿着曲面分佈,且每一個UI元素有曲面效果
    讓UI沿着曲面分佈是比較好實現的,只須要計算合理的位置和角度,讓UI總體上呈現出曲面的效果。目前有不少VR的曲面效果都是採用這種簡單的方法實現的,可是這種方案實現的曲面效果並非很好,是一種假曲面化的效果。以下圖所示,在上下邊緣能夠看到很明顯的直線。
    ide

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

    因此在沿着曲面分佈的前提下,若是每一個」UI元素都有曲面效果「,那麼總體上纔會看出曲面效果。以下圖所示
    UI元素都有曲面效果
    UI元素都有曲面效果

結合實現難度和效果,咱們選擇了方案 3函數

曲面化中的數學原理

不管是讓UI沿着曲面分佈,仍是實現每一個UI自身的曲面效果,實質上都是作同一種數學運算——計算平面上的點映射到曲面上的座標。
因爲咱們曲面化是一個圓柱面,圓柱軸心線與Y軸平行,變化先後Y軸座標是同樣的,下面是原理:
字體

掘金不支持數學公式
掘金不支持數學公式

幾何示意圖
幾何示意圖

其中的關鍵點是 變換先後的長度對應關係弧長與半徑的比是角度。上面給出的過程是一種特殊狀況,實際過程當中會有些變化,好比圓心不在原點,可是都是能夠經過以上的方法推導出來結果。

曲面化

讓UI沿着曲面分佈

對每一個UI計算曲面化以後的座標,上面已經給出了計算方法,須要注意的是,計算玩座標以後還須要調整UI的角度,讓UI的前方是圓心到變換以後座標的方向。好比上面變換以後位於B點的UItransform.forward = transform.position.normalized
到了這一步,總體的UI就有了上面所說的假曲面化的效果。ui

每一個UI元素的曲面效果

Unity提供了BaseMeshEffect對UI元素生成的mesh作一些修改來實現一些效果,不一樣Unity版本這個API有些差別,這裏用到的是Unity5.3.4,主要是重寫ModifyMesh(VertexHelper vh)方法。
ModifyMesh方法主要內容:this

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尺寸改變的狀況下才觸發從新計算頂點,固然能夠根據實際使用狀況調整策略。頂點計算比較耗時,建議先判斷在計算。spa

2. 取出頂點

取出來的頂點是一個UIVertex的列表,通常狀況下,列表中每3個構成一個三角形,若是改變列表中元素的位置,會致使UI顯示異常,因此最後輸出給VertexHelper的頂點也是有順序的。設計

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三角形細化方法,若是是其餘更復雜的曲面,不太建議用這種方式處理,由於涉及到曲面的數據計算,效果也很難保證。

相關文章
相關標籤/搜索