using System; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Rendering; namespace FidelityFX { /// /// Port of ffx_cacao.cpp/.h /// public static class Cacao { /// /// Matrix to flip a couple of axes from Unity view space to CACAO's expected view space. /// public static Matrix4x4 UnityToCacaoViewMatrix = Matrix4x4.Scale(new Vector3(1f, -1f, -1f)); /// /// The quality levels that FidelityFX CACAO can generate SSAO at. This affects the number of samples taken for generating SSAO. /// public enum Quality { Lowest = 0, Low = 1, Medium = 2, High = 3, Highest = 4, } /// /// A structure for the settings used by FidelityFX CACAO. These settings may be updated with each draw call. /// [Serializable] public struct Settings { [Range(0, 10), Tooltip("World (view) space size of the occlusion sphere.")] public float radius; [Range(0, 5), Tooltip("Effect strength linear multiplier.")] public float shadowMultiplier; [Range(0.5f, 5), Tooltip("Effect strength pow modifier.")] public float shadowPower; [Range(0, 1), Tooltip("Effect max limit (applied after multiplier but before blur).")] public float shadowClamp; [Range(0, 0.2f), Tooltip("Limits self-shadowing (makes the sampling area less of a hemisphere, more of a spherical cone, to avoid self-shadowing and various artifacts due to low tessellation and depth buffer imprecision, etc.).")] public float horizonAngleThreshold; [Range(0, 1000), Tooltip("Distance to start fading out the effect.")] public float fadeOutFrom; [Range(0, 1000), Tooltip("Distance at which the effect is faded out.")] public float fadeOutTo; [Tooltip("Effect quality, affects number of taps etc.")] public Quality qualityLevel; [Range(0, 1), Tooltip("Only for quality level Highest.")] public float adaptiveQualityLimit; [Range(0, 8), Tooltip("Number of edge-sensitive smart blur passes to apply.")] public uint blurPassCount; [Range(0, 1), Tooltip("How much to bleed over edges; 1: not at all, 0.5: half-half; 0.0: completely ignore edges.")] public float sharpness; [Range(0, Mathf.PI), Tooltip("Used to rotate sampling kernel; If using temporal AA / supersampling, suggested to rotate by ( (frame%3)/3.0*PI ) or similar. Kernel is already symmetrical, which is why we use PI and not 2*PI.")] public float temporalSupersamplingAngleOffset; [Range(0, 2), Tooltip("Used to scale sampling kernel; If using temporal AA / supersampling, suggested to scale by ( 1.0f + (((frame%3)-1.0)/3.0)*0.1 ) or similar.")] public float temporalSupersamplingRadiusOffset; [Range(0, 5), Tooltip("Used for high-res detail AO using neighboring depth pixels: adds a lot of detail but also reduces temporal stability (adds aliasing).")] public float detailShadowStrength; [Tooltip("This option should be set to true if FidelityFX-CACAO should reconstruct a normal buffer from the depth buffer. It is required to be true if no normal buffer is provided.")] public bool generateNormals; [Tooltip("Sigma squared value for use in bilateral upsampler giving Gaussian blur term. Should be greater than 0.0.")] public float bilateralSigmaSquared; [Tooltip("Sigma squared value for use in bilateral upsampler giving similarity weighting for neighbouring pixels. Should be greater than 0.0.")] public float bilateralSimilarityDistanceSigma; } public static readonly Settings DefaultSettings = new() { radius = 1.2f, shadowMultiplier = 1.0f, shadowPower = 1.5f, shadowClamp = 0.98f, horizonAngleThreshold = 0.06f, fadeOutFrom = 50f, fadeOutTo = 300f, qualityLevel = Quality.Highest, adaptiveQualityLimit = 0.45f, blurPassCount = 2, sharpness = 0.98f, temporalSupersamplingAngleOffset = 0f, temporalSupersamplingRadiusOffset = 0f, detailShadowStrength = 0.5f, generateNormals = false, bilateralSigmaSquared = 5f, bilateralSimilarityDistanceSigma = 0.01f, }; /// /// The parameters necessary when changing the screen size of FidelityFX CACAO. /// public struct ScreenSizeInfo { public uint Width; public uint Height; public bool UseDownsampledSsao; } /// /// A structure containing all of the input and output render targets used by FidelityFX CACAO. /// public struct DispatchInfo { public ResourceView DepthView; public ResourceView? NormalsView; // This will be ignored if GenerateNormals is true public ResourceView OutputView; } /// /// A structure containing sizes of each of the buffers used by FidelityFX CACAO. /// internal struct BufferSizeInfo { public uint InputOutputBufferWidth; public uint InputOutputBufferHeight; public uint SsaoBufferWidth; public uint SsaoBufferHeight; public uint DepthBufferXOffset; public uint DepthBufferYOffset; public uint DepthBufferWidth; public uint DepthBufferHeight; public uint DeinterleavedDepthBufferXOffset; public uint DeinterleavedDepthBufferYOffset; public uint DeinterleavedDepthBufferWidth; public uint DeinterleavedDepthBufferHeight; public uint ImportanceMapWidth; public uint ImportanceMapHeight; public uint DownsampledSsaoBufferWidth; public uint DownsampledSsaoBufferHeight; } /// /// A C# structure for the constant buffer used by FidelityFX CACAO. /// [Serializable, StructLayout(LayoutKind.Sequential)] internal struct Constants { public Vector2 DepthUnpackConsts; public Vector2 CameraTanHalfFOV; public Vector2 NDCToViewMul; public Vector2 NDCToViewAdd; public Vector2 DepthBufferUVToViewMul; public Vector2 DepthBufferUVToViewAdd; public float EffectRadius; public float EffectShadowStrength; public float EffectShadowPow; public float EffectShadowClamp; public float EffectFadeOutMul; public float EffectFadeOutAdd; public float EffectHorizonAngleThreshold; public float EffectSamplingRadiusNearLimitRec; public float DepthPrecisionOffsetMod; public float NegRecEffectRadius; public float LoadCounterAvgDiv; public float AdaptiveSampleCountLimit; public float InvSharpness; public int BlurNumPasses; public float BilateralSigmaSquared; public float BilateralSimilarityDistanceSigma; public PatternRotScaleMatrices PatternRotScaleMatrices0; public PatternRotScaleMatrices PatternRotScaleMatrices1; public PatternRotScaleMatrices PatternRotScaleMatrices2; public PatternRotScaleMatrices PatternRotScaleMatrices3; public float NormalsUnpackMul; public float NormalsUnpackAdd; public float DetailAOStrength; public int Dummy0; public Vector2 SSAOBufferDimensions; public Vector2 SSAOBufferInverseDimensions; public Vector2 DepthBufferDimensions; public Vector2 DepthBufferInverseDimensions; public Vector2Int DepthBufferOffset; public Vector2Int Pad; public Vector4 PerPassFullResUVOffset0; public Vector4 PerPassFullResUVOffset1; public Vector4 PerPassFullResUVOffset2; public Vector4 PerPassFullResUVOffset3; public Vector2 InputOutputBufferDimensions; public Vector2 InputOutputBufferInverseDimensions; public Vector2 ImportanceMapDimensions; public Vector2 ImportanceMapInverseDimensions; public Vector2 DeinterleavedDepthBufferDimensions; public Vector2 DeinterleavedDepthBufferInverseDimensions; public Vector2 DeinterleavedDepthBufferOffset; public Vector2 DeinterleavedDepthBufferNormalisedOffset; public Matrix4x4 NormalsWorldToViewspaceMatrix; } [Serializable, StructLayout(LayoutKind.Sequential)] internal struct PatternRotScaleMatrices { // This is a really ugly solution, but fixed-size arrays are not blittable by Unity public Vector4 PatternRotScaleMatrix0; public Vector4 PatternRotScaleMatrix1; public Vector4 PatternRotScaleMatrix2; public Vector4 PatternRotScaleMatrix3; public Vector4 PatternRotScaleMatrix4; } // Nasty workaround for the fact that we can't store these matrices in an array internal static ref Vector4 GetPerPassFullResUVOffset(this ref Constants consts, int index) { switch (index) { case 0: return ref consts.PerPassFullResUVOffset0; case 1: return ref consts.PerPassFullResUVOffset1; case 2: return ref consts.PerPassFullResUVOffset2; case 3: return ref consts.PerPassFullResUVOffset3; default: throw new IndexOutOfRangeException(); } } // Nasty workaround for the fact that we can't store these matrices in an array internal static ref PatternRotScaleMatrices GetPatternRotScaleMatrices(this ref Constants consts, int index) { switch (index) { case 0: return ref consts.PatternRotScaleMatrices0; case 1: return ref consts.PatternRotScaleMatrices1; case 2: return ref consts.PatternRotScaleMatrices2; case 3: return ref consts.PatternRotScaleMatrices3; default: throw new IndexOutOfRangeException(); } } // Nasty workaround for the fact that we can't store these matrices in an array internal static ref Vector4 GetPatternRotScaleMatrix(this ref PatternRotScaleMatrices matrices, int index) { switch (index) { case 0: return ref matrices.PatternRotScaleMatrix0; case 1: return ref matrices.PatternRotScaleMatrix1; case 2: return ref matrices.PatternRotScaleMatrix2; case 3: return ref matrices.PatternRotScaleMatrix3; case 4: return ref matrices.PatternRotScaleMatrix4; default: throw new IndexOutOfRangeException(); } } /// /// An immutable structure wrapping all of the necessary information to bind a specific buffer or attachment of a render target to a compute shader. /// public readonly struct ResourceView { /// /// This value is the equivalent of not setting any value at all; all struct fields will have their default values. /// It does not refer to a valid texture, therefore any variable set to this value should be checked for IsValid and reassigned before being bound to a shader. /// public static readonly ResourceView Unassigned = new ResourceView(default); /// /// This value contains a valid texture reference that can be bound to a shader, however it is just an empty placeholder texture. /// Binding this to a shader can be seen as setting the texture variable inside the shader to null. /// public static readonly ResourceView None = new ResourceView(BuiltinRenderTextureType.None); public ResourceView(in RenderTargetIdentifier renderTarget, RenderTextureSubElement subElement = RenderTextureSubElement.Default, int mipLevel = 0) { RenderTarget = renderTarget; SubElement = subElement; MipLevel = mipLevel; } public bool IsValid => !RenderTarget.Equals(default); public readonly RenderTargetIdentifier RenderTarget; public readonly RenderTextureSubElement SubElement; public readonly int MipLevel; } } }