Files
ichni_Official/Assets/Shaders/ScriptablePostProcessor/Volumes/AnimeBloom.cs
2026-06-30 05:43:26 -04:00

191 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
// BeforePostProcess: 在 ToneMapping 之前运行,确保 HDR bloom 颜色不被截断
// 这与 Unity 原生 Bloom 的执行阶段完全一致
public override CustomPostProcessInjectionPoint InjectionPoint => CustomPostProcessInjectionPoint.BeforePostProcess;
public override int OrderInInjectionPoint => 5;
[Header("Glow Settings")]
public ClampedFloatParameter intensity = new(1f, 0f, 10f);
// 注意threshold 在 Gamma 空间中设置C# 侧会转为 Linear。
// 0.9 在 gamma 空间 ≈ 0.79 在 linear 空间,这与 Unity 原生默认行为完全一致。
public MinFloatParameter threshold = new(0.9f, 0f);
[Header("Scatter")]
// scatter 在 [0,1] 范围内由用户设置。
// C# 侧映射Mathf.Lerp(0.05f, 0.95f, scatter.value),与 Unity 原生完全一致。
// 低值 = 光晕聚拢,高值 = 光晕向外大范围扩散。
public ClampedFloatParameter scatter = new(0.7f, 0f, 1f);
public MinFloatParameter clamp = new(65472f, 1f);
[Header("Iterations")]
// 迭代次数Dual Kawase 每层只需 1 Pass性能远低于原生双趟高斯。
// 4 次迭代 Dual Kawase ≈ 原生 6 次双趟高斯的扩散范围,但 Pass 数只有其 1/3。
public ClampedIntParameter diffusion = new(4, 1, 8);
[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);
[Header("Tint")]
// tint 染色C# 侧会归一化亮度(只保留色相/饱和度),与原生行为一致。
// 这样调整 tint 颜色不会意外改变 bloom 总亮度。
public ColorParameter tint = new(Color.white, true, true, true);
// 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;
public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination)
{
if (material == null) return;
var desc = renderingData.cameraData.cameraTargetDescriptor;
desc.msaaSamples = 1;
desc.depthBufferBits = 0;
// 【移动端极限优化】:强制降级到 32位 HDR 格式 (B10G11R11)
// 相比主相机默认的 64位 R16G16B16A16带宽消耗直接砍半且肉眼几乎无损
if (SystemInfo.IsFormatSupported(GraphicsFormat.B10G11R11_UFloatPack32, FormatUsage.Linear | FormatUsage.Render))
{
desc.graphicsFormat = GraphicsFormat.B10G11R11_UFloatPack32;
}
// ─────────────────────────────────────────────────────────────
// 1. 参数打包(完全对齐 Unity SetupBloom 的计算逻辑)
// ─────────────────────────────────────────────────────────────
// scatter: 用户 [0,1] → shader [0.05, 0.95](与原生 Lerp 映射一致)
float scatterMapped = Mathf.Lerp(0.05f, 0.95f, scatter.value);
// threshold: Gamma → Linear原生使用 GammaToLinearSpace 转换)
float thresholdLinear = Mathf.GammaToLinearSpace(threshold.value);
float thresholdKnee = thresholdLinear * 0.5f; // 硬编码 soft knee与原生一致
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)
{
_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 LoopDual 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 _bloomMipDown[i], desc, FilterMode.Bilinear,
TextureWrapMode.Clamp, name: "_BloomDown" + i);
// Down[i-1] → Down[i] via Pass 1 (Dual Kawase Downsample)
Blitter.BlitCameraTexture(cmd, _bloomMipDown[i - 1], _bloomMipDown[i], material, 1);
}
// ─────────────────────────────────────────────────────────────
// 6. Upsample LoopDual Kawase 升采样 + lerp(high, low, scatter)
//
// 与 Unity 原生完全一致的数据流向:
// _BlitTexture = highMip = Down[i] (当前层高分辨率细节)
// _SourceTexLowMip = lowMip = Up[i+1] 或 Down[last](低分辨率扩散光晕)
// output = Up[i]
//
// 从最底层开始,逐层往上合并,最终 Up[0] 就是完整的 bloom 纹理
// ─────────────────────────────────────────────────────────────
// 分配所有 Up RT从最大 mip 往上,分辨率逐步翻倍)
// 由于 desc 已经在 downsample 中不断减半,我们要重新从 Down[i] 读尺寸
for (int i = mipCount - 1; i >= 0; i--)
{
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipUp[i],
_bloomMipDown[i].rt.descriptor, FilterMode.Bilinear,
TextureWrapMode.Clamp, name: "_BloomUp" + i);
}
// 最底层lowMip = Down[last]highMip 也是 Down[last](无 low 可用,直接 lerp 自身,结果仍 = Down[last]
// 简化做法:直接将 Down[last] 拷到 Up[last] 作为起点
Blitter.BlitCameraTexture(cmd, _bloomMipDown[mipCount - 1], _bloomMipUp[mipCount - 1]);
// 从第二底层开始向上 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);
}
public void Dispose()
{
if (_bloomMipDown == null) return;
for (int i = 0; i < _bloomMipDown.Length; i++)
{
_bloomMipDown[i]?.Release();
_bloomMipUp[i]?.Release();
}
}
}
}