先是在:Unity开发从看懂到看开 文章中看到了一些 unity 的坑点
我也是忍不住发了两个吐槽点:
其中 unity - UGUI Canvas.RenderSubBatch 之后 stencil buffer 被重置了 的问题我也遇到了
导致部分 particle system 的 stencil 配置后的效果不对
偶然查看到另一篇博主以前写过:Unity GUI(uGUI)使用心得与性能总结,果然 cavnas batch,或是使用 Mask, RectMask2D,等等,都会再包括再最后的尾部的 UI 绘制时清理 stencil buffer 的内容
解决方式
用一个 SpriteRenderer 设置好 sortingOrder 保证在 CanvasRenderSubBatch 之后,particle system 之前写入 stencil buffer 1,后续的 particle system 的 mask 效果看起来才有效,但是需要将这个 SpriteRenderer 的位置、大小都要调整到与 UGUI ScrollView 对应的位置上
优化
这个方法已经应用于实际项目
但是在不同分辨率下,这个 SpriteRenderer
的方法就不好去控制对应分辨率下的大小了
所以我使用了自定义的网格组件对象(CustomQuadMesh
)来替代 SpriteRenderer
该自定义的网格组件如下:
using System;
using UnityEngine;
public class CustomQuadMesh : MonoBehaviour
{
public GetUguiRectWPos wposCom;
private Mesh mesh;
private Vector3[] lastVectices;
private void Start()
{
lastVectices = new Vector3[4];
}
private void LateUpdate()
{
var vertices = wposCom.vectices;
bool changed = false;
for (int i = 0; i < 4; i++)
{
if (!lastVectices[i].Equals(vertices[i]))
{
changed = true;
break;
}
}
if (changed)
{
Array.Copy(vertices, lastVectices, 4);
if (mesh == null)
{
// new mesh
mesh = new Mesh();
// vertices
mesh.vertices = vertices;
// indices
mesh.triangles = new int[6]
{
0, 1, 2,
0, 2, 3
};
mesh.UploadMeshData(false);
var mf = GetComponent<MeshFilter>();
mf.sharedMesh = mesh;
}
else
{
// vertices
mesh.vertices = vertices;
}
mesh.UploadMeshData(false);
gameObject.transform.position = wposCom.transform.position;
}
}
}
上面的自定义的网格对象中,有一个叫:GetUguiRectWPos 的组件类:
using UnityEngine;
public class GetUguiRectWPos : MonoBehaviour
{
public Vector3[] vectices;
//#if UNITY_EDITOR
// private GameObject[] spherePrefabs;
// private GameObject[] sphereGos;
//#endif
private RectTransform rt;
private void Start()
{
rt = GetComponent<RectTransform>();
vectices = new Vector3[4];
//#if UNITY_EDITOR
// var prefabNames = new string[4]
// {
// "Prefabs/SphereRed",
// "Prefabs/SphereGreen",
// "Prefabs/SphereBlue",
// "Prefabs/SphereYellow"
// };
// spherePrefabs = new GameObject[4];
// sphereGos = new GameObject[4];
// for (int i = 0; i < prefabNames.Length; i++)
// {
// var pn = prefabNames[i];
// spherePrefabs[i] = Resources.Load<GameObject>(pn);
// }
// for (int i = 0; i < spherePrefabs.Length; i++)
// {
// var p = spherePrefabs[i];
// sphereGos[i] = GameObject.Instantiate(p);
// sphereGos[i].transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
// }
//#endif
}
private void LateUpdate()
{
rt.GetWorldCorners(vectices);
//#if UNITY_EDITOR
// if (sphereGos != null)
// {
// for (int i = 0; i < vectices.Length; i++)
// {
// var sp = sphereGos[i];
// if (sp == null)
// {
// continue;
// }
// sp.transform.position = vectices[i];
// }
// }
//#endif
}
}
该 GetUguiRectWPos 的作用只是将某个 ugui 的 quad 大小的世界坐标拿到,然后宫 CustomQuadMesh 组件类使用
接着是,CustomQuadMesh 类中的 GameObject 挂载上:MeshFilter, MeshRenderer
其中 MeshRenderer 的材质我们提前创建并设置到 MeshRenderer 中
材质的 shader 如下
// author : jave.lin
Shader "Game/UI/STMask/WriteMaskFromWQuad" {
Properties {
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader {
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
}
ColorMask 0
ZWrite Off
Lighting Off
Cull Off
Stencil
{
Ref 1
Comp Always
Pass Replace
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
fixed4 _Color;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityWorldToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target {
return _Color;
}
ENDCG
}
}
}
该 shader lab 主要的作用:
- 写入 stencil buffer
- 不写入 color buffer
便于后续的 particle system 只绘制 stencil buffer equal 1 的像素
更好的优化方式
可以参考我后续添加的一篇 blog:Unity - RenderDoc 分析为何 UGUI ScrollView 下 Stencil 不能与 其他 3D 层的对象有交互?(Mask 组件的问题)
可以自行写一个 Stencil Write, Stencil Clear 的组件,替代 Unity Mask 组件,这样可以比较灵活的控制 UI & 3D 层的内容的 Stencil 交互