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.
293 lines
14 KiB
293 lines
14 KiB
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace FidelityFX
|
|
{
|
|
/// <summary>
|
|
/// This class is responsible for hooking into various Unity events and translating them to the FSR2 subsystem.
|
|
/// This includes creation and destruction of the FSR2 context, as well as dispatching commands at the right time.
|
|
/// This component also exposes various FSR2 parameters to the Unity inspector.
|
|
/// </summary>
|
|
[RequireComponent(typeof(Camera))]
|
|
public class Fsr2ImageEffect : MonoBehaviour
|
|
{
|
|
public Fsr2.QualityMode qualityMode = Fsr2.QualityMode.Quality;
|
|
|
|
public bool performSharpenPass = true;
|
|
[Range(0, 1)] public float sharpness = 0.8f;
|
|
|
|
[Tooltip("Allow the use of half precision compute operations, potentially improving performance")]
|
|
public bool enableFP16 = false;
|
|
|
|
[Header("Exposure")]
|
|
public bool enableAutoExposure = true;
|
|
public float preExposure = 1.0f;
|
|
public Texture exposure = null;
|
|
|
|
[Header("Reactivity, Transparency & Composition")]
|
|
public Texture reactiveMask = null;
|
|
public Texture transparencyAndCompositionMask = null;
|
|
public bool autoGenerateReactiveMask = true;
|
|
[SerializeField] private GenerateReactiveParameters generateReactiveParameters = new GenerateReactiveParameters();
|
|
public GenerateReactiveParameters GenerateReactiveParams => generateReactiveParameters;
|
|
|
|
[System.Serializable]
|
|
public class GenerateReactiveParameters
|
|
{
|
|
[Range(0, 2)] public float scale = 1.0f;
|
|
[Range(0, 1)] public float cutoffThreshold = 0.2f;
|
|
[Range(0, 1)] public float binaryValue = 0.9f;
|
|
public Fsr2.GenerateReactiveFlags flags = Fsr2.GenerateReactiveFlags.ApplyTonemap | Fsr2.GenerateReactiveFlags.ApplyThreshold | Fsr2.GenerateReactiveFlags.UseComponentsMax;
|
|
}
|
|
|
|
private Fsr2Context _context;
|
|
private Vector2Int _renderSize;
|
|
private Vector2Int _displaySize;
|
|
private bool _reset;
|
|
|
|
private Camera _renderCamera;
|
|
private RenderTexture _originalRenderTarget;
|
|
private DepthTextureMode _originalDepthTextureMode;
|
|
private Rect _originalRect;
|
|
|
|
private readonly Fsr2.DispatchDescription _dispatchDescription = new Fsr2.DispatchDescription();
|
|
private readonly Fsr2.GenerateReactiveDescription _genReactiveDescription = new Fsr2.GenerateReactiveDescription();
|
|
|
|
private Fsr2.QualityMode _prevQualityMode;
|
|
private Vector2Int _prevDisplaySize;
|
|
private bool _prevGenReactiveMask;
|
|
|
|
private CommandBuffer _dispatchCommandBuffer;
|
|
private CommandBuffer _opaqueInputCommandBuffer;
|
|
private CommandBuffer _inputsCommandBuffer;
|
|
|
|
private RenderTextureFormat DefaultFormat => _renderCamera.allowHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
|
|
|
|
private void OnEnable()
|
|
{
|
|
// Set up the original camera to output all of the required FSR2 input resources at the desired resolution
|
|
_renderCamera = GetComponent<Camera>();
|
|
_originalDepthTextureMode = _renderCamera.depthTextureMode;
|
|
_renderCamera.depthTextureMode = _originalDepthTextureMode | DepthTextureMode.Depth | DepthTextureMode.MotionVectors;
|
|
|
|
// Initialize FSR2 context
|
|
Fsr2.InitializationFlags flags = 0;
|
|
if (_renderCamera.allowHDR) flags |= Fsr2.InitializationFlags.EnableHighDynamicRange;
|
|
if (enableFP16) flags |= Fsr2.InitializationFlags.EnableFP16Usage;
|
|
if (enableAutoExposure) flags |= Fsr2.InitializationFlags.EnableAutoExposure;
|
|
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
flags |= Fsr2.InitializationFlags.EnableDebugChecking;
|
|
#endif
|
|
|
|
_displaySize = new Vector2Int(_renderCamera.pixelWidth, _renderCamera.pixelHeight);
|
|
Fsr2.GetRenderResolutionFromQualityMode(out var renderWidth, out var renderHeight, _displaySize.x, _displaySize.y, qualityMode);
|
|
_renderSize = new Vector2Int(renderWidth, renderHeight);
|
|
_context = Fsr2.CreateContext(_displaySize, _renderSize, flags);
|
|
|
|
_dispatchCommandBuffer = new CommandBuffer { name = "FSR2 Dispatch" };
|
|
|
|
// Create command buffers to bind the camera's output at the right moments in the render loop
|
|
_opaqueInputCommandBuffer = new CommandBuffer { name = "FSR2 Opaque Input" };
|
|
_opaqueInputCommandBuffer.GetTemporaryRT(Fsr2Pipeline.SrvOpaqueOnly, _renderSize.x, _renderSize.y, 0, default, DefaultFormat);
|
|
_opaqueInputCommandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, Fsr2Pipeline.SrvOpaqueOnly);
|
|
|
|
_inputsCommandBuffer = new CommandBuffer { name = "FSR2 Inputs" };
|
|
_inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputColor, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Color);
|
|
_inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputDepth, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Depth);
|
|
_inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputMotionVectors, BuiltinRenderTextureType.MotionVectors);
|
|
_renderCamera.AddCommandBuffer(CameraEvent.BeforeImageEffects, _inputsCommandBuffer);
|
|
|
|
if (autoGenerateReactiveMask)
|
|
{
|
|
_renderCamera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _opaqueInputCommandBuffer);
|
|
}
|
|
|
|
// Apply a mipmap bias so that textures retain their original sharpness
|
|
float biasOffset = Fsr2.GetMipmapBiasOffset(_renderSize.x, _displaySize.x);
|
|
Fsr2.GlobalCallbacks.ApplyMipmapBias(biasOffset);
|
|
|
|
_prevDisplaySize = _displaySize;
|
|
_prevQualityMode = qualityMode;
|
|
_prevGenReactiveMask = autoGenerateReactiveMask;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
float biasOffset = Fsr2.GetMipmapBiasOffset(_renderSize.x, _prevDisplaySize.x);
|
|
Fsr2.GlobalCallbacks.ApplyMipmapBias(-biasOffset);
|
|
|
|
if (_opaqueInputCommandBuffer != null)
|
|
{
|
|
_renderCamera.RemoveCommandBuffer(CameraEvent.BeforeForwardAlpha, _opaqueInputCommandBuffer);
|
|
_opaqueInputCommandBuffer.Release();
|
|
_opaqueInputCommandBuffer = null;
|
|
}
|
|
|
|
if (_inputsCommandBuffer != null)
|
|
{
|
|
_renderCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, _inputsCommandBuffer);
|
|
_inputsCommandBuffer.Release();
|
|
_inputsCommandBuffer = null;
|
|
}
|
|
|
|
if (_dispatchCommandBuffer != null)
|
|
{
|
|
_dispatchCommandBuffer.Release();
|
|
_dispatchCommandBuffer = null;
|
|
}
|
|
|
|
_renderCamera.depthTextureMode = _originalDepthTextureMode;
|
|
|
|
if (_context != null)
|
|
{
|
|
_context.Destroy();
|
|
_context = null;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_renderCamera.pixelWidth != _prevDisplaySize.x || _renderCamera.pixelHeight != _prevDisplaySize.y || qualityMode != _prevQualityMode)
|
|
{
|
|
// Force all resources to be destroyed and recreated with the new settings
|
|
OnDisable();
|
|
OnEnable();
|
|
}
|
|
|
|
if (autoGenerateReactiveMask != _prevGenReactiveMask)
|
|
{
|
|
if (autoGenerateReactiveMask)
|
|
_renderCamera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _opaqueInputCommandBuffer);
|
|
else
|
|
_renderCamera.RemoveCommandBuffer(CameraEvent.BeforeForwardAlpha, _opaqueInputCommandBuffer);
|
|
|
|
_prevGenReactiveMask = autoGenerateReactiveMask;
|
|
}
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
_reset = true;
|
|
}
|
|
|
|
private void OnPreCull()
|
|
{
|
|
// Render to a smaller portion of the screen by manipulating the camera's viewport rect
|
|
_originalRect = _renderCamera.rect;
|
|
_renderCamera.aspect = (_renderCamera.pixelWidth * _originalRect.width) / (_renderCamera.pixelHeight * _originalRect.height);
|
|
_renderCamera.rect = new Rect(0, 0, _originalRect.width * _renderSize.x / _displaySize.x, _originalRect.height * _renderSize.y / _displaySize.y);
|
|
|
|
// Set up the parameters to auto-generate a reactive mask
|
|
if (autoGenerateReactiveMask)
|
|
{
|
|
_genReactiveDescription.ColorOpaqueOnly = null;
|
|
_genReactiveDescription.ColorPreUpscale = null;
|
|
_genReactiveDescription.OutReactive = null;
|
|
_genReactiveDescription.RenderSize = _renderSize;
|
|
_genReactiveDescription.Scale = generateReactiveParameters.scale;
|
|
_genReactiveDescription.CutoffThreshold = generateReactiveParameters.cutoffThreshold;
|
|
_genReactiveDescription.BinaryValue = generateReactiveParameters.binaryValue;
|
|
_genReactiveDescription.Flags = generateReactiveParameters.flags;
|
|
}
|
|
|
|
// Set up the main FSR2 dispatch parameters
|
|
// The input and output textures are left blank here, as they are already being bound elsewhere in this source file
|
|
_dispatchDescription.Color = null;
|
|
_dispatchDescription.Depth = null;
|
|
_dispatchDescription.MotionVectors = null;
|
|
_dispatchDescription.Exposure = null;
|
|
_dispatchDescription.Reactive = null;
|
|
_dispatchDescription.TransparencyAndComposition = null;
|
|
|
|
if (!enableAutoExposure && exposure != null) _dispatchDescription.Exposure = exposure;
|
|
if (reactiveMask != null) _dispatchDescription.Reactive = reactiveMask;
|
|
if (transparencyAndCompositionMask != null) _dispatchDescription.TransparencyAndComposition = transparencyAndCompositionMask;
|
|
|
|
_dispatchDescription.Output = null;
|
|
_dispatchDescription.PreExposure = preExposure;
|
|
_dispatchDescription.EnableSharpening = performSharpenPass;
|
|
_dispatchDescription.Sharpness = sharpness;
|
|
_dispatchDescription.MotionVectorScale.x = -_renderCamera.pixelWidth;
|
|
_dispatchDescription.MotionVectorScale.y = -_renderCamera.pixelHeight;
|
|
_dispatchDescription.RenderSize = _renderSize;
|
|
_dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime;
|
|
_dispatchDescription.CameraNear = _renderCamera.nearClipPlane;
|
|
_dispatchDescription.CameraFar = _renderCamera.farClipPlane;
|
|
_dispatchDescription.CameraFovAngleVertical = _renderCamera.fieldOfView * Mathf.Deg2Rad;
|
|
_dispatchDescription.ViewSpaceToMetersFactor = 1.0f; // 1 unit is 1 meter in Unity
|
|
_dispatchDescription.Reset = _reset;
|
|
_reset = false;
|
|
|
|
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);
|
|
}
|
|
|
|
// Perform custom jittering of the camera's projection matrix according to FSR2's recipe
|
|
int jitterPhaseCount = Fsr2.GetJitterPhaseCount(_renderSize.x, _displaySize.x);
|
|
Fsr2.GetJitterOffset(out float jitterX, out float jitterY, Time.frameCount, jitterPhaseCount);
|
|
|
|
_dispatchDescription.JitterOffset = new Vector2(jitterX, jitterY);
|
|
|
|
jitterX = 2.0f * jitterX / _renderSize.x;
|
|
jitterY = 2.0f * jitterY / _renderSize.y;
|
|
|
|
var jitterTranslationMatrix = Matrix4x4.Translate(new Vector3(jitterX, jitterY, 0));
|
|
_renderCamera.nonJitteredProjectionMatrix = _renderCamera.projectionMatrix;
|
|
_renderCamera.projectionMatrix = jitterTranslationMatrix * _renderCamera.nonJitteredProjectionMatrix;
|
|
_renderCamera.useJitteredProjectionMatrixForTransparentRendering = true;
|
|
}
|
|
|
|
private void OnRenderImage(RenderTexture src, RenderTexture dest)
|
|
{
|
|
// Restore the camera's viewport rect so we can output at full resolution
|
|
_renderCamera.rect = _originalRect;
|
|
_renderCamera.ResetProjectionMatrix();
|
|
|
|
if (dest != null)
|
|
{
|
|
Debug.LogError("FSR2 is not set to output directly to the backbuffer! Please ensure that FSR2 is the final pass in the image effects chain.");
|
|
Graphics.Blit(src, dest);
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
_dispatchCommandBuffer.Clear();
|
|
|
|
// Update the input resource descriptions
|
|
_dispatchDescription.InputResourceSize = new Vector2Int(src.width, src.height);
|
|
|
|
if (autoGenerateReactiveMask)
|
|
{
|
|
_dispatchCommandBuffer.GetTemporaryRT(Fsr2Pipeline.UavAutoReactive, _renderSize.x, _renderSize.y, 0, default, GraphicsFormat.R8_UNorm, 1, true);
|
|
_context.GenerateReactiveMask(_genReactiveDescription, _dispatchCommandBuffer);
|
|
_dispatchCommandBuffer.ReleaseTemporaryRT(Fsr2Pipeline.SrvOpaqueOnly);
|
|
|
|
_dispatchDescription.Reactive = Fsr2Pipeline.UavAutoReactive;
|
|
}
|
|
|
|
// We are rendering to the backbuffer, so we need a temporary render texture for FSR2 to output to
|
|
_dispatchCommandBuffer.GetTemporaryRT(Fsr2Pipeline.UavUpscaledOutput, _displaySize.x, _displaySize.y, 0, default, DefaultFormat, default, 1, true);
|
|
|
|
_context.Dispatch(_dispatchDescription, _dispatchCommandBuffer);
|
|
|
|
// Output the upscaled image to the backbuffer
|
|
_dispatchCommandBuffer.Blit(Fsr2Pipeline.UavUpscaledOutput, dest);
|
|
_dispatchCommandBuffer.ReleaseTemporaryRT(Fsr2Pipeline.UavUpscaledOutput);
|
|
|
|
if (autoGenerateReactiveMask)
|
|
{
|
|
_dispatchCommandBuffer.ReleaseTemporaryRT(Fsr2Pipeline.UavAutoReactive);
|
|
}
|
|
|
|
Graphics.ExecuteCommandBuffer(_dispatchCommandBuffer);
|
|
|
|
// Shut up the Unity warning about not writing to the destination texture
|
|
RenderTexture.active = dest;
|
|
}
|
|
}
|
|
}
|