140 lines
6.7 KiB
C#
140 lines
6.7 KiB
C#
using Echovoid.Runtime.Behavior.Rendering;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
using UnityEngine.Rendering.Universal;
|
||
|
||
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 之前
|
||
|
||
[Header("Glow Settings")]
|
||
[Tooltip("泛光强度。值越大越亮。")]
|
||
public ClampedFloatParameter intensity = new(0f, 0f, 10f);
|
||
|
||
[Tooltip("阈值。亮度超过此值的像素才会发光。\n关键:设为 1.1 可以过滤掉白墙(1.0),只让灯光发光。")]
|
||
public MinFloatParameter threshold = new(1.1f, 0f); // 默认设为 1.1
|
||
|
||
[Tooltip("柔膝 (Soft Knee)。让阈值过渡更平滑,避免高光边缘有硬切痕迹。")]
|
||
public ClampedFloatParameter softKnee = new(0.5f, 0f, 1f);
|
||
|
||
[Tooltip("最大亮度钳制。防止极亮像素(如太阳)产生乱跳的噪点。")]
|
||
public MinFloatParameter clamp = new(65472f, 1f); // 默认很大,基本不限制
|
||
|
||
[Header("Anime Style")]
|
||
[Tooltip("扩散半径。值越大,光晕越松散、范围越大(二次元感核心)。")]
|
||
public ClampedFloatParameter scatter = new(0.7f, 0f, 5f); // 推荐 0.5 - 1.0
|
||
|
||
[Tooltip("迭代次数。次数越多,光晕越平滑、范围越大,但性能开销越高。")]
|
||
public ClampedIntParameter diffusion = new(6, 1, 8); // 6次通常足够高品质
|
||
|
||
[Tooltip("泛光染色。可以做粉色霓虹、蓝色科技光等效果。")]
|
||
public ColorParameter tint = new(Color.white, true, true, true);
|
||
|
||
// 内部使用的 RT 数组
|
||
private RTHandle[] _bloomPyramidUp;
|
||
private RTHandle[] _bloomPyramidDown;
|
||
private const int k_MaxPyramidSize = 16;
|
||
|
||
public override string GetShaderName() => "SLS/Postprocessing/AnimeBloom";
|
||
|
||
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;
|
||
|
||
// 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)
|
||
{
|
||
_bloomPyramidUp = new RTHandle[k_MaxPyramidSize];
|
||
_bloomPyramidDown = new RTHandle[k_MaxPyramidSize];
|
||
}
|
||
|
||
// 3. Prefilter Pass (提取高亮)
|
||
// 先降一半分辨率,节省性能且增加模糊感
|
||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||
desc.height = Mathf.Max(1, desc.height >> 1);
|
||
|
||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidDown[0], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipDown0");
|
||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidUp[0], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipUp0");
|
||
|
||
// Source -> Down[0] (Prefilter)
|
||
Blitter.BlitCameraTexture(cmd, source, _bloomPyramidDown[0], material, 0);
|
||
|
||
// 4. Downsample Loop (降采样金字塔)
|
||
int lastDown = 0;
|
||
for (int i = 1; i < iterations; 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);
|
||
|
||
// Down[i-1] -> Down[i]
|
||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[i - 1], _bloomPyramidDown[i], material, 1);
|
||
lastDown = i;
|
||
}
|
||
|
||
// 5. Upsample Loop (升采样并混合)
|
||
// 从最小的一张开始,往上叠加
|
||
// 先把最小的 Down 直接拷给 Up
|
||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[lastDown], _bloomPyramidUp[lastDown]);
|
||
|
||
for (int i = lastDown - 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);
|
||
}
|
||
|
||
// 6. Composite (合成)
|
||
// 此时 _bloomPyramidUp[0] 包含了最终的泛光纹理
|
||
material.SetTexture(InternalShaderHelpers.ID._BloomTex, _bloomPyramidUp[0]);
|
||
|
||
// Source + BloomTex -> Destination
|
||
Blitter.BlitCameraTexture(cmd, source, destination, material, 3);
|
||
}
|
||
|
||
// 清理 RT
|
||
public void Dispose()
|
||
{
|
||
if (_bloomPyramidDown != null)
|
||
{
|
||
for (int i = 0; i < _bloomPyramidDown.Length; i++)
|
||
{
|
||
if (_bloomPyramidDown[i] != null) _bloomPyramidDown[i].Release();
|
||
if (_bloomPyramidUp[i] != null) _bloomPyramidUp[i].Release();
|
||
}
|
||
}
|
||
}
|
||
|
||
public override bool IsActive() => intensity.value > 0f;
|
||
}
|
||
} |