径向模糊这个概念最初我是在高中的时候学习PS有介绍的。
当时就觉得这个效果很棒。
当然PS那个径向模糊比我实现的这个效果要好(我这个也不能实现太好的效果,运行在手机上会受不了),毕竟PS是离线编辑,且针对一帧图像处理的,不存在什么性能不性能的问题,怎么炫就怎么来。
实现
从PS上的径向模糊不难理解,就是对指定某个径向点后,如下图中的绿色点C是径向点,红色的P,Q两点别分与C点直线上的前前后后都用采样点,就那些蓝色点,越是靠近C点,采样距离越小,所以模糊程度响度来说会比较小的(意思就会相对边缘的来说会比较清晰)。
来看看运行中,我们将其他参数调整一下,只看采样距离
注意查看远处的三颗球,红色圆圈是球的原始位置,黄色箭头是前前后后分别采样的位置,越靠近径向点,采样距离就越近。
还看不出来的话,你就直接看人物的头部,中间部分是最清晰的。
控制采样步长
在代码上只要控制好采样步长就可以了:
float2 stepDir = normalize(vec) * _SampleDistance; // 每次的采样步长方向
float stepLenFactor = len * 0.1 * _Intensity; // len : 0~0.5 再乘上 0.1 就是0~0.05,越是靠近中心开,采样距离会越小,模糊度就会相对边缘来说更小
stepDir *= stepLenFactor; // 控制步长值,stepLenFactor=len * 0.1 * _Intensity中的:0.1是经验数值可以不管,或是外部公开控制也是可以的
fixed4 sum = 0;
for (int it = 0; it < sampleCount; it++) {
float2 appliedStep = stepDir * it;
sum += tex2D(_DownSampleRT, i.uv + appliedStep); // 正向采样
sum += tex2D(_DownSampleRT, i.uv - appliedStep); // 反向采样
}
主要看这么一句:
float stepLenFactor = len * 0.1 * _Intensity; // len : 0~0.5 再乘上 0.1 就是0~0.05,越是靠近中心开,采样距离会越小,模糊度就会相对边缘来说更小
就可以控制越径向点月经采样距离越小
完整代码如下:
完整的Shader
// jave.lin 2020.03.21 径向模糊 - Radial Blur
Shader "Custom/RadialBlur" {
CGINCLUDE
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
float _Intensity; // 径向效果的强度
float _FadeRadius; // 淡出径向效果的半径范围
float _SampleDistance; // 每次采样的距离
sampler2D _DownSampleRT; // 原图降采样后的图
sampler2D _SrcTex; // 原始图像纹理
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target {
const int sampleCount = 5; // 单想采样次数,乘以2就是真的总次数
const float invSampleCount = 1.0 / ((float)sampleCount * 2);
float2 vec = i.uv - 0.5;
float len = length(vec);
float fade = smoothstep(0, _FadeRadius, len); // 平滑淡出的径向效果值
float2 stepDir = normalize(vec) * _SampleDistance; // 每次的采样步长方向
float stepLenFactor = len * 0.1 * _Intensity; // len : 0~0.5 再乘上 0.1 就是0~0.05,越是靠近中心开,采样距离会越小,模糊度就会相对边缘来说更小
stepDir *= stepLenFactor; // 控制步长值,stepLenFactor=len * 0.1 * _Intensity中的:0.1是经验数值可以不管,或是外部公开控制也是可以的
fixed4 sum = 0;
for (int it = 0; it < sampleCount; it++) {
float2 appliedStep = stepDir * it;
sum += tex2D(_DownSampleRT, i.uv + appliedStep); // 正向采样
sum += tex2D(_DownSampleRT, i.uv - appliedStep); // 反向采样
}
sum *= invSampleCount; // 均值模糊
return lerp(tex2D(_SrcTex, i.uv), sum, fade * _Intensity);
}
ENDCG
SubShader {
Cull Off ZWrite Off ZTest Always
Pass {
NAME "RADIA_BLUR"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
均值模糊
完整的Shader中,也可以看到,我也采样的是均值模糊sum *= invSampleCount; // 均值模糊
,性能上也是比较高的方式。
DownSample
接下来我们再对采样图做了downSample(就是对径向模糊的数据源采样图做了降低分辨率),最后就只有58 x 27左右的尺寸,在移动端上性能是很可观的,而且降低采样图尺寸后除了占用VRAM(显存)少了,就连模糊的效果也提升了,实在是一举两得。
完整的CSharp
using UnityEngine;
/// <summary>
/// jave.lin 2020.03.21 径向模糊 Radial Blur
/// </summary>
public class RadialBlurScript : MonoBehaviour
{
private static int _Intensity_hash = Shader.PropertyToID("_Intensity");
private static int _FadeRadius_hash = Shader.PropertyToID("_FadeRadius");
private static int _SampleDistance_hash = Shader.PropertyToID("_SampleDistance");
private static int _SrcTex_hash = Shader.PropertyToID("_SrcTex");
private static int _DownSampleRT_hash = Shader.PropertyToID("_DownSampleRT");
public Material mat;
[Header("Radial Blur")]
[Range(0, 1)]
public float intensity = 1; // 效果强度
[Range(0f, 1f)]
public float fadeRadius = 0.38f; // 半径范围内的都淡出
[Range(0f, 1f)]
public float sampleDistance = 0.25f; // 径向模糊每次采样的距离
[Range(1, 40)]
public int blurDownSample = 40; // 模糊降低输出采样率
private Camera cam;
private void Start()
{
cam = GetComponent<Camera>();
cam.depthTextureMode |= DepthTextureMode.Depth;
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (!mat)
{
Graphics.Blit(source, destination);
return;
}
var rw = Screen.width / blurDownSample;
var rh = Screen.height / blurDownSample;
RenderTexture downSampleRT = RenderTexture.GetTemporary(rw, rh, 0);
downSampleRT.filterMode = FilterMode.Bilinear;
downSampleRT.name = "downSampleRT";
Graphics.Blit(source, downSampleRT);
mat.SetFloat(_Intensity_hash, intensity);
mat.SetFloat(_FadeRadius_hash, fadeRadius);
mat.SetFloat(_SampleDistance_hash, sampleDistance);
mat.SetTexture(_DownSampleRT_hash, downSampleRT);
mat.SetTexture(_SrcTex_hash, source);
Graphics.Blit(null, destination, mat, 0);
RenderTexture.ReleaseTemporary(downSampleRT);
}
}
运行效果
Project
backup : UnityShader_RadialBlurTesting_2018.3.0f2