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在不同平臺表現不一致

原文地址