最近在项目中有需求在UI中增加一个雷达图。首先考虑到使用BaseMeshEffect
通过OnPopulateMesh
中计算顶点位置实现,但由于项目已上线,增加cs脚本无法热更新支持,因此考虑在shader中实现,并将材质的一些属性暴露出来,供lua控制修改。
最终得到效果是这样的:
一开始的想法是在片段着色器中计算,通过比较某个点是否在多边形内来控制输出颜色,而多边形的各个顶点是通过雷达图各维度计算出来的。这么一来在frag中需要进行大量的计算、if...else
判断和大量的for
循环。
调整思路,将雷达图的各个维度分解为一个一个的三角形,三角形中心重叠在一起且中心角的角度相等(等分了360度),于是决定在vert阶段做手脚,将一个由两个三角形组成的Image,通过修改顶点绘制成为一个三角形,且三角形的顶角角度、相邻两边长度都受shader的属性控制,大概的示意图如下:
实现过程中的一些要点如下:
绘制Image时,可以通过uv坐标判断出当前绘制的是哪个顶点;
使用
PositionAsUV1
组件传递个顶点相对于轴点的坐标;利用简单的三角函数计算,将Image的四个顶点的坐标表示成角度和长度的函数;
片段着色器中,根据像素距离边缘的尺寸处理透明度;
以下详细说明:
Image顶点的判断
因为是一个正方形,很容易根据纹理uv判断:
1 | v2f vert(appdata_t IN) |
position的传递
借助UGUI的组件PositionAsUV1
。此处传到TEXCOORD1
的position
是相对于轴点Pivot的坐标,而vertex
:
1 | struct appdata_t |
是顶点在Canvas上的坐标。
修改顶点位置
这个是最重要的过程了。这里有四个输入参数:
ValueStart
: 起始侧的数值,范围0-1,对应起始边的长度;ValueEnd
: 终止侧的数值,范围0-1,对应终止边的长度;AngleStart
: 起始角度,从此角度开始绘制三角形;AngleRange
: 范围角度,三角形从起始角度开始持续的角度;
具体参见下图:
需要得到的四个点的坐标,也标注在图上了,左上角、右上角、左下角、右下角以此记为lt、rt、lb、rb。除了lb不受影响之外,原来的lt、rb及rt都需要经过一些变换,表示成这前边四个参数的函数,图上表示为lt’、rb’和rt’。主要处理lt’和rb’,因为rt可处理为lt和rb的中点。
在处理各个顶点时,要注意传入的vertex
和position
也是对应定点的值。因此必须分别处理四个顶点,最后将处理完成的顶点赋值给vertex
:
1 | float AngleEnd = _AngleStart + _AngleRange; |
像素透明度计算
截止上一步,已经可以绘制出各个三角形了,但是目前这些图形使用的都是统一的颜色,视觉效果不佳。现在给雷达图增加描边效果。
原理是在顶点着色器中,保存顶点到雷达外部边缘的距离edge,实际上除了左下角的点之外,另外三个顶点的edge值都是0。左下角的点需要计算点到对边的距离。考虑到各个三角形的_AngleRange
都是一样的,为了简化计算,做了近似处理,直接使用两个边长的乘积:
1 | edge = _ValueStart * _ValueEnd; |
接下来在片段着色器中获取到插值后的edge,使用smoothstep()来计算alpha值,edge小于0时将alpha取1,edge小于0.8时将alpha取0:
1 | half alpha = smoothstep(0.8, 1.0, 1.0 - IN.edge); |
然后将参数alpha值和step的阈值参数化:
1 | half alpha = _InnerAlpha + (1 - _InnerAlpha) * smoothstep( 1.0 - _EdgeThickness, 1.0, 1.0 - IN.edge); |
最终效果和相关代码参见这里。