Unity UI界面局部模糊

最近开发碰到的需求:打开一个弹窗,只在弹窗覆盖的区域下方实现局部模糊,其他地方仍然保持清晰,弹窗的位置不固定。
先看下效果,白色方块是一个RawImage,用来表示一个弹窗,点击按钮,进行局部模糊。
在这里插入图片描述
原理是先对RawImage覆盖的区域进行截屏,然后用Shader做高斯模糊。实际开发中,在弹窗出现前,先对弹窗所覆盖区域进行局部截屏,然后把处理好的RenderTexture赋值给弹窗的背景。
截屏使用的代码参考这篇文章:Unity3D 局部截图、全屏截图、带UI截图三种方法

代码实现

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 对_rawImage所在区域截屏并模糊处理
/// </summary>
public class 局部模糊 : MonoBehaviour
{
    public RawImage _rawImage;

    public void OnButtonClick()
    {
        RectTransform rectTransform = this.transform.GetComponent<RectTransform>();
        BlurRegion(rectTransform);
    }
    
    /// <summary>
    /// uiRoot是界面根节点
    /// </summary>
    private void BlurRegion(RectTransform uiRoot, int iterations = 3, float blurSpread = 2.0f, int downSample = 2)
    {
        //先将_rawImage的透明度改为0,不影响截屏
        Color c = _rawImage.color;
        c.a = 0;
        _rawImage.color = c;
        StartCoroutine(BlurRegionCoroutine(uiRoot, _rawImage, iterations, blurSpread, downSample));
    }
    
    /// <summary>
    /// 注意:根节点这里Canvas的Render mode为Overlay,其他模式,Canvas的宽高和屏幕宽高如果不一致,则需要转换
    /// </summary>
    private IEnumerator BlurRegionCoroutine(RectTransform uiRoot, RawImage rawImage, int iterations, float blurSpread, int downSample)
    {
        yield return new WaitForEndOfFrame();
        
        RectTransform imageRt = rawImage.rectTransform;
        int imageWidth = (int)(imageRt.rect.width);
        int imageHeight = (int)(imageRt.rect.height);
        Texture2D texture2D = new Texture2D(imageWidth, imageHeight, TextureFormat.RGB24, false);

        //计算rawImage作为uiRoot子物体时的局部坐标,因为rawImage可能是孙子节点,所以需要转换
        Vector3 imagePos = uiRoot.InverseTransformPoint(rawImage.transform.position);
        //计算rawImage左下角坐标,屏幕左下角为原点(0, 0)
        Vector2 imagePivot = imageRt.pivot;
        float leftBottomX = Screen.width * 0.5f - imageWidth * imagePivot.x + imagePos.x;
        float leftBottomY = Screen.height * 0.5f - imageHeight * imagePivot.y + imagePos.y;
        
        //从屏幕读取像素, leftBottomX,leftBottomY 是读取的初始位置,width,height是读取像素的宽度和高度
        texture2D.ReadPixels(new Rect(leftBottomX, leftBottomY, imageWidth, imageHeight), 0, 0);
        texture2D.Apply(false, true);
        
        //使用《Shader入门精要》中用到的高斯模糊
        Shader shader = AssetDatabase.LoadAssetAtPath<Shader>("Assets/Resources/Chapter12-GaussianBlur.shader");
        if (shader == null || rawImage == null)
            yield break;
        var material = new Material(shader);
        if (material == null)
            yield break;

        RenderImage(rawImage, iterations, blurSpread, downSample, material, texture2D);
        Color c = rawImage.color;
        c.a = 1;
        rawImage.color = c;
    }

    private void RenderImage(RawImage rawImage, int iterations, float blurSpread, int downSample, Material material, Texture2D texture2D)
    {
        int rtW = texture2D.width / downSample;
        int rtH = texture2D.height / downSample;
        
        // 首先定义了第一个缓存buffer0,并把src中的图像缩放后存储到buffer0中。在迭代过程中,我们又定义了第二个缓存buffer1。
        // 在执行第一个Pass时,输入是buffer0,输出是buffer1,完毕后首先把buffer0释放,再把结果值buffer1存储到buffer0中,
        // 重新分配buffer1,然后再调用第二个Pass,重复上述过程。迭代完成后,buffer0将存储最终的图像
        RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
        buffer0.filterMode = FilterMode.Bilinear;
        Graphics.Blit(texture2D, buffer0);
        
        for (int i = 0; i < iterations; i++)
        {
            material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
            RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            
            // Render the vertical pass
            Graphics.Blit(buffer0, buffer1, material, 0);
            
            RenderTexture.ReleaseTemporary(buffer0);
            buffer0 = buffer1;
            buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            
            // Render the horizontal pass
            Graphics.Blit(buffer0, buffer1, material, 1);
            
            RenderTexture.ReleaseTemporary(buffer0);
            buffer0 = buffer1;
        }

        var buffer = RenderTexture.GetTemporary(buffer0.descriptor); //最终效果
        Graphics.Blit(buffer0, buffer);
        RenderTexture.ReleaseTemporary(buffer0);
        rawImage.texture = buffer;
    }
}

高斯模糊Shader来自《Shader入门精要》,也一并粘贴过来

Shader "Unity Shaders Book/Chapter 12/Gaussian Blur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BlurSize ("Blur Size", Float) = 1.0
	}
	SubShader {
		// CGINCLUDE类似于C++中头文件的功能。由于高斯模糊需要定义两个Pass,但它们使用的片元着色器代码
		// 是完全相同的,使用CGINCLUDE可以避免我们编写两个完全一样的frag函数。
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		sampler2D _MainTex;  
		half4 _MainTex_TexelSize;
		float _BlurSize;
		  
		struct v2f {
			float4 pos : SV_POSITION;
			half2 uv[5]: TEXCOORD0;
		};
		  
		v2f vertBlurVertical(appdata_img v) {
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			// 数组的第一个坐标存储了当前的采样纹理,而剩余的四个坐标则是高斯模糊中对邻域采样时使用的纹理坐标。
			// 我们还和属性_BlurSize相乘来控制采样距离。
			o.uv[0] = uv;
			o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
			o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
			o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
			o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
					 
			return o;
		}
		
		v2f vertBlurHorizontal(appdata_img v) {
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			
			o.uv[0] = uv;
			o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
			o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
			o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
			o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
					 
			return o;
		}
		
		fixed4 fragBlur(v2f i) : SV_Target {
			float weight[3] = {0.4026, 0.2442, 0.0545};
			
			fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
			
			for (int it = 1; it < 3; it++) {
				sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
				sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
			}
			
			return fixed4(sum, 1.0);
		}
		    
		ENDCG
		
		ZTest Always Cull Off ZWrite Off
		
		Pass {
			//定义名字,可以在其他shader中使用该pass
			NAME "GAUSSIAN_BLUR_VERTICAL"
			
			CGPROGRAM
			  
			#pragma vertex vertBlurVertical  
			#pragma fragment fragBlur
			  
			ENDCG  
		}
		
		Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL"
			
			CGPROGRAM  
			
			#pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
			
			ENDCG
		}
	} 
	FallBack "Diffuse"
}

边缘柔化

目前模糊效果边缘比较硬,为了优化可以在RawImage上层添加一个RectMask2D,并设置Softness
在这里插入图片描述
而RawImage的RectTransform改成锚定RectMask2D的四条边,这样方便通过RectMask2D来修改范围。
最终效果
在这里插入图片描述

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Unity UI界面Demo是一个Unity游戏引擎中的界面设计示例。这个Demo包含了很多基本的UI元素,比如按钮、文本框、下拉框、滑动条、进度条等等,并且展示了它们在不同情况下的使用方式和效果。 这个Demo可以帮助开发者学习如何在Unity中使用UI元素,以及如何实现基本的UI交互。通过这个Demo,开发者可以了解如何创建UI元素、设置它们的样式和属性、添加事件监听以及处理相关的逻辑。同时,这个Demo还通过漂亮的界面设计和动画效果,展示了如何将UI元素组合起来,创造出更加丰富和生动的用户界面。 总的来说,Unity UI界面Demo是一个非常有用的教学资源,它能够帮助开发者快速掌握Unity中的UI设计和实现技术,提高开发效率和游戏的用户体验。 ### 回答2: Unity UI界面Demo是一种展示Unity UI设计所使用的演示。在Unity UI界面Demo中,用户可以观看一个已经制作好的UI界面模型,探索它的全部功能和设计特点。其中包含了各种不同的UI元素,比如按钮、输入框、进度条、文本框等。 通过观察UI界面Demo,用户可以更好地理解Unity UI的优点和核心概念。这种演示设计用于减少UI视觉设计师和开发人员之间的交流障碍。 此外,在演示时,用户还可以尝试改变和调整UI元素的属性、颜色和布局来了解UI的自定义功能,从而提高自己的UI设计水平。总之,Unity UI界面Demo是一种非常实用的工具,可以在短时间内展示和学习UI设计的技巧和知识,是许多UI设计师和开发人员必备的学习资源之一。 ### 回答3: Unity UI界面Demo是一种展示Unity开发环境中UI元素的演示程序。这个Demo能够通过一些简单的图形界面元素,展示出Unity引擎中强大的UI制作功能。Demo包含了许多常用的UI元素,比如按钮、文本框、图片、滚动条等等,这样开发者就可以通过它们的使用,学习如何在Unity中创建各种类型的UI界面,以及如何将它们组合成一个完美的应用程序。 这个Demo还包含了一些不同的设计风格和交互方式,例如可以自定义UI组件的样式和颜色,增加动画效果,使UI界面更加生动。还可以绑定事件处理程序,在用户交互时触发执行,从而实现更多交互方式。 Unity UI界面Demo不仅仅展示了UnityUI制作功能,也帮助开发者更好地扩展自己的应用程序,它可以被用作构建任何类型的应用程序UI界面的起点,从一个简单的游戏UI到更复杂的商业应用程序UI界面的开发,都可以从这里开始。因此,这个Demo也是Unity开发者掌握UI开发的重要资源之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值