前言
这篇文章是应用游戏引擎摸索地图可视化的开篇。传统的地图渲染通常是在iOS/Android/Web平台进行的,为了探索更酷炫的地图展现,会记录基于UE4/Unity进行地图渲染的摸索过程。
地图根底元素 - 线
线作为地图渲染的根本元素,在地图中能够代表各种模式的路线。路线数据通常以离散点串模式存储,因而如何将点串绘制成有宽度的线是渲染最关注的问题。本文记录了绘制有宽度的线的办法,并对优化线展现成果的各种线帽和拐角进行了论述。
绘制有宽度的线
路线数据通常以离散点串和其对应线宽进行存储,为了在游戏引擎中进行显示,就须要将其扩大为有宽度的线。UE4和Unity都能够应用代码生成Mesh进行根本图元的渲染展现(UE4应用Procedural Mesh Component,Unity应用MeshFilter和MeshRenderer),而Mesh渲染的根本单位是三角形,因而问题就转化为如何依据点串和线宽,结构出一组三角形使其可能拼合产生具备宽度的线。
对于只有两个点的直线,通过获取与直线垂直的向量,向两个方向各扩大lineWidth/2长度产生顶点,划分为三角形即可。
而对于多个离散点形成的线,绘制的时候遇到2个问题:
- 仅应用相邻点计算垂直向量,导致裁减出的线拐角处会有断裂,如下图所示。能够看到,仅仅每个相邻线段进行裁减是不够的,还须要思考如何解决线的拐角。
- 思考解决线的拐角,但获取顶点裁减向量的方向和大小不对,导致绘制的线不等宽。下图依据相隔顶点连线的垂线确定裁减向量,但因向量随顶点地位变动而变动,因而不能作为生成等宽线的根据。
有了下面的思考,工作就变成了裁减出等宽且有拐角的线:相隔点的顶点地位会变动,但由其确定的向量方向是不变的,因而依附顶点两侧线段的单位向量,就能确定出惟一的裁减向量。确定裁减方向后,还须要确定裁减向量的大小使得最终的线等宽。
伪代码如下,裁减方向可由线段单位向量组合确定,须要留神裁减长度并不是lineWidth/2,而是须要依据线段夹角进行计算调整。裁减向量计算好之后,即可依据离散点串生裁减顶点,依据顶点坐标剖分为三角形,构建Mesh进行渲染。
// 计算裁减方向Vec2f a = (P1 - P0) * normalized()Vec2f b = (P2 - P1) * normalized()Vec2f avg = a + bVec2f direction = Vec2f(-avg.y, avg.x).normalized() //裁减方向为avg的垂直方向// 计算裁减长度float t = Abs(Asin(a × b)) / 2 // 单位向量叉乘取得夹角正弦float length = lineWidth / 2 / Cos(t) // 依据角度调整裁减长度
绘制线帽LineCap
依据上一节操作曾经能够绘制出有宽度的线,但也可能看出线在结尾和结尾处都是矩形,不够优雅好看。因而本节次要会解决绘制线帽的问题。
较为罕用的LineCap次要有以下三种:
- Butt 无线帽模式,上一节绘制的线默认即为Butt
- Round 在线的两端增加额定的半圆,其半径为lineWidth/2
- Square 在线两端增加额定的矩形,其高度为lineWidth/2
Square模式的线帽绘制较为简单,只须要在结尾和结尾局部依据延长方向额定增加矩形即可,两个矩形能够很简略的划分为四个三角形,增加在画线mesh中一起渲染。而Round模式的半圆线帽在绘制上就麻烦了许多,在实际过程中次要摸索了以下三个计划:
1、应用三角形近似绘制半圆
最直观的形式就是间接绘制半圆线帽,然而渲染的最小单元是三角形,因而只能通过增加多个三角形近似示意半圆。这种形式须要依据增加三角形的个数,进行几何运算确定各个顶点坐标,通过三角形组合成半圆,尽管办法直观可行,但为了使线帽圆滑,额定增加的较多顶点和进行的大量数学运算都会对性能带来影响,存在性能和成果的取舍。
2、应用图片近似绘制半圆
第二种计划借助图片能够省去增加额定顶点和进行数学计算的步骤,近似失去半圆线帽。
图片工具大小为16×16像素,左右两局部别离绘制半圆和矩形。对于半圆局部,外部点透明度设置为1,圆弧上笼罩的像素点,通过调低透明度值弱化锯齿感,圆弧之外局部则将透明度设置为0,整体应用透明度构建出近似的半圆。矩形局部则作为工具,用于填充非线帽局部。
这种计划在构建线Mesh时,与Square线帽计划统一,但须要将纹理uv值也与顶点进行绑定。Square线帽额定增加的矩形绑定图片左侧半圆的uv,而原有线局部绑定右侧矩形uv即可。渲染时,能够在片元着色器中逐像素提取到映射的图片色彩值,输入色彩应用顶点原色,但透明度值采纳图片的透明度值,从而将圆弧外侧像素剔除。应用该计划须要开启透明度混合,从而不显示圆弧外侧像素。
这种计划也是半圆的近似示意,在间隔较近察看时会呈现圆弧线帽发虚,起因是受限于图片大小,如果减少图片大小能够缓解问题,但也会减少开销,也须要做性能和成果的取舍均衡。
3、逐像素绘制半圆
第三种计划由计划二演进而来,不是应用图片剔除像素,而是借助于半圆的个性,在片元着色器中剔除所有不满足条件的像素,做到绘制像素级的半圆线帽。其次要原理是在增加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/...
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。