Unity UI Mirror Reflection

Unity 中 UI 镜像效果实现

1. 效果展示

UI镜像效果


2. 代码解析

1
if (!isActiveAndEnabled || vh.currentVertCount == 0 || (vh.currentVertCount % 4 != 0 && vh.currentVertCount % 6 != 0)) return;
  • VertexHelper类提供了一个便捷的接口来管理顶点、UV 坐标、法线、颜色等网格数据
  • vh.currentVertCount获取绘制图形总的顶点数,这里判断模4模6是因为,如果仅挂载Image组件,会将两个三角形的6个顶点合并为4个,但如果额外挂载诸如Outline等组件,顶点数又拆分成6的倍数

1
2
3
4
5
6
7
8
9
10
11
12
vh.GetUIVertexStream(inputVerts);

for(int i = 0; i < inputVerts.Count;i += 6)
{
quad[0] = inputVerts[i + 0];
quad[1] = inputVerts[i + 1];
quad[2] = inputVerts[i + 2];
quad[3] = inputVerts[i + 4];

UIVertexUtil.AddQuadToStream(quad, outputVerts);
AddMirrorReflectionQuad(quad, outputVerts);
}
  • GetUIVertexStream方法从VertexHelper实例提取顶点,此时共享的顶点会被拆分出来,所有后面每次循环i+=6
  • quad[]数组存储矩形四个顶点(左下,左上,右上,右下)的顶点数据,如下图所示

拆分共享顶点


1
2
3
4
5
6
7
8
9
public static void AddQuadToStream(UIVertex[] quad, List<UIVertex> stream)
{
stream.Add(quad[0]);
stream.Add(quad[1]);
stream.Add(quad[2]);
stream.Add(quad[2]);
stream.Add(quad[3]);
stream.Add(quad[0]);
}
  • quad[]的顶点数据按照上图顺序回填到顶点列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    if (height < (v1.position.y - targetRect.yMin) || height < (v2.position.y - targetRect.yMin))
{
v1 = UIVertexUtil.Lerp(v0, v1, GetLerpFactor(v0.position.y, v1.position.y));
v2 = UIVertexUtil.Lerp(v3, v2, GetLerpFactor(v3.position.y, v2.position.y));
}

MirrorReflectionVertex(ref v0);
MirrorReflectionVertex(ref v1);
MirrorReflectionVertex(ref v2);
MirrorReflectionVertex(ref v3);

quad[0] = v1;
quad[1] = v0;
quad[2] = v3;
quad[3] = v2;

UIVertexUtil.AddQuadToStream(quad, outputVerts);

------------------------------------------------------------------------------------

private float GetLerpFactor(float bottom, float top)
{
return (height + targetRect.yMin - bottom) / (top - bottom);
}

public static UIVertex Lerp(UIVertex a, UIVertex b, float t)
{
UIVertex output = default(UIVertex);

output.position = Vector3.Lerp(a.position, b.position, t);
output.normal = Vector3.Lerp(a.normal, b.normal, t);
output.tangent = Vector4.Lerp(a.tangent, b.tangent, t);
output.uv0 = Vector2.Lerp(a.uv0, b.uv0, t);
output.uv1 = Vector2.Lerp(a.uv1, b.uv1, t);
output.color = Color.Lerp(a.color, b.color, t);

return output;
}
  • v1,v2是矩形顶部两顶点,如果在合法范围内则进行插值调整
  • 循环处理所有顶点并回填到outputVerts顶点列表,
  • GetLerpFactor方法计算镜像高度为height时的插值因子
  • Lerp方法对顶点各个属性进行插值

1
2
3
4
5
6
7
8
9
10
11
12
private void MirrorReflectionVertex(ref UIVertex vt)
{
Color color = vt.color;
Vector3 position = vt.position;

float factor = Mathf.Clamp01((position.y - targetRect.yMin) / height);
color *= Color.Lerp(startColor, endColor, factor);
position.y = targetRect.yMin - (position.y - targetRect.yMin) - spacing;

vt.color = color;
vt.position = position;
}
  • MirrorReflectionVertex方法计算镜像后顶点的坐标和颜色
  • 将插值后的颜色与原颜色相乘获得渐变效果
  • targetRect.yMin实际是镜像对称线的y坐标,在此基础上减去要镜像的坐标的高度position.y - targetRect.yMin,再减去空隙spacing得到最终镜像后的坐标

1
2
vh.Clear();
vh.AddUIVertexTriangleStream(outputVerts);
  • 使用AddUIVertexTriangleStream方法将处理好的顶点渲染出来
  • unity在渲染UI元素时根据顶点颜色插值,每个三角形内像素颜色是根据其相对位置在顶点颜色之间插值计算得出的

