You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
12 KiB
309 lines
12 KiB
using System;
|
|
using System.Runtime.InteropServices;
|
|
using FidelityFX.FSR2;
|
|
using UnityEngine.Experimental.Rendering;
|
|
|
|
namespace UnityEngine.Rendering.PostProcessing
|
|
{
|
|
[Scripting.Preserve]
|
|
[Serializable]
|
|
public class Upscaling
|
|
{
|
|
public Func<PostProcessRenderContext, IUpscalerCallbacks> callbacksFactory { get; set; } = (context) => new UpscalerCallbacksBase();
|
|
|
|
public enum UpscalerType
|
|
{
|
|
[InspectorName("FidelityFX Super Resolution 2.2 (FSR2)")] FSR2,
|
|
[InspectorName("FidelityFX Super Resolution 3.1 (FSR3)")] FSR3,
|
|
}
|
|
|
|
[Tooltip("Which upscaling technology to use.")]
|
|
public UpscalerType upscalerType = UpscalerType.FSR2;
|
|
|
|
[Tooltip("Standard scaling ratio presets.")]
|
|
public Fsr2.QualityMode qualityMode = Fsr2.QualityMode.Quality;
|
|
|
|
[Tooltip("Apply sharpening to the image after upscaling.")]
|
|
public bool performSharpenPass = true;
|
|
[Tooltip("Strength of the sharpening effect.")]
|
|
[Range(0, 1)] public float sharpness = 0.8f;
|
|
|
|
[Tooltip("Choose where to get the exposure value from. Use auto-exposure from either the upscaler or Unity, provide a manual exposure texture, or use a default value.")]
|
|
public ExposureSource exposureSource = ExposureSource.Auto;
|
|
[Tooltip("Value by which the input signal will be divided, to get back to the original signal produced by the game.")]
|
|
public float preExposure = 1.0f;
|
|
[Tooltip("Optional 1x1 texture containing the exposure value for the current frame.")]
|
|
public Texture exposure = null;
|
|
|
|
public enum ExposureSource
|
|
{
|
|
Default,
|
|
Auto,
|
|
Unity,
|
|
Manual,
|
|
}
|
|
|
|
[Tooltip("Enable a debug view to analyze the upscaling process.")]
|
|
public bool enableDebugView = false;
|
|
|
|
[Tooltip("Optional texture to control the influence of the current frame on the reconstructed output. If unset, either an auto-generated or a default cleared reactive mask will be used.")]
|
|
public Texture reactiveMask = null;
|
|
[Tooltip("Optional texture for marking areas of specialist rendering which should be accounted for during the upscaling process. If unset, a default cleared mask will be used.")]
|
|
public Texture transparencyAndCompositionMask = null;
|
|
[Tooltip("Automatically generate a reactive mask based on the difference between opaque-only render output and the final render output including alpha transparencies.")]
|
|
public bool autoGenerateReactiveMask = true;
|
|
[Tooltip("Parameters to control the process of auto-generating a reactive mask.")]
|
|
public GenerateReactiveParameters generateReactiveParameters = new GenerateReactiveParameters();
|
|
|
|
[Serializable]
|
|
public class GenerateReactiveParameters
|
|
{
|
|
[Tooltip("A value to scale the output")]
|
|
[Range(0, 2)] public float scale = 0.5f;
|
|
[Tooltip("A threshold value to generate a binary reactive mask")]
|
|
[Range(0, 1)] public float cutoffThreshold = 0.2f;
|
|
[Tooltip("A value to set for the binary reactive mask")]
|
|
[Range(0, 1)] public float binaryValue = 0.9f;
|
|
[Tooltip("Flags to determine how to generate the reactive mask")]
|
|
public Fsr2.GenerateReactiveFlags flags = Fsr2.GenerateReactiveFlags.ApplyTonemap | Fsr2.GenerateReactiveFlags.ApplyThreshold | Fsr2.GenerateReactiveFlags.UseComponentsMax;
|
|
}
|
|
|
|
[Tooltip("(Experimental) Automatically generate and use Reactive mask and Transparency & composition mask internally.")]
|
|
public bool autoGenerateTransparencyAndComposition = false;
|
|
[Tooltip("Parameters to control the process of auto-generating transparency and composition masks.")]
|
|
public GenerateTcrParameters generateTransparencyAndCompositionParameters = new GenerateTcrParameters();
|
|
|
|
[Serializable]
|
|
public class GenerateTcrParameters
|
|
{
|
|
[Tooltip("Setting this value too small will cause visual instability. Larger values can cause ghosting.")]
|
|
[Range(0, 1)] public float autoTcThreshold = 0.05f;
|
|
[Tooltip("Smaller values will increase stability at hard edges of translucent objects.")]
|
|
[Range(0, 2)] public float autoTcScale = 1.0f;
|
|
[Tooltip("Larger values result in more reactive pixels.")]
|
|
[Range(0, 10)] public float autoReactiveScale = 5.0f;
|
|
[Tooltip("Maximum value reactivity can reach.")]
|
|
[Range(0, 1)] public float autoReactiveMax = 0.9f;
|
|
}
|
|
|
|
public Vector2 Jitter { get; private set; }
|
|
public Vector2 JitterOffset { get; private set; }
|
|
public Vector2Int MaxRenderSize => _maxRenderSize;
|
|
public Vector2Int UpscaleSize => _upscaleSize;
|
|
public bool Reset => _resetHistory;
|
|
public RenderTargetIdentifier ColorOpaqueOnly { get; set; }
|
|
|
|
private bool _initialized;
|
|
private Upscaler _upscaler;
|
|
private Vector2Int _maxRenderSize;
|
|
private Vector2Int _upscaleSize;
|
|
private bool _resetHistory;
|
|
|
|
private IUpscalerCallbacks _callbacks;
|
|
|
|
private UpscalerType _prevUpscalerType;
|
|
private Fsr2.QualityMode _prevQualityMode;
|
|
private ExposureSource _prevExposureSource;
|
|
private Vector2Int _prevUpscaleSize;
|
|
|
|
private Rect _originalRect;
|
|
|
|
public bool IsSupported()
|
|
{
|
|
return SystemInfo.supportsComputeShaders && SystemInfo.supportsMotionVectors;
|
|
}
|
|
|
|
public DepthTextureMode GetCameraFlags()
|
|
{
|
|
return DepthTextureMode.Depth | DepthTextureMode.MotionVectors;
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
DestroyUpscaler();
|
|
}
|
|
|
|
public void ResetHistory()
|
|
{
|
|
_resetHistory = true;
|
|
}
|
|
|
|
public void ConfigureJitteredProjectionMatrix(PostProcessRenderContext context)
|
|
{
|
|
ApplyJitter(context.camera);
|
|
}
|
|
|
|
public void ConfigureCameraViewport(PostProcessRenderContext context)
|
|
{
|
|
var camera = context.camera;
|
|
_originalRect = camera.rect;
|
|
|
|
// Determine the desired rendering and display resolutions
|
|
_upscaleSize = new Vector2Int(camera.pixelWidth, camera.pixelHeight);
|
|
Fsr2.GetRenderResolutionFromQualityMode(out int maxRenderWidth, out int maxRenderHeight, _upscaleSize.x, _upscaleSize.y, qualityMode);
|
|
_maxRenderSize = new Vector2Int(maxRenderWidth, maxRenderHeight);
|
|
|
|
// Render to a smaller portion of the screen by manipulating the camera's viewport rect
|
|
camera.aspect = (float)_upscaleSize.x / _upscaleSize.y;
|
|
camera.rect = new Rect(0, 0, _originalRect.width * _maxRenderSize.x / _upscaleSize.x, _originalRect.height * _maxRenderSize.y / _upscaleSize.y);
|
|
}
|
|
|
|
public void ResetCameraViewport(PostProcessRenderContext context)
|
|
{
|
|
context.camera.rect = _originalRect;
|
|
}
|
|
|
|
public void Render(PostProcessRenderContext context)
|
|
{
|
|
// Monitor for any resolution changes and recreate the upscaler context if necessary
|
|
// We can't create an upscaler context without info from the post-processing context, so delay the initial setup until here
|
|
if (!_initialized || _upscaler == null || _upscaleSize.x != _prevUpscaleSize.x || _upscaleSize.y != _prevUpscaleSize.y ||
|
|
upscalerType != _prevUpscalerType || qualityMode != _prevQualityMode || exposureSource != _prevExposureSource)
|
|
{
|
|
DestroyUpscaler();
|
|
CreateUpscaler(context);
|
|
}
|
|
|
|
_upscaler?.Render(context, this);
|
|
|
|
_resetHistory = false;
|
|
}
|
|
|
|
private void CreateUpscaler(PostProcessRenderContext context)
|
|
{
|
|
if (_upscaler == null || upscalerType != _prevUpscalerType)
|
|
{
|
|
_upscaler = upscalerType switch
|
|
{
|
|
UpscalerType.FSR2 when FSR2Upscaler.IsSupported => new FSR2Upscaler(),
|
|
UpscalerType.FSR3 when FSR3Upscaler.IsSupported => new FSR3Upscaler(),
|
|
_ => new FSR2Upscaler(), // Fallback for when the selected upscaler is not supported on the current hardware
|
|
};
|
|
|
|
_prevUpscalerType = upscalerType;
|
|
}
|
|
|
|
_prevQualityMode = qualityMode;
|
|
_prevExposureSource = exposureSource;
|
|
_prevUpscaleSize = _upscaleSize;
|
|
|
|
_callbacks = callbacksFactory(context);
|
|
|
|
_upscaler.CreateContext(context, this);
|
|
|
|
// Apply a mipmap bias so that textures retain their sharpness
|
|
float biasOffset = Fsr2.GetMipmapBiasOffset(_maxRenderSize.x, _upscaleSize.x);
|
|
if (_callbacks != null && !float.IsNaN(biasOffset) && !float.IsInfinity(biasOffset))
|
|
{
|
|
_callbacks.ApplyMipmapBias(biasOffset);
|
|
}
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
private void DestroyUpscaler()
|
|
{
|
|
_initialized = false;
|
|
|
|
_upscaler?.DestroyContext();
|
|
|
|
if (_callbacks != null)
|
|
{
|
|
// Undo the current mipmap bias offset
|
|
_callbacks.UndoMipmapBias();
|
|
_callbacks = null;
|
|
}
|
|
}
|
|
|
|
private void ApplyJitter(Camera camera)
|
|
{
|
|
var scaledRenderSize = GetScaledRenderSize(camera);
|
|
|
|
// Perform custom jittering of the camera's projection matrix according to FSR's recipe
|
|
int jitterPhaseCount = Fsr2.GetJitterPhaseCount(scaledRenderSize.x, _upscaleSize.x);
|
|
Fsr2.GetJitterOffset(out float jitterX, out float jitterY, Time.frameCount, jitterPhaseCount);
|
|
|
|
JitterOffset = new Vector2(jitterX, jitterY);
|
|
|
|
jitterX = 2.0f * jitterX / scaledRenderSize.x;
|
|
jitterY = 2.0f * jitterY / scaledRenderSize.y;
|
|
|
|
var jitterTranslationMatrix = Matrix4x4.Translate(new Vector3(jitterX, jitterY, 0));
|
|
camera.nonJitteredProjectionMatrix = camera.projectionMatrix;
|
|
camera.projectionMatrix = jitterTranslationMatrix * camera.nonJitteredProjectionMatrix;
|
|
camera.useJitteredProjectionMatrixForTransparentRendering = true;
|
|
|
|
Jitter = new Vector2(jitterX, jitterY);
|
|
}
|
|
|
|
internal Vector2Int GetScaledRenderSize(Camera camera)
|
|
{
|
|
if (!RuntimeUtilities.IsDynamicResolutionEnabled(camera))
|
|
return _maxRenderSize;
|
|
|
|
return new Vector2Int(Mathf.CeilToInt(_maxRenderSize.x * ScalableBufferManager.widthScaleFactor), Mathf.CeilToInt(_maxRenderSize.y * ScalableBufferManager.heightScaleFactor));
|
|
}
|
|
|
|
internal static BuiltinRenderTextureType GetDepthTexture(Camera cam)
|
|
{
|
|
return cam.renderingPath is RenderingPath.Forward or RenderingPath.VertexLit ? BuiltinRenderTextureType.Depth : BuiltinRenderTextureType.CameraTarget;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A collection of callbacks required by the upscaler.
|
|
/// This allows some customization by the game dev on how to integrate upscaling into their own game setup.
|
|
/// </summary>
|
|
public interface IUpscalerCallbacks
|
|
{
|
|
/// <summary>
|
|
/// Apply a mipmap bias to in-game textures to prevent them from becoming blurry as the internal rendering resolution lowers.
|
|
/// This will need to be customized on a per-game basis, as there is no clear universal way to determine what are "in-game" textures.
|
|
/// The default implementation will simply apply a mipmap bias to all 2D textures, which will include things like UI textures and which might miss things like terrain texture arrays.
|
|
///
|
|
/// Depending on how your game organizes its assets, you will want to create a filter that more specifically selects the textures that need to have this mipmap bias applied.
|
|
/// You may also want to store the bias offset value and apply it to any assets that are loaded in on demand.
|
|
/// </summary>
|
|
void ApplyMipmapBias(float biasOffset);
|
|
|
|
void UndoMipmapBias();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default implementation of IUpscalerCallbacks.
|
|
/// These are fine for testing but a proper game will want to extend and override these methods.
|
|
/// </summary>
|
|
public class UpscalerCallbacksBase: IUpscalerCallbacks
|
|
{
|
|
protected float CurrentBiasOffset = 0;
|
|
|
|
public virtual void ApplyMipmapBias(float biasOffset)
|
|
{
|
|
if (float.IsNaN(biasOffset) || float.IsInfinity(biasOffset))
|
|
return;
|
|
|
|
CurrentBiasOffset += biasOffset;
|
|
|
|
if (Mathf.Approximately(CurrentBiasOffset, 0f))
|
|
{
|
|
CurrentBiasOffset = 0f;
|
|
}
|
|
|
|
foreach (var texture in Resources.FindObjectsOfTypeAll<Texture2D>())
|
|
{
|
|
if (texture.mipmapCount <= 1)
|
|
continue;
|
|
|
|
texture.mipMapBias += biasOffset;
|
|
}
|
|
}
|
|
|
|
public virtual void UndoMipmapBias()
|
|
{
|
|
if (CurrentBiasOffset == 0f)
|
|
return;
|
|
|
|
ApplyMipmapBias(-CurrentBiasOffset);
|
|
}
|
|
}
|
|
}
|