繪制地圖基礎元素-線(上篇)

前言

這篇文章是使用游戲引擎探索地圖可視化的開篇叫乌。傳統(tǒng)的地圖渲染通常是在iOS/Android/Web平臺進行的,為了探究更酷炫的地圖展示蔗坯,會記錄基于UE4/Unity進行地圖渲染的探索過程元镀。

地圖基礎元素 - 線

線作為地圖渲染的基本元素露泊,在地圖中可以代表各種形式的道路。道路數(shù)據(jù)通常以離散點串形式存儲,因此如何將點串繪制成有寬度的線是渲染最關(guān)注的問題宣赔。本文記錄了繪制有寬度的線的方法预麸,并對優(yōu)化線展示效果的各種線帽和拐角進行了闡述。

繪制有寬度的線

道路數(shù)據(jù)通常以離散點串和其對應線寬進行存儲儒将,為了在游戲引擎中進行顯示吏祸,就需要將其擴展為有寬度的線。UE4和Unity都可以使用代碼生成Mesh進行基本圖元的渲染展示(UE4使用Procedural Mesh Component钩蚊,Unity使用MeshFilter和MeshRenderer)贡翘,而Mesh渲染的基本單位是三角形,因此問題就轉(zhuǎn)化為如何根據(jù)點串和線寬砰逻,構(gòu)造出一組三角形使其能夠拼合產(chǎn)生具有寬度的線鸣驱。

對于只有兩個點的直線,通過獲取與直線垂直的向量蝠咆,向兩個方向各擴展lineWidth/2長度產(chǎn)生頂點踊东,劃分為三角形即可。

image

而對于多個離散點構(gòu)成的線刚操,繪制的時候遇到2個問題:

  • 僅使用相鄰點計算垂直向量闸翅,導致擴充出的線拐角處會有斷裂,如下圖所示菊霜〖峒剑可以看到,僅僅每個相鄰線段進行擴充是不夠的鉴逞,還需要考慮如何處理線的拐角记某。
image
  • 考慮處理線的拐角,但獲取頂點擴充向量的方向和大小不對构捡,導致繪制的線不等寬辙纬。下圖根據(jù)相隔頂點連線的垂線確定擴充向量,但因向量隨頂點位置變化而變化叭喜,因此不能作為生成等寬線的依據(jù)贺拣。
image

有了上面的思考,任務就變成了擴充出等寬且有拐角的線:相隔點的頂點位置會變化捂蕴,但由其確定的向量方向是不變的譬涡,因此依靠頂點兩側(cè)線段的單位向量,就能確定出唯一的擴充向量啥辨。確定擴充方向后涡匀,還需要確定擴充向量的大小使得最終的線等寬。

image

偽代碼如下溉知,擴充方向可由線段單位向量組合確定陨瘩,需要注意擴充長度并不是lineWidth/2腕够,而是需要根據(jù)線段夾角進行計算調(diào)整。擴充向量計算好之后舌劳,即可根據(jù)離散點串生擴充頂點帚湘,根據(jù)頂點坐標剖分為三角形,構(gòu)建Mesh進行渲染甚淡。

// 計算擴充方向
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)  // 根據(jù)角度調(diào)整擴充長度

繪制線帽LineCap

根據(jù)上一節(jié)操作已經(jīng)可以繪制出有寬度的線大诸,但也能夠看出線在開頭和結(jié)尾處都是矩形,不夠優(yōu)雅美觀贯卦。因此本節(jié)主要會解決繪制線帽的問題资柔。

較為常用的LineCap主要有以下三種:

  • Butt 無線帽模式,上一節(jié)繪制的線默認即為Butt
  • Round 在線的兩端添加額外的半圓撵割,其半徑為lineWidth/2
  • Square 在線兩端添加額外的矩形贿堰,其高度為lineWidth/2
image

Square形式的線帽繪制較為簡單,只需要在開頭和結(jié)尾部分根據(jù)延伸方向額外添加矩形即可啡彬,兩個矩形可以很簡單的劃分為四個三角形官边,添加在畫線mesh中一同渲染。而Round形式的半圓線帽在繪制上就麻煩了許多外遇,在實踐過程中主要探索了以下三個方案:

1、使用三角形近似繪制半圓