3. 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIMirror : BaseMeshEffect
{
[SerializeField]
private float height = 50f;
[SerializeField]
private float spacing = 50f;
[SerializeField]
private Color startColor = new Color(1f, 1f, 1f, 0.75f);
[SerializeField]
private Color endColor = new Color(1f, 1f, 1f, 0f);

private Rect targetRect;

public Graphic targetGraphic { get { return base.graphic; } }

public float Height
{
get { return height; }
set
{
if (Mathf.Approximately(height, value))
{
height = value;
SetDirty();
}
}
}

public float Spacing
{
get { return spacing; }
set
{
if (Mathf.Approximately(spacing, value))
{
spacing = value;
SetDirty();
}
}
}

public Color StartColor
{
get { return startColor; }
set
{
if(value != startColor)
{
startColor = value;
SetDirty();
}
}
}

public Color EndColor
{
get { return endColor; }
set
{
if(value != endColor)
{
endColor = value;
SetDirty();
}
}
}

private void SetDirty()
{
if (graphic)
{
graphic.SetVerticesDirty();
}
}

public override void ModifyMesh(VertexHelper vh)
{
if (!isActiveAndEnabled || vh.currentVertCount == 0 || (vh.currentVertCount % 4 != 0 && vh.currentVertCount % 6 != 0)) return;

targetRect = graphic.rectTransform.rect;

UIVertex[] quad = UIVertexUtil.QuadVerts;
List<UIVertex> inputVerts = UIVertexUtil.InputVerts;
List<UIVertex> outputVerts = UIVertexUtil.OutputVerts;
inputVerts.Clear();
outputVerts.Clear();

vh.GetUIVertexStream(inputVerts);

for(int i = 0; i < inputVerts.Count; i += 6)
{
quad[0] = inputVerts[i + 0];
quad[1] = inputVerts[i + 1];
quad[2] = inputVerts[i + 2];
quad[3] = inputVerts[i + 4];

UIVertexUtil.AddQuadToStream(quad, outputVerts);
AddMirrorReflectionQuad(quad, outputVerts);
}

vh.Clear();
vh.AddUIVertexTriangleStream(outputVerts);
}

private void AddMirrorReflectionQuad(UIVertex[] quad, List<UIVertex> outputVerts)
{
UIVertex v0 = quad[0];
UIVertex v1 = quad[1];
UIVertex v2 = quad[2];
UIVertex v3 = quad[3];

if (height < (v0.position.y - targetRect.yMin) && height < (v3.position.y - targetRect.yMin)) return;

if (height < (v1.position.y - targetRect.yMin) || height < (v2.position.y - targetRect.yMin))
{
v1 = UIVertexUtil.Lerp(v0, v1, GetLerpFactor(v0.position.y, v1.position.y));
v2 = UIVertexUtil.Lerp(v3, v2, GetLerpFactor(v3.position.y, v2.position.y));
}

MirrorReflectionVertex(ref v0);
MirrorReflectionVertex(ref v1);
MirrorReflectionVertex(ref v2);
MirrorReflectionVertex(ref v3);

quad[0] = v1;
quad[1] = v0;
quad[2] = v3;
quad[3] = v2;

UIVertexUtil.AddQuadToStream(quad, outputVerts);
}

private float GetLerpFactor(float bottom, float top)
{
return (height + targetRect.yMin - bottom) / (top - bottom);
}

private void MirrorReflectionVertex(ref UIVertex vt)
{
Color color = vt.color;
Vector3 position = vt.position;

float factor = Mathf.Clamp01((position.y - targetRect.yMin) / height);
color *= Color.Lerp(startColor, endColor, factor);
position.y = targetRect.yMin - (position.y - targetRect.yMin) - spacing;

vt.color = color;
vt.position = position;
}
}

internal class UIVertexUtil
{
public static readonly UIVertex[] QuadVerts = new UIVertex[4];
public static readonly List<UIVertex> InputVerts = new List<UIVertex>();
public static readonly List<UIVertex> OutputVerts = new List<UIVertex>();

public static void AddQuadToStream(UIVertex[] quad, List<UIVertex> stream)
{
stream.Add(quad[0]);
stream.Add(quad[1]);
stream.Add(quad[2]);
stream.Add(quad[2]);
stream.Add(quad[3]);
stream.Add(quad[0]);
}

public static UIVertex Lerp(UIVertex a, UIVertex b, float t)
{
UIVertex output = default(UIVertex);

output.position = Vector3.Lerp(a.position, b.position, t);
output.normal = Vector3.Lerp(a.normal, b.normal, t);
output.tangent = Vector4.Lerp(a.tangent, b.tangent, t);
output.uv0 = Vector2.Lerp(a.uv0, b.uv0, t);
output.uv1 = Vector2.Lerp(a.uv1, b.uv1, t);
output.color = Color.Lerp(a.color, b.color, t);

return output;
}
}


完结散花~

聖夜讃歌11453300


Unity UI Mirror Reflection
https://baifabaiquan.cn/2024/09/13/Unity UI Mirror Reflection/
作者
白发败犬
发布于
2024年9月13日
许可协议