329 lines
13 KiB
C#

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.Experimental.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif // UNITY_EDITOR
namespace UnityEngine.Rendering.HighDefinition
{
// Struct storing per-camera data, to handle accumulation and dirtiness
internal struct CameraData
{
public void ResetIteration()
{
accumulatedWeight = 0.0f;
currentIteration = 0;
}
public uint width;
public uint height;
public bool skyEnabled;
public bool fogEnabled;
public float accumulatedWeight;
public uint currentIteration;
}
// Helper class to manage time-scale in Unity when recording multi-frame sequences where one final frame is an accumulation of multiple sub-frames
internal class SubFrameManager
{
// Shutter settings
float m_ShutterInterval = 0.0f;
float m_ShutterFullyOpen = 0.0f;
float m_ShutterBeginsClosing = 1.0f;
AnimationCurve m_ShutterCurve;
// Internal state
float m_OriginalCaptureDeltaTime = 0;
float m_OriginalFixedDeltaTime = 0;
// Per-camera data cache
Dictionary<int, CameraData> m_CameraCache = new Dictionary<int, CameraData>();
internal CameraData GetCameraData(int camID)
{
CameraData camData;
if (!m_CameraCache.TryGetValue(camID, out camData))
{
camData.ResetIteration();
m_CameraCache.Add(camID, camData);
}
return camData;
}
internal void SetCameraData(int camID, CameraData camData)
{
m_CameraCache[camID] = camData;
}
// The number of sub-frames that will be used to reconstruct a converged frame
public uint subFrameCount
{
get { return m_AccumulationSamples; }
set { m_AccumulationSamples = value; }
}
uint m_AccumulationSamples = 0;
// True when a recording session is in progress
public bool isRecording
{
get { return m_IsRecording; }
}
bool m_IsRecording = false;
// Resets the sub-frame sequence
internal void Reset(int camID)
{
CameraData camData = GetCameraData(camID);
camData.ResetIteration();
SetCameraData(camID, camData);
}
internal void Reset()
{
foreach (int camID in m_CameraCache.Keys.ToList())
Reset(camID);
}
internal void Clear()
{
m_CameraCache.Clear();
}
internal void SelectiveReset(uint maxSamples)
{
foreach (int camID in m_CameraCache.Keys.ToList())
{
CameraData camData = GetCameraData(camID);
if (camData.currentIteration >= maxSamples)
{
camData.ResetIteration();
SetCameraData(camID, camData);
}
}
}
void Init(int samples, float shutterInterval)
{
m_AccumulationSamples = (uint)samples;
m_ShutterInterval = samples > 1 ? shutterInterval : 0;
m_IsRecording = true;
Clear();
m_OriginalCaptureDeltaTime = Time.captureDeltaTime;
Time.captureDeltaTime = m_OriginalCaptureDeltaTime / m_AccumulationSamples;
// This is required for physics simulations
m_OriginalFixedDeltaTime = Time.fixedDeltaTime;
Time.fixedDeltaTime = m_OriginalFixedDeltaTime / m_AccumulationSamples;
}
internal void BeginRecording(int samples, float shutterInterval, float shutterFullyOpen = 0.0f, float shutterBeginsClosing = 1.0f)
{
Init(samples, shutterInterval);
m_ShutterFullyOpen = shutterFullyOpen;
m_ShutterBeginsClosing = shutterBeginsClosing;
}
internal void BeginRecording(int samples, float shutterInterval, AnimationCurve shutterProfile)
{
Init(samples, shutterInterval);
m_ShutterCurve = shutterProfile;
}
internal void EndRecording()
{
m_IsRecording = false;
Time.captureDeltaTime = m_OriginalCaptureDeltaTime;
Time.fixedDeltaTime = m_OriginalFixedDeltaTime;
m_ShutterCurve = null;
}
// Should be called before rendering a new frame in a sequence (when accumulation is desired)
internal void PrepareNewSubFrame()
{
uint maxIteration = 0;
foreach (int camID in m_CameraCache.Keys.ToList())
maxIteration = Math.Max(maxIteration, GetCameraData(camID).currentIteration);
if (maxIteration == m_AccumulationSamples)
{
Reset();
}
}
// Helper function to compute the weight of a frame for a specific point in time
float ShutterProfile(float time)
{
if (time > m_ShutterInterval)
{
return 0;
}
// Scale the subframe time so the m_ShutterInterval spans between 0 and 1
time = time / m_ShutterInterval;
// In case we have a curve profile, use this and return
if (m_ShutterCurve != null)
{
return m_ShutterCurve.Evaluate(time);
}
// Otherwise use linear open and closing times
if (time < m_ShutterFullyOpen)
{
float openingSlope = 1.0f / m_ShutterFullyOpen;
return openingSlope * time;
}
else if (time > m_ShutterBeginsClosing)
{
float closingSlope = 1.0f / (1.0f - m_ShutterBeginsClosing);
return 1.0f - closingSlope * (time - m_ShutterBeginsClosing);
}
else
{
return 1.0f;
}
}
// returns the accumulation weights for the current sub-frame
// x: weight for the current frame
// y: sum of weights until now, without the current frame
// z: one over the sum of weights until now, including the current frame
// w: unused
internal Vector4 ComputeFrameWeights(int camID)
{
CameraData camData = GetCameraData(camID);
float totalWeight = camData.accumulatedWeight;
float time = m_AccumulationSamples > 0 ? (float)camData.currentIteration / m_AccumulationSamples : 0.0f;
float weight = isRecording ? ShutterProfile(time) : 1.0f;
if (camData.currentIteration < m_AccumulationSamples)
camData.accumulatedWeight += weight;
SetCameraData(camID, camData);
return (camData.accumulatedWeight > 0) ?
new Vector4(weight, totalWeight, 1.0f / camData.accumulatedWeight, 0.0f) :
new Vector4(weight, totalWeight, 0.0f, 0.0f);
}
}
public partial class HDRenderPipeline
{
SubFrameManager m_SubFrameManager = new SubFrameManager();
// Public API for multi-frame recording
/// <summary>
/// Should be called to start a multi-frame recording session. Each final frame will be an accumulation of multiple sub-frames.
/// </summary>
/// <param name="samples">The number of subframes. Each recorded frame will be an accumulation of this number of framesIn case path tracing is enabled, this value will override the settign in the volume.</param>
/// <param name="shutterInterval">The duration the shutter of the virtual camera is open (for motion blur). Between 0 and 1.</param>
/// <param name="shutterFullyOpen">The time it takes for the shutter to fully open. Between 0 and 1.</param>
/// <param name="shutterBeginsClosing">The time when the shutter starts closing. Between 0 and 1.</param>
public void BeginRecording(int samples, float shutterInterval, float shutterFullyOpen = 0.0f, float shutterBeginsClosing = 1.0f)
{
m_SubFrameManager.BeginRecording(samples, shutterInterval, shutterFullyOpen, shutterBeginsClosing);
}
/// <summary>
/// Should be called to start a multi-frame recording session. Each final frame will be an accumulation of multiple sub-frames.
/// </summary>
/// <param name="samples">The number of subframes. Each recorded frame will be an accumulation of this number of frames. In case path tracing is enabled, this value will override the settign in the volume.</param>
/// <param name="shutterInterval">The duration the shutter of the virtual camera is open (for motion blur). Between 0 and 1.</param>
/// <param name="shutterProfile">An animation curve (between 0 and 1) denoting the motion of the camera shutter.</param>
public void BeginRecording(int samples, float shutterInterval, AnimationCurve shutterProfile)
{
m_SubFrameManager.BeginRecording(samples, shutterInterval, shutterProfile);
}
/// <summary>
/// Should be called to finish a multi-frame recording session
/// </summary>
public void EndRecording()
{
m_SubFrameManager.EndRecording();
}
/// <summary>
/// Should be called during a recording session when preparing to render a new sub-frame of a multi-frame sequence where each final frame is an accumulation of multiple sub-frames.
/// </summary>
public void PrepareNewSubFrame()
{
m_SubFrameManager.PrepareNewSubFrame();
}
struct RenderAccumulationParameters
{
public ComputeShader accumulationCS;
public int accumulationKernel;
public SubFrameManager subFrameManager;
public bool needExposure;
public HDCamera hdCamera;
}
RenderAccumulationParameters PrepareRenderAccumulationParameters(HDCamera hdCamera, bool needExposure, bool inputFromRadianceTexture)
{
var parameters = new RenderAccumulationParameters();
parameters.accumulationCS = m_Asset.renderPipelineResources.shaders.accumulationCS;
parameters.accumulationKernel = parameters.accumulationCS.FindKernel("KMain");
parameters.subFrameManager = m_SubFrameManager;
parameters.needExposure = needExposure;
parameters.hdCamera = hdCamera;
parameters.accumulationCS.shaderKeywords = null;
if (inputFromRadianceTexture)
{
parameters.accumulationCS.EnableKeyword("INPUT_FROM_RADIANCE_TEXTURE");
}
return parameters;
}
static void RenderAccumulation(in RenderAccumulationParameters parameters, RTHandle inputTexture, RTHandle outputTexture, RTHandle historyTexture, CommandBuffer cmd)
{
ComputeShader accumulationShader = parameters.accumulationCS;
// Check the validity of the state before moving on with the computation
if (!accumulationShader)
return;
// Get the per-camera data
int camID = parameters.hdCamera.camera.GetInstanceID();
Vector4 frameWeights = parameters.subFrameManager.ComputeFrameWeights(camID);
CameraData camData = parameters.subFrameManager.GetCameraData(camID);
// Accumulate the path tracing results
cmd.SetComputeIntParam(accumulationShader, HDShaderIDs._AccumulationFrameIndex, (int)camData.currentIteration);
cmd.SetComputeIntParam(accumulationShader, HDShaderIDs._AccumulationNumSamples, (int)parameters.subFrameManager.subFrameCount);
cmd.SetComputeTextureParam(accumulationShader, parameters.accumulationKernel, HDShaderIDs._AccumulatedFrameTexture, historyTexture);
cmd.SetComputeTextureParam(accumulationShader, parameters.accumulationKernel, HDShaderIDs._CameraColorTextureRW, outputTexture);
if (!inputTexture.Equals(outputTexture))
{
cmd.SetComputeTextureParam(accumulationShader, parameters.accumulationKernel, HDShaderIDs._RadianceTexture, inputTexture);
}
cmd.SetComputeVectorParam(accumulationShader, HDShaderIDs._AccumulationWeights, frameWeights);
cmd.SetComputeIntParam(accumulationShader, HDShaderIDs._AccumulationNeedsExposure, parameters.needExposure ? 1 : 0);
cmd.DispatchCompute(accumulationShader, parameters.accumulationKernel, (parameters.hdCamera.actualWidth + 7) / 8, (parameters.hdCamera.actualHeight + 7) / 8, parameters.hdCamera.viewCount);
// Increment the iteration counter, if we haven't converged yet
if (camData.currentIteration < parameters.subFrameManager.subFrameCount)
{
camData.currentIteration++;
parameters.subFrameManager.SetCameraData(camID, camData);
}
}
}
}