最直觀的方式就是直接繪制半圓線帽契吉,但是渲染的最小單元是三角形跳仿,因此只能通過添加多個三角形近似表示半圓。這種方式需要根據(jù)添加三角形的個數(shù)捐晶,進行幾何運算確定各個頂點坐標菲语,通過三角形組合成半圓,雖然方法直觀可行惑灵,但為了使線帽圓滑山上,額外添加的較多頂點和進行的大量數(shù)學運算都會對性能帶來影響,存在性能和效果的取舍英支。

image
2佩憾、使用圖片近似繪制半圓

第二種方案借助圖片可以省去添加額外頂點和進行數(shù)學計算的步驟,近似得到半圓線帽干花。

image

圖片工具大小為16×16像素妄帘,左右兩部分分別繪制半圓和矩形。對于半圓部分池凄,內(nèi)部點透明度設置為1抡驼,圓弧上覆蓋的像素點,通過調(diào)低透明度值弱化鋸齒感肿仑,圓弧之外部分則將透明度設置為0致盟,整體使用透明度構(gòu)建出近似的半圓碎税。矩形部分則作為工具,用于填充非線帽部分馏锡。

這種方案在構(gòu)建線Mesh時雷蹂,與Square線帽方案一致,但需要將紋理uv值也與頂點進行綁定眷篇。Square線帽額外添加的矩形綁定圖片左側(cè)半圓的uv萎河,而原有線部分綁定右側(cè)矩形uv即可。渲染時蕉饼,可以在片元著色器中逐像素提取到映射的圖片顏色值虐杯,輸出顏色使用頂點原色,但透明度值采用圖片的透明度值昧港,從而將圓弧外側(cè)像素剔除擎椰。使用該方案需要開啟透明度混合,從而不顯示圓弧外側(cè)像素创肥。

這種方案也是半圓的近似表示达舒,在距離較近觀察時會出現(xiàn)圓弧線帽發(fā)虛,原因是受限于圖片大小叹侄,如果增加圖片大小可以緩解問題巩搏,但也會增加開銷,也需要做性能和效果的取舍平衡趾代。

3贯底、逐像素繪制半圓

第三種方案由方案二演進而來,不是使用圖片剔除像素撒强,而是借助于半圓的特性禽捆,在片元著色器中剔除所有不滿足條件的像素,做到繪制像素級的半圓線帽飘哨。其主要原理是在添加Square線帽后胚想,判斷渲染時像素距離線起始頂點距離,若超過lineWidth/2(即紅色部分)則剔除像素芽隆,從而逐像素繪制出半圓線帽浊服。

image

像素剔除會在片元著色器中并行進行,效率高但無法存儲上下文信息胚吁,而剔除邏輯需要獲取圓心信息臼闻,同時片元著色器的坐標已經(jīng)轉(zhuǎn)化為裁剪空間的齊次坐標,無法進行幾何運算囤采,因此需要將一些輔助信息傳遞到片元著色器中進行操作述呐。

輔助信息定義為二維向量geometryInfo,其含義為頂點在線中的相對位置蕉毯,點串的起點作為(0,0)乓搬,終點作為(1,0)思犁,中間的點根據(jù)距離轉(zhuǎn)化為[0,1]間的數(shù)值。根據(jù)擴充向量得到的頂點进肯,則根據(jù)擴充方向激蹲,向量y值賦值為1或-1。因為已經(jīng)人為定義了線寬為2的相對坐標系江掩,因此線帽上頂點的輔助信息x值可以轉(zhuǎn)化為-1和2学辱,這樣任何小于0和大于1的x值都可以表示該點是線帽部分,而且可以很方便的和(0,0)环形、(1,0)做距離計算策泣,并與半圓半徑1進行比較。

image

geometryInfo綁定在每個頂點傳入shader后抬吟,會在片元著色器中按像素進行線性插值萨咕,因此每一個像素都會獲得一個可以標識自己局部位置的輔助信息,借助于該信息進行距離判斷就可以進行像素剔除火本,這里展示的是Unity Shader代碼危队,UE4可以在Material中還原邏輯。

