691 lines
27 KiB
C#
691 lines
27 KiB
C#
using Nova;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace NovaSamples.Effects
|
|
{
|
|
internal struct BlurFilter : IEquatable<BlurFilter>
|
|
{
|
|
public const int MinDownscale = 0;
|
|
public const int MaxDownscale = 6;
|
|
|
|
public float BlurRadius;
|
|
public float Contrast;
|
|
public float Saturation;
|
|
public float Brightness;
|
|
public float Grain;
|
|
|
|
public BlurMode BlurMode;
|
|
public int BlurDownscaleFactor;
|
|
public bool UseComputeShader;
|
|
|
|
public int GetBlurableSize(Texture texture)
|
|
{
|
|
if (texture == null)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int downscale = Mathf.Clamp(BlurDownscaleFactor, MinDownscale, MaxDownscale);
|
|
|
|
int max = Mathf.Max(texture.width, texture.height);
|
|
|
|
return Mathf.Max(Mathf.RoundToInt(max / Mathf.Pow(2, downscale)), 1);
|
|
}
|
|
|
|
public bool Equals(BlurFilter other)
|
|
{
|
|
return BlurRadius == other.BlurRadius &&
|
|
Contrast == other.Contrast &&
|
|
Saturation == other.Saturation &&
|
|
Brightness == other.Brightness &&
|
|
Grain == other.Grain &&
|
|
BlurMode == other.BlurMode &&
|
|
UseComputeShader == other.UseComputeShader &&
|
|
BlurDownscaleFactor == other.BlurDownscaleFactor;
|
|
|
|
}
|
|
}
|
|
|
|
internal class TextureBlur : IDisposable
|
|
{
|
|
#region Constants
|
|
private const string BlurShaderName = "BlurSinglePassShader";
|
|
private const string BlurComputeName = "BlurSinglePassCompute";
|
|
private const int MinPasses = 1;
|
|
private const int MaxPasses = 6;
|
|
private const float BlurPixelSize = 0.25f;
|
|
private const float BlurLogBase = 2;
|
|
private const float TexelPassDownScale = 1 / BlurLogBase;
|
|
private const int TempTextureDepth = 0;
|
|
private const int BlurPass = 0;
|
|
private const int ColorFiltersPass = 1;
|
|
private const string COLOR_ADJUSTMENT_ON = "COLOR_ADJUSTMENT_ON";
|
|
private const string LINEAR_SPACE = "LINEAR_SPACE";
|
|
private const string GRAIN_ON = "GRAIN_ON";
|
|
private const string BlurKernel = "Blur";
|
|
private const float Epsilon = 1e-5f;
|
|
|
|
/// An arbitrary scalar, but grain values over ~0.2 are very noisy and seem less useful
|
|
private const float GrainScalar = 1 / 5f;
|
|
|
|
private static readonly Vector4 DefaultColors = new Vector4(0, 1, 1, 1);
|
|
|
|
[NonSerialized]
|
|
private static uint NextInstanceID = 0;
|
|
|
|
[NonSerialized]
|
|
private static int BlurID = Shader.PropertyToID("_BlurBox");
|
|
[NonSerialized]
|
|
private static int UVScaleID = Shader.PropertyToID("_UVScale");
|
|
[NonSerialized]
|
|
private static int InputTextureID = Shader.PropertyToID("_Input");
|
|
[NonSerialized]
|
|
private static int OutputTextureID = Shader.PropertyToID("_Output");
|
|
[NonSerialized]
|
|
private static int ColorAdjustmentID = Shader.PropertyToID("_ColorAdjustments");
|
|
[NonSerialized]
|
|
private static int GrainID = Shader.PropertyToID("_Grain");
|
|
[NonSerialized]
|
|
private static int OffsetID = Shader.PropertyToID("_Offset");
|
|
[NonSerialized]
|
|
private static int ClipUVsID = Shader.PropertyToID("_ClipUVs");
|
|
[NonSerialized]
|
|
private static int GrainUVsID = Shader.PropertyToID("_GrainUVs");
|
|
#endregion
|
|
|
|
#region Properties
|
|
public RenderTexture OutputTexture => finalBlurredTexture;
|
|
|
|
|
|
private Material[] blurMaterials = new Material[MaxPasses];
|
|
|
|
[NonSerialized]
|
|
private static Shader blurShader = null;
|
|
private static Shader BlurShader
|
|
{
|
|
get
|
|
{
|
|
if (blurShader == null)
|
|
{
|
|
blurShader = Resources.Load(BlurShaderName) as Shader;
|
|
}
|
|
|
|
return blurShader;
|
|
}
|
|
}
|
|
|
|
[NonSerialized]
|
|
private static ComputeShader sharedComputeShader = null;
|
|
private static ComputeShader SharedComputeShader
|
|
{
|
|
get
|
|
{
|
|
if (sharedComputeShader == null)
|
|
{
|
|
sharedComputeShader = Resources.Load(BlurComputeName) as ComputeShader;
|
|
}
|
|
|
|
return sharedComputeShader;
|
|
}
|
|
}
|
|
|
|
#region Materials
|
|
/// <summary>
|
|
/// Need multiple materials because material.SetFloat() doesn't
|
|
/// have a CommandBuffer equivalent and therefore can't be queued
|
|
/// without preconfiguring multiple materials for the different steps.
|
|
/// </summary>
|
|
private Material[] BlurMaterials
|
|
{
|
|
get
|
|
{
|
|
if (blurMaterials[0] == null)
|
|
{
|
|
for (int i = 0; i < MaxPasses; ++i)
|
|
{
|
|
blurMaterials[i] = new Material(BlurShader);
|
|
blurMaterials[i].hideFlags = HideFlags.HideAndDontSave;
|
|
}
|
|
}
|
|
|
|
return blurMaterials;
|
|
}
|
|
}
|
|
|
|
private Material colorAdjustmentMaterial = null;
|
|
private Material ColorAdjustmentMaterial
|
|
{
|
|
get
|
|
{
|
|
if (colorAdjustmentMaterial == null)
|
|
{
|
|
colorAdjustmentMaterial = new Material(BlurShader);
|
|
colorAdjustmentMaterial.hideFlags = HideFlags.HideAndDontSave;
|
|
colorAdjustmentMaterial.EnableKeyword(COLOR_ADJUSTMENT_ON);
|
|
}
|
|
|
|
return colorAdjustmentMaterial;
|
|
}
|
|
}
|
|
|
|
private Material grainMaterial = null;
|
|
private Material GrainMaterial
|
|
{
|
|
get
|
|
{
|
|
if (grainMaterial == null)
|
|
{
|
|
grainMaterial = new Material(BlurShader);
|
|
grainMaterial.hideFlags = HideFlags.HideAndDontSave;
|
|
grainMaterial.EnableKeyword(GRAIN_ON);
|
|
}
|
|
|
|
return grainMaterial;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Compute Shaders
|
|
private ComputeShader computeShader = null;
|
|
private ComputeShader ComputeShader
|
|
{
|
|
get
|
|
{
|
|
if (computeShader == null && SharedComputeShader != null)
|
|
{
|
|
computeShader = ComputeShader.Instantiate(SharedComputeShader);
|
|
computeShader.hideFlags = HideFlags.HideAndDontSave;
|
|
|
|
if (UseLinearSpaceComputeShader)
|
|
{
|
|
computeShader.EnableKeyword(LINEAR_SPACE);
|
|
}
|
|
}
|
|
|
|
return computeShader;
|
|
}
|
|
}
|
|
|
|
private ComputeShader colorAdjustmentComputeShader = null;
|
|
private ComputeShader ColorAdjustmentComputeShader
|
|
{
|
|
get
|
|
{
|
|
if (colorAdjustmentComputeShader == null && SharedComputeShader != null)
|
|
{
|
|
colorAdjustmentComputeShader = ComputeShader.Instantiate(SharedComputeShader);
|
|
colorAdjustmentComputeShader.hideFlags = HideFlags.HideAndDontSave;
|
|
colorAdjustmentComputeShader.EnableKeyword(COLOR_ADJUSTMENT_ON);
|
|
|
|
if (UseLinearSpaceComputeShader)
|
|
{
|
|
colorAdjustmentComputeShader.EnableKeyword(LINEAR_SPACE);
|
|
}
|
|
}
|
|
|
|
return colorAdjustmentComputeShader;
|
|
}
|
|
}
|
|
|
|
private int computeKernel = -1;
|
|
private int ComputeKernel
|
|
{
|
|
get
|
|
{
|
|
if (computeKernel < 0)
|
|
{
|
|
computeKernel = ComputeShader.FindKernel(BlurKernel);
|
|
}
|
|
return computeKernel;
|
|
}
|
|
}
|
|
|
|
private Vector3Int computeThreads = Vector3Int.zero;
|
|
private Vector3Int ComputeThreads
|
|
{
|
|
get
|
|
{
|
|
if (computeThreads == Vector3Int.zero)
|
|
{
|
|
ComputeShader.GetKernelThreadGroupSizes(ComputeKernel, out uint x, out uint y, out uint z);
|
|
computeThreads = new Vector3Int((int)x, (int)y, (int)z);
|
|
}
|
|
|
|
return computeThreads;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private CommandBuffer commandBuffer = null;
|
|
private CommandBuffer CommandBuffer
|
|
{
|
|
get
|
|
{
|
|
if (commandBuffer == null)
|
|
{
|
|
commandBuffer = new CommandBuffer() { name = $"Blur Effect {NextInstanceID++}" };
|
|
}
|
|
|
|
return commandBuffer;
|
|
}
|
|
}
|
|
|
|
private int prevMaxBlurableSize = 0;
|
|
private BlurFilter prevParams = default;
|
|
private Vector2 prevUVScale = Vector2.zero;
|
|
private Vector2 prevUIBlockSize = Vector2.zero;
|
|
private Rect prevViewportRect;
|
|
private List<Vector4> computeBlurs = new List<Vector4>();
|
|
private int[] tempTextureIDs = new int[]
|
|
{
|
|
Shader.PropertyToID("_TextureBlurTemp1"),
|
|
Shader.PropertyToID("_TextureBlurTemp2"),
|
|
};
|
|
private RenderTexture finalBlurredTexture = null;
|
|
#endregion
|
|
|
|
public void ApplyFilter(UIBlock2D uiBlock, Rect viewportRect, BlurFilter filter, Texture texture, out bool blurTextureChanged)
|
|
{
|
|
Vector2 uiBlockSize = uiBlock.CalculatedSize.XY.Value;
|
|
Vector2 uvScale = GetUVScale(uiBlock, texture);
|
|
int blurableSize = filter.GetBlurableSize(texture);
|
|
|
|
blurTextureChanged = TryUpdateOutputTexture(uiBlock, texture);
|
|
|
|
bool propertyChanged = !prevParams.Equals(filter);
|
|
propertyChanged |= prevUIBlockSize != uiBlockSize;
|
|
propertyChanged |= prevUVScale != uvScale;
|
|
propertyChanged |= prevViewportRect != viewportRect;
|
|
propertyChanged |= prevMaxBlurableSize != blurableSize;
|
|
|
|
if (blurTextureChanged || propertyChanged || CommandBuffer.sizeInBytes == 0)
|
|
{
|
|
prevParams = filter;
|
|
prevUIBlockSize = uiBlockSize;
|
|
prevUVScale = uvScale;
|
|
prevMaxBlurableSize = blurableSize;
|
|
prevViewportRect = viewportRect;
|
|
|
|
UpdateBlurCommands(uiBlock, filter, uvScale, viewportRect, texture);
|
|
}
|
|
|
|
Graphics.ExecuteCommandBuffer(CommandBuffer);
|
|
}
|
|
|
|
private bool TryUpdateOutputTexture(UIBlock2D uiBlock, Texture texture)
|
|
{
|
|
Vector2 inputTextureSize = texture.Size();
|
|
|
|
bool needFinalTexture = finalBlurredTexture == null;
|
|
bool finalTextureDimensionsChanged = !needFinalTexture && finalBlurredTexture.Size() != inputTextureSize;
|
|
|
|
if (!needFinalTexture && !finalTextureDimensionsChanged)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ReleaseOutputTexture(uiBlock);
|
|
|
|
finalBlurredTexture = RenderTexture.GetTemporary((int)inputTextureSize.x, (int)inputTextureSize.y, TempTextureDepth);
|
|
finalBlurredTexture.name = "Blurred Texture";
|
|
|
|
return true;
|
|
}
|
|
|
|
private void ReleaseOutputTexture(UIBlock2D uiBlock)
|
|
{
|
|
if (finalBlurredTexture != null)
|
|
{
|
|
RenderTexture.ReleaseTemporary(finalBlurredTexture);
|
|
finalBlurredTexture = null;
|
|
uiBlock.ClearImage();
|
|
}
|
|
}
|
|
|
|
private void UpdateBlurCommands(UIBlock2D uiBlock, BlurFilter filter, Vector2 uvScale, Rect viewportRect, Texture inputTexture)
|
|
{
|
|
CommandBuffer.Clear();
|
|
|
|
float blur = Mathf.Max(filter.BlurRadius, 0);
|
|
|
|
// input is in range -1 to 1, so we shift by default colors to put into range 0 to 2
|
|
Vector4 csb = new Vector4(filter.Contrast, filter.Saturation, filter.Brightness, 0) + DefaultColors;
|
|
|
|
float grainFilter = filter.Grain * GrainScalar;
|
|
|
|
float baseWidth = finalBlurredTexture.width;
|
|
float baseHeight = finalBlurredTexture.height;
|
|
float basis = Mathf.Min(filter.GetBlurableSize(inputTexture), Mathf.Max(baseWidth, baseHeight));
|
|
|
|
int blurableWidth = Mathf.RoundToInt(baseWidth > baseHeight ? basis : basis * baseWidth / baseHeight);
|
|
int blurableHeight = Mathf.RoundToInt(baseHeight > baseWidth ? basis : basis * baseHeight / baseWidth);
|
|
Vector2 blurTextureSize = new Vector2(blurableWidth, blurableHeight);
|
|
|
|
Vector2 inputTextureSize = inputTexture.Size();
|
|
|
|
Vector2 pixelsPerUnit = (inputTextureSize / uvScale) / uiBlock.CalculatedSize.XY.Value;
|
|
|
|
float pixelSize = BlurPixelSize * Mathf.Max(pixelsPerUnit.x, pixelsPerUnit.y);
|
|
|
|
Vector2 texelBlur = blur * new Vector2(pixelSize / inputTextureSize.x, pixelSize / inputTextureSize.y);
|
|
|
|
// Divide by BlurPixelSize because, while texelBlur is technically the right offset,
|
|
// the algorithm will sample the pixels adjacent to the exterior edges in the first pass
|
|
// those external samples will bleed into the actual UIBlock rect over sequential passes.
|
|
// So we clip extra pixels to have the final interior rectangle only include samples from
|
|
// the input texture.
|
|
Vector2 blurUVs = texelBlur / BlurPixelSize;
|
|
|
|
Rect scissor = GetScissorRect(blurTextureSize, viewportRect, blurUVs);
|
|
|
|
Vector2 centerUV = viewportRect.center;
|
|
Vector2 sizeUV = viewportRect.size;
|
|
|
|
if (blur == 0)
|
|
{
|
|
bool colorAdjustments = !IsDefaultColors(csb);
|
|
bool grain = grainFilter > 0;
|
|
|
|
if (colorAdjustments || grain)
|
|
{
|
|
ColorAdjustmentMaterial.SetKeyword(COLOR_ADJUSTMENT_ON, colorAdjustments);
|
|
ColorAdjustmentMaterial.SetKeyword(GRAIN_ON, grain);
|
|
ColorAdjustmentMaterial.SetVector(ClipUVsID, GetClipUVs(scissor, blurTextureSize));
|
|
ColorAdjustmentMaterial.SetVector(GrainUVsID, new Vector4(centerUV.x, centerUV.y, sizeUV.x, sizeUV.y));
|
|
ColorAdjustmentMaterial.SetVector(ColorAdjustmentID, csb);
|
|
ColorAdjustmentMaterial.SetFloat(GrainID, grainFilter);
|
|
ColorAdjustmentMaterial.SetVector(UVScaleID, GetShaderUVScale(finalBlurredTexture));
|
|
|
|
CommandBuffer.Blit(inputTexture, finalBlurredTexture, ColorAdjustmentMaterial, ColorFiltersPass);
|
|
}
|
|
else
|
|
{
|
|
CommandBuffer.Blit(inputTexture, finalBlurredTexture);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
float blurMax = Mathf.Max(blurTextureSize.x, blurTextureSize.y);
|
|
|
|
float passBasis = Mathf.Min(blurMax, blur);
|
|
|
|
int logPass = Mathf.FloorToInt(Mathf.Log(passBasis, BlurLogBase)) + 1;
|
|
int passes = Mathf.Clamp(logPass, MinPasses, MaxPasses);
|
|
|
|
bool useComputeShader = filter.UseComputeShader && ComputeShader != null && ComputeShaderSupported;
|
|
|
|
UnityEngine.Experimental.Rendering.GraphicsFormat format = QualitySettings.activeColorSpace == ColorSpace.Linear ? UnityEngine.Experimental.Rendering.GraphicsFormat.R8G8B8A8_SRGB :
|
|
UnityEngine.Experimental.Rendering.GraphicsFormat.R8G8B8A8_UNorm;
|
|
|
|
CommandBuffer.GetTemporaryRT(tempTextureIDs[0], (int)blurTextureSize.x, (int)blurTextureSize.y, TempTextureDepth, FilterMode.Bilinear, format, antiAliasing: 1, enableRandomWrite: useComputeShader);
|
|
CommandBuffer.GetTemporaryRT(tempTextureIDs[1], (int)blurTextureSize.x, (int)blurTextureSize.y, TempTextureDepth, FilterMode.Bilinear, format, antiAliasing: 1, enableRandomWrite: useComputeShader);
|
|
|
|
if (useComputeShader)
|
|
{
|
|
UpdateComputeCommands(passes, texelBlur, csb, blurTextureSize, scissor, inputTexture);
|
|
}
|
|
else
|
|
{
|
|
UpdateBlitCommands(passes, texelBlur, csb, blurTextureSize, scissor, inputTexture);
|
|
}
|
|
|
|
GrainMaterial.SetKeyword(GRAIN_ON, grainFilter > 0);
|
|
GrainMaterial.SetFloat(GrainID, grainFilter);
|
|
GrainMaterial.SetVector(ClipUVsID, GetClipUVs(scissor, blurTextureSize));
|
|
GrainMaterial.SetVector(GrainUVsID, new Vector4(centerUV.x, centerUV.y, sizeUV.x, sizeUV.y));
|
|
GrainMaterial.SetVector(UVScaleID, GetShaderUVScale(finalBlurredTexture));
|
|
|
|
CommandBuffer.Blit(tempTextureIDs[0], finalBlurredTexture, GrainMaterial, ColorFiltersPass);
|
|
|
|
CommandBuffer.ReleaseTemporaryRT(tempTextureIDs[0]);
|
|
CommandBuffer.ReleaseTemporaryRT(tempTextureIDs[1]);
|
|
}
|
|
|
|
private void UpdateComputeCommands(int passes, Vector2 texelBlur, Vector4 csb, Vector2 blurableSize, Rect scissor, Texture inputTexture)
|
|
{
|
|
Vector3Int threads = ComputeThreads;
|
|
|
|
int blurGroupsX = Mathf.Max(Mathf.CeilToInt((float)scissor.width / threads.x), 1);
|
|
int blurGroupsY = Mathf.Max(Mathf.CeilToInt((float)scissor.height / threads.y), 1);
|
|
int blurGroupsZ = Mathf.Max(threads.z, 1);
|
|
|
|
int kernel = ComputeKernel;
|
|
|
|
computeBlurs.Clear();
|
|
|
|
computeBlurs.Add(new Vector4(-texelBlur.x, -texelBlur.y, texelBlur.x, texelBlur.y));
|
|
|
|
ComputeShader firstShader = !IsDefaultColors(csb) ? ColorAdjustmentComputeShader : ComputeShader;
|
|
|
|
CommandBuffer.SetComputeVectorParam(firstShader, OffsetID, scissor.position);
|
|
CommandBuffer.SetComputeVectorParam(firstShader, ColorAdjustmentID, csb);
|
|
CommandBuffer.SetComputeVectorParam(firstShader, BlurID, computeBlurs[0]);
|
|
CommandBuffer.SetComputeVectorParam(firstShader, UVScaleID, GetShaderUVScale((int)blurableSize.x, (int)blurableSize.y));
|
|
CommandBuffer.SetComputeTextureParam(firstShader, kernel, InputTextureID, inputTexture);
|
|
CommandBuffer.SetComputeTextureParam(firstShader, kernel, OutputTextureID, tempTextureIDs[0]);
|
|
CommandBuffer.DispatchCompute(firstShader, kernel, blurGroupsX, blurGroupsY, blurGroupsZ);
|
|
|
|
CommandBuffer.SetComputeVectorParam(ComputeShader, OffsetID, scissor.position);
|
|
CommandBuffer.SetComputeVectorParam(ComputeShader, UVScaleID, GetShaderUVScale((int)blurableSize.x, (int)blurableSize.y));
|
|
|
|
texelBlur *= TexelPassDownScale;
|
|
|
|
for (int pass = 1; pass < passes; ++pass)
|
|
{
|
|
computeBlurs.Add(new Vector4(-texelBlur.x, -texelBlur.y, texelBlur.x, texelBlur.y));
|
|
|
|
CommandBuffer.SetComputeVectorParam(ComputeShader, BlurID, computeBlurs[pass]);
|
|
CommandBuffer.SetComputeTextureParam(ComputeShader, kernel, InputTextureID, tempTextureIDs[0]);
|
|
CommandBuffer.SetComputeTextureParam(ComputeShader, kernel, OutputTextureID, tempTextureIDs[1]);
|
|
CommandBuffer.DispatchCompute(ComputeShader, kernel, blurGroupsX, blurGroupsY, blurGroupsZ);
|
|
|
|
Swap01(tempTextureIDs);
|
|
|
|
texelBlur *= TexelPassDownScale;
|
|
}
|
|
|
|
for (int pass = passes - 2; pass >= 0; --pass)
|
|
{
|
|
CommandBuffer.SetComputeVectorParam(ComputeShader, BlurID, computeBlurs[pass]);
|
|
CommandBuffer.SetComputeTextureParam(ComputeShader, kernel, InputTextureID, tempTextureIDs[0]);
|
|
CommandBuffer.SetComputeTextureParam(ComputeShader, kernel, OutputTextureID, tempTextureIDs[1]);
|
|
CommandBuffer.DispatchCompute(ComputeShader, kernel, blurGroupsX, blurGroupsY, blurGroupsZ);
|
|
|
|
Swap01(tempTextureIDs);
|
|
}
|
|
}
|
|
|
|
private void UpdateBlitCommands(int passes, Vector2 texelBlur, Vector4 csb, Vector2 blurableSize, Rect scissor, Texture inputTexture)
|
|
{
|
|
ColorAdjustmentMaterial.EnableKeyword(COLOR_ADJUSTMENT_ON);
|
|
ColorAdjustmentMaterial.DisableKeyword(GRAIN_ON);
|
|
|
|
Material blurMaterial = !IsDefaultColors(csb) ? ColorAdjustmentMaterial : BlurMaterials[0];
|
|
|
|
Vector4 clipUVs = GetClipUVs(scissor, blurableSize);
|
|
|
|
blurMaterial.SetVector(BlurID, new Vector4(-texelBlur.x, -texelBlur.y, texelBlur.x, texelBlur.y));
|
|
BlurMaterials[0].SetVector(BlurID, new Vector4(-texelBlur.x, -texelBlur.y, texelBlur.x, texelBlur.y));
|
|
blurMaterial.SetVector(ColorAdjustmentID, csb);
|
|
|
|
BlurMaterials[0].SetVector(ClipUVsID, clipUVs);
|
|
blurMaterial.SetVector(ClipUVsID, clipUVs);
|
|
|
|
CommandBuffer.Blit(inputTexture, tempTextureIDs[0], blurMaterial, BlurPass);
|
|
|
|
texelBlur *= TexelPassDownScale;
|
|
|
|
for (int pass = 1; pass < passes; ++pass)
|
|
{
|
|
blurMaterial = BlurMaterials[pass];
|
|
|
|
blurMaterial.SetVector(BlurID, new Vector4(-texelBlur.x, -texelBlur.y, texelBlur.x, texelBlur.y));
|
|
blurMaterial.SetVector(ClipUVsID, clipUVs);
|
|
|
|
CommandBuffer.Blit(tempTextureIDs[0], tempTextureIDs[1], blurMaterial, BlurPass);
|
|
|
|
Swap01(tempTextureIDs);
|
|
|
|
texelBlur *= TexelPassDownScale;
|
|
}
|
|
|
|
for (int pass = passes - 2; pass >= 0; --pass)
|
|
{
|
|
CommandBuffer.Blit(tempTextureIDs[0], tempTextureIDs[1], BlurMaterials[pass], BlurPass);
|
|
|
|
Swap01(tempTextureIDs);
|
|
}
|
|
}
|
|
|
|
public void Unset(UIBlock2D uiBlock)
|
|
{
|
|
ReleaseOutputTexture(uiBlock);
|
|
CommandBuffer.Clear();
|
|
prevParams = default;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
DisposeCommandBuffer();
|
|
|
|
for (int i = 0; i < MaxPasses; ++i)
|
|
{
|
|
Destroy(blurMaterials[i]);
|
|
blurMaterials[i] = null;
|
|
}
|
|
|
|
Destroy(colorAdjustmentMaterial);
|
|
colorAdjustmentMaterial = null;
|
|
Destroy(computeShader);
|
|
computeShader = null;
|
|
Destroy(colorAdjustmentComputeShader);
|
|
colorAdjustmentComputeShader = null;
|
|
}
|
|
|
|
public void DisposeCommandBuffer()
|
|
{
|
|
if (commandBuffer == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
commandBuffer.Dispose();
|
|
commandBuffer = null;
|
|
}
|
|
|
|
private static void Swap01<T>(T[] textures)
|
|
{
|
|
T temp = textures[0];
|
|
textures[0] = textures[1];
|
|
textures[1] = temp;
|
|
}
|
|
|
|
public static Vector2 GetUVScale(UIBlock2D uiBlock, Texture image)
|
|
{
|
|
ImageAdjustment imageAdjustment = uiBlock.ImageAdjustment;
|
|
|
|
if (imageAdjustment.ScaleMode == ImageScaleMode.Manual)
|
|
{
|
|
return imageAdjustment.UVScale;
|
|
}
|
|
|
|
Vector2 imageSize = new Vector2(image.width, image.height);
|
|
Vector2 uiBlockSize = uiBlock.CalculatedSize.XY.Value;
|
|
float imageAspectRatio = imageSize.x / imageSize.y;
|
|
float uiBlockAspectRatio = uiBlockSize.x / uiBlockSize.y;
|
|
float relativeAspectRatio = imageAspectRatio / uiBlockAspectRatio;
|
|
|
|
switch (imageAdjustment.ScaleMode)
|
|
{
|
|
case ImageScaleMode.Fit:
|
|
return relativeAspectRatio > 1 ? new Vector2(1, 1 / relativeAspectRatio) : new Vector2(relativeAspectRatio, 1);
|
|
case ImageScaleMode.Envelope:
|
|
return relativeAspectRatio > 1 ? new Vector2(relativeAspectRatio, 1) : new Vector2(1, 1 / relativeAspectRatio);
|
|
default:
|
|
return Vector2.one;
|
|
}
|
|
}
|
|
|
|
private static Vector4 GetShaderUVScale(Texture image) => GetShaderUVScale(image.width, image.height);
|
|
private static Vector4 GetShaderUVScale(int width, int height) => new Vector4(1f / width, 1f / height, width, height);
|
|
|
|
private static Rect GetScissorRect(Vector2 blurableSize, Rect viewportRect, Vector2 blurUVs)
|
|
{
|
|
float x = Mathf.Max(Mathf.FloorToInt((viewportRect.x - blurUVs.x) * blurableSize.x), 0);
|
|
float y = Mathf.Max(Mathf.FloorToInt((viewportRect.y - blurUVs.y) * blurableSize.y), 0);
|
|
float width = Mathf.Min(Mathf.CeilToInt((viewportRect.width + 2 * blurUVs.x) * blurableSize.x), blurableSize.x);
|
|
float height = Mathf.Min(Mathf.CeilToInt((viewportRect.height + 2 * blurUVs.y) * blurableSize.y), blurableSize.y);
|
|
|
|
return new Rect(x, y, width, height);
|
|
}
|
|
|
|
private Vector4 GetClipUVs(Rect scissor, Vector2 full)
|
|
{
|
|
Vector2 minUV = Vector2.Max(scissor.min, Vector2.zero) / full;
|
|
Vector2 maxUV = Vector2.Min(scissor.max, full) / full;
|
|
|
|
return new Vector4(minUV.x, minUV.y, maxUV.x, maxUV.y);
|
|
}
|
|
|
|
private static void Destroy<T>(T obj) where T : UnityEngine.Object
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Application.isPlaying)
|
|
{
|
|
UnityEngine.Object.Destroy(obj);
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(obj);
|
|
}
|
|
}
|
|
|
|
private static bool IsDefaultColors(Vector4 csb)
|
|
{
|
|
return Mathf.Abs(csb.x - DefaultColors.x) < Epsilon &&
|
|
Mathf.Abs(csb.y - DefaultColors.y) < Epsilon &&
|
|
Mathf.Abs(csb.z - DefaultColors.z) < Epsilon &&
|
|
Mathf.Abs(csb.w - DefaultColors.w) < Epsilon;
|
|
}
|
|
|
|
private static bool ComputeShaderSupported => SystemInfo.supportsComputeShaders && !IsMobileOpenGLLinear;
|
|
|
|
/// <summary>
|
|
/// https://issuetracker.unity3d.com/issues/android-compute-shader-is-not-working-with-linear-color-space-when-it-is-built-to-android-device
|
|
/// https://forum.unity.com/threads/compute-shaders-dont-work-on-android-with-linear-lighting.611314/
|
|
/// </summary>
|
|
private static bool IsMobileOpenGLLinear => QualitySettings.activeColorSpace == ColorSpace.Linear && IsOpenGL(SystemInfo.graphicsDeviceType) && Application.isMobilePlatform;
|
|
private static bool IsOpenGL(GraphicsDeviceType deviceType) => deviceType == GraphicsDeviceType.OpenGLES3 || deviceType == GraphicsDeviceType.OpenGLCore;
|
|
private static bool UseLinearSpaceComputeShader => QualitySettings.activeColorSpace == ColorSpace.Linear && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Metal;
|
|
}
|
|
|
|
internal static class TextureExtensions
|
|
{
|
|
public static Vector2 Size(this Texture texture) => new Vector2(texture.width, texture.height);
|
|
}
|
|
|
|
internal static class MaterialExtensions
|
|
{
|
|
public static void SetKeyword(this Material material, string keyword, bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
material.EnableKeyword(keyword);
|
|
}
|
|
else
|
|
{
|
|
material.DisableKeyword(keyword);
|
|
}
|
|
}
|
|
}
|
|
}
|