FiE-Game/Assets/PostProcessing/Runtime/Components/MotionBlurComponent.cs

445 lines
19 KiB
C#
Raw Normal View History

2023-07-24 21:52:50 +02:00
using UnityEngine.Rendering;
namespace UnityEngine.PostProcessing
{
using Settings = MotionBlurModel.Settings;
public sealed class MotionBlurComponent : PostProcessingComponentCommandBuffer<MotionBlurModel>
{
static class Uniforms
{
internal static readonly int _VelocityScale = Shader.PropertyToID("_VelocityScale");
internal static readonly int _MaxBlurRadius = Shader.PropertyToID("_MaxBlurRadius");
internal static readonly int _RcpMaxBlurRadius = Shader.PropertyToID("_RcpMaxBlurRadius");
internal static readonly int _VelocityTex = Shader.PropertyToID("_VelocityTex");
internal static readonly int _MainTex = Shader.PropertyToID("_MainTex");
internal static readonly int _Tile2RT = Shader.PropertyToID("_Tile2RT");
internal static readonly int _Tile4RT = Shader.PropertyToID("_Tile4RT");
internal static readonly int _Tile8RT = Shader.PropertyToID("_Tile8RT");
internal static readonly int _TileMaxOffs = Shader.PropertyToID("_TileMaxOffs");
internal static readonly int _TileMaxLoop = Shader.PropertyToID("_TileMaxLoop");
internal static readonly int _TileVRT = Shader.PropertyToID("_TileVRT");
internal static readonly int _NeighborMaxTex = Shader.PropertyToID("_NeighborMaxTex");
internal static readonly int _LoopCount = Shader.PropertyToID("_LoopCount");
internal static readonly int _TempRT = Shader.PropertyToID("_TempRT");
internal static readonly int _History1LumaTex = Shader.PropertyToID("_History1LumaTex");
internal static readonly int _History2LumaTex = Shader.PropertyToID("_History2LumaTex");
internal static readonly int _History3LumaTex = Shader.PropertyToID("_History3LumaTex");
internal static readonly int _History4LumaTex = Shader.PropertyToID("_History4LumaTex");
internal static readonly int _History1ChromaTex = Shader.PropertyToID("_History1ChromaTex");
internal static readonly int _History2ChromaTex = Shader.PropertyToID("_History2ChromaTex");
internal static readonly int _History3ChromaTex = Shader.PropertyToID("_History3ChromaTex");
internal static readonly int _History4ChromaTex = Shader.PropertyToID("_History4ChromaTex");
internal static readonly int _History1Weight = Shader.PropertyToID("_History1Weight");
internal static readonly int _History2Weight = Shader.PropertyToID("_History2Weight");
internal static readonly int _History3Weight = Shader.PropertyToID("_History3Weight");
internal static readonly int _History4Weight = Shader.PropertyToID("_History4Weight");
}
enum Pass
{
VelocitySetup,
TileMax1,
TileMax2,
TileMaxV,
NeighborMax,
Reconstruction,
FrameCompression,
FrameBlendingChroma,
FrameBlendingRaw
}
public class ReconstructionFilter
{
// Texture format for storing 2D vectors.
RenderTextureFormat m_VectorRTFormat = RenderTextureFormat.RGHalf;
// Texture format for storing packed velocity/depth.
RenderTextureFormat m_PackedRTFormat = RenderTextureFormat.ARGB2101010;
public ReconstructionFilter()
{
CheckTextureFormatSupport();
}
void CheckTextureFormatSupport()
{
// If 2:10:10:10 isn't supported, use ARGB32 instead.
if (!SystemInfo.SupportsRenderTextureFormat(m_PackedRTFormat))
m_PackedRTFormat = RenderTextureFormat.ARGB32;
}
public bool IsSupported()
{
return SystemInfo.supportsMotionVectors;
}
public void ProcessImage(PostProcessingContext context, CommandBuffer cb, ref Settings settings, RenderTargetIdentifier source, RenderTargetIdentifier destination, Material material)
{
const float kMaxBlurRadius = 5f;
// Calculate the maximum blur radius in pixels.
int maxBlurPixels = (int)(kMaxBlurRadius * context.height / 100);
// Calculate the TileMax size.
// It should be a multiple of 8 and larger than maxBlur.
int tileSize = ((maxBlurPixels - 1) / 8 + 1) * 8;
// Pass 1 - Velocity/depth packing
var velocityScale = settings.shutterAngle / 360f;
cb.SetGlobalFloat(Uniforms._VelocityScale, velocityScale);
cb.SetGlobalFloat(Uniforms._MaxBlurRadius, maxBlurPixels);
cb.SetGlobalFloat(Uniforms._RcpMaxBlurRadius, 1f / maxBlurPixels);
int vbuffer = Uniforms._VelocityTex;
cb.GetTemporaryRT(vbuffer, context.width, context.height, 0, FilterMode.Point, m_PackedRTFormat, RenderTextureReadWrite.Linear);
cb.Blit((Texture)null, vbuffer, material, (int)Pass.VelocitySetup);
// Pass 2 - First TileMax filter (1/2 downsize)
int tile2 = Uniforms._Tile2RT;
cb.GetTemporaryRT(tile2, context.width / 2, context.height / 2, 0, FilterMode.Point, m_VectorRTFormat, RenderTextureReadWrite.Linear);
cb.SetGlobalTexture(Uniforms._MainTex, vbuffer);
cb.Blit(vbuffer, tile2, material, (int)Pass.TileMax1);
// Pass 3 - Second TileMax filter (1/2 downsize)
int tile4 = Uniforms._Tile4RT;
cb.GetTemporaryRT(tile4, context.width / 4, context.height / 4, 0, FilterMode.Point, m_VectorRTFormat, RenderTextureReadWrite.Linear);
cb.SetGlobalTexture(Uniforms._MainTex, tile2);
cb.Blit(tile2, tile4, material, (int)Pass.TileMax2);
cb.ReleaseTemporaryRT(tile2);
// Pass 4 - Third TileMax filter (1/2 downsize)
int tile8 = Uniforms._Tile8RT;
cb.GetTemporaryRT(tile8, context.width / 8, context.height / 8, 0, FilterMode.Point, m_VectorRTFormat, RenderTextureReadWrite.Linear);
cb.SetGlobalTexture(Uniforms._MainTex, tile4);
cb.Blit(tile4, tile8, material, (int)Pass.TileMax2);
cb.ReleaseTemporaryRT(tile4);
// Pass 5 - Fourth TileMax filter (reduce to tileSize)
var tileMaxOffs = Vector2.one * (tileSize / 8f - 1f) * -0.5f;
cb.SetGlobalVector(Uniforms._TileMaxOffs, tileMaxOffs);
cb.SetGlobalFloat(Uniforms._TileMaxLoop, (int)(tileSize / 8f));
int tile = Uniforms._TileVRT;
cb.GetTemporaryRT(tile, context.width / tileSize, context.height / tileSize, 0, FilterMode.Point, m_VectorRTFormat, RenderTextureReadWrite.Linear);
cb.SetGlobalTexture(Uniforms._MainTex, tile8);
cb.Blit(tile8, tile, material, (int)Pass.TileMaxV);
cb.ReleaseTemporaryRT(tile8);
// Pass 6 - NeighborMax filter
int neighborMax = Uniforms._NeighborMaxTex;
int neighborMaxWidth = context.width / tileSize;
int neighborMaxHeight = context.height / tileSize;
cb.GetTemporaryRT(neighborMax, neighborMaxWidth, neighborMaxHeight, 0, FilterMode.Point, m_VectorRTFormat, RenderTextureReadWrite.Linear);
cb.SetGlobalTexture(Uniforms._MainTex, tile);
cb.Blit(tile, neighborMax, material, (int)Pass.NeighborMax);
cb.ReleaseTemporaryRT(tile);
// Pass 7 - Reconstruction pass
cb.SetGlobalFloat(Uniforms._LoopCount, Mathf.Clamp(settings.sampleCount / 2, 1, 64));
cb.SetGlobalTexture(Uniforms._MainTex, source);
cb.Blit(source, destination, material, (int)Pass.Reconstruction);
cb.ReleaseTemporaryRT(vbuffer);
cb.ReleaseTemporaryRT(neighborMax);
}
}
public class FrameBlendingFilter
{
struct Frame
{
public RenderTexture lumaTexture;
public RenderTexture chromaTexture;
float m_Time;
RenderTargetIdentifier[] m_MRT;
public float CalculateWeight(float strength, float currentTime)
{
if (Mathf.Approximately(m_Time, 0f))
return 0f;
var coeff = Mathf.Lerp(80f, 16f, strength);
return Mathf.Exp((m_Time - currentTime) * coeff);
}
public void Release()
{
if (lumaTexture != null)
RenderTexture.ReleaseTemporary(lumaTexture);
if (chromaTexture != null)
RenderTexture.ReleaseTemporary(chromaTexture);
lumaTexture = null;
chromaTexture = null;
}
public void MakeRecord(CommandBuffer cb, RenderTargetIdentifier source, int width, int height, Material material)
{
Release();
lumaTexture = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.R8, RenderTextureReadWrite.Linear);
chromaTexture = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.R8, RenderTextureReadWrite.Linear);
lumaTexture.filterMode = FilterMode.Point;
chromaTexture.filterMode = FilterMode.Point;
if (m_MRT == null)
m_MRT = new RenderTargetIdentifier[2];
m_MRT[0] = lumaTexture;
m_MRT[1] = chromaTexture;
cb.SetGlobalTexture(Uniforms._MainTex, source);
cb.SetRenderTarget(m_MRT, lumaTexture);
cb.DrawMesh(GraphicsUtils.quad, Matrix4x4.identity, material, 0, (int)Pass.FrameCompression);
m_Time = Time.time;
}
public void MakeRecordRaw(CommandBuffer cb, RenderTargetIdentifier source, int width, int height, RenderTextureFormat format)
{
Release();
lumaTexture = RenderTexture.GetTemporary(width, height, 0, format);
lumaTexture.filterMode = FilterMode.Point;
cb.SetGlobalTexture(Uniforms._MainTex, source);
cb.Blit(source, lumaTexture);
m_Time = Time.time;
}
}
bool m_UseCompression;
RenderTextureFormat m_RawTextureFormat;
Frame[] m_FrameList;
int m_LastFrameCount;
public FrameBlendingFilter()
{
m_UseCompression = CheckSupportCompression();
m_RawTextureFormat = GetPreferredRenderTextureFormat();
m_FrameList = new Frame[4];
}
public void Dispose()
{
foreach (var frame in m_FrameList)
frame.Release();
}
public void PushFrame(CommandBuffer cb, RenderTargetIdentifier source, int width, int height, Material material)
{
// Push only when actual update (do nothing while pausing)
var frameCount = Time.frameCount;
if (frameCount == m_LastFrameCount) return;
// Update the frame record.
var index = frameCount % m_FrameList.Length;
if (m_UseCompression)
m_FrameList[index].MakeRecord(cb, source, width, height, material);
else
m_FrameList[index].MakeRecordRaw(cb, source, width, height, m_RawTextureFormat);
m_LastFrameCount = frameCount;
}
public void BlendFrames(CommandBuffer cb, float strength, RenderTargetIdentifier source, RenderTargetIdentifier destination, Material material)
{
var t = Time.time;
var f1 = GetFrameRelative(-1);
var f2 = GetFrameRelative(-2);
var f3 = GetFrameRelative(-3);
var f4 = GetFrameRelative(-4);
cb.SetGlobalTexture(Uniforms._History1LumaTex, f1.lumaTexture);
cb.SetGlobalTexture(Uniforms._History2LumaTex, f2.lumaTexture);
cb.SetGlobalTexture(Uniforms._History3LumaTex, f3.lumaTexture);
cb.SetGlobalTexture(Uniforms._History4LumaTex, f4.lumaTexture);
cb.SetGlobalTexture(Uniforms._History1ChromaTex, f1.chromaTexture);
cb.SetGlobalTexture(Uniforms._History2ChromaTex, f2.chromaTexture);
cb.SetGlobalTexture(Uniforms._History3ChromaTex, f3.chromaTexture);
cb.SetGlobalTexture(Uniforms._History4ChromaTex, f4.chromaTexture);
cb.SetGlobalFloat(Uniforms._History1Weight, f1.CalculateWeight(strength, t));
cb.SetGlobalFloat(Uniforms._History2Weight, f2.CalculateWeight(strength, t));
cb.SetGlobalFloat(Uniforms._History3Weight, f3.CalculateWeight(strength, t));
cb.SetGlobalFloat(Uniforms._History4Weight, f4.CalculateWeight(strength, t));
cb.SetGlobalTexture(Uniforms._MainTex, source);
cb.Blit(source, destination, material, m_UseCompression ? (int)Pass.FrameBlendingChroma : (int)Pass.FrameBlendingRaw);
}
// Check if the platform has the capability of compression.
static bool CheckSupportCompression()
{
return
SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.R8) &&
SystemInfo.supportedRenderTargetCount > 1;
}
// Determine which 16-bit render texture format is available.
static RenderTextureFormat GetPreferredRenderTextureFormat()
{
RenderTextureFormat[] formats =
{
RenderTextureFormat.RGB565,
RenderTextureFormat.ARGB1555,
RenderTextureFormat.ARGB4444
};
foreach (var f in formats)
if (SystemInfo.SupportsRenderTextureFormat(f)) return f;
return RenderTextureFormat.Default;
}
// Retrieve a frame record with relative indexing.
// Use a negative index to refer to previous frames.
Frame GetFrameRelative(int offset)
{
var index = (Time.frameCount + m_FrameList.Length + offset) % m_FrameList.Length;
return m_FrameList[index];
}
}
ReconstructionFilter m_ReconstructionFilter;
public ReconstructionFilter reconstructionFilter
{
get
{
if (m_ReconstructionFilter == null)
m_ReconstructionFilter = new ReconstructionFilter();
return m_ReconstructionFilter;
}
}
FrameBlendingFilter m_FrameBlendingFilter;
public FrameBlendingFilter frameBlendingFilter
{
get
{
if (m_FrameBlendingFilter == null)
m_FrameBlendingFilter = new FrameBlendingFilter();
return m_FrameBlendingFilter;
}
}
int m_FrameCount = 0;
public override bool active
{
get
{
var settings = model.settings;
return model.enabled
&& ((settings.shutterAngle > 0f && reconstructionFilter.IsSupported()) || settings.frameBlending > 0f)
&& SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2 // No movecs on GLES2 platforms
&& !context.interrupted;
}
}
public override string GetName()
{
return "Motion Blur";
}
public void ResetHistory()
{
if (m_FrameBlendingFilter != null)
m_FrameBlendingFilter.Dispose();
m_FrameBlendingFilter = null;
m_FrameCount = 0;
}
public override DepthTextureMode GetCameraFlags()
{
return DepthTextureMode.Depth | DepthTextureMode.MotionVectors;
}
public override CameraEvent GetCameraEvent()
{
return CameraEvent.BeforeImageEffects;
}
public override void OnEnable()
{
m_FrameCount = 0;
}
public override void PopulateCommandBuffer(CommandBuffer cb)
{
#if UNITY_EDITOR
// Don't render motion blur preview when the editor is not playing as it can in some
// cases results in ugly artifacts (i.e. when resizing the game view).
if (!Application.isPlaying)
return;
#endif
// Skip rendering in the first 2 frames - workaround for a weird repaint issue in editor
if (m_FrameCount < 2)
{
m_FrameCount++;
return;
}
var material = context.materialFactory.Get("Hidden/Post FX/Motion Blur");
var blitMaterial = context.materialFactory.Get("Hidden/Post FX/Blit");
var settings = model.settings;
var fbFormat = context.isHdr
? RenderTextureFormat.DefaultHDR
: RenderTextureFormat.Default;
int tempRT = Uniforms._TempRT;
cb.GetTemporaryRT(tempRT, context.width, context.height, 0, FilterMode.Point, fbFormat);
if (settings.shutterAngle > 0f && settings.frameBlending > 0f)
{
// Motion blur + frame blending
reconstructionFilter.ProcessImage(context, cb, ref settings, BuiltinRenderTextureType.CameraTarget, tempRT, material);
frameBlendingFilter.BlendFrames(cb, settings.frameBlending, tempRT, BuiltinRenderTextureType.CameraTarget, material);
frameBlendingFilter.PushFrame(cb, tempRT, context.width, context.height, material);
}
else if (settings.shutterAngle > 0f)
{
// No frame blending
cb.SetGlobalTexture(Uniforms._MainTex, BuiltinRenderTextureType.CameraTarget);
cb.Blit(BuiltinRenderTextureType.CameraTarget, tempRT, blitMaterial, 0);
reconstructionFilter.ProcessImage(context, cb, ref settings, tempRT, BuiltinRenderTextureType.CameraTarget, material);
}
else if (settings.frameBlending > 0f)
{
// Frame blending only
cb.SetGlobalTexture(Uniforms._MainTex, BuiltinRenderTextureType.CameraTarget);
cb.Blit(BuiltinRenderTextureType.CameraTarget, tempRT, blitMaterial, 0);
frameBlendingFilter.BlendFrames(cb, settings.frameBlending, tempRT, BuiltinRenderTextureType.CameraTarget, material);
frameBlendingFilter.PushFrame(cb, tempRT, context.width, context.height, material);
}
// Cleaning up
cb.ReleaseTemporaryRT(tempRT);
}
public override void OnDisable()
{
if (m_FrameBlendingFilter != null)
m_FrameBlendingFilter.Dispose();
}
}
}