180 lines
6.3 KiB
C#
180 lines
6.3 KiB
C#
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
using UnityEngine.Rendering.Universal;
|
||
using UnityEngine.Rendering.RenderGraphModule;
|
||
using UnityEngine.Rendering.RenderGraphModule.Util;
|
||
|
||
public class UIBlurDualKawaseFeature : ScriptableRendererFeature
|
||
{
|
||
[System.Serializable]
|
||
public class Settings
|
||
{
|
||
public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingTransparents;
|
||
|
||
[Range(0.25f, 1f)] public float downscale = 0.5f; // 0.5(中高端) / 0.25(低端更稳)
|
||
[Range(1, 6)] public int blurPasses = 4; // 3~4 移动端常用
|
||
[Range(0.5f, 4f)] public float offset = 1.0f; // 糊的“扩散感”
|
||
|
||
public bool updateEveryFrame = false; // 推荐 false,只在需要时刷新
|
||
public string refreshFlagName = "_UIBlurRefresh"; // UI 触发用
|
||
public string globalTextureName = "_UIBlurTexture";
|
||
|
||
public Shader blurShader; // Hidden/URP/DualKawaseBlur
|
||
}
|
||
|
||
public Settings settings = new Settings();
|
||
|
||
class Pass : ScriptableRenderPass
|
||
{
|
||
readonly Settings s;
|
||
readonly Material mat;
|
||
|
||
// 持久 RT:让 UI 能跨帧拿到(需要 ImportTexture):contentReference[oaicite:2]{index=2}
|
||
RTHandle persistentOutput;
|
||
|
||
static readonly int SourceTexId = Shader.PropertyToID("_SourceTex");
|
||
static readonly int OffsetId = Shader.PropertyToID("_Offset");
|
||
|
||
public Pass(Settings settings, Material material)
|
||
{
|
||
s = settings;
|
||
mat = material;
|
||
}
|
||
|
||
public RTHandle OutputRT => persistentOutput;
|
||
|
||
public void EnsureOutput(ref RenderTextureDescriptor desc, int w, int h)
|
||
{
|
||
desc.width = w;
|
||
desc.height = h;
|
||
desc.depthBufferBits = 0;
|
||
desc.msaaSamples = 1;
|
||
desc.useMipMap = false;
|
||
desc.autoGenerateMips = false;
|
||
|
||
RenderingUtils.ReAllocateIfNeeded(
|
||
ref persistentOutput,
|
||
desc,
|
||
FilterMode.Bilinear,
|
||
TextureWrapMode.Clamp,
|
||
name: s.globalTextureName
|
||
);
|
||
}
|
||
|
||
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
||
{
|
||
// 资源/相机数据(官方示例也是这么取 activeColorTexture):contentReference[oaicite:3]{index=3}
|
||
var resourceData = frameData.Get<UniversalResourceData>();
|
||
var cameraData = frameData.Get<UniversalCameraData>();
|
||
|
||
// 避免从 backbuffer blit(官方示例也这么做):contentReference[oaicite:4]{index=4}
|
||
if (resourceData.isActiveTargetBackBuffer)
|
||
return;
|
||
|
||
// 刷新策略:不每帧更新时,只有收到 UI 请求才做一轮
|
||
if (!s.updateEveryFrame)
|
||
{
|
||
if (Shader.GetGlobalInt(s.refreshFlagName) == 0)
|
||
return;
|
||
|
||
Shader.SetGlobalInt(s.refreshFlagName, 0);
|
||
}
|
||
|
||
// 基础描述符(跟相机一致)
|
||
var desc = cameraData.cameraTargetDescriptor;
|
||
desc.depthBufferBits = 0;
|
||
desc.msaaSamples = 1;
|
||
|
||
int baseW = Mathf.Max(16, Mathf.RoundToInt(desc.width * s.downscale));
|
||
int baseH = Mathf.Max(16, Mathf.RoundToInt(desc.height * s.downscale));
|
||
|
||
EnsureOutput(ref desc, baseW, baseH);
|
||
|
||
// 把持久 RTHandle 导入 RenderGraph(跨帧可用):contentReference[oaicite:5]{index=5}
|
||
TextureHandle outTex = renderGraph.ImportTexture(persistentOutput);
|
||
|
||
// 生成金字塔:0层(base), 1层(/2), 2层(/4)...
|
||
int n = Mathf.Clamp(s.blurPasses, 1, 8);
|
||
TextureHandle[] pyr = new TextureHandle[n];
|
||
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
int w = Mathf.Max(8, baseW >> i);
|
||
int h = Mathf.Max(8, baseH >> i);
|
||
|
||
var d = desc;
|
||
d.width = w;
|
||
d.height = h;
|
||
pyr[i] = UniversalRenderer.CreateRenderGraphTexture(renderGraph, d, $"_UIBlurPyr{i}", false);
|
||
}
|
||
|
||
mat.SetFloat(OffsetId, s.offset);
|
||
|
||
// source:相机颜色
|
||
TextureHandle src = resourceData.activeColorTexture;
|
||
|
||
// Downsample chain (pass 0)
|
||
{
|
||
var p = new RenderGraphUtils.BlitMaterialParameters(src, pyr[0], mat, 0);
|
||
renderGraph.AddBlitPass(p, "UIBlur DualKawase Down 0");
|
||
}
|
||
for (int i = 1; i < n; i++)
|
||
{
|
||
var p = new RenderGraphUtils.BlitMaterialParameters(pyr[i - 1], pyr[i], mat, 0);
|
||
renderGraph.AddBlitPass(p, $"UIBlur DualKawase Down {i}");
|
||
}
|
||
|
||
// Upsample chain (pass 1)
|
||
for (int i = n - 1; i > 0; i--)
|
||
{
|
||
var p = new RenderGraphUtils.BlitMaterialParameters(pyr[i], pyr[i - 1], mat, 1);
|
||
renderGraph.AddBlitPass(p, $"UIBlur DualKawase Up {i}");
|
||
}
|
||
|
||
// 最终写到持久输出 RT
|
||
{
|
||
var p = new RenderGraphUtils.BlitMaterialParameters(pyr[0], outTex, mat, 1);
|
||
renderGraph.AddBlitPass(p, "UIBlur DualKawase Final");
|
||
}
|
||
}
|
||
|
||
public void Cleanup()
|
||
{
|
||
persistentOutput?.Release();
|
||
persistentOutput = null;
|
||
}
|
||
}
|
||
|
||
Material _mat;
|
||
Pass _pass;
|
||
|
||
public override void Create()
|
||
{
|
||
if (settings.blurShader == null)
|
||
settings.blurShader = Shader.Find("Hidden/URP/DualKawaseBlur");
|
||
|
||
if (settings.blurShader != null)
|
||
_mat = CoreUtils.CreateEngineMaterial(settings.blurShader);
|
||
|
||
_pass = new Pass(settings, _mat) { renderPassEvent = settings.passEvent };
|
||
}
|
||
|
||
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
||
{
|
||
if (_mat == null) return;
|
||
if (renderingData.cameraData.cameraType != CameraType.Game) return;
|
||
|
||
// 这里提前把全局纹理指向持久 RT(即使本帧不刷新,也能用旧结果)
|
||
if (_pass.OutputRT != null)
|
||
Shader.SetGlobalTexture(settings.globalTextureName, _pass.OutputRT);
|
||
|
||
renderer.EnqueuePass(_pass);
|
||
}
|
||
|
||
protected override void Dispose(bool disposing)
|
||
{
|
||
_pass?.Cleanup();
|
||
CoreUtils.Destroy(_mat);
|
||
}
|
||
}
|