fixed4 frag (v2f i) : SV_Target
{
    if(i.geometryInfo.x < 0)  // 起點側(cè)線帽
    {    
        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) // 終點側(cè)線帽
    {
        if(dot(float2(i.geometryInfo.x - 1, i.geometryInfo.y), float2(i.geometryInfo.x - 1, i.geometryInfo.y)) > 1)
        {   
            discard; 
        }
    }

    return i.color;
 }

使用該方案生成的圓角钙畔,在近距離觀看時因為線帽的渲染像素增多茫陆,因此也不會產(chǎn)生虛化或者鋸齒感,能夠得到圓滑的效果擎析。

image

繪制線拐角LineJoin

線帽已經(jīng)圓潤優(yōu)雅之后簿盅,同時也發(fā)現(xiàn)繪制的線在一些極端情況下拐角會存在bad case。例如下圖所示叔锐,對于夾角較小的線會產(chǎn)生非常大的尖角;而對于線段呈直角情況顯示的也同樣是直角拐角见秽,不夠圓潤美觀愉烙。本節(jié)主要會解決繪制線拐角的問題。

image

較為常用的LineJoin主要有以下三種:

  • Miter 尖角樣式解取,上一節(jié)繪制的線即屬于Miter
  • Bevel 切角樣式步责,以橫切面替代尖角
  • Round 圓角樣式,以圓弧替代尖角
image

有了擴充線和線帽的繪制經(jīng)驗禀苦,從上圖可以看出Bevel和Round樣式不需要根據(jù)線段夾角計算擴充向量蔓肯。繪制時按照矩形擴展后,Bevel樣式只需要根據(jù)擴充頂點補齊一個三角形構(gòu)成切面振乏。而對于Round樣式蔗包,除了起終點外,每一個頂點擴充處根據(jù)矩形方向繪制兩個半圓慧邮,疊加就能達到圓拐角效果调限。

image

半圓部分的繪制原理和繪制半圓線帽一樣舟陆,添加矩形再剔除多余像素,因此需要將geometryInfo擴充為四維向量耻矮,后兩位表示頂點在當前段的相對位置秦躯,同樣在片元著色器中進行像素剔除。這里片元著色器的代碼邏輯與圓角線帽類似裆装,不再贅述踱承。最終的拐角效果如下圖。

image

整體的繪制流程可以簡單總結(jié)為下圖哨免,等寬線作為線渲染的主體茎活,線帽/拐角作為線渲染的效果優(yōu)化項。在具體實踐中铁瞒,可以通過設置配置項的方式方便的更改線帽/拐角的樣式妙色。

image

作者:程序員阿Tu

鏈接:https://zhuanlan.zhihu.com/p/266026334

來源:知乎

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)慧耍,非商業(yè)轉(zhuǎn)載請注明出處身辨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芍碧,隨后出現(xiàn)的幾起案子煌珊,更是在濱河造成了極大的恐慌,老刑警劉巖泌豆,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件定庵,死亡現(xiàn)場離奇詭異,居然都是意外死亡踪危,警方通過查閱死者的電腦和手機蔬浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贞远,“玉大人畴博,你說我怎么就攤上這事±吨伲” “怎么了俱病?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袱结。 經(jīng)常有香客問我亮隙,道長,這世上最難降的妖魔是什么垢夹? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任溢吻,我火速辦了婚禮,結(jié)果婚禮上果元,老公的妹妹穿的比我還像新娘煤裙。我一直安慰自己掩完,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布硼砰。 她就那樣靜靜地躺著且蓬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪题翰。 梳的紋絲不亂的頭發(fā)上恶阴,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音豹障,去河邊找鬼冯事。 笑死,一個胖子當著我的面吹牛血公,可吹牛的內(nèi)容都是我干的昵仅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼累魔,長吁一口氣:“原來是場噩夢啊……” “哼摔笤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垦写,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤吕世,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后梯投,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體命辖,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年分蓖,在試婚紗的時候發(fā)現(xiàn)自己被綠了尔艇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡么鹤,死狀恐怖终娃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情午磁,我是刑警寧澤尝抖,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布毡们,位于F島的核電站迅皇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏衙熔。R本人自食惡果不足惜登颓,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望红氯。 院中可真熱鬧框咙,春花似錦咕痛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至者铜,卻和暖如春腔丧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背作烟。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工愉粤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拿撩。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓衣厘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親压恒。 傳聞我的和親對象是個殘疾皇子影暴,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344