290 lines
12 KiB
C#
290 lines
12 KiB
C#
|
using System;
|
||
|
|
||
|
namespace UnityEngine.Rendering.PostProcessing
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// This class holds settings for the Temporal Anti-aliasing (TAA) effect.
|
||
|
/// </summary>
|
||
|
[UnityEngine.Scripting.Preserve]
|
||
|
[Serializable]
|
||
|
public sealed class TemporalAntialiasing
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The diameter (in texels) inside which jitter samples are spread. Smaller values result
|
||
|
/// in crisper but more aliased output, while larger values result in more stable but
|
||
|
/// blurrier output.
|
||
|
/// </summary>
|
||
|
[Tooltip("The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable, but blurrier, output.")]
|
||
|
[Range(0.1f, 1f)]
|
||
|
public float jitterSpread = 0.75f;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Controls the amount of sharpening applied to the color buffer. High values may introduce
|
||
|
/// dark-border artifacts.
|
||
|
/// </summary>
|
||
|
[Tooltip("Controls the amount of sharpening applied to the color buffer. High values may introduce dark-border artifacts.")]
|
||
|
[Range(0f, 3f)]
|
||
|
public float sharpness = 0.25f;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The blend coefficient for a stationary fragment. Controls the percentage of history
|
||
|
/// sample blended into the final color.
|
||
|
/// </summary>
|
||
|
[Tooltip("The blend coefficient for a stationary fragment. Controls the percentage of history sample blended into the final color.")]
|
||
|
[Range(0f, 0.99f)]
|
||
|
public float stationaryBlending = 0.95f;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The blend coefficient for a fragment with significant motion. Controls the percentage of
|
||
|
/// history sample blended into the final color.
|
||
|
/// </summary>
|
||
|
[Tooltip("The blend coefficient for a fragment with significant motion. Controls the percentage of history sample blended into the final color.")]
|
||
|
[Range(0f, 0.99f)]
|
||
|
public float motionBlending = 0.85f;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets a custom function that will be called to generate the jittered projection matrice.
|
||
|
/// </summary>
|
||
|
public Func<Camera, Vector2, Matrix4x4> jitteredMatrixFunc;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current jitter amount
|
||
|
/// </summary>
|
||
|
public Vector2 jitter { get; private set; }
|
||
|
|
||
|
enum Pass
|
||
|
{
|
||
|
SolverDilate,
|
||
|
SolverNoDilate
|
||
|
}
|
||
|
|
||
|
readonly RenderTargetIdentifier[] m_Mrt = new RenderTargetIdentifier[2];
|
||
|
bool m_ResetHistory = true;
|
||
|
|
||
|
const int k_SampleCount = 8;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current sample index.
|
||
|
/// </summary>
|
||
|
public int sampleIndex { get; private set; }
|
||
|
|
||
|
// Ping-pong between two history textures as we can't read & write the same target in the
|
||
|
// same pass
|
||
|
const int k_NumEyes = 2;
|
||
|
const int k_NumHistoryTextures = 2;
|
||
|
readonly RenderTexture[][] m_HistoryTextures = new RenderTexture[k_NumEyes][];
|
||
|
|
||
|
readonly int[] m_HistoryPingPong = new int[k_NumEyes];
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns <c>true</c> if the effect is currently enabled and supported.
|
||
|
/// </summary>
|
||
|
/// <returns><c>true</c> if the effect is currently enabled and supported</returns>
|
||
|
public bool IsSupported()
|
||
|
{
|
||
|
return SystemInfo.supportedRenderTargetCount >= 2
|
||
|
&& SystemInfo.supportsMotionVectors
|
||
|
#if !UNITY_2017_3_OR_NEWER
|
||
|
&& !RuntimeUtilities.isVREnabled
|
||
|
#endif
|
||
|
&& SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
|
||
|
}
|
||
|
|
||
|
internal DepthTextureMode GetCameraFlags()
|
||
|
{
|
||
|
return DepthTextureMode.Depth | DepthTextureMode.MotionVectors;
|
||
|
}
|
||
|
|
||
|
internal void ResetHistory()
|
||
|
{
|
||
|
m_ResetHistory = true;
|
||
|
}
|
||
|
|
||
|
Vector2 GenerateRandomOffset()
|
||
|
{
|
||
|
// The variance between 0 and the actual halton sequence values reveals noticeable instability
|
||
|
// in Unity's shadow maps, so we avoid index 0.
|
||
|
var offset = new Vector2(
|
||
|
HaltonSeq.Get((sampleIndex & 1023) + 1, 2) - 0.5f,
|
||
|
HaltonSeq.Get((sampleIndex & 1023) + 1, 3) - 0.5f
|
||
|
);
|
||
|
|
||
|
if (++sampleIndex >= k_SampleCount)
|
||
|
sampleIndex = 0;
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Generates a jittered projection matrix for a given camera.
|
||
|
/// </summary>
|
||
|
/// <param name="camera">The camera to get a jittered projection matrix for.</param>
|
||
|
/// <returns>A jittered projection matrix.</returns>
|
||
|
public Matrix4x4 GetJitteredProjectionMatrix(Camera camera)
|
||
|
{
|
||
|
Matrix4x4 cameraProj;
|
||
|
jitter = GenerateRandomOffset();
|
||
|
jitter *= jitterSpread;
|
||
|
|
||
|
if (jitteredMatrixFunc != null)
|
||
|
{
|
||
|
cameraProj = jitteredMatrixFunc(camera, jitter);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cameraProj = camera.orthographic
|
||
|
? RuntimeUtilities.GetJitteredOrthographicProjectionMatrix(camera, jitter)
|
||
|
: RuntimeUtilities.GetJitteredPerspectiveProjectionMatrix(camera, jitter);
|
||
|
}
|
||
|
|
||
|
jitter = new Vector2(jitter.x / camera.pixelWidth, jitter.y / camera.pixelHeight);
|
||
|
return cameraProj;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Prepares the jittered and non jittered projection matrices.
|
||
|
/// </summary>
|
||
|
/// <param name="context">The current post-processing context.</param>
|
||
|
public void ConfigureJitteredProjectionMatrix(PostProcessRenderContext context)
|
||
|
{
|
||
|
var camera = context.camera;
|
||
|
camera.nonJitteredProjectionMatrix = camera.projectionMatrix;
|
||
|
camera.projectionMatrix = GetJitteredProjectionMatrix(camera);
|
||
|
camera.useJitteredProjectionMatrixForTransparentRendering = false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Prepares the jittered and non jittered projection matrices for stereo rendering.
|
||
|
/// </summary>
|
||
|
/// <param name="context">The current post-processing context.</param>
|
||
|
// TODO: We'll probably need to isolate most of this for SRPs
|
||
|
public void ConfigureStereoJitteredProjectionMatrices(PostProcessRenderContext context)
|
||
|
{
|
||
|
#if UNITY_2017_3_OR_NEWER
|
||
|
var camera = context.camera;
|
||
|
jitter = GenerateRandomOffset();
|
||
|
jitter *= jitterSpread;
|
||
|
|
||
|
for (var eye = Camera.StereoscopicEye.Left; eye <= Camera.StereoscopicEye.Right; eye++)
|
||
|
{
|
||
|
// This saves off the device generated projection matrices as non-jittered
|
||
|
context.camera.CopyStereoDeviceProjectionMatrixToNonJittered(eye);
|
||
|
var originalProj = context.camera.GetStereoNonJitteredProjectionMatrix(eye);
|
||
|
|
||
|
// Currently no support for custom jitter func, as VR devices would need to provide
|
||
|
// original projection matrix as input along with jitter
|
||
|
var jitteredMatrix = RuntimeUtilities.GenerateJitteredProjectionMatrixFromOriginal(context, originalProj, jitter);
|
||
|
context.camera.SetStereoProjectionMatrix(eye, jitteredMatrix);
|
||
|
}
|
||
|
|
||
|
// jitter has to be scaled for the actual eye texture size, not just the intermediate texture size
|
||
|
// which could be double-wide in certain stereo rendering scenarios
|
||
|
jitter = new Vector2(jitter.x / context.screenWidth, jitter.y / context.screenHeight);
|
||
|
camera.useJitteredProjectionMatrixForTransparentRendering = false;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void GenerateHistoryName(RenderTexture rt, int id, PostProcessRenderContext context)
|
||
|
{
|
||
|
rt.name = "Temporal Anti-aliasing History id #" + id;
|
||
|
|
||
|
if (context.stereoActive)
|
||
|
rt.name += " for eye " + context.xrActiveEye;
|
||
|
}
|
||
|
|
||
|
RenderTexture CheckHistory(int id, PostProcessRenderContext context)
|
||
|
{
|
||
|
int activeEye = context.xrActiveEye;
|
||
|
|
||
|
if (m_HistoryTextures[activeEye] == null)
|
||
|
m_HistoryTextures[activeEye] = new RenderTexture[k_NumHistoryTextures];
|
||
|
|
||
|
var rt = m_HistoryTextures[activeEye][id];
|
||
|
|
||
|
if (m_ResetHistory || rt == null || !rt.IsCreated())
|
||
|
{
|
||
|
RenderTexture.ReleaseTemporary(rt);
|
||
|
|
||
|
rt = context.GetScreenSpaceTemporaryRT(0, context.sourceFormat);
|
||
|
GenerateHistoryName(rt, id, context);
|
||
|
|
||
|
rt.filterMode = FilterMode.Bilinear;
|
||
|
m_HistoryTextures[activeEye][id] = rt;
|
||
|
|
||
|
context.command.BlitFullscreenTriangle(context.source, rt);
|
||
|
}
|
||
|
else if (rt.width != context.width || rt.height != context.height)
|
||
|
{
|
||
|
// On size change, simply copy the old history to the new one. This looks better
|
||
|
// than completely discarding the history and seeing a few aliased frames.
|
||
|
var rt2 = context.GetScreenSpaceTemporaryRT(0, context.sourceFormat);
|
||
|
GenerateHistoryName(rt2, id, context);
|
||
|
|
||
|
rt2.filterMode = FilterMode.Bilinear;
|
||
|
m_HistoryTextures[activeEye][id] = rt2;
|
||
|
|
||
|
context.command.BlitFullscreenTriangle(rt, rt2);
|
||
|
RenderTexture.ReleaseTemporary(rt);
|
||
|
}
|
||
|
|
||
|
return m_HistoryTextures[activeEye][id];
|
||
|
}
|
||
|
|
||
|
internal void Render(PostProcessRenderContext context)
|
||
|
{
|
||
|
var sheet = context.propertySheets.Get(context.resources.shaders.temporalAntialiasing);
|
||
|
|
||
|
var cmd = context.command;
|
||
|
cmd.BeginSample("TemporalAntialiasing");
|
||
|
|
||
|
int pp = m_HistoryPingPong[context.xrActiveEye];
|
||
|
var historyRead = CheckHistory(++pp % 2, context);
|
||
|
var historyWrite = CheckHistory(++pp % 2, context);
|
||
|
m_HistoryPingPong[context.xrActiveEye] = ++pp % 2;
|
||
|
|
||
|
const float kMotionAmplification = 100f * 60f;
|
||
|
sheet.properties.SetVector(ShaderIDs.Jitter, jitter);
|
||
|
sheet.properties.SetFloat(ShaderIDs.Sharpness, sharpness);
|
||
|
sheet.properties.SetVector(ShaderIDs.FinalBlendParameters, new Vector4(stationaryBlending, motionBlending, kMotionAmplification, 0f));
|
||
|
sheet.properties.SetTexture(ShaderIDs.HistoryTex, historyRead);
|
||
|
|
||
|
// TODO: Account for different possible RenderViewportScale value from previous frame...
|
||
|
|
||
|
int pass = context.camera.orthographic ? (int)Pass.SolverNoDilate : (int)Pass.SolverDilate;
|
||
|
m_Mrt[0] = context.destination;
|
||
|
m_Mrt[1] = historyWrite;
|
||
|
|
||
|
cmd.BlitFullscreenTriangle(context.source, m_Mrt, context.source, sheet, pass);
|
||
|
cmd.EndSample("TemporalAntialiasing");
|
||
|
|
||
|
m_ResetHistory = false;
|
||
|
}
|
||
|
|
||
|
internal void Release()
|
||
|
{
|
||
|
if (m_HistoryTextures != null)
|
||
|
{
|
||
|
for (int i = 0; i < m_HistoryTextures.Length; i++)
|
||
|
{
|
||
|
if (m_HistoryTextures[i] == null)
|
||
|
continue;
|
||
|
|
||
|
for (int j = 0; j < m_HistoryTextures[i].Length; j++)
|
||
|
{
|
||
|
RenderTexture.ReleaseTemporary(m_HistoryTextures[i][j]);
|
||
|
m_HistoryTextures[i][j] = null;
|
||
|
}
|
||
|
|
||
|
m_HistoryTextures[i] = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sampleIndex = 0;
|
||
|
m_HistoryPingPong[0] = 0;
|
||
|
m_HistoryPingPong[1] = 0;
|
||
|
|
||
|
ResetHistory();
|
||
|
}
|
||
|
}
|
||
|
}
|