
效果

目的
学习径向模糊的制作方法?
怎么使用径向模糊,
有那些需要注意的内容。
径向模糊 | Radial Blur
上面一篇介绍到基础模糊效果,这次学习经向模糊,
经向模糊大部分使用到BOSS战斗或者飞行,赛车表现效果。

径向模糊,是一种从中心向外呈幅射状的逐渐模糊的效果
制作思路
由此我们可以大概确定下径向模糊实现的流程:
第一步:确定径向模糊的中心点,通常取图像的正中心点、
第二步:计算采样像素与中心点的距离,根据距离确定偏移程度,即离中心点越远,偏移量越大。
第三步:将采样点的颜色值做加权求和,本例使用平均求和。
实现过程
准备径向模糊Shader
径向模糊的特点是从某个像素为中心向外辐射状扩散,因此需要采样的像素在原像素和中间点像素的连线上,不同连线上的点不会相互影响.
Shader
shader我们准备两个Pass 一个处理模糊方向,一个混合模糊和原画
已中心点为起点,当前像素为终点,进行

Shader "URP/4_RadialBlur"
{
Properties
{
_MainTex ("_MainTex", 2D) = "white" {}
_Blur("_Blur",Float) = 0
[int]_Loop("_Loop",range(1,10)) = 1
_X("_X",Float) = 0.5
_Y("_Y",Float) = 0.5
_Instensity("_Instensity",Float) = 0
}
SubShader
{
Tags { "RenderPipeline"="UniversalPipeline" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment radialfrag
#include "Assets/Post/04_RadialBliur/RadialBlur.hlsl" //函数库
ENDHLSL
}
}
}
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float _Blur;
float _Loop;
float _Y;
float _X;
CBUFFER_END
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
TEXTURE2D(_SourceTex); SAMPLER(sampler_SourceTex);
struct appdata
{
float4 positionOS : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.positionOS.xyz);
o.uv = v.texcoord;
return o;
}
half4 radialfrag(v2f i) : SV_Target
{
float4 col = 0;
float2 dir = (float2(_X,_Y) - i.uv) * _Blur * 0.01;
for(int t = 0; t < _Loop; t++)
{
col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + dir * t)/ _Loop;
}
return col;
}
测试一下是否起作用。
增加一个图片,调整数值, X = 0.5, Y = 0.5 是中心点,测试效果是否正确。

这个是在单材质上测试。

Volume
定义几个属性
采样中心点,迭代次数 模糊采样距离 降采样 模糊强度等。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class RadialBlurVolume : VolumeComponent, IPostProcessComponent
{
[Tooltip("模糊中心点")]
public FloatParameter X = new FloatParameter(0.5f);
public FloatParameter Y = new FloatParameter(0.5f);
[Range(0f, 10f), Tooltip("模糊的迭代次数")]
public IntParameter BlurTimes = new ClampedIntParameter(1, 1, 10);
[Range(0f, 10f), Tooltip("模糊半径")]
public FloatParameter BlurRange = new ClampedFloatParameter(1.0f, 0.0f, 10.0f);
[Range(0f, 10f), Tooltip("降采样次数")]
public IntParameter RTDownSampling = new ClampedIntParameter(1, 1, 10);
public bool IsActive() => RTDownSampling.value > 0f;
public bool IsTileCompatible() => false;
}
效果

