bloom替换
This commit is contained in:
@@ -2,48 +2,58 @@ using Echovoid.Runtime.Behavior.Rendering;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace SLSUtilities.Rendering.PostProcessing
|
||||
{
|
||||
[System.Serializable, VolumeComponentMenu("SLS/Postprocessing/Anime Bloom")]
|
||||
public class AnimeBloom : ScriptablePostProcessorVolume
|
||||
{
|
||||
// 放在 ToneMapping 之前效果最强,但放在 AfterPostProcess 最安全(不易过曝)
|
||||
// 这里建议 AfterPostProcess,配合 HDR 使用
|
||||
public override CustomPostProcessInjectionPoint InjectionPoint => CustomPostProcessInjectionPoint.AfterPostProcess;
|
||||
public override int OrderInInjectionPoint => 5; // 放在 Vignette 之前
|
||||
// BeforePostProcess: 在 ToneMapping 之前运行,确保 HDR bloom 颜色不被截断
|
||||
// 这与 Unity 原生 Bloom 的执行阶段完全一致
|
||||
public override CustomPostProcessInjectionPoint InjectionPoint => CustomPostProcessInjectionPoint.BeforePostProcess;
|
||||
public override int OrderInInjectionPoint => 5;
|
||||
|
||||
[Header("Glow Settings")]
|
||||
[Tooltip("泛光强度。值越大越亮。")]
|
||||
public ClampedFloatParameter intensity = new(0f, 0f, 10f);
|
||||
public ClampedFloatParameter intensity = new(1f, 0f, 10f);
|
||||
|
||||
[Tooltip("阈值。亮度超过此值的像素才会发光。\n关键:设为 1.1 可以过滤掉白墙(1.0),只让灯光发光。")]
|
||||
public MinFloatParameter threshold = new(1.1f, 0f); // 默认设为 1.1
|
||||
// 注意:threshold 在 Gamma 空间中设置,C# 侧会转为 Linear。
|
||||
// 0.9 在 gamma 空间 ≈ 0.79 在 linear 空间,这与 Unity 原生默认行为完全一致。
|
||||
public MinFloatParameter threshold = new(0.9f, 0f);
|
||||
|
||||
[Tooltip("柔膝 (Soft Knee)。让阈值过渡更平滑,避免高光边缘有硬切痕迹。")]
|
||||
public ClampedFloatParameter softKnee = new(0.5f, 0f, 1f);
|
||||
[Header("Scatter")]
|
||||
// scatter 在 [0,1] 范围内由用户设置。
|
||||
// C# 侧映射:Mathf.Lerp(0.05f, 0.95f, scatter.value),与 Unity 原生完全一致。
|
||||
// 低值 = 光晕聚拢,高值 = 光晕向外大范围扩散。
|
||||
public ClampedFloatParameter scatter = new(0.7f, 0f, 1f);
|
||||
|
||||
[Tooltip("最大亮度钳制。防止极亮像素(如太阳)产生乱跳的噪点。")]
|
||||
public MinFloatParameter clamp = new(65472f, 1f); // 默认很大,基本不限制
|
||||
public MinFloatParameter clamp = new(65472f, 1f);
|
||||
|
||||
[Header("Anime Style")]
|
||||
[Tooltip("扩散半径。值越大,光晕越松散、范围越大(二次元感核心)。")]
|
||||
public ClampedFloatParameter scatter = new(0.7f, 0f, 5f); // 推荐 0.5 - 1.0
|
||||
[Header("Iterations")]
|
||||
// 迭代次数:Dual Kawase 每层只需 1 Pass,性能远低于原生双趟高斯。
|
||||
// 4 次迭代 Dual Kawase ≈ 原生 6 次双趟高斯的扩散范围,但 Pass 数只有其 1/3。
|
||||
public ClampedIntParameter diffusion = new(4, 1, 8);
|
||||
|
||||
[Tooltip("迭代次数。次数越多,光晕越平滑、范围越大,但性能开销越高。")]
|
||||
public ClampedIntParameter diffusion = new(6, 1, 8); // 6次通常足够高品质
|
||||
[Header("Optimization (Mobile)")]
|
||||
[Tooltip("初始降采样倍率(首趟大幅压缩分辨率)。\n1=原生半分辨率(1/2);2=1/4分辨率;3=1/8分辨率。\n调大此值可用极低的迭代次数跑出巨大且柔和的光晕,大幅节省 GPU 带宽。")]
|
||||
public ClampedIntParameter initialDownscaleShift = new(1, 1, 3);
|
||||
|
||||
[Tooltip("采样偏移跨度放大(Kernel Scale)。\n1.0=原生标准跨度。适度放大(如1.2-1.5)可拉扯光晕扩散范围,但过大会产生轻微十字星/方格马赛克。")]
|
||||
public ClampedFloatParameter kernelScale = new(1.0f, 0.5f, 3.0f);
|
||||
|
||||
[Tooltip("泛光染色。可以做粉色霓虹、蓝色科技光等效果。")]
|
||||
[Header("Tint")]
|
||||
// tint 染色:C# 侧会归一化亮度(只保留色相/饱和度),与原生行为一致。
|
||||
// 这样调整 tint 颜色不会意外改变 bloom 总亮度。
|
||||
public ColorParameter tint = new(Color.white, true, true, true);
|
||||
|
||||
// 内部使用的 RT 数组
|
||||
private RTHandle[] _bloomPyramidUp;
|
||||
private RTHandle[] _bloomPyramidDown;
|
||||
private const int k_MaxPyramidSize = 16;
|
||||
// RT 数组(仅 Down 金字塔 + Up 金字塔,数量与 diffusion 对齐)
|
||||
private RTHandle[] _bloomMipDown;
|
||||
private RTHandle[] _bloomMipUp;
|
||||
private const int k_MaxMips = 8;
|
||||
|
||||
public override string GetShaderName() => "SLS/Postprocessing/AnimeBloom";
|
||||
public override bool IsActive() => intensity.value > 0f;
|
||||
|
||||
[System.Obsolete]
|
||||
public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination)
|
||||
{
|
||||
if (material == null) return;
|
||||
@@ -51,91 +61,131 @@ namespace SLSUtilities.Rendering.PostProcessing
|
||||
var desc = renderingData.cameraData.cameraTargetDescriptor;
|
||||
desc.msaaSamples = 1;
|
||||
desc.depthBufferBits = 0;
|
||||
|
||||
// 1. 设置参数
|
||||
Vector4 bloomParams = new Vector4(intensity.value, threshold.value, softKnee.value, clamp.value);
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomParams, bloomParams);
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomTint, tint.value);
|
||||
material.SetFloat(InternalShaderHelpers.ID._BlurRadius, scatter.value);
|
||||
|
||||
// 2. 初始化金字塔数组
|
||||
int iterations = Mathf.Clamp(diffusion.value, 1, k_MaxPyramidSize);
|
||||
|
||||
// 确保 RT 数组大小足够
|
||||
if (_bloomPyramidUp == null || _bloomPyramidUp.Length != k_MaxPyramidSize)
|
||||
|
||||
// 【移动端极限优化】:强制降级到 32位 HDR 格式 (B10G11R11),
|
||||
// 相比主相机默认的 64位 R16G16B16A16,带宽消耗直接砍半,且肉眼几乎无损!
|
||||
if (SystemInfo.IsFormatSupported(GraphicsFormat.B10G11R11_UFloatPack32, FormatUsage.Linear | FormatUsage.Render))
|
||||
{
|
||||
_bloomPyramidUp = new RTHandle[k_MaxPyramidSize];
|
||||
_bloomPyramidDown = new RTHandle[k_MaxPyramidSize];
|
||||
desc.graphicsFormat = GraphicsFormat.B10G11R11_UFloatPack32;
|
||||
}
|
||||
|
||||
// 3. Prefilter Pass (提取高亮)
|
||||
// 先降一半分辨率,节省性能且增加模糊感
|
||||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||||
desc.height = Mathf.Max(1, desc.height >> 1);
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 1. 参数打包(完全对齐 Unity SetupBloom 的计算逻辑)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidDown[0], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipDown0");
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidUp[0], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipUp0");
|
||||
// scatter: 用户 [0,1] → shader [0.05, 0.95](与原生 Lerp 映射一致)
|
||||
float scatterMapped = Mathf.Lerp(0.05f, 0.95f, scatter.value);
|
||||
|
||||
// Source -> Down[0] (Prefilter)
|
||||
Blitter.BlitCameraTexture(cmd, source, _bloomPyramidDown[0], material, 0);
|
||||
// threshold: Gamma → Linear(原生使用 GammaToLinearSpace 转换)
|
||||
float thresholdLinear = Mathf.GammaToLinearSpace(threshold.value);
|
||||
float thresholdKnee = thresholdLinear * 0.5f; // 硬编码 soft knee,与原生一致
|
||||
|
||||
// 4. Downsample Loop (降采样金字塔)
|
||||
int lastDown = 0;
|
||||
for (int i = 1; i < iterations; i++)
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomScatterParams,
|
||||
new Vector4(scatterMapped, clamp.value, thresholdLinear, thresholdKnee));
|
||||
|
||||
material.SetFloat(InternalShaderHelpers.ID._AnimeBloom_KernelScale, kernelScale.value);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2. Tint 归一化(原生做法:亮度归一为 1,只携带色相/饱和度)
|
||||
// 这样 tint 调整颜色时不会改变 bloom 总亮度。
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Color tintLinear = tint.value.linear;
|
||||
float luma = 0.2126f * tintLinear.r + 0.7152f * tintLinear.g + 0.0722f * tintLinear.b;
|
||||
Color tintNormalized = luma > 0f ? tintLinear * (1f / luma) : Color.white;
|
||||
|
||||
// 将 intensity 和归一化 tint 一并打包进 _BloomParams(原生 uber 方式)
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomParams,
|
||||
new Vector4(intensity.value, tintNormalized.r, tintNormalized.g, tintNormalized.b));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 3. RT 数组初始化
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
int mipCount = Mathf.Clamp(diffusion.value, 1, k_MaxMips);
|
||||
|
||||
if (_bloomMipDown == null || _bloomMipDown.Length != k_MaxMips)
|
||||
{
|
||||
// 每次分辨率减半
|
||||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||||
_bloomMipDown = new RTHandle[k_MaxMips];
|
||||
_bloomMipUp = new RTHandle[k_MaxMips];
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 4. Prefilter(提取高亮像素 + 初始激进降采样)
|
||||
// 根据用户选择,起始分辨率可能是 1/2, 1/4 或 1/8。
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
int shift = initialDownscaleShift.value;
|
||||
desc.width = Mathf.Max(1, desc.width >> shift);
|
||||
desc.height = Mathf.Max(1, desc.height >> shift);
|
||||
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipDown[0], desc, FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp, name: "_BloomDown0");
|
||||
|
||||
// Source → Down[0] via Pass 0 (Prefilter)
|
||||
Blitter.BlitCameraTexture(cmd, source, _bloomMipDown[0], material, 0);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 5. Downsample Loop:Dual Kawase 降采样金字塔(Pass 1)
|
||||
// 每次分辨率减半,产生多层不同尺度的模糊纹理
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
for (int i = 1; i < mipCount; i++)
|
||||
{
|
||||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||||
desc.height = Mathf.Max(1, desc.height >> 1);
|
||||
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidDown[i], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipDown" + i);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidUp[i], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipUp" + i);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipDown[i], desc, FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp, name: "_BloomDown" + i);
|
||||
|
||||
// Down[i-1] -> Down[i]
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[i - 1], _bloomPyramidDown[i], material, 1);
|
||||
lastDown = i;
|
||||
// Down[i-1] → Down[i] via Pass 1 (Dual Kawase Downsample)
|
||||
Blitter.BlitCameraTexture(cmd, _bloomMipDown[i - 1], _bloomMipDown[i], material, 1);
|
||||
}
|
||||
|
||||
// 5. Upsample Loop (升采样并混合)
|
||||
// 从最小的一张开始,往上叠加
|
||||
// 先把最小的 Down 直接拷给 Up
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[lastDown], _bloomPyramidUp[lastDown]);
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 6. Upsample Loop:Dual Kawase 升采样 + lerp(high, low, scatter)
|
||||
//
|
||||
// 与 Unity 原生完全一致的数据流向:
|
||||
// _BlitTexture = highMip = Down[i] (当前层高分辨率细节)
|
||||
// _SourceTexLowMip = lowMip = Up[i+1] 或 Down[last](低分辨率扩散光晕)
|
||||
// output = Up[i]
|
||||
//
|
||||
// 从最底层开始,逐层往上合并,最终 Up[0] 就是完整的 bloom 纹理
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
for (int i = lastDown - 1; i >= 0; i--)
|
||||
// 分配所有 Up RT(从最大 mip 往上,分辨率逐步翻倍)
|
||||
// 由于 desc 已经在 downsample 中不断减半,我们要重新从 Down[i] 读尺寸
|
||||
for (int i = mipCount - 1; i >= 0; i--)
|
||||
{
|
||||
// 设置上一级 Up 为输入
|
||||
// Upsample Pass 会混合:Up[i+1] (Blur) + Down[i] (High Res Detail)
|
||||
// 这里我们稍微简化逻辑:直接把 Up[i+1] 升采样并叠加到 Up[i] 上
|
||||
// 为了保留细节,Dual Kawase 通常是将 Up[i+1] 叠加回 Down[i] 存入 Up[i]
|
||||
|
||||
// 第一步:先把 Down[i] 拷进 Up[i] 作为底图
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[i], _bloomPyramidUp[i]);
|
||||
|
||||
// 第二步:把 Up[i+1] 升采样并 Additive 混合进 Up[i]
|
||||
// Shader Pass 2 开启了 Blend One One
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidUp[i + 1], _bloomPyramidUp[i], material, 2);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipUp[i],
|
||||
_bloomMipDown[i].rt.descriptor, FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp, name: "_BloomUp" + i);
|
||||
}
|
||||
|
||||
// 6. Composite (合成)
|
||||
// 此时 _bloomPyramidUp[0] 包含了最终的泛光纹理
|
||||
material.SetTexture(InternalShaderHelpers.ID._BloomTex, _bloomPyramidUp[0]);
|
||||
// 最底层:lowMip = Down[last],highMip 也是 Down[last](无 low 可用,直接 lerp 自身,结果仍 = Down[last])
|
||||
// 简化做法:直接将 Down[last] 拷到 Up[last] 作为起点
|
||||
Blitter.BlitCameraTexture(cmd, _bloomMipDown[mipCount - 1], _bloomMipUp[mipCount - 1]);
|
||||
|
||||
// Source + BloomTex -> Destination
|
||||
// 从第二底层开始向上 upsample
|
||||
for (int i = mipCount - 2; i >= 0; i--)
|
||||
{
|
||||
// highMip:当前层降采样纹理 Down[i],作为 Blit 的 source(_BlitTexture)
|
||||
// lowMip: 上一级升采样结果 Up[i+1],通过 SetTexture 传入
|
||||
cmd.SetGlobalTexture(InternalShaderHelpers.ID._SourceTexLowMip, _bloomMipUp[i + 1]);
|
||||
Blitter.BlitCameraTexture(cmd, _bloomMipDown[i], _bloomMipUp[i], material, 2);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 7. Composite:将 bloom 叠加回原始画面(Pass 3)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
material.SetTexture(InternalShaderHelpers.ID._BloomTex, _bloomMipUp[0]);
|
||||
Blitter.BlitCameraTexture(cmd, source, destination, material, 3);
|
||||
}
|
||||
|
||||
// 清理 RT
|
||||
public void Dispose()
|
||||
{
|
||||
if (_bloomPyramidDown != null)
|
||||
if (_bloomMipDown == null) return;
|
||||
for (int i = 0; i < _bloomMipDown.Length; i++)
|
||||
{
|
||||
for (int i = 0; i < _bloomPyramidDown.Length; i++)
|
||||
{
|
||||
if (_bloomPyramidDown[i] != null) _bloomPyramidDown[i].Release();
|
||||
if (_bloomPyramidUp[i] != null) _bloomPyramidUp[i].Release();
|
||||
}
|
||||
_bloomMipDown[i]?.Release();
|
||||
_bloomMipUp[i]?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsActive() => intensity.value > 0f;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user