特效模型与UGUI混排的解决方案 VR教程

flashyiyi 2018-02-11 18:09:27
将RootCanvas设置为Screen Space - Camera。
 

把下层需要分层的UI组件放在多个子Canvas内,并且打上Override Sorting的勾(禁用Canvas的特殊排序)
 


之后调整各层的z值,就能进行正常排序。除了需要分一下Z的区间外,没有副作用。
 

 
我本来以为还要做一些工作,结果什么都不用做。导致这个误解的原因是我并没有使用过Unity自己的2D系统,不太理解sortingOrder的实际含义,以为这个和Z排是互斥的。而事实上sortingOrder只是透明物体排序时的一个权重值,第一权重是renderQueue,第二权重是sortingOrder,最后是Z。所以只要让sortingOrder保持为0,就是纯粹的Z排了。
 
 
这里也能看出,虽然Canvas并没有继承自Renderer,但是在渲染管线里同样是被当做Renderer处理的。因为在Z排的时候,是根据其包围盒的中心点排序的。如果Canvas内不同UI组件的Z值不等于0,虽然不会影响到内部排序,却会导致Canvas包围盒的中心点的Z值变化,影响到Canvas的排序。
 
 
总之,Canvas(不管RootCanvas还是子Canvas)其实就是个普通的Renderer,所以该怎么排序就怎么做就是了。但是Canvas内的UI组件并不是Renderer,它们只能用UGUI的那套排序和合批机制,最后拼合成一套Mesh和材质,整体投入渲染管线。
 
 
如果用NGUI的概念来描述,Canvas就是UIPanel,Graphic就是UIWiget。但Canvas还更进一步,并没有使用特殊的RenderQueue,而是走了正规程序,利用系统的SortingOrder来决定渲染顺序。当选中Override Sorting,SortingOrder和其他物体一样固定为0的时候,就成为了和其他Renderer完全一样的东西。
 
 
新Z排方案和旧方案的混合使用
 
此外,还要注意这种利用Z排管理的Canvas,和普通的Canvas混合使用的情况。
 
我们先把勾选了Override Sorting的Canvas称为ZCanvas(RootCanvas本身也是ZCanvas)
如果取消勾选Override Sorting,那么这个普通Canvas的包围盒就会和上一级的ZCanvas合并在一起(建议把它的Z恢复成0,防止总体Z值和预期不符),成为了ZCanvas的一部分,内部用Hierarchy顺序决定排序。
 
 
而ZCanvas还是可以和其他的ZCanvas,以及模型特效正常按Z混排。
 
 
 
 
 
红色图标是ZCanvas,Z值靠前,所以在钟前面。药水图标是SpriteRenderer,Z值靠后,所以在所有界面后面
 
多增加一层UI
 
 
总之
  • 不要修改Sorting Order,它的优先级太高
  • 不要修改未勾选Override Sorting的Canvas,以及其他UI组件的Z值。要让他们相对Z值为0,否则会影响ZCanvas的包围盒,造成预期之外的情况。
     
满足这两个要求,再用Override Sorting的开闭来决定是否要从上一级Canvas的管理中解放出来。不被管理的Canvas都按Z排序,还是比较容易理解的。
 
 
实际应用便是,每级窗口一个ZCanvas,UI内的特效希望显示在UI前方,相对Z值便小于0,否则就大于零(注意Z值越小越靠前)。内部UI(包括普通Canvas)相对Z值保持为0。如果想把特效“夹”在中间,就内部新建一个ZCanvas,修改相对Z值把自己推上来。
 
 
不必要的ZCanvas也可以省略,由需求决定。

遮罩
但非UI物体混排时,还会遇到遮罩的问题。
 
效率最高方法的其实是分开多个摄像机,通过rect改变相机的渲染范围。但多摄像机管理比较麻烦。单摄像机情况下,要不RenderToTexture,要不用Shader解决。
 
 
Shader方面有两种方法,一种是利用Stencil,但UGUI的Mask组件绘制的Stencil并不能被外界利用。所以比较方便的是传入Shader一个ClipRect,Shader内将Rect外部的像素透明度设成0。
 
 
01
02
03
04
05
06
07
08
09
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
using UnityEngine;
namespace UGUIExtend
{
    /// <summary>
    /// 按RectTransform裁切Renderer
    /// </summary>
    [ExecuteInEditMode]
    public class UIClipAble : MonoBehaviour
    {
        static readonly int clipRectId = Shader.PropertyToID("_ClipRect");
 
