2021-09-09 20:42:29 -04:00

434 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Experimental.Rendering.RenderGraphModule;
namespace UnityEngine.Rendering.HighDefinition
{
abstract partial class HDShadowAtlas
{
internal struct HDShadowAtlasInitParameters
{
internal RenderPipelineResources renderPipelineResources;
internal RenderGraph renderGraph;
internal bool useSharedTexture;
internal int width;
internal int height;
internal int atlasShaderID;
internal int maxShadowRequests;
internal string name;
internal Material clearMaterial;
internal HDShadowInitParameters initParams;
internal BlurAlgorithm blurAlgorithm;
internal FilterMode filterMode;
internal DepthBits depthBufferBits;
internal RenderTextureFormat format;
internal ConstantBuffer<ShaderVariablesGlobal> cb;
internal HDShadowAtlasInitParameters(RenderPipelineResources renderPipelineResources, RenderGraph renderGraph, bool useSharedTexture, int width, int height, int atlasShaderID,
Material clearMaterial, int maxShadowRequests, HDShadowInitParameters initParams, ConstantBuffer<ShaderVariablesGlobal> cb)
{
this.renderPipelineResources = renderPipelineResources;
this.renderGraph = renderGraph;
this.useSharedTexture = useSharedTexture;
this.width = width;
this.height = height;
this.atlasShaderID = atlasShaderID;
this.clearMaterial = clearMaterial;
this.maxShadowRequests = maxShadowRequests;
this.initParams = initParams;
this.blurAlgorithm = BlurAlgorithm.None;
this.filterMode = FilterMode.Bilinear;
this.depthBufferBits = DepthBits.Depth16;
this.format = RenderTextureFormat.Shadowmap;
this.name = "";
this.cb = cb;
}
}
public enum BlurAlgorithm
{
None,
EVSM, // exponential variance shadow maps
IM // Improved Moment shadow maps
}
protected List<HDShadowRequest> m_ShadowRequests = new List<HDShadowRequest>();
public int width { get; private set; }
public int height { get; private set; }
Material m_ClearMaterial;
LightingDebugSettings m_LightingDebugSettings;
FilterMode m_FilterMode;
DepthBits m_DepthBufferBits;
RenderTextureFormat m_Format;
string m_Name;
string m_MomentName;
string m_MomentCopyName;
string m_IntermediateSummedAreaName;
string m_SummedAreaName;
int m_AtlasShaderID;
RenderPipelineResources m_RenderPipelineResources;
// Moment shadow data
BlurAlgorithm m_BlurAlgorithm;
// This is only a reference that is hold by the atlas, but its lifetime is responsibility of the shadow manager.
ConstantBuffer<ShaderVariablesGlobal> m_GlobalConstantBuffer;
// This must be true for atlas that contain cached data (effectively this
// drives what to do with mixed cached shadow map -> if true we filter with only static
// if false we filter only for dynamic)
protected bool m_IsACacheForShadows;
public HDShadowAtlas() {}
public virtual void InitAtlas(HDShadowAtlasInitParameters initParams)
{
this.width = initParams.width;
this.height = initParams.height;
m_FilterMode = initParams.filterMode;
m_DepthBufferBits = initParams.depthBufferBits;
m_Format = initParams.format;
m_Name = initParams.name;
// With render graph, textures are "allocated" every frame so we need to prepare strings beforehand.
m_MomentName = m_Name + "Moment";
m_MomentCopyName = m_Name + "MomentCopy";
m_IntermediateSummedAreaName = m_Name + "IntermediateSummedArea";
m_SummedAreaName = m_Name + "SummedAreaFinal";
m_AtlasShaderID = initParams.atlasShaderID;
m_ClearMaterial = initParams.clearMaterial;
m_BlurAlgorithm = initParams.blurAlgorithm;
m_RenderPipelineResources = initParams.renderPipelineResources;
m_IsACacheForShadows = false;
m_GlobalConstantBuffer = initParams.cb;
InitializeRenderGraphOutput(initParams.renderGraph, initParams.useSharedTexture);
}
public HDShadowAtlas(HDShadowAtlasInitParameters initParams)
{
InitAtlas(initParams);
}
public void UpdateSize(Vector2Int size)
{
width = size.x;
height = size.y;
}
internal void AddShadowRequest(HDShadowRequest shadowRequest)
{
m_ShadowRequests.Add(shadowRequest);
}
public void UpdateDebugSettings(LightingDebugSettings lightingDebugSettings)
{
m_LightingDebugSettings = lightingDebugSettings;
}
struct RenderShadowsParameters
{
public ShaderVariablesGlobal globalCB;
public List<HDShadowRequest> shadowRequests;
public Material clearMaterial;
public bool debugClearAtlas;
public int atlasShaderID;
public BlurAlgorithm blurAlgorithm;
// EVSM
public ComputeShader evsmShadowBlurMomentsCS;
// IM
public ComputeShader imShadowBlurMomentsCS;
}
RenderShadowsParameters PrepareRenderShadowsParameters(in ShaderVariablesGlobal globalCB)
{
var parameters = new RenderShadowsParameters();
parameters.globalCB = globalCB;
parameters.shadowRequests = m_ShadowRequests;
parameters.clearMaterial = m_ClearMaterial;
parameters.debugClearAtlas = m_LightingDebugSettings.clearShadowAtlas;
parameters.atlasShaderID = m_AtlasShaderID;
parameters.blurAlgorithm = m_BlurAlgorithm;
// EVSM
parameters.evsmShadowBlurMomentsCS = m_RenderPipelineResources.shaders.evsmBlurCS;
// IM
parameters.imShadowBlurMomentsCS = m_RenderPipelineResources.shaders.momentShadowsCS;
return parameters;
}
static void RenderShadows(in RenderShadowsParameters parameters,
RTHandle atlasRenderTexture,
ShadowDrawingSettings shadowDrawSettings,
ScriptableRenderContext renderContext,
bool renderingOnAShadowCache,
ConstantBuffer<ShaderVariablesGlobal> constantBuffer,
CommandBuffer cmd)
{
cmd.SetRenderTarget(atlasRenderTexture, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
// Clear the whole atlas to avoid garbage outside of current request when viewing it.
if (parameters.debugClearAtlas)
CoreUtils.DrawFullScreen(cmd, parameters.clearMaterial, null, 0);
foreach (var shadowRequest in parameters.shadowRequests)
{
bool shouldSkipRequest = shadowRequest.shadowMapType != ShadowMapType.CascadedDirectional ? !shadowRequest.shouldRenderCachedComponent && renderingOnAShadowCache :
shadowRequest.shouldUseCachedShadowData;
if (shouldSkipRequest)
continue;
bool mixedInDynamicAtlas = false;
#if UNITY_2021_1_OR_NEWER
if (shadowRequest.isMixedCached)
{
mixedInDynamicAtlas = !renderingOnAShadowCache;
shadowDrawSettings.objectsFilter = mixedInDynamicAtlas ? ShadowObjectsFilter.DynamicOnly : ShadowObjectsFilter.StaticOnly;
}
else
{
shadowDrawSettings.objectsFilter = ShadowObjectsFilter.AllObjects;
}
#endif
cmd.SetGlobalDepthBias(1.0f, shadowRequest.slopeBias);
cmd.SetViewport(renderingOnAShadowCache ? shadowRequest.cachedAtlasViewport : shadowRequest.dynamicAtlasViewport);
cmd.SetGlobalFloat(HDShaderIDs._ZClip, shadowRequest.zClip ? 1.0f : 0.0f);
if (!mixedInDynamicAtlas)
CoreUtils.DrawFullScreen(cmd, parameters.clearMaterial, null, 0);
shadowDrawSettings.lightIndex = shadowRequest.lightIndex;
shadowDrawSettings.splitData = shadowRequest.splitData;
var globalCB = parameters.globalCB;
// Setup matrices for shadow rendering:
Matrix4x4 viewProjection = shadowRequest.deviceProjectionYFlip * shadowRequest.view;
globalCB._ViewMatrix = shadowRequest.view;
globalCB._InvViewMatrix = shadowRequest.view.inverse;
globalCB._ProjMatrix = shadowRequest.deviceProjectionYFlip;
globalCB._InvProjMatrix = shadowRequest.deviceProjectionYFlip.inverse;
globalCB._ViewProjMatrix = viewProjection;
globalCB._InvViewProjMatrix = viewProjection.inverse;
constantBuffer.PushGlobal(cmd, globalCB, HDShaderIDs._ShaderVariablesGlobal);
cmd.SetGlobalVectorArray(HDShaderIDs._ShadowFrustumPlanes, shadowRequest.frustumPlanes);
// TODO: remove this execute when DrawShadows will use a CommandBuffer
renderContext.ExecuteCommandBuffer(cmd);
cmd.Clear();
renderContext.DrawShadows(ref shadowDrawSettings);
}
cmd.SetGlobalFloat(HDShaderIDs._ZClip, 1.0f); // Re-enable zclip globally
cmd.SetGlobalDepthBias(0.0f, 0.0f); // Reset depth bias.
}
public bool HasBlurredEVSM()
{
return (m_BlurAlgorithm == BlurAlgorithm.EVSM);
}
// This is a 9 tap filter, a gaussian with std. dev of 3. This standard deviation with this amount of taps probably cuts
// the tail of the gaussian a bit too much, and it is a very fat curve, but it seems to work fine for our use case.
static readonly Vector4[] evsmBlurWeights =
{
new Vector4(0.1531703f, 0.1448929f, 0.1226492f, 0.0929025f),
new Vector4(0.06297021f, 0.0f, 0.0f, 0.0f),
};
unsafe static void EVSMBlurMoments(RenderShadowsParameters parameters,
RTHandle atlasRenderTexture,
RTHandle[] momentAtlasRenderTextures,
bool blurOnACache,
CommandBuffer cmd)
{
ComputeShader shadowBlurMomentsCS = parameters.evsmShadowBlurMomentsCS;
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RenderEVSMShadowMaps)))
{
int generateAndBlurMomentsKernel = shadowBlurMomentsCS.FindKernel("ConvertAndBlur");
int blurMomentsKernel = shadowBlurMomentsCS.FindKernel("Blur");
int copyMomentsKernel = shadowBlurMomentsCS.FindKernel("CopyMoments");
cmd.SetComputeTextureParam(shadowBlurMomentsCS, generateAndBlurMomentsKernel, HDShaderIDs._DepthTexture, atlasRenderTexture);
cmd.SetComputeVectorArrayParam(shadowBlurMomentsCS, HDShaderIDs._BlurWeightsStorage, evsmBlurWeights);
// We need to store in which of the two moment texture a request will have its last version stored in for a final patch up at the end.
var finalAtlasTexture = stackalloc int[parameters.shadowRequests.Count];
int requestIdx = 0;
foreach (var shadowRequest in parameters.shadowRequests)
{
bool shouldSkipRequest = shadowRequest.shadowMapType != ShadowMapType.CascadedDirectional ? !shadowRequest.shouldRenderCachedComponent && blurOnACache :
shadowRequest.shouldUseCachedShadowData;
if (shouldSkipRequest)
continue;
var viewport = blurOnACache ? shadowRequest.cachedAtlasViewport : shadowRequest.dynamicAtlasViewport;
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RenderEVSMShadowMapsBlur)))
{
int downsampledWidth = Mathf.CeilToInt(viewport.width * 0.5f);
int downsampledHeight = Mathf.CeilToInt(viewport.height * 0.5f);
Vector2 DstRectOffset = new Vector2(viewport.min.x * 0.5f, viewport.min.y * 0.5f);
cmd.SetComputeTextureParam(shadowBlurMomentsCS, generateAndBlurMomentsKernel, HDShaderIDs._OutputTexture, momentAtlasRenderTextures[0]);
cmd.SetComputeVectorParam(shadowBlurMomentsCS, HDShaderIDs._SrcRect, new Vector4(viewport.min.x, viewport.min.y, viewport.width, viewport.height));
cmd.SetComputeVectorParam(shadowBlurMomentsCS, HDShaderIDs._DstRect, new Vector4(DstRectOffset.x, DstRectOffset.y, 1.0f / atlasRenderTexture.rt.width, 1.0f / atlasRenderTexture.rt.height));
cmd.SetComputeFloatParam(shadowBlurMomentsCS, HDShaderIDs._EVSMExponent, shadowRequest.evsmParams.x);
int dispatchSizeX = ((int)downsampledWidth + 7) / 8;
int dispatchSizeY = ((int)downsampledHeight + 7) / 8;
cmd.DispatchCompute(shadowBlurMomentsCS, generateAndBlurMomentsKernel, dispatchSizeX, dispatchSizeY, 1);
int currentAtlasMomentSurface = 0;
RTHandle GetMomentRT() { return momentAtlasRenderTextures[currentAtlasMomentSurface]; }
RTHandle GetMomentRTCopy() { return momentAtlasRenderTextures[(currentAtlasMomentSurface + 1) & 1]; }
cmd.SetComputeVectorParam(shadowBlurMomentsCS, HDShaderIDs._SrcRect, new Vector4(DstRectOffset.x, DstRectOffset.y, downsampledWidth, downsampledHeight));
for (int i = 0; i < shadowRequest.evsmParams.w; ++i)
{
currentAtlasMomentSurface = (currentAtlasMomentSurface + 1) & 1;
cmd.SetComputeTextureParam(shadowBlurMomentsCS, blurMomentsKernel, HDShaderIDs._InputTexture, GetMomentRTCopy());
cmd.SetComputeTextureParam(shadowBlurMomentsCS, blurMomentsKernel, HDShaderIDs._OutputTexture, GetMomentRT());
cmd.DispatchCompute(shadowBlurMomentsCS, blurMomentsKernel, dispatchSizeX, dispatchSizeY, 1);
}
finalAtlasTexture[requestIdx++] = currentAtlasMomentSurface;
}
}
// We patch up the atlas with the requests that, due to different count of blur passes, remained in the copy
for (int i = 0; i < parameters.shadowRequests.Count; ++i)
{
if (finalAtlasTexture[i] != 0)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RenderEVSMShadowMapsCopyToAtlas)))
{
var shadowRequest = parameters.shadowRequests[i];
var viewport = blurOnACache ? shadowRequest.cachedAtlasViewport : shadowRequest.dynamicAtlasViewport;
int downsampledWidth = Mathf.CeilToInt(viewport.width * 0.5f);
int downsampledHeight = Mathf.CeilToInt(viewport.height * 0.5f);
cmd.SetComputeVectorParam(shadowBlurMomentsCS, HDShaderIDs._SrcRect, new Vector4(viewport.min.x * 0.5f, viewport.min.y * 0.5f, downsampledWidth, downsampledHeight));
cmd.SetComputeTextureParam(shadowBlurMomentsCS, copyMomentsKernel, HDShaderIDs._InputTexture, momentAtlasRenderTextures[1]);
cmd.SetComputeTextureParam(shadowBlurMomentsCS, copyMomentsKernel, HDShaderIDs._OutputTexture, momentAtlasRenderTextures[0]);
int dispatchSizeX = ((int)downsampledWidth + 7) / 8;
int dispatchSizeY = ((int)downsampledHeight + 7) / 8;
cmd.DispatchCompute(shadowBlurMomentsCS, copyMomentsKernel, dispatchSizeX, dispatchSizeY, 1);
}
}
}
}
}
static void IMBlurMoment(RenderShadowsParameters parameters,
RTHandle atlas,
RTHandle atlasMoment,
RTHandle intermediateSummedAreaTexture,
RTHandle summedAreaTexture,
CommandBuffer cmd)
{
// If the target kernel is not available
ComputeShader momentCS = parameters.imShadowBlurMomentsCS;
if (momentCS == null) return;
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.RenderMomentShadowMaps)))
{
int computeMomentKernel = momentCS.FindKernel("ComputeMomentShadows");
int summedAreaHorizontalKernel = momentCS.FindKernel("MomentSummedAreaTableHorizontal");
int summedAreaVerticalKernel = momentCS.FindKernel("MomentSummedAreaTableVertical");
// First of all let's clear the moment shadow map
CoreUtils.SetRenderTarget(cmd, atlasMoment, ClearFlag.Color, Color.black);
CoreUtils.SetRenderTarget(cmd, intermediateSummedAreaTexture, ClearFlag.Color, Color.black);
CoreUtils.SetRenderTarget(cmd, summedAreaTexture, ClearFlag.Color, Color.black);
// Alright, so the thing here is that for every sub-shadow map of the atlas, we need to generate the moment shadow map
foreach (var shadowRequest in parameters.shadowRequests)
{
// Let's bind the resources of this
cmd.SetComputeTextureParam(momentCS, computeMomentKernel, HDShaderIDs._ShadowmapAtlas, atlas);
cmd.SetComputeTextureParam(momentCS, computeMomentKernel, HDShaderIDs._MomentShadowAtlas, atlasMoment);
cmd.SetComputeVectorParam(momentCS, HDShaderIDs._MomentShadowmapSlotST, new Vector4(shadowRequest.dynamicAtlasViewport.width, shadowRequest.dynamicAtlasViewport.height, shadowRequest.dynamicAtlasViewport.min.x, shadowRequest.dynamicAtlasViewport.min.y));
// First of all we need to compute the moments
int numTilesX = Math.Max((int)shadowRequest.dynamicAtlasViewport.width / 8, 1);
int numTilesY = Math.Max((int)shadowRequest.dynamicAtlasViewport.height / 8, 1);
cmd.DispatchCompute(momentCS, computeMomentKernel, numTilesX, numTilesY, 1);
// Do the horizontal pass of the summed area table
cmd.SetComputeTextureParam(momentCS, summedAreaHorizontalKernel, HDShaderIDs._SummedAreaTableInputFloat, atlasMoment);
cmd.SetComputeTextureParam(momentCS, summedAreaHorizontalKernel, HDShaderIDs._SummedAreaTableOutputInt, intermediateSummedAreaTexture);
cmd.SetComputeFloatParam(momentCS, HDShaderIDs._IMSKernelSize, shadowRequest.kernelSize);
cmd.SetComputeVectorParam(momentCS, HDShaderIDs._MomentShadowmapSize, new Vector2((float)atlasMoment.referenceSize.x, (float)atlasMoment.referenceSize.y));
int numLines = Math.Max((int)shadowRequest.dynamicAtlasViewport.width / 64, 1);
cmd.DispatchCompute(momentCS, summedAreaHorizontalKernel, numLines, 1, 1);
// Do the horizontal pass of the summed area table
cmd.SetComputeTextureParam(momentCS, summedAreaVerticalKernel, HDShaderIDs._SummedAreaTableInputInt, intermediateSummedAreaTexture);
cmd.SetComputeTextureParam(momentCS, summedAreaVerticalKernel, HDShaderIDs._SummedAreaTableOutputInt, summedAreaTexture);
cmd.SetComputeVectorParam(momentCS, HDShaderIDs._MomentShadowmapSize, new Vector2((float)atlasMoment.referenceSize.x, (float)atlasMoment.referenceSize.y));
cmd.SetComputeFloatParam(momentCS, HDShaderIDs._IMSKernelSize, shadowRequest.kernelSize);
int numColumns = Math.Max((int)shadowRequest.dynamicAtlasViewport.height / 64, 1);
cmd.DispatchCompute(momentCS, summedAreaVerticalKernel, numColumns, 1, 1);
// Push the global texture
cmd.SetGlobalTexture(HDShaderIDs._SummedAreaTableInputInt, summedAreaTexture);
}
}
}
public virtual void DisplayAtlas(RTHandle atlasTexture, CommandBuffer cmd, Material debugMaterial, Rect atlasViewport, float screenX, float screenY, float screenSizeX, float screenSizeY, float minValue, float maxValue, MaterialPropertyBlock mpb, float scaleFactor = 1)
{
if (atlasTexture == null)
return;
Vector4 validRange = new Vector4(minValue, 1.0f / (maxValue - minValue));
float rWidth = 1.0f / width;
float rHeight = 1.0f / height;
Vector4 scaleBias = Vector4.Scale(new Vector4(rWidth, rHeight, rWidth, rHeight), new Vector4(atlasViewport.width, atlasViewport.height, atlasViewport.x, atlasViewport.y));
mpb.SetTexture("_AtlasTexture", atlasTexture);
mpb.SetVector("_TextureScaleBias", scaleBias);
mpb.SetVector("_ValidRange", validRange);
mpb.SetFloat("_RcpGlobalScaleFactor", scaleFactor);
cmd.SetViewport(new Rect(screenX, screenY, screenSizeX, screenSizeY));
cmd.DrawProcedural(Matrix4x4.identity, debugMaterial, debugMaterial.FindPass("RegularShadow"), MeshTopology.Triangles, 3, 1, mpb);
}
public virtual void Clear()
{
m_ShadowRequests.Clear();
}
public void Release(RenderGraph renderGraph)
{
CleanupRenderGraphOutput(renderGraph);
}
}
}