基於UE4/Unity繪製地圖基礎元素-線(上篇)

前言

這篇文章是使用遊戲引擎探索地圖可視化的開篇。傳統的地圖渲染一般是在iOS/Android/Web平臺進行的,爲了探究更酷炫的地圖展現,會記錄基於UE4/Unity進行地圖渲染的探索過程。程序員

地圖基礎元素 - 線

線做爲地圖渲染的基本元素,在地圖中能夠表明各類形式的道路。道路數據一般以離散點串形式存儲,所以如何將點串繪製成有寬度的線是渲染最關注的問題。本文記錄了繪製有寬度的線的方法,並對優化線展現效果的各類線帽和拐角進行了闡述。工具

繪製有寬度的線

道路數據一般以離散點串和其對應線寬進行存儲,爲了在遊戲引擎中進行顯示,就須要將其擴展爲有寬度的線。UE4和Unity均可以使用代碼生成Mesh進行基本圖元的渲染展現(UE4使用Procedural Mesh Component,Unity使用MeshFilter和MeshRenderer),而Mesh渲染的基本單位是三角形,所以問題就轉化爲如何根據點串和線寬,構造出一組三角形使其可以拼合產生具備寬度的線。性能

對於只有兩個點的直線,經過獲取與直線垂直的向量,向兩個方向各擴展lineWidth/2長度產生頂點,劃分爲三角形便可。優化

而對於多個離散點構成的線,繪製的時候遇到2個問題:spa

  • 僅使用相鄰點計算垂直向量,致使擴充出的線拐角處會有斷裂,以下圖所示。能夠看到,僅僅每一個相鄰線段進行擴充是不夠的,還須要考慮如何處理線的拐角。

  • 考慮處理線的拐角,但獲取頂點擴充向量的方向和大小不對,致使繪製的線不等寬。下圖根據相隔頂點連線的垂線肯定擴充向量,但因向量隨頂點位置變化而變化,所以不能做爲生成等寬線的依據。

有了上面的思考,任務就變成了擴充出等寬且有拐角的線:相隔點的頂點位置會變化,但由其肯定的向量方向是不變的,所以依靠頂點兩側線段的單位向量,就能肯定出惟一的擴充向量。肯定擴充方向後,還須要肯定擴充向量的大小使得最終的線等寬。3d

僞代碼以下,擴充方向可由線段單位向量組合肯定,須要注意擴充長度並非lineWidth/2,而是須要根據線段夾角進行計算調整。擴充向量計算好以後,便可根據離散點串生擴充頂點,根據頂點座標剖分爲三角形,構建Mesh進行渲染。code

// 計算擴充方向
Vec2f a = (P1 - P0) * normalized()
Vec2f b = (P2 - P1) * normalized()
Vec2f avg = a + b
Vec2f direction =  Vec2f(-avg.y, avg.x).normalized() //擴充方向爲avg的垂直方向

// 計算擴充長度
float t =  Abs(Asin(a × b)) / 2  // 單位向量叉乘得到夾角正弦
float length = lineWidth / 2 / Cos(t)  // 根據角度調整擴充長度

繪製線帽LineCap

根據上一節操做已經能夠繪製出有寬度的線,但也可以看出線在開頭和結尾處都是矩形,不夠優雅美觀。所以本節主要會解決繪製線帽的問題。orm

較爲經常使用的LineCap主要有如下三種:blog

  • Butt 無線帽模式,上一節繪製的線默認即爲Butt
  • Round 在線的兩端添加額外的半圓,其半徑爲lineWidth/2
  • Square 在線兩端添加額外的矩形,其高度爲lineWidth/2

Square形式的線帽繪製較爲簡單,只須要在開頭和結尾部分根據延伸方向額外添加矩形便可,兩個矩形能夠很簡單的劃分爲四個三角形,添加在畫線mesh中一同渲染。而Round形式的半圓線帽在繪製上就麻煩了許多,在實踐過程當中主要探索瞭如下三個方案:遊戲

一、使用三角形近似繪製半圓

最直觀的方式就是直接繪製半圓線帽,可是渲染的最小單元是三角形,所以只能經過添加多個三角形近似表示半圓。這種方式須要根據添加三角形的個數,進行幾何運算肯定各個頂點座標,經過三角形組合成半圓,雖然方法直觀可行,但爲了使線帽圓滑,額外添加的較多頂點和進行的大量數學運算都會對性能帶來影響,存在性能和效果的取捨。

二、使用圖片近似繪製半圓

第二種方案藉助圖片能夠省去添加額外頂點和進行數學計算的步驟,近似獲得半圓線帽。

圖片工具大小爲16×16像素,左右兩部分分別繪製半圓和矩形。對於半圓部分,內部點透明度設置爲1,圓弧上覆蓋的像素點,經過調低透明度值弱化鋸齒感,圓弧以外部分則將透明度設置爲0,總體使用透明度構建出近似的半圓。矩形部分則做爲工具,用於填充非線帽部分。

