414 lines
14 KiB
C#
414 lines
14 KiB
C#
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
namespace Qjjxk.Blur
|
|
{
|
|
[ExecuteAlways]
|
|
[DisallowMultipleComponent]
|
|
public class BlurRawImage : MonoBehaviour
|
|
{
|
|
[SerializeField, ReadOnly] private Shader blurShader;
|
|
|
|
public BlurType blurType;
|
|
private BlurType _blurType;
|
|
[Range(0f, 64f)] public float blurStrength;
|
|
private float _blurStrength;
|
|
[Range(0, 32)] public float blurRadius;
|
|
private float _blurRadius;
|
|
[Range(0, 6)] public int iterations = 1;
|
|
private int _iterations;
|
|
[Range(1, 8)] public float downSample = 1;
|
|
private float _downSample;
|
|
|
|
private static readonly int _blurRadiusId = Shader.PropertyToID("_BlurRadius");
|
|
private static readonly int _regionId = Shader.PropertyToID("_Region");
|
|
private static readonly int _iterationId = Shader.PropertyToID("_Iteration");
|
|
|
|
private Scene _scene;
|
|
private Canvas _canvas;
|
|
private bool _needResize;
|
|
private Vector3 _localPos;
|
|
private Vector2 _selfSize;
|
|
private Camera _mainCamera;
|
|
private Vector2 _sizeDelta;
|
|
private RawImage _rawImage;
|
|
private int _initStep = -1;
|
|
private int _width, _height;
|
|
private bool _needRefresh = true;
|
|
private MonitorDimension _monitor;
|
|
private Material _blurMat, _blurMat2;
|
|
private RectTransform _rectTransform;
|
|
private RenderTexture _tempRT, _blurRT;
|
|
private BlurCameraPass _blurCameraPass;
|
|
private UniversalAdditionalCameraData _cameraData;
|
|
private Material[] _blurMats;
|
|
#if UNITY_EDITOR && UNITY_6000_0_OR_NEWER
|
|
private RenderTexture _cameraRT;
|
|
#endif
|
|
|
|
private void Awake()
|
|
{
|
|
_mainCamera = Camera.main;
|
|
blurShader = Shader.Find("Qjjxk/BlurImage");
|
|
var haveRawImage = TryGetComponent(out _rawImage);
|
|
if (haveRawImage) _canvas = _rawImage.canvas;
|
|
|
|
if (_mainCamera && blurShader && _canvas) _initStep++;
|
|
else _initStep--;
|
|
if (_initStep < -1) return;
|
|
|
|
_scene = gameObject.scene;
|
|
_blurType = blurType;
|
|
_blurRadius = blurRadius;
|
|
_iterations = iterations;
|
|
downSample = 1;
|
|
_downSample = downSample;
|
|
_blurMat = new Material(blurShader);
|
|
_blurMat2 = new Material(blurShader);
|
|
_blurMats = new[]
|
|
{
|
|
new Material(blurShader),
|
|
new Material(blurShader),
|
|
new Material(blurShader),
|
|
new Material(blurShader),
|
|
new Material(blurShader),
|
|
new Material(blurShader),
|
|
};
|
|
_rectTransform = _rawImage.rectTransform;
|
|
_sizeDelta = _rectTransform.sizeDelta.Round();
|
|
_localPos = _rectTransform.localPosition.Round();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_initStep < 1 || !_scene.isLoaded) return;
|
|
|
|
if (_blurRT == null || !_blurRT.IsCreated())
|
|
{
|
|
RefreshPixelSize();
|
|
_blurRT = new RenderTexture(_width, _height, 0) { name = "BlurRT" };
|
|
_tempRT = new RenderTexture(_width, _height, 0) { name = "TempRT" };
|
|
_blurRT.Create();
|
|
_tempRT.Create();
|
|
}
|
|
|
|
_rawImage.texture = _blurRT;
|
|
_rawImage.enabled = true;
|
|
RegisterMonitor(true);
|
|
|
|
// 每次重新创建 Pass 以更新内部 RTHandle
|
|
_blurCameraPass = new BlurCameraPass(_blurType, _blurRT, _tempRT, _blurMats, _blurMat, _blurMat2, _iterations);
|
|
|
|
_cameraData = _mainCamera.GetUniversalAdditionalCameraData();
|
|
if (iterations != 0) RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
downSample = 1;
|
|
if (_initStep > -1)
|
|
{
|
|
blurShader = Shader.Find("Qjjxk/BlurImage");
|
|
_rawImage.texture = _blurRT;
|
|
return;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
EditorUtility.DisplayDialog("Error",
|
|
"The MainCamera or blur Shader was not found, or the script has not been added to the component containing the RawImage.", "OK");
|
|
EditorApplication.delayCall += () => { DestroyImmediate(this); };
|
|
#endif
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (_initStep < 0) return;
|
|
_initStep++;
|
|
|
|
RefreshPixelSize();
|
|
|
|
_tempRT = new RenderTexture(_width, _height, 0) { name = "TempRT" };
|
|
_blurRT = new RenderTexture(_width, _height, 0) { name = "BlurRT" };
|
|
#if UNITY_EDITOR && UNITY_6000_0_OR_NEWER
|
|
_cameraRT = new RenderTexture(_mainCamera.pixelWidth, _mainCamera.pixelHeight, 16) { name = "CameraRT" };
|
|
#endif
|
|
|
|
OnEnable();
|
|
|
|
SetBlurStrength();
|
|
SetBlurRange();
|
|
}
|
|
|
|
|
|
private void Update()
|
|
{
|
|
if (_initStep < 1) return;
|
|
|
|
if (_rawImage != null && (_rawImage.texture == null || _rawImage.texture != _blurRT))
|
|
{
|
|
_rawImage.texture = _blurRT;
|
|
}
|
|
|
|
RefreshBlurType();
|
|
|
|
RefreshStrength();
|
|
|
|
RefreshIterations();
|
|
|
|
RefreshDownSample();
|
|
|
|
RefreshBlurRadius();
|
|
|
|
if (_rectTransform.hasChanged)
|
|
{
|
|
RefreshPixelSize();
|
|
SetBlurRange();
|
|
_rectTransform.hasChanged = false;
|
|
}
|
|
|
|
ForceCameraRender();
|
|
}
|
|
|
|
private void OnBeginCameraRendering(ScriptableRenderContext context, Camera tarCamera)
|
|
{
|
|
if (tarCamera != _mainCamera) return;
|
|
if (_needResize) ResizeRT();
|
|
if (_cameraData != null && _cameraData.scriptableRenderer != null && _blurCameraPass != null)
|
|
_cameraData.scriptableRenderer.EnqueuePass(_blurCameraPass);
|
|
}
|
|
|
|
private void RefreshBlurType()
|
|
{
|
|
if (_blurType == blurType) return;
|
|
_blurType = blurType;
|
|
if (_blurCameraPass != null) _blurCameraPass.blurType = _blurType;
|
|
_needRefresh = true;
|
|
RefreshStrength(true);
|
|
}
|
|
|
|
private void RefreshStrength(bool force = false)
|
|
{
|
|
if (Mathf.Approximately(_blurStrength, blurStrength) && !force) return;
|
|
_blurStrength = blurStrength;
|
|
|
|
if (_blurType == BlurType.Gaussian)
|
|
{
|
|
switch (_blurStrength)
|
|
{
|
|
case <= 2:
|
|
iterations = 1;
|
|
blurRadius = _blurStrength;
|
|
break;
|
|
case <= 10:
|
|
iterations = 2;
|
|
blurRadius = (_blurStrength + 0.5f) / Mathf.Sqrt(iterations);
|
|
break;
|
|
case <= 20:
|
|
iterations = 3;
|
|
blurRadius = _blurStrength / Mathf.Sqrt(iterations);
|
|
break;
|
|
case <= 50:
|
|
iterations = 4;
|
|
blurRadius = (_blurStrength - 0.5f) / Mathf.Sqrt(iterations);
|
|
break;
|
|
default:
|
|
iterations = 5;
|
|
blurRadius = (_blurStrength - 1) / Mathf.Sqrt(iterations);
|
|
break;
|
|
}
|
|
}
|
|
else if (_blurType == BlurType.Kawase)
|
|
{
|
|
iterations = _blurStrength switch
|
|
{
|
|
<= 0 => 1,
|
|
<= 10 => 3,
|
|
<= 20 => 4,
|
|
<= 30 => 5,
|
|
_ => 6
|
|
};
|
|
blurRadius = _blurStrength / 2;
|
|
}
|
|
}
|
|
|
|
private void RegisterMonitor(bool isReg)
|
|
{
|
|
if (!_monitor) _monitor = _canvas.GetComponent<MonitorDimension>();
|
|
if (!_monitor)
|
|
{
|
|
_monitor = _canvas.gameObject.AddComponent<MonitorDimension>();
|
|
_monitor.hideFlags = HideFlags.HideInInspector;
|
|
}
|
|
|
|
_monitor.Register(this, isReg);
|
|
}
|
|
|
|
public void SetWindowSize()
|
|
{
|
|
if (_initStep < 1) return;
|
|
|
|
RefreshPixelSize();
|
|
_needResize = true;
|
|
SetBlurRange();
|
|
}
|
|
|
|
private void RefreshPixelSize()
|
|
{
|
|
_width = Mathf.RoundToInt(_rectTransform.rect.width * _canvas.scaleFactor);
|
|
_height = Mathf.RoundToInt(_rectTransform.rect.height * _canvas.scaleFactor);
|
|
}
|
|
|
|
private void ResizeRT()
|
|
{
|
|
_needResize = false;
|
|
if (_blurRT != null) _blurRT.Resize(_width / _downSample, _height / _downSample);
|
|
if (_tempRT != null) _tempRT.Resize(_width / _downSample, _height / _downSample);
|
|
#if UNITY_EDITOR && UNITY_6000_0_OR_NEWER
|
|
if (_cameraRT != null) _cameraRT.Resize(_canvas.pixelRect.width, _canvas.pixelRect.height);
|
|
#endif
|
|
}
|
|
|
|
private void RefreshIterations()
|
|
{
|
|
if (_iterations == iterations) return;
|
|
SetRenderPipeline();
|
|
_iterations = iterations;
|
|
if (_blurCameraPass != null) _blurCameraPass.iterations = _iterations;
|
|
if (_blurMats != null && _blurMats.Length > 0 && _blurMats[0] != null) _blurMats[0].SetFloat(_iterationId, _iterations);
|
|
_needRefresh = true;
|
|
}
|
|
|
|
private void SetRenderPipeline()
|
|
{
|
|
if (_iterations != 0 && iterations == 0)
|
|
{
|
|
_blurRT.Release();
|
|
_blurRT.Create();
|
|
RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
|
|
}
|
|
else if (_iterations == 0 && iterations != 0)
|
|
{
|
|
RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
|
|
}
|
|
}
|
|
|
|
private void RefreshDownSample()
|
|
{
|
|
if (Mathf.Approximately(_downSample, downSample)) return;
|
|
_downSample = downSample;
|
|
_needResize = true;
|
|
SetBlurStrength();
|
|
_needRefresh = true;
|
|
}
|
|
|
|
private void RefreshBlurRadius()
|
|
{
|
|
if (Mathf.Approximately(_blurRadius, blurRadius)) return;
|
|
_blurRadius = blurRadius;
|
|
SetBlurStrength();
|
|
_needRefresh = true;
|
|
}
|
|
|
|
private void SetBlurStrength()
|
|
{
|
|
if (_blurMat != null) _blurMat.SetFloat(_blurRadiusId, _blurRadius / _downSample);
|
|
if (_blurMat2 != null) _blurMat2.SetFloat(_blurRadiusId, _blurRadius / _downSample * 0.618f);
|
|
|
|
if (_blurMats != null)
|
|
{
|
|
for (var i = 0; i < _blurMats.Length; i++)
|
|
{
|
|
if (_blurMats[i] != null)
|
|
_blurMats[i].SetFloat(_blurRadiusId, _blurRadius * 2 / _downSample * Mathf.Pow(0.5f, i));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetBlurRange()
|
|
{
|
|
if (_rectTransform == null || _canvas == null || _blurMat == null) return;
|
|
|
|
Vector3[] corners = new Vector3[4];
|
|
_rectTransform.GetWorldCorners(corners);
|
|
|
|
Camera cam = _canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : _canvas.worldCamera;
|
|
|
|
Vector2 screenMin = RectTransformUtility.WorldToScreenPoint(cam, corners[0]);
|
|
Vector2 screenMax = RectTransformUtility.WorldToScreenPoint(cam, corners[2]);
|
|
|
|
Rect canvasRect = _canvas.pixelRect;
|
|
float xMin = screenMin.x / canvasRect.width;
|
|
float xMax = screenMax.x / canvasRect.width;
|
|
float yMin = screenMin.y / canvasRect.height;
|
|
float yMax = screenMax.y / canvasRect.height;
|
|
|
|
_blurMat.SetVector(_regionId, new Vector4(xMin, yMin, xMax, yMax));
|
|
_needRefresh = true;
|
|
}
|
|
|
|
private void ForceCameraRender()
|
|
{
|
|
if (!Application.isEditor) return;
|
|
if (!_needRefresh && Application.isPlaying) return;
|
|
_needRefresh = false;
|
|
#if UNITY_EDITOR && UNITY_6000_0_OR_NEWER
|
|
if (_cameraRT == null)
|
|
{
|
|
if (_mainCamera != null)
|
|
{
|
|
_cameraRT = new RenderTexture(_mainCamera.pixelWidth, _mainCamera.pixelHeight, 16) { name = "CameraRT" };
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
RenderPipeline.SubmitRenderRequest(_mainCamera, new RenderPipeline.StandardRequest { destination = _cameraRT });
|
|
#else
|
|
_mainCamera.Render();
|
|
#endif
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
|
|
if (_initStep < 1 || !_scene.isLoaded) return;
|
|
|
|
_rawImage.texture = null;
|
|
_rawImage.enabled = false;
|
|
RegisterMonitor(false);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_tempRT != null)
|
|
{
|
|
_tempRT.Release();
|
|
DestroyImmediate(_tempRT);
|
|
}
|
|
if (_blurRT != null)
|
|
{
|
|
_blurRT.Release();
|
|
DestroyImmediate(_blurRT);
|
|
}
|
|
#if UNITY_EDITOR && UNITY_6000_0_OR_NEWER
|
|
if (_cameraRT != null)
|
|
{
|
|
_cameraRT.Release();
|
|
DestroyImmediate(_cameraRT);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public enum BlurType
|
|
{
|
|
Gaussian,
|
|
Kawase,
|
|
Grainy
|
|
}
|
|
} |