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(); if (!_monitor) { _monitor = _canvas.gameObject.AddComponent(); _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 } }