Browse Source
Reworked upscaling integration to allow for multiple upscaler algorithms that can be switched on the fly.
dec2024update
Reworked upscaling integration to allow for multiple upscaler algorithms that can be switched on the fly.
dec2024update
17 changed files with 878 additions and 405 deletions
-
19Assets/Scripts/DebugDumper.cs
-
38Packages/com.unity.postprocessing@3.2.2/PostProcessing/Editor/PostProcessLayerEditor.cs
-
12Packages/com.unity.postprocessing@3.2.2/PostProcessing/PostProcessResources.asset
-
2Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/DepthOfField.cs
-
345Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/SuperResolution.cs
-
318Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling.cs
-
3Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling.cs.meta
-
3Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling.meta
-
126Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling/FSR2Upscaler.cs
-
2Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling/FSR2Upscaler.cs.meta
-
126Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling/FSR3Upscaler.cs
-
11Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling/FSR3Upscaler.cs.meta
-
204Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling/Upscaler.cs
-
3Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/Effects/Upscaling/Upscaler.cs.meta
-
51Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/PostProcessLayer.cs
-
6Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/PostProcessRenderContext.cs
-
14Packages/com.unity.postprocessing@3.2.2/PostProcessing/Runtime/PostProcessResources.cs
@ -1,345 +0,0 @@ |
|||
// Copyright (c) 2024 Nico de Poel
|
|||
//
|
|||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|||
// of this software and associated documentation files (the "Software"), to deal
|
|||
// in the Software without restriction, including without limitation the rights
|
|||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||
// copies of the Software, and to permit persons to whom the Software is
|
|||
// furnished to do so, subject to the following conditions:
|
|||
//
|
|||
// The above copyright notice and this permission notice shall be included in all
|
|||
// copies or substantial portions of the Software.
|
|||
//
|
|||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||
// THE SOFTWARE.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
using FidelityFX; |
|||
using FidelityFX.FSR3; |
|||
|
|||
namespace UnityEngine.Rendering.PostProcessing |
|||
{ |
|||
[UnityEngine.Scripting.Preserve] |
|||
[Serializable] |
|||
public class SuperResolution |
|||
{ |
|||
public Func<PostProcessRenderContext, IFsr3UpscalerCallbacks> callbacksFactory { get; set; } = (context) => new Fsr3UpscalerCallbacksBase(); |
|||
|
|||
[Tooltip("Standard scaling ratio presets.")] |
|||
public Fsr3Upscaler.QualityMode qualityMode = Fsr3Upscaler.QualityMode.Quality; |
|||
|
|||
[Tooltip("Apply RCAS 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("Allow the use of half precision compute operations, potentially improving performance if the platform supports it.")] |
|||
public bool enableFP16 = false; |
|||
|
|||
[Tooltip("Choose where to get the exposure value from. Use auto-exposure from either FSR3 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 Fsr3Upscaler.GenerateReactiveFlags flags = Fsr3Upscaler.GenerateReactiveFlags.ApplyTonemap | Fsr3Upscaler.GenerateReactiveFlags.ApplyThreshold | Fsr3Upscaler.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 Vector2Int renderSize => _maxRenderSize; |
|||
public Vector2Int displaySize => _displaySize; |
|||
public RenderTargetIdentifier colorOpaqueOnly { get; set; } |
|||
|
|||
private Fsr3UpscalerContext _fsrContext; |
|||
private Vector2Int _maxRenderSize; |
|||
private Vector2Int _displaySize; |
|||
private bool _resetHistory; |
|||
|
|||
private IFsr3UpscalerCallbacks _callbacks; |
|||
|
|||
private readonly Fsr3Upscaler.DispatchDescription _dispatchDescription = new Fsr3Upscaler.DispatchDescription(); |
|||
private readonly Fsr3Upscaler.GenerateReactiveDescription _genReactiveDescription = new Fsr3Upscaler.GenerateReactiveDescription(); |
|||
|
|||
private Fsr3Upscaler.QualityMode _prevQualityMode; |
|||
private ExposureSource _prevExposureSource; |
|||
private Vector2Int _prevDisplaySize; |
|||
|
|||
private Rect _originalRect; |
|||
|
|||
public bool IsSupported() |
|||
{ |
|||
return SystemInfo.supportsComputeShaders && SystemInfo.supportsMotionVectors; |
|||
} |
|||
|
|||
public DepthTextureMode GetCameraFlags() |
|||
{ |
|||
return DepthTextureMode.Depth | DepthTextureMode.MotionVectors; |
|||
} |
|||
|
|||
public void Release() |
|||
{ |
|||
DestroyFsrContext(); |
|||
} |
|||
|
|||
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
|
|||
_displaySize = new Vector2Int(camera.pixelWidth, camera.pixelHeight); |
|||
Fsr3Upscaler.GetRenderResolutionFromQualityMode(out int maxRenderWidth, out int maxRenderHeight, _displaySize.x, _displaySize.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)_displaySize.x / _displaySize.y; |
|||
camera.rect = new Rect(0, 0, _originalRect.width * _maxRenderSize.x / _displaySize.x, _originalRect.height * _maxRenderSize.y / _displaySize.y); |
|||
} |
|||
|
|||
public void ResetCameraViewport(PostProcessRenderContext context) |
|||
{ |
|||
context.camera.rect = _originalRect; |
|||
} |
|||
|
|||
public void Render(PostProcessRenderContext context) |
|||
{ |
|||
var cmd = context.command; |
|||
cmd.BeginSample("FSR3 Upscaler"); |
|||
|
|||
// Monitor for any resolution changes and recreate the FSR3 Upscaler context if necessary
|
|||
// We can't create an FSR3 Upscaler context without info from the post-processing context, so delay the initial setup until here
|
|||
if (_fsrContext == null || _displaySize.x != _prevDisplaySize.x || _displaySize.y != _prevDisplaySize.y || qualityMode != _prevQualityMode || exposureSource != _prevExposureSource) |
|||
{ |
|||
DestroyFsrContext(); |
|||
CreateFsrContext(context); |
|||
} |
|||
|
|||
SetupDispatchDescription(context); |
|||
|
|||
if (autoGenerateReactiveMask) |
|||
{ |
|||
SetupAutoReactiveDescription(context); |
|||
|
|||
var scaledRenderSize = _genReactiveDescription.RenderSize; |
|||
cmd.GetTemporaryRT(Fsr3ShaderIDs.UavAutoReactive, scaledRenderSize.x, scaledRenderSize.y, 0, default, GraphicsFormat.R8_UNorm, 1, true); |
|||
_fsrContext.GenerateReactiveMask(_genReactiveDescription, cmd); |
|||
_dispatchDescription.Reactive = new ResourceView(Fsr3ShaderIDs.UavAutoReactive); |
|||
} |
|||
|
|||
_fsrContext.Dispatch(_dispatchDescription, cmd); |
|||
|
|||
cmd.EndSample("FSR3 Upscaler"); |
|||
|
|||
_resetHistory = false; |
|||
} |
|||
|
|||
private void CreateFsrContext(PostProcessRenderContext context) |
|||
{ |
|||
_prevQualityMode = qualityMode; |
|||
_prevExposureSource = exposureSource; |
|||
_prevDisplaySize = _displaySize; |
|||
|
|||
// Initialize FSR3 Upscaler context
|
|||
Fsr3Upscaler.InitializationFlags flags = 0; |
|||
if (context.camera.allowHDR) flags |= Fsr3Upscaler.InitializationFlags.EnableHighDynamicRange; |
|||
if (enableFP16) flags |= Fsr3Upscaler.InitializationFlags.EnableFP16Usage; |
|||
if (exposureSource == ExposureSource.Auto) flags |= Fsr3Upscaler.InitializationFlags.EnableAutoExposure; |
|||
if (RuntimeUtilities.IsDynamicResolutionEnabled(context.camera)) flags |= Fsr3Upscaler.InitializationFlags.EnableDynamicResolution; |
|||
|
|||
_callbacks = callbacksFactory(context); |
|||
_fsrContext = Fsr3Upscaler.CreateContext(_displaySize, _maxRenderSize, context.resources.computeShaders.superResolution, flags); |
|||
|
|||
// Apply a mipmap bias so that textures retain their sharpness
|
|||
float biasOffset = Fsr3Upscaler.GetMipmapBiasOffset(_maxRenderSize.x, _displaySize.x); |
|||
if (!float.IsNaN(biasOffset) && !float.IsInfinity(biasOffset)) |
|||
{ |
|||
_callbacks.ApplyMipmapBias(biasOffset); |
|||
} |
|||
} |
|||
|
|||
private void DestroyFsrContext() |
|||
{ |
|||
if (_fsrContext != null) |
|||
{ |
|||
_fsrContext.Destroy(); |
|||
_fsrContext = null; |
|||
} |
|||
|
|||
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 FSR3's recipe
|
|||
int jitterPhaseCount = Fsr3Upscaler.GetJitterPhaseCount(scaledRenderSize.x, _displaySize.x); |
|||
Fsr3Upscaler.GetJitterOffset(out float jitterX, out float jitterY, Time.frameCount, jitterPhaseCount); |
|||
|
|||
_dispatchDescription.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); |
|||
} |
|||
|
|||
private void SetupDispatchDescription(PostProcessRenderContext context) |
|||
{ |
|||
var camera = context.camera; |
|||
|
|||
// Set up the main FSR3 Upscaler dispatch parameters
|
|||
_dispatchDescription.Color = new ResourceView(context.source); |
|||
_dispatchDescription.Depth = new ResourceView(GetDepthTexture(context.camera), RenderTextureSubElement.Depth); |
|||
_dispatchDescription.MotionVectors = new ResourceView(BuiltinRenderTextureType.MotionVectors); |
|||
_dispatchDescription.Exposure = ResourceView.Unassigned; |
|||
_dispatchDescription.Reactive = ResourceView.Unassigned; |
|||
_dispatchDescription.TransparencyAndComposition = ResourceView.Unassigned; |
|||
|
|||
if (exposureSource == ExposureSource.Manual && exposure != null) _dispatchDescription.Exposure = new ResourceView(exposure); |
|||
if (exposureSource == ExposureSource.Unity) _dispatchDescription.Exposure = new ResourceView(context.autoExposureTexture); |
|||
if (reactiveMask != null) _dispatchDescription.Reactive = new ResourceView(reactiveMask); |
|||
if (transparencyAndCompositionMask != null) _dispatchDescription.TransparencyAndComposition = new ResourceView(transparencyAndCompositionMask); |
|||
|
|||
var scaledRenderSize = GetScaledRenderSize(context.camera); |
|||
|
|||
_dispatchDescription.Output = new ResourceView(context.destination); |
|||
_dispatchDescription.PreExposure = preExposure; |
|||
_dispatchDescription.EnableSharpening = performSharpenPass; |
|||
_dispatchDescription.Sharpness = sharpness; |
|||
_dispatchDescription.MotionVectorScale.x = -scaledRenderSize.x; |
|||
_dispatchDescription.MotionVectorScale.y = -scaledRenderSize.y; |
|||
_dispatchDescription.RenderSize = scaledRenderSize; |
|||
_dispatchDescription.UpscaleSize = _displaySize; |
|||
_dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime; |
|||
_dispatchDescription.CameraNear = camera.nearClipPlane; |
|||
_dispatchDescription.CameraFar = camera.farClipPlane; |
|||
_dispatchDescription.CameraFovAngleVertical = camera.fieldOfView * Mathf.Deg2Rad; |
|||
_dispatchDescription.ViewSpaceToMetersFactor = 1.0f; // 1 unit is 1 meter in Unity
|
|||
_dispatchDescription.Reset = _resetHistory; |
|||
_dispatchDescription.Flags = enableDebugView ? Fsr3Upscaler.DispatchFlags.DrawDebugView : 0; |
|||
|
|||
// Set up the parameters for the optional experimental auto-TCR feature
|
|||
_dispatchDescription.EnableAutoReactive = autoGenerateTransparencyAndComposition; |
|||
if (autoGenerateTransparencyAndComposition) |
|||
{ |
|||
_dispatchDescription.ColorOpaqueOnly = new ResourceView(colorOpaqueOnly); |
|||
_dispatchDescription.AutoTcThreshold = generateTransparencyAndCompositionParameters.autoTcThreshold; |
|||
_dispatchDescription.AutoTcScale = generateTransparencyAndCompositionParameters.autoTcScale; |
|||
_dispatchDescription.AutoReactiveScale = generateTransparencyAndCompositionParameters.autoReactiveScale; |
|||
_dispatchDescription.AutoReactiveMax = generateTransparencyAndCompositionParameters.autoReactiveMax; |
|||
} |
|||
|
|||
if (SystemInfo.usesReversedZBuffer) |
|||
{ |
|||
// Swap the near and far clip plane distances as FSR3 expects this when using inverted depth
|
|||
(_dispatchDescription.CameraNear, _dispatchDescription.CameraFar) = (_dispatchDescription.CameraFar, _dispatchDescription.CameraNear); |
|||
} |
|||
} |
|||
|
|||
private void SetupAutoReactiveDescription(PostProcessRenderContext context) |
|||
{ |
|||
// Set up the parameters to auto-generate a reactive mask
|
|||
_genReactiveDescription.ColorOpaqueOnly = new ResourceView(colorOpaqueOnly); |
|||
_genReactiveDescription.ColorPreUpscale = new ResourceView(context.source); |
|||
_genReactiveDescription.OutReactive = new ResourceView(Fsr3ShaderIDs.UavAutoReactive); |
|||
_genReactiveDescription.RenderSize = GetScaledRenderSize(context.camera); |
|||
_genReactiveDescription.Scale = generateReactiveParameters.scale; |
|||
_genReactiveDescription.CutoffThreshold = generateReactiveParameters.cutoffThreshold; |
|||
_genReactiveDescription.BinaryValue = generateReactiveParameters.binaryValue; |
|||
_genReactiveDescription.Flags = generateReactiveParameters.flags; |
|||
} |
|||
|
|||
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)); |
|||
} |
|||
|
|||
private static BuiltinRenderTextureType GetDepthTexture(Camera cam) |
|||
{ |
|||
RenderingPath renderingPath = cam.renderingPath; |
|||
return renderingPath == RenderingPath.Forward || renderingPath == RenderingPath.VertexLit ? BuiltinRenderTextureType.Depth : BuiltinRenderTextureType.CameraTarget; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,318 @@ |
|||
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, |
|||
[InspectorName("Arm Accuracy Super Resolution (ASR)")] ASR, |
|||
[InspectorName("Snapdragon Game Super Resolution 2 (SGSR2)")] SGSR2, |
|||
[InspectorName("PlayStation Spectral Super Resolution (PSSR)")] PSSR, |
|||
} |
|||
|
|||
[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 int _frameIndex; |
|||
|
|||
private void ApplyJitter(Camera camera) |
|||
{ |
|||
var scaledRenderSize = GetScaledRenderSize(camera); |
|||
|
|||
// Stop frame debugger from freaking out
|
|||
if (Time.deltaTime > 0) |
|||
_frameIndex++; |
|||
|
|||
// 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, _frameIndex, 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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
fileFormatVersion: 2 |
|||
guid: bd735f2dacb54f5e9a0722ec5851acee |
|||
timeCreated: 1729512005 |
|||
@ -0,0 +1,3 @@ |
|||
fileFormatVersion: 2 |
|||
guid: a0c49999ca0d45bfb0922a4f7366ae28 |
|||
timeCreated: 1729511667 |
|||
@ -0,0 +1,126 @@ |
|||
using FidelityFX; |
|||
using FidelityFX.FSR2; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEngine.Rendering.PostProcessing |
|||
{ |
|||
internal class FSR2Upscaler: Upscaler |
|||
{ |
|||
public static bool IsSupported => SystemInfo.supportsComputeShaders; |
|||
|
|||
private Fsr2Context _fsrContext; |
|||
|
|||
private readonly Fsr2.DispatchDescription _dispatchDescription = new(); |
|||
private readonly Fsr2.GenerateReactiveDescription _genReactiveDescription = new(); |
|||
|
|||
public override void CreateContext(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
// Initialize FSR2 context
|
|||
Fsr2.InitializationFlags flags = 0; |
|||
if (context.camera.allowHDR) flags |= Fsr2.InitializationFlags.EnableHighDynamicRange; |
|||
if (config.exposureSource == Upscaling.ExposureSource.Auto) flags |= Fsr2.InitializationFlags.EnableAutoExposure; |
|||
if (RuntimeUtilities.IsDynamicResolutionEnabled(context.camera)) flags |= Fsr2.InitializationFlags.EnableDynamicResolution; |
|||
|
|||
_fsrContext = Fsr2.CreateContext(config.UpscaleSize, config.MaxRenderSize, context.resources.computeShaders.fsr2Upscaler, flags); |
|||
} |
|||
|
|||
public override void DestroyContext() |
|||
{ |
|||
base.DestroyContext(); |
|||
|
|||
if (_fsrContext != null) |
|||
{ |
|||
_fsrContext.Destroy(); |
|||
_fsrContext = null; |
|||
} |
|||
} |
|||
|
|||
public override void Render(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
var cmd = context.command; |
|||
cmd.BeginSample("FSR2"); |
|||
|
|||
SetupDispatchDescription(context, config); |
|||
|
|||
if (config.autoGenerateReactiveMask) |
|||
{ |
|||
SetupAutoReactiveDescription(context, config); |
|||
|
|||
var scaledRenderSize = _genReactiveDescription.RenderSize; |
|||
cmd.GetTemporaryRT(Fsr2ShaderIDs.UavAutoReactive, scaledRenderSize.x, scaledRenderSize.y, 0, default, GraphicsFormat.R8_UNorm, 1, true); |
|||
_fsrContext.GenerateReactiveMask(_genReactiveDescription, cmd); |
|||
_dispatchDescription.Reactive = new ResourceView(Fsr2ShaderIDs.UavAutoReactive); |
|||
} |
|||
|
|||
_fsrContext.Dispatch(_dispatchDescription, cmd); |
|||
|
|||
cmd.EndSample("FSR2"); |
|||
} |
|||
|
|||
private void SetupDispatchDescription(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
var camera = context.camera; |
|||
|
|||
// Set up the main FSR2 dispatch parameters
|
|||
_dispatchDescription.Color = new ResourceView(context.source); |
|||
_dispatchDescription.Depth = new ResourceView(Upscaling.GetDepthTexture(context.camera), RenderTextureSubElement.Depth); |
|||
_dispatchDescription.MotionVectors = new ResourceView(BuiltinRenderTextureType.MotionVectors); |
|||
_dispatchDescription.Exposure = ResourceView.Unassigned; |
|||
_dispatchDescription.Reactive = ResourceView.Unassigned; |
|||
_dispatchDescription.TransparencyAndComposition = ResourceView.Unassigned; |
|||
|
|||
if (config.exposureSource == Upscaling.ExposureSource.Manual && config.exposure != null) _dispatchDescription.Exposure = new ResourceView(config.exposure); |
|||
if (config.exposureSource == Upscaling.ExposureSource.Unity) _dispatchDescription.Exposure = new ResourceView(context.autoExposureTexture); |
|||
if (config.reactiveMask != null) _dispatchDescription.Reactive = new ResourceView(config.reactiveMask); |
|||
if (config.transparencyAndCompositionMask != null) _dispatchDescription.TransparencyAndComposition = new ResourceView(config.transparencyAndCompositionMask); |
|||
|
|||
var scaledRenderSize = config.GetScaledRenderSize(context.camera); |
|||
|
|||
_dispatchDescription.Output = new ResourceView(context.destination); |
|||
_dispatchDescription.PreExposure = config.preExposure; |
|||
_dispatchDescription.EnableSharpening = config.performSharpenPass; |
|||
_dispatchDescription.Sharpness = config.sharpness; |
|||
_dispatchDescription.JitterOffset = config.JitterOffset; |
|||
_dispatchDescription.MotionVectorScale.x = -scaledRenderSize.x; |
|||
_dispatchDescription.MotionVectorScale.y = -scaledRenderSize.y; |
|||
_dispatchDescription.RenderSize = scaledRenderSize; |
|||
_dispatchDescription.InputResourceSize = scaledRenderSize; |
|||
_dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime; |
|||
_dispatchDescription.CameraNear = camera.nearClipPlane; |
|||
_dispatchDescription.CameraFar = camera.farClipPlane; |
|||
_dispatchDescription.CameraFovAngleVertical = camera.fieldOfView * Mathf.Deg2Rad; |
|||
_dispatchDescription.ViewSpaceToMetersFactor = 1.0f; // 1 unit is 1 meter in Unity
|
|||
_dispatchDescription.Reset = config.Reset; |
|||
|
|||
// Set up the parameters for the optional experimental auto-TCR feature
|
|||
_dispatchDescription.EnableAutoReactive = config.autoGenerateTransparencyAndComposition; |
|||
if (config.autoGenerateTransparencyAndComposition) |
|||
{ |
|||
_dispatchDescription.ColorOpaqueOnly = new ResourceView(config.ColorOpaqueOnly); |
|||
_dispatchDescription.AutoTcThreshold = config.generateTransparencyAndCompositionParameters.autoTcThreshold; |
|||
_dispatchDescription.AutoTcScale = config.generateTransparencyAndCompositionParameters.autoTcScale; |
|||
_dispatchDescription.AutoReactiveScale = config.generateTransparencyAndCompositionParameters.autoReactiveScale; |
|||
_dispatchDescription.AutoReactiveMax = config.generateTransparencyAndCompositionParameters.autoReactiveMax; |
|||
} |
|||
|
|||
if (SystemInfo.usesReversedZBuffer) |
|||
{ |
|||
// Swap the near and far clip plane distances as FSR2 expects this when using inverted depth
|
|||
(_dispatchDescription.CameraNear, _dispatchDescription.CameraFar) = (_dispatchDescription.CameraFar, _dispatchDescription.CameraNear); |
|||
} |
|||
} |
|||
|
|||
private void SetupAutoReactiveDescription(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
// Set up the parameters to auto-generate a reactive mask
|
|||
_genReactiveDescription.ColorOpaqueOnly = new ResourceView(config.ColorOpaqueOnly); |
|||
_genReactiveDescription.ColorPreUpscale = new ResourceView(context.source); |
|||
_genReactiveDescription.OutReactive = new ResourceView(Fsr2ShaderIDs.UavAutoReactive); |
|||
_genReactiveDescription.RenderSize = config.GetScaledRenderSize(context.camera); |
|||
_genReactiveDescription.Scale = config.generateReactiveParameters.scale; |
|||
_genReactiveDescription.CutoffThreshold = config.generateReactiveParameters.cutoffThreshold; |
|||
_genReactiveDescription.BinaryValue = config.generateReactiveParameters.binaryValue; |
|||
_genReactiveDescription.Flags = config.generateReactiveParameters.flags; |
|||
} |
|||
} |
|||
} |
|||
@ -1,5 +1,5 @@ |
|||
fileFormatVersion: 2 |
|||
guid: 804fb4cfea0948247a52576cc4a79609 |
|||
guid: a0118021cf3d4485b761155cbdcaa35d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
@ -0,0 +1,126 @@ |
|||
using FidelityFX; |
|||
using FidelityFX.FSR3; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEngine.Rendering.PostProcessing |
|||
{ |
|||
internal class FSR3Upscaler: Upscaler |
|||
{ |
|||
public static bool IsSupported => SystemInfo.supportsComputeShaders; |
|||
|
|||
private Fsr3UpscalerContext _fsrContext; |
|||
|
|||
private readonly Fsr3Upscaler.DispatchDescription _dispatchDescription = new(); |
|||
private readonly Fsr3Upscaler.GenerateReactiveDescription _genReactiveDescription = new(); |
|||
|
|||
public override void CreateContext(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
// Initialize FSR3 Upscaler context
|
|||
Fsr3Upscaler.InitializationFlags flags = 0; |
|||
if (context.camera.allowHDR) flags |= Fsr3Upscaler.InitializationFlags.EnableHighDynamicRange; |
|||
if (config.exposureSource == Upscaling.ExposureSource.Auto) flags |= Fsr3Upscaler.InitializationFlags.EnableAutoExposure; |
|||
if (RuntimeUtilities.IsDynamicResolutionEnabled(context.camera)) flags |= Fsr3Upscaler.InitializationFlags.EnableDynamicResolution; |
|||
|
|||
_fsrContext = Fsr3Upscaler.CreateContext(config.UpscaleSize, config.MaxRenderSize, context.resources.computeShaders.fsr3Upscaler, flags); |
|||
} |
|||
|
|||
public override void DestroyContext() |
|||
{ |
|||
base.DestroyContext(); |
|||
|
|||
if (_fsrContext != null) |
|||
{ |
|||
_fsrContext.Destroy(); |
|||
_fsrContext = null; |
|||
} |
|||
} |
|||
|
|||
public override void Render(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
var cmd = context.command; |
|||
cmd.BeginSample("FSR3 Upscaler"); |
|||
|
|||
SetupDispatchDescription(context, config); |
|||
|
|||
if (config.autoGenerateReactiveMask) |
|||
{ |
|||
SetupAutoReactiveDescription(context, config); |
|||
|
|||
var scaledRenderSize = _genReactiveDescription.RenderSize; |
|||
cmd.GetTemporaryRT(Fsr3ShaderIDs.UavAutoReactive, scaledRenderSize.x, scaledRenderSize.y, 0, default, GraphicsFormat.R8_UNorm, 1, true); |
|||
_fsrContext.GenerateReactiveMask(_genReactiveDescription, cmd); |
|||
_dispatchDescription.Reactive = new ResourceView(Fsr3ShaderIDs.UavAutoReactive); |
|||
} |
|||
|
|||
_fsrContext.Dispatch(_dispatchDescription, cmd); |
|||
|
|||
cmd.EndSample("FSR3 Upscaler"); |
|||
} |
|||
|
|||
private void SetupDispatchDescription(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
var camera = context.camera; |
|||
|
|||
// Set up the main FSR3 Upscaler dispatch parameters
|
|||
_dispatchDescription.Color = new ResourceView(context.source); |
|||
_dispatchDescription.Depth = new ResourceView(Upscaling.GetDepthTexture(context.camera), RenderTextureSubElement.Depth); |
|||
_dispatchDescription.MotionVectors = new ResourceView(BuiltinRenderTextureType.MotionVectors); |
|||
_dispatchDescription.Exposure = ResourceView.Unassigned; |
|||
_dispatchDescription.Reactive = ResourceView.Unassigned; |
|||
_dispatchDescription.TransparencyAndComposition = ResourceView.Unassigned; |
|||
|
|||
if (config.exposureSource == Upscaling.ExposureSource.Manual && config.exposure != null) _dispatchDescription.Exposure = new ResourceView(config.exposure); |
|||
if (config.exposureSource == Upscaling.ExposureSource.Unity) _dispatchDescription.Exposure = new ResourceView(context.autoExposureTexture); |
|||
if (config.reactiveMask != null) _dispatchDescription.Reactive = new ResourceView(config.reactiveMask); |
|||
if (config.transparencyAndCompositionMask != null) _dispatchDescription.TransparencyAndComposition = new ResourceView(config.transparencyAndCompositionMask); |
|||
|
|||
var scaledRenderSize = config.GetScaledRenderSize(context.camera); |
|||
|
|||
_dispatchDescription.Output = new ResourceView(context.destination); |
|||
_dispatchDescription.PreExposure = config.preExposure; |
|||
_dispatchDescription.EnableSharpening = config.performSharpenPass; |
|||
_dispatchDescription.Sharpness = config.sharpness; |
|||
_dispatchDescription.JitterOffset = config.JitterOffset; |
|||
_dispatchDescription.MotionVectorScale.x = -scaledRenderSize.x; |
|||
_dispatchDescription.MotionVectorScale.y = -scaledRenderSize.y; |
|||
_dispatchDescription.RenderSize = scaledRenderSize; |
|||
_dispatchDescription.UpscaleSize = config.UpscaleSize; |
|||
_dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime; |
|||
_dispatchDescription.CameraNear = camera.nearClipPlane; |
|||
_dispatchDescription.CameraFar = camera.farClipPlane; |
|||
_dispatchDescription.CameraFovAngleVertical = camera.fieldOfView * Mathf.Deg2Rad; |
|||
_dispatchDescription.ViewSpaceToMetersFactor = 1.0f; // 1 unit is 1 meter in Unity
|
|||
_dispatchDescription.Reset = config.Reset; |
|||
|
|||
// Set up the parameters for the optional experimental auto-TCR feature
|
|||
_dispatchDescription.EnableAutoReactive = config.autoGenerateTransparencyAndComposition; |
|||
if (config.autoGenerateTransparencyAndComposition) |
|||
{ |
|||
_dispatchDescription.ColorOpaqueOnly = new ResourceView(config.ColorOpaqueOnly); |
|||
_dispatchDescription.AutoTcThreshold = config.generateTransparencyAndCompositionParameters.autoTcThreshold; |
|||
_dispatchDescription.AutoTcScale = config.generateTransparencyAndCompositionParameters.autoTcScale; |
|||
_dispatchDescription.AutoReactiveScale = config.generateTransparencyAndCompositionParameters.autoReactiveScale; |
|||
_dispatchDescription.AutoReactiveMax = config.generateTransparencyAndCompositionParameters.autoReactiveMax; |
|||
} |
|||
|
|||
if (SystemInfo.usesReversedZBuffer) |
|||
{ |
|||
// Swap the near and far clip plane distances as FSR3 Upscaler expects this when using inverted depth
|
|||
(_dispatchDescription.CameraNear, _dispatchDescription.CameraFar) = (_dispatchDescription.CameraFar, _dispatchDescription.CameraNear); |
|||
} |
|||
} |
|||
|
|||
private void SetupAutoReactiveDescription(PostProcessRenderContext context, Upscaling config) |
|||
{ |
|||
// Set up the parameters to auto-generate a reactive mask
|
|||
_genReactiveDescription.ColorOpaqueOnly = new ResourceView(config.ColorOpaqueOnly); |
|||
_genReactiveDescription.ColorPreUpscale = new ResourceView(context.source); |
|||
_genReactiveDescription.OutReactive = new ResourceView(Fsr3ShaderIDs.UavAutoReactive); |
|||
_genReactiveDescription.RenderSize = config.GetScaledRenderSize(context.camera); |
|||
_genReactiveDescription.Scale = config.generateReactiveParameters.scale; |
|||
_genReactiveDescription.CutoffThreshold = config.generateReactiveParameters.cutoffThreshold; |
|||
_genReactiveDescription.BinaryValue = config.generateReactiveParameters.binaryValue; |
|||
_genReactiveDescription.Flags = (Fsr3Upscaler.GenerateReactiveFlags)config.generateReactiveParameters.flags; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
fileFormatVersion: 2 |
|||
guid: fbdc9b27493f9ee43aa512e20a9ce7bf |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|||
@ -0,0 +1,204 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using FidelityFX; |
|||
using FidelityFX.FSR2; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEngine.Rendering.PostProcessing |
|||
{ |
|||
internal abstract class Upscaler |
|||
{ |
|||
public abstract void CreateContext(PostProcessRenderContext context, Upscaling config); |
|||
|
|||
public virtual void DestroyContext() |
|||
{ |
|||
DestroyRenderTexture(ref _reactiveMask); |
|||
DestroyConstantsBuffer(ref _reactiveMaskConstants); |
|||
DestroyConstantsBuffer(ref _sharpeningConstants); |
|||
} |
|||
|
|||
public abstract void Render(PostProcessRenderContext context, Upscaling config); |
|||
|
|||
private ConstantsBuffer<GenerateReactiveConstants> _reactiveMaskConstants; |
|||
private RenderTexture _reactiveMask; |
|||
|
|||
/// <summary>
|
|||
/// Generalized standalone version of the FSR2 reactive mask auto-generating pass that can be used without needing an active FSR2 context.
|
|||
/// This allows auto-generated reactive masks to be reused for other non-FSR upscaling techniques.
|
|||
/// </summary>
|
|||
protected Texture GenerateReactiveMask(CommandBuffer cmd, PostProcessRenderContext context, Upscaling config, GraphicsFormat format = GraphicsFormat.R8_UNorm) |
|||
{ |
|||
ComputeShader shader = context.resources.computeShaders.fsr2Upscaler?.autoGenReactivePass; |
|||
if (shader == null) |
|||
return Texture2D.blackTexture; |
|||
|
|||
_reactiveMaskConstants ??= ConstantsBuffer<GenerateReactiveConstants>.Create(); |
|||
|
|||
if (_reactiveMask == null) |
|||
{ |
|||
// Use a persistent RT so it can easily be passed to native render plugins
|
|||
CreateRenderTexture(ref _reactiveMask, "Reactive Mask", config.MaxRenderSize, format, true); |
|||
} |
|||
|
|||
Vector2Int scaledRenderSize = config.GetScaledRenderSize(context.camera); |
|||
|
|||
const int threadGroupWorkRegionDim = 8; |
|||
int dispatchX = (scaledRenderSize.x + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; |
|||
int dispatchY = (scaledRenderSize.y + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; |
|||
|
|||
cmd.BeginSample("Generate Reactive Mask"); |
|||
|
|||
_reactiveMaskConstants.Value.scale = config.generateReactiveParameters.scale; |
|||
_reactiveMaskConstants.Value.threshold = config.generateReactiveParameters.cutoffThreshold; |
|||
_reactiveMaskConstants.Value.binaryValue = config.generateReactiveParameters.binaryValue; |
|||
_reactiveMaskConstants.Value.flags = (uint)config.generateReactiveParameters.flags; |
|||
_reactiveMaskConstants.UpdateBufferData(cmd); |
|||
|
|||
int kernelIndex = shader.FindKernel("CS"); |
|||
cmd.SetComputeTextureParam(shader, kernelIndex, Fsr2ShaderIDs.SrvOpaqueOnly, config.ColorOpaqueOnly); |
|||
cmd.SetComputeTextureParam(shader, kernelIndex, Fsr2ShaderIDs.SrvInputColor, context.source); |
|||
cmd.SetComputeTextureParam(shader, kernelIndex, Fsr2ShaderIDs.UavAutoReactive, _reactiveMask); |
|||
|
|||
cmd.SetComputeConstantBufferParam(shader, Fsr2ShaderIDs.CbGenReactive, _reactiveMaskConstants, 0, Marshal.SizeOf<GenerateReactiveConstants>()); |
|||
|
|||
cmd.DispatchCompute(shader, kernelIndex, dispatchX, dispatchY, 1); |
|||
|
|||
cmd.EndSample("Generate Reactive Mask"); |
|||
return _reactiveMask; |
|||
} |
|||
|
|||
[Serializable, StructLayout(LayoutKind.Sequential)] |
|||
internal struct GenerateReactiveConstants |
|||
{ |
|||
public float scale; |
|||
public float threshold; |
|||
public float binaryValue; |
|||
public uint flags; |
|||
} |
|||
|
|||
private ConstantsBuffer<CasConstants> _sharpeningConstants; |
|||
private static readonly int CasInputColor = Shader.PropertyToID("r_input_color"); |
|||
private static readonly int CasOutputColor = Shader.PropertyToID("rw_output_color"); |
|||
private static readonly int CasConstantBuffer = Shader.PropertyToID("cbCAS"); |
|||
|
|||
/// <summary>
|
|||
/// Generalized standalone version of the CAS sharpening filter that can be applied after any non-FSR upscaling technique.
|
|||
/// </summary>
|
|||
protected void ApplySharpening(CommandBuffer cmd, PostProcessRenderContext context, in Vector2Int imageSize, float sharpness, RenderTargetIdentifier input, RenderTargetIdentifier output) |
|||
{ |
|||
ComputeShader shader = context.resources.computeShaders.casSharpening; |
|||
if (shader == null) |
|||
{ |
|||
cmd.CopyTexture(input, output); |
|||
return; |
|||
} |
|||
|
|||
const int threadGroupWorkRegionDimRcas = 16; |
|||
int threadGroupsX = (imageSize.x + threadGroupWorkRegionDimRcas - 1) / threadGroupWorkRegionDimRcas; |
|||
int threadGroupsY = (imageSize.y + threadGroupWorkRegionDimRcas - 1) / threadGroupWorkRegionDimRcas; |
|||
|
|||
cmd.BeginSample("CAS Sharpening"); |
|||
|
|||
// Compute the constants
|
|||
_sharpeningConstants ??= ConstantsBuffer<CasConstants>.Create(); |
|||
int sharpnessIndex = Mathf.RoundToInt(Mathf.Clamp01(sharpness) * (CasConfigs.Length - 1)); |
|||
_sharpeningConstants.Value = CasConfigs[sharpnessIndex]; |
|||
_sharpeningConstants.UpdateBufferData(cmd); |
|||
|
|||
// Dispatch CAS
|
|||
int kernelIndex = shader.FindKernel("CS"); |
|||
cmd.SetComputeTextureParam(shader, kernelIndex, CasInputColor, input); |
|||
cmd.SetComputeTextureParam(shader, kernelIndex, CasOutputColor, output); |
|||
|
|||
cmd.SetComputeConstantBufferParam(shader, CasConstantBuffer, _sharpeningConstants, 0, Marshal.SizeOf<CasConstants>()); |
|||
|
|||
cmd.DispatchCompute(shader, kernelIndex, threadGroupsX, threadGroupsY, 1); |
|||
|
|||
cmd.EndSample("CAS Sharpening"); |
|||
} |
|||
|
|||
[Serializable, StructLayout(LayoutKind.Sequential)] |
|||
private struct CasConstants |
|||
{ |
|||
public CasConstants(uint sharpness, uint halfSharp) |
|||
{ |
|||
// Since we don't use CAS for scaling, most of these values end up being constants
|
|||
scaling0 = scaling1 = 1065353216; |
|||
scaling2 = scaling3 = 0; |
|||
sharpness0 = sharpness; |
|||
sharpness1 = halfSharp; |
|||
sharpness2 = 1090519040; |
|||
sharpness3 = 0; |
|||
} |
|||
|
|||
public readonly uint scaling0; |
|||
public readonly uint scaling1; |
|||
public readonly uint scaling2; |
|||
public readonly uint scaling3; |
|||
public readonly uint sharpness0; |
|||
public readonly uint sharpness1; |
|||
public readonly uint sharpness2; |
|||
public readonly uint sharpness3; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The FidelityFX C++ codebase uses floats bitwise converted to ints to pass sharpness parameters to the CAS shader.
|
|||
/// This is not possible in C# without enabling unsafe code compilation, so to avoid that we instead use a table of precomputed values.
|
|||
/// </summary>
|
|||
private static readonly CasConstants[] CasConfigs = |
|||
{ |
|||
new(3187671040u, 45056u), |
|||
new(3187831332u, 45075u), |
|||
new(3187997869u, 45095u), |
|||
new(3188171023u, 45117u), |
|||
new(3188351197u, 45139u), |
|||
new(3188538827u, 45161u), |
|||
new(3188734385u, 45185u), |
|||
new(3188938384u, 45210u), |
|||
new(3189151382u, 45236u), |
|||
new(3189373991u, 45263u), |
|||
new(3189606873u, 45292u), |
|||
new(3189850757u, 45322u), |
|||
new(3190106443u, 45353u), |
|||
new(3190374807u, 45386u), |
|||
new(3190656816u, 45420u), |
|||
new(3190953540u, 45456u), |
|||
new(3191266159u, 45494u), |
|||
new(3191595985u, 45535u), |
|||
new(3191944482u, 45577u), |
|||
new(3192313280u, 45622u), |
|||
new(3192704205u, 45670u), |
|||
}; |
|||
|
|||
protected bool CreateRenderTexture(ref RenderTexture rt, string name, in Vector2Int size, GraphicsFormat format, bool enableRandomWrite = false) |
|||
{ |
|||
rt = new RenderTexture(size.x, size.y, 0, format) { name = name, enableRandomWrite = enableRandomWrite }; |
|||
return rt.Create(); |
|||
} |
|||
|
|||
protected bool CreateRenderTexture(ref RenderTexture rt, string name, in Vector2Int size, RenderTextureFormat format, bool enableRandomWrite = false) |
|||
{ |
|||
rt = new RenderTexture(size.x, size.y, 0, format) { name = name, enableRandomWrite = enableRandomWrite }; |
|||
return rt.Create(); |
|||
} |
|||
|
|||
protected void DestroyRenderTexture(ref RenderTexture rt) |
|||
{ |
|||
if (rt == null) |
|||
return; |
|||
|
|||
rt.Release(); |
|||
rt = null; |
|||
} |
|||
|
|||
protected void DestroyConstantsBuffer<TConst>(ref ConstantsBuffer<TConst> cb) |
|||
where TConst: struct |
|||
{ |
|||
if (cb == null) |
|||
return; |
|||
|
|||
cb.Destroy(); |
|||
cb = null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
fileFormatVersion: 2 |
|||
guid: fd3ec2f422404d659f599fbfaf38dcd7 |
|||
timeCreated: 1729512050 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue