高斯模糊 / 噪点 / 毛玻璃 / 雪花屏 等模糊采样的原理
Shader代码
C#调用代码在最后,最终效果图如下<blur>
cpp
Shader "ZUI/UI_Blur"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_BlurSize ("Blur Strength", Range(0, 20)) = 1 // 模糊强度,0=无模糊
_BlurIteration ("Blur Iteration", Range(1,5)) = 2 // 模糊迭代次数,值越高越细腻
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
fixed4 _Color;
float _BlurSize;
int _BlurIteration;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex);
OUT.color = IN.color * _Color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
float2 uv = IN.texcoord;
fixed4 col = fixed4(0,0,0,0);
float2 blurStep = _MainTex_TexelSize * _BlurSize;
int iteration = _BlurIteration;
int sampleCount = 0;
for(int x = -iteration; x <= iteration; x++)
{
for(int y = -iteration; y <= iteration; y++)
{
float2 offset = float2(x, y) * blurStep;
col += tex2D(_MainTex, uv + offset) * IN.color;
sampleCount++;
}
}
col /= sampleCount;
return col;
}
ENDCG
}
}
}
模糊处理的数学原理
模糊采样的理论核心属于 数字图像处理(Digital Image Processing) 中的 图像平滑(Image Smoothing) 分支,其本质是邻域滤波(Neighborhood Filtering) 技术,理论体系成熟
不好意思,我是没办法把这个公式和下面的实际图片对应起来
(已写代码实现,分别三个,上面只提供了一个 blur的代码)

blur

noise

grass


数学没学好,不知道 1/MN 是何意味
**参考:《计算机图形学原理及实践》(Computer Graphics: Principles and Practice)**作者:James D. Foley 等地位:计算机图形学的权威著作,被称为「图形学红宝书」。
相关内容:第 15 章「图像合成与处理」,讲解了如何将图像处理的滤波理论移植到计算机图形学的 Shader 实现中,明确了「纹理采样 + 邻域加权」是实时模糊的核心工程方案。
==========================
至少做过,或思考过一个实际公式,第一个突破口比较重要
1/MN 就是二维参数的意思
经常写代码的同学应该知道,我们只需要把 S,T 统一成一个参数: Step
1/MN 也大概率是 1/(M + N ) 也应该不是乘法(实际上真的是乘法)
因为 foreach(x, + foreach (y 就是乘法逻辑

最终采样逻辑(Grid),因为Step固定):

额外说下毛玻璃Noise 的图例
(Shader 代码没有给出,图片效果为如上文章前半有给出)
主要比blur 的 step

更多还是死记硬背吧,

参考:
这大哥,在讲解美术toon 调整的时候,眼睛会发光。。。。(完全,非数学)
Toon Shader - 怎么得到更好的效果_哔哩哔哩_bilibili
https://blog.csdn.net/wolf96/article/details/45419239
C#调用shader 代码
cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
/// <summary>
/// 父节点统一控制所有子物体Image的模糊效果(Shader材质版)
/// 挂载:任意父节点GameObject
/// 功能:递归查找所有子Image,统一调节模糊强度、开关模糊
/// </summary>
public class ImageBlurGroup : MonoBehaviour
{
public bool useValidate;
[Header("==== 模糊控制参数 ====")]
[Range(0, 10)] public float blurStrength = 2f; // 模糊强度,0=关闭模糊
[Header("UI模糊材质球(拖拽赋值)")]
public Material blurMaterial; // 拖拽刚才创建的Mat_UIBlur进来
private List<Image> allTargetImages = new List<Image>(); // 所有待控制的Image集合
private Dictionary<Image, Material> originMaterialDic = new Dictionary<Image, Material>(); // 记录每个Image的原始材质,用于关闭模糊时还原
private void Awake()
{
// 初始化:递归查找父节点下所有的Image组件
FindAllChildImages(transform);
}
private void Start()
{
// 初始设置一次模糊强度
UpdateAllImageBlur();
}
//加了这个方法很方便,可以Runtime调;但好像Unity放在后台就会一直跑CPU,内存会一直爆炸
private void OnValidate()
{
if(useValidate==false) return;
// 在编辑器模式下,调节参数时实时生效(无需运行游戏)
if (allTargetImages.Count > 0)
{
UpdateAllImageBlur();
}
}
/// <summary>
/// 递归查找指定节点下的所有Image组件(核心方法)
/// </summary>
private void FindAllChildImages(Transform parentTrans)
{
// 获取当前节点的Image组件
Image img = parentTrans.GetComponent<Image>();
if (img != null)
{
allTargetImages.Add(img);
// 记录每个Image的原始材质,用于后续还原
originMaterialDic[img] = img.material;
}
// 递归遍历所有子节点
for (int i = 0; i < parentTrans.childCount; i++)
{
Transform childTrans = parentTrans.GetChild(i);
FindAllChildImages(childTrans);
}
}
/// <summary>
/// 统一更新所有Image的模糊效果(核心方法)
/// </summary>
public void UpdateAllImageBlur()
{
if (blurMaterial == null)
{
Debug.LogWarning("请给模糊控制器赋值【模糊材质球】!");
return;
}
foreach (var image in allTargetImages)
{
if (image == null) continue;
// 模糊强度为0 → 关闭模糊,还原原始材质
if (blurStrength <= 0)
{
if (originMaterialDic.ContainsKey(image))
{
image.material = originMaterialDic[image];
}
}
// 模糊强度>0 → 开启模糊,赋值模糊材质+设置强度
else
{
image.material = blurMaterial;
image.material.SetFloat("_BlurSize", blurStrength);
}
}
}
/// <summary>
/// 外部调用:开启模糊(可传参设置强度)
/// </summary>
public void OpenBlur(float strength = 2f)
{
blurStrength = strength;
UpdateAllImageBlur();
}
/// <summary>
/// 外部调用:关闭模糊
/// </summary>
public void CloseBlur()
{
blurStrength = 0;
UpdateAllImageBlur();
}
}