        [SerializeField] public RectTransform mask;
 
        MaterialPropertyBlock materialBlock;
        Renderer[] renderers;
        Vector4 clipRect;
 
        private void OnEnable()
        {
            materialBlock = new MaterialPropertyBlock();
            renderers = GetComponentsInChildren<Renderer>();
        }
 
        static readonly Vector3[] corners = new Vector3[4];
        private void Update()
        {
            if (mask != null)
            {
                mask.GetWorldCorners(corners);
                Vector4 r = new Vector4(corners[0].x, corners[0].y, corners[2].x, corners[2].y);
                if (r != clipRect)
                {
                    clipRect = r;
                    materialBlock.SetVector(clipRectId, clipRect);
                    foreach (Renderer renderer in renderers)
                    {
                        renderer.SetPropertyBlock(materialBlock);
                    }
                }
            }
        }
    }
}
 
(需要手动更换材质Shader。mask可以指定任意RectTransfrom,不需要限定为RectMask2D)
 
 
 
但要注意,这种做法和Stencil不同,遮罩外部的图像只是透明度被设为了0,但还是会照常渲染。Stencil虽然不会导致这种情况,但是Stencil会增加两次遮罩的绘制操作(一次绘制,一次擦除)
 
 
一定要用Stencil的做法,可以添加一个和UI遮挡一样大的Sprite Mask,它对粒子系统和SpriteRenderer有效。
 
 
 
粒子系统需要打开遮罩选项(5.5+支持)不方便把界面拆成多个Canvas的情况
 
拆Canvas毕竟会导致断批,如果特效和UI的交叠情况特别复杂的话,可以选择直接用特殊的UI组件来“模仿”特效的实现。
 
 
以UI组件的方式重新实现的LineRenderer
 
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
using System.Collections.Generic;
 
namespace UnityEngine.UI.Extensions
{
    [AddComponentMenu("UI/Extensions/Primitives/UILineRenderer")]
    [RequireComponent(typeof(RectTransform))]
    public class UILineRenderer : UIPrimitiveBase
        {
                private enum SegmentType
                {
                        Start,
            Middle,
            End,
            Full,
                }
 
                public enum JoinType
                {
                        Bevel,
            Miter
                }
 
                public enum BezierType
                {
                        None,
            Quick,
            Basic,
            Improved,
            Catenary,
        }
 
                private const float MIN_MITER_JOIN = 15 * Mathf.Deg2Rad;
 
                // A bevel 'nice' join displaces the vertices of the line segment instead of simply rendering a
                // quad to connect the endpoints. This improves the look of textured and transparent lines, since
                // there is no overlapping.
        private const float MIN_BEVEL_NICE_JOIN = 30 * Mathf.Deg2Rad;
 
                private static Vector2 UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_TOP_CENTER_LEFT, UV_TOP_CENTER_RIGHT, UV_BOTTOM_CENTER_LEFT, UV_BOTTOM_CENTER_RIGHT, UV_TOP_RIGHT, UV_BOTTOM_RIGHT;
                private static Vector2[] startUvs, middleUvs, endUvs, fullUvs;
 
        [SerializeField, Tooltip("Points to draw lines between\n Can be improved using the Resolution Option")]
        internal Vector2[] m_points;
        [SerializeField, Tooltip("Segments to be drawn\n This is a list of arrays of points")]
                internal List<Vector2[]> m_segments;
 
        [SerializeField, Tooltip("Thickness of the line")]
        internal float lineThickness = 2;
        [SerializeField, Tooltip("Use the relative bounds of the Rect Transform (0,0 -> 0,1) or screen space coordinates")]
        internal bool relativeSize;
        [SerializeField, Tooltip("Do the points identify a single line or split pairs of lines")]
        internal bool lineList;