這種方案在構建線Mesh時,與Square線帽方案一致,但須要將紋理uv值也與頂點進行綁定。Square線帽額外添加的矩形綁定圖片左側半圓的uv,而原有線部分綁定右側矩形uv便可。渲染時,能夠在片元着色器中逐像素提取到映射的圖片顏色值,輸出顏色使用頂點原色,但透明度值採用圖片的透明度值,從而將圓弧外側像素剔除。使用該方案須要開啓透明度混合,從而不顯示圓弧外側像素。

這種方案也是半圓的近似表示,在距離較近觀察時會出現圓弧線帽發虛,緣由是受限於圖片大小,若是增長圖片大小能夠緩解問題,但也會增長開銷,也須要作性能和效果的取捨平衡。

三、逐像素繪製半圓

第三種方案由方案二演進而來,不是使用圖片剔除像素,而是藉助於半圓的特性,在片元着色器中剔除全部不知足條件的像素,作到繪製像素級的半圓線帽。其主要原理是在添加Square線帽後,判斷渲染時像素距離線起始頂點距離,若超過lineWidth/2(即紅色部分)則剔除像素,從而逐像素繪製出半圓線帽。

像素剔除會在片元着色器中並行進行,效率高但沒法存儲上下文信息,而剔除邏輯須要獲取圓心信息,同時片元着色器的座標已經轉化爲裁剪空間的齊次座標,沒法進行幾何運算,所以須要將一些輔助信息傳遞到片元着色器中進行操做。

輔助信息定義爲二維向量geometryInfo,其含義爲頂點在線中的相對位置,點串的起點做爲(0,0),終點做爲(1,0),中間的點根據距離轉化爲[0,1]間的數值。根據擴充向量獲得的頂點,則根據擴充方向,向量y值賦值爲1或-1。由於已經人爲定義了線寬爲2的相對座標系,所以線帽上頂點的輔助信息x值能夠轉化爲-1和2,這樣任何小於0和大於1的x值均可以表示該點是線帽部分,並且能夠很方便的和(0,0)、(1,0)作距離計算,並與半圓半徑1進行比較。

geometryInfo綁定在每一個頂點傳入shader後,會在片元着色器中按像素進行線性插值,所以每個像素都會得到一個能夠標識本身局部位置的輔助信息,藉助於該信息進行距離判斷就能夠進行像素剔除,這裏展現的是Unity Shader代碼,UE4能夠在Material中還原邏輯。

fixed4 frag (v2f i) : SV_Target
{
    if(i.geometryInfo.x < 0)  // 起點側線帽
    {    
        if(dot(float2(i.geometryInfo.x, i.geometryInfo.y), float2(i.geometryInfo.x, i.geometryInfo.y)) > 1)
        {   
            discard; // 距離圓心距離大於1則剔除
        }
    } 
    else if(i.geometryInfo.x > 1) // 終點側線帽
    {
        if(dot(float2(i.geometryInfo.x - 1, i.geometryInfo.y), float2(i.geometryInfo.x - 1, i.geometryInfo.y)) > 1)
        {   
            discard; 
        }
    }

    return i.color;
 }

使用該方案生成的圓角,在近距離觀看時由於線帽的渲染像素增多,所以也不會產生虛化或者鋸齒感,可以獲得圓滑的效果。

繪製線拐角LineJoin

線帽已經圓潤優雅以後,同時也發現繪製的線在一些極端狀況下拐角會存在bad case。例以下圖所示,對於夾角較小的線會產生很是大的尖角;而對於線段呈直角狀況顯示的也一樣是直角拐角,不夠圓潤美觀。本節主要會解決繪製線拐角的問題。

較爲經常使用的LineJoin主要有如下三種:

  • Miter 尖角樣式,上一節繪製的線即屬於Miter
  • Bevel 切角樣式,以橫切面替代尖角
  • Round 圓角樣式,以圓弧替代尖角

有了擴充線和線帽的繪製經驗,從上圖能夠看出Bevel和Round樣式不須要根據線段夾角計算擴充向量。繪製時按照矩形擴展後,Bevel樣式只須要根據擴充頂點補齊一個三角形構成切面。而對於Round樣式,除了起終點外,每個頂點擴充處根據矩形方向繪製兩個半圓,疊加就能達到圓拐角效果。

半圓部分的繪製原理和繪製半圓線帽同樣,添加矩形再剔除多餘像素,所以須要將geometryInfo擴充爲四維向量,後兩位表示頂點在當前段的相對位置,一樣在片元着色器中進行像素剔除。這裏片元着色器的代碼邏輯與圓角線帽相似,再也不贅述。最終的拐角效果以下圖。

總體的繪製流程能夠簡單總結爲下圖,等寬線做爲線渲染的主體,線帽/拐角做爲線渲染的效果優化項。在具體實踐中,能夠經過設置配置項的方式方便的更改線帽/拐角的樣式。

做者:程序員阿Tu

連接:https://zhuanlan.zhihu.com/p/...

來源:知乎

著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索