Unity3D Shader实现动态圆形屏幕遮罩

语言: CN / TW / HK

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 _items = new 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在不同平台表现不一致

原文地址