RendererFeature 类
后处理组件也设置完成,那我们开始正式编辑渲染逻辑。
前设置模板
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class RadialBlurRenderFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
public Shader shader;
}
public Settings settings = new Settings();
RadialBlurPass radialBlurPass; // 定义我们创建出Pass
public override void Create()
{
this.name = "RadialBlur"; // 模糊渲染的名字
radialBlurPass = new RadialBlurPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader); // 初始化 我们的渲染层级和Shader
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(radialBlurPass);
}
}
public class RadialBlurPass : ScriptableRenderPass
{
static readonly string RenderTag = " Blur Effects"; // 设置渲染标签
RadialBlurVolume radialBlur; // 定义组件类型
Material Radialmaterial; // 后处理材质
//RenderTargetIdentifier renderTargetIdentifier; // 设置当前渲染目标
RenderTargetIdentifier BlurTex;
RenderTargetIdentifier Temp1;
public RadialBlurPass(RenderPassEvent evt, Shader blurshader)
{
renderPassEvent = evt;
var shader = blurshader;
if (shader == null)
{
Debug.LogError("没有指定Shader");
return;
}
Radialmaterial = CoreUtils.CreateEngineMaterial(blurshader);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (Radialmaterial == null)
{
Debug.LogError("材质初始化失败");
return;
}
if (!renderingData.cameraData.postProcessEnabled)
{
Debug.LogError("");
return;
}
var stack = VolumeManager.instance.stack; // 传入 volume
radialBlur = stack.GetComponent<RadialBlurVolume>(); // 获取到后处理组件
if (radialBlur == null)
{
Debug.LogError("获取组件失败");
return;
}
var cmd = CommandBufferPool.Get(RenderTag); // 渲染标签
Render(cmd, ref renderingData); // 调用渲染函数
context.ExecuteCommandBuffer(cmd); // 执行函数,回收。
CommandBufferPool.Release(cmd);
}
void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
RenderTargetIdentifier sourceRT = renderingData.cameraData.renderer.cameraColorTarget; // 定义RT
RenderTextureDescriptor inRTDesc = renderingData.cameraData.cameraTargetDescriptor;
inRTDesc.depthBufferBits = 0; // 清除深度
// 定义屏幕尺寸
var width = (int)(inRTDesc.width / radialBlur.RTDownSampling.value);
var height = (int)(inRTDesc.height / radialBlur.RTDownSampling.value);
}
}
开始处理模糊
绑定后处理组件的参数
Radialmaterial.SetFloat("_Loop", radialBlur.BlurTimes.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_X", radialBlur.X.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_Y", radialBlur.Y.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_Blur", radialBlur.BlurRange.value); // Shader变量 和 Volume 组件属性 绑定
创建临时RT
int TempID1 = Shader.PropertyToID("Temp1");
int BlurTexID = Shader.PropertyToID("_BlurTex"); // 临时
// 获取一张临时RT
cmd.GetTemporaryRT(TempID1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去
cmd.GetTemporaryRT(BlurTexID, inRTDesc); // 模糊图
前把原图传临时RT,RT和模糊储存到 BlurTex 里,
BlurTex = new RenderTargetIdentifier(BlurTexID);
Temp1 = new RenderTargetIdentifier(TempID1);
cmd.Blit(sourceRT, Temp1); // 摄像机渲染的图储存到 Temp1
cmd.Blit(Temp1, BlurTex, Radialmaterial, 0); // 临时图像 进行径向模糊
cmd.Blit(BlurTex, sourceRT); // 模糊 和原图混合
模糊结果输出到画面。
完成


扩展 : 可以调整模糊半径
有时候我们不希望模糊中间部分,我们希望中间部分没有模糊。
所以在Shader路我们设置一个半径
half4 radialfrag(v2f i) : SV_Target
{
float4 col = 0;
float2 dir = (float2(_X,_Y) - i.uv) * _Blur * 0.01;
float blurParams = saturate(distance(i.uv,float2(_X,_Y)) / _BufferRadius); // 控制不模糊的半径
for(int t = 0; t < _Loop; t++)
{
col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv + dir * t * blurParams)/ _Loop;
}
return col;
}
我们在后处理组件中增加控制
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class RadialBlurVolume : VolumeComponent, IPostProcessComponent
{
[Tooltip("模糊中心点")]
public FloatParameter X = new FloatParameter(0.5f);
public FloatParameter Y = new FloatParameter(0.5f);
[Range(0f, 10f), Tooltip("模糊的迭代次数")]
public IntParameter BlurTimes = new ClampedIntParameter(1, 1, 10);
[Range(0f, 10f), Tooltip("模糊半径")]
public FloatParameter BlurRange = new ClampedFloatParameter(1.0f, 0.0f, 10.0f);
[Range(0f, 10f), Tooltip("降采样次数")]
public IntParameter RTDownSampling = new ClampedIntParameter(1, 1, 10);
[Range(0f, 10f), Tooltip("模糊半径")]
public FloatParameter BufferRadius = new ClampedFloatParameter(1.0f, 0.0f, 5.0f);
public bool IsActive() => RTDownSampling.value > 0f;
public bool IsTileCompatible() => false;
}
在脚本里绑定属性
Radialmaterial.SetFloat("_BufferRadius", radialBlur.BufferRadius.value); // Shader变量 和 Volume 组件属性 绑定
效果对比
全模糊效果

中间没有模糊

全代码
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class RadialBlurRenderFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
public Shader shader;
}
public Settings settings = new Settings();
RadialBlurPass radialBlurPass; // 定义我们创建出Pass
public override void Create()
{
this.name = "RadialBlur"; // 模糊渲染的名字
radialBlurPass = new RadialBlurPass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader); // 初始化 我们的渲染层级和Shader
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(radialBlurPass);
}
}
public class RadialBlurPass : ScriptableRenderPass
{
static readonly string RenderTag = " Blur Effects"; // 设置渲染标签
RadialBlurVolume radialBlur; // 定义组件类型
Material Radialmaterial; // 后处理材质
//RenderTargetIdentifier renderTargetIdentifier; // 设置当前渲染目标
RenderTargetIdentifier BlurTex;
RenderTargetIdentifier Temp1;
public RadialBlurPass(RenderPassEvent evt, Shader blurshader)
{
renderPassEvent = evt;
var shader = blurshader;
if (shader == null)
{
Debug.LogError("没有指定Shader");
return;
}
Radialmaterial = CoreUtils.CreateEngineMaterial(blurshader);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (Radialmaterial == null)
{
Debug.LogError("材质初始化失败");
return;
}
if (!renderingData.cameraData.postProcessEnabled)
{
Debug.LogError("");
return;
}
var stack = VolumeManager.instance.stack; // 传入 volume
radialBlur = stack.GetComponent<RadialBlurVolume>(); // 获取到后处理组件
if (radialBlur == null)
{
Debug.LogError("获取组件失败");
return;
}
var cmd = CommandBufferPool.Get(RenderTag); // 渲染标签
Render(cmd, ref renderingData); // 调用渲染函数
context.ExecuteCommandBuffer(cmd); // 执行函数,回收。
CommandBufferPool.Release(cmd);
}
void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
RenderTargetIdentifier sourceRT = renderingData.cameraData.renderer.cameraColorTarget; // 定义RT
RenderTextureDescriptor inRTDesc = renderingData.cameraData.cameraTargetDescriptor;
inRTDesc.depthBufferBits = 0; // 清除深度
// 定义屏幕尺寸
var width = (int)(inRTDesc.width / radialBlur.RTDownSampling.value);
var height = (int)(inRTDesc.height / radialBlur.RTDownSampling.value);
Radialmaterial.SetFloat("_Loop", radialBlur.BlurTimes.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_X", radialBlur.X.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_Y", radialBlur.Y.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_Blur", radialBlur.BlurRange.value); // Shader变量 和 Volume 组件属性 绑定
Radialmaterial.SetFloat("_BufferRadius", radialBlur.BufferRadius.value); // Shader变量 和 Volume 组件属性 绑定
int TempID1 = Shader.PropertyToID("Temp1");
int BlurTexID = Shader.PropertyToID("_BlurTex"); // 临时
// 获取一张临时RT
cmd.GetTemporaryRT(TempID1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去
cmd.GetTemporaryRT(BlurTexID, inRTDesc); // 模糊图
BlurTex = new RenderTargetIdentifier(BlurTexID);
Temp1 = new RenderTargetIdentifier(TempID1);
cmd.Blit(sourceRT, Temp1); // 摄像机渲染的图储存到 Temp1
cmd.Blit(Temp1, BlurTex, Radialmaterial, 0); // 临时图像 进行径向模糊
cmd.Blit(BlurTex, sourceRT); // 模糊 和原图混合
}
}
