Unity3D Shader实现动态圆形屏幕遮罩
highlight: an-old-hope
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
策划想加一个切换场景时有圆形遮罩淡入淡出的效果。
屏幕可视范围跟随目标物体移动,可修改可视范围大小,边缘渐变大小、以及遮罩颜色,支持最高物体数量可在Shader中修改,当前版本支持最多9个物体。
效果图如下:
控制面板如下:
Shader代码如下:
```csharp Shader "Peter/DarkEffect" { Properties { _MainTex ("Texture", 2D) = "white" {} }
SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc"
//追踪物体最多个数 #define ItemSize 9
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };
sampler2D _MainTex;
fixed4 _DarkColor; float _SmoothLength; fixed _ItemCnt; float4 _Item[ItemSize];
v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; }
fixed CalcAlpha(float4 vt, float4 pt) { if(pt.z < 0) { return 1; }
float distPow2 = pow(vt.x - pt.x, 2) + pow(vt.y - pt.y, 2);
float dist = (distPow2 > 0) ? sqrt(distPow2) : 0;
float smoothLength = _SmoothLength;
if(smoothLength < 0)
{
smoothLength = 0;
}
float maxValue = pt.z;
float minValue = pt.z - smoothLength;
if(minValue < 0)
{
minValue = 0;
smoothLength = pt.z;
}
if(dist <= minValue)
{
return 0;
}
else if (dist > maxValue)
{
return 1;
}
fixed retVal = (dist - minValue) / smoothLength;
return retVal;
}
fixed4 frag (v2f i) : SV_Target { fixed alphaVal = 1; fixed tmpVal = 1;
for(fixed index = 0; index < _ItemCnt; ++index)
{
tmpVal = CalcAlpha(i.vertex, _Item[index]);
if(tmpVal < alphaVal)
{
alphaVal = tmpVal;
}
}
alphaVal *= _DarkColor.a;
return tex2D(_MainTex, i.uv) * ( 1 - alphaVal) + _DarkColor * alphaVal;
}
ENDCG } } } ```
C#调用代码如下:
```csharp using System.Collections; using System.Collections.Generic; using UnityEngine;
[ExecuteInEditMode] [RequireComponent(typeof(Camera))] public class DarkEffect : MonoBehaviour { [System.Serializable] public class Item { [SerializeField] public Transform target;
[SerializeField] public int radius;
public Vector3 GetScreenPosition(Camera cam) { return cam.WorldToScreenPoint(target.position); } }
//渐变像素数量
public int _smoothLength = 20;
//遮罩混合颜色
public Color _darkColor = Color.black;
//目标物体
public List
protected Material _mainMaterial; protected Camera _mainCamera;
Vector4[] _itemDatas; Item _tmpItem; Vector4 _tmpVt; Vector3 _tmpPos; int _tmpScreenHeight;
private void OnEnable()
{
_mainMaterial = new Material(Shader.Find("Peter/DarkEffect"));
_mainCamera = GetComponent
private void OnRenderImage(RenderTexture source, RenderTexture destination) {
if (_itemDatas == null || _itemDatas.Length != _items.Count) { _itemDatas = new Vector4[_items.Count]; }
_tmpScreenHeight = Screen.height;
for (int i = 0; i < _items.Count; i++) { _tmpItem = _items[i]; _tmpPos = _tmpItem.GetScreenPosition(_mainCamera);
_tmpVt.x = _tmpPos.x; _tmpVt.y = _tmpScreenHeight - _tmpPos.y; _tmpVt.z = _tmpItem.radius; _tmpVt.w = 0;
_itemDatas[i] = _tmpVt; }
_mainMaterial.SetInt("_SmoothLength", _smoothLength); _mainMaterial.SetColor("_DarkColor", _darkColor); _mainMaterial.SetInt("_ItemCnt", _itemDatas.Length); _mainMaterial.SetVectorArray("_Item", _itemDatas);
Graphics.Blit(source, destination, _mainMaterial); } } ```
然后自己在控制脚本中为了实现自己需求增加了如下代码:
```csharp public static DarkEffect instance; private void Start() { instance = this; _items.Clear(); _items.Add(new Item()); _items[0].target = transform.position; _items[0].radius = 0; Invoke("FadeOut", 0.1f); }
//淡入
public void FadeIn(Transform transform, Action action = null)
{
mask.gameObject.SetActive(true);
_items[0].radius = 1600;
_items[0].target = transform.position;
DOTween.To(() => _items[0].radius, _ => _items[0].radius = _, 0, 0.6f).OnComplete(() => FadeOut()).OnComplete(() => action?.Invoke());
}
//淡出
public void FadeOut()
{
_items[0].radius = 0;
_items[0].target = transform.position;
DOTween.To(() => _items[0].radius, v => _items[0].radius = v, 1600, 0.6f);
}
```
最终效果如下图所示:
要注意radius的大小能否在不同目标位置下遮住所有屏幕。自己测试了下手游可以设为1800。
打包真机测试遇到了遮罩位置与unity中不同的问题,可以参考下面博客解决。
unity shader在不同平台表现不一致