您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1665 行
81 KiB
1665 行
81 KiB
using UnityEngine.Rendering;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UnityEngine.Experimental.Rendering
|
|
{
|
|
using ShadowRequestVector = VectorArray<ShadowmapBase.ShadowRequest>;
|
|
using ShadowDataVector = VectorArray<ShadowData>;
|
|
using ShadowPayloadVector = VectorArray<ShadowPayload>;
|
|
using ShadowIndicesVector = VectorArray<int>;
|
|
using ShadowAlgoVector = VectorArray<int>;
|
|
|
|
using Profiling;
|
|
|
|
// Standard shadow map atlas implementation using one large shadow map
|
|
public class ShadowAtlas : ShadowmapBase, IDisposable
|
|
{
|
|
public const uint k_MaxCascadesInShader = 4;
|
|
protected readonly int m_TempDepthId;
|
|
protected readonly int m_ZClipId;
|
|
protected RenderTexture m_Shadowmap;
|
|
protected RenderTargetIdentifier m_ShadowmapId;
|
|
protected VectorArray<CachedEntry> m_EntryCache = new VectorArray<CachedEntry>(0, true);
|
|
private VectorArray<CachedEntry> m_EntryPool = new VectorArray<CachedEntry>(16, false);
|
|
protected uint m_ActiveEntriesCount;
|
|
protected FrameId m_FrameId;
|
|
protected string m_ShaderKeyword;
|
|
protected uint m_TexSlot;
|
|
protected uint m_SampSlot;
|
|
protected uint[] m_TmpWidths = new uint[ShadowmapBase.ShadowRequest.k_MaxFaceCount];
|
|
protected uint[] m_TmpHeights = new uint[ShadowmapBase.ShadowRequest.k_MaxFaceCount];
|
|
protected Vector4[] m_TmpSplits = new Vector4[k_MaxCascadesInShader];
|
|
protected float[] m_TmpBorders = new float[((k_MaxCascadesInShader + 3) / 4) * 4];
|
|
protected ShadowAlgoVector m_SupportedAlgorithms = new ShadowAlgoVector(0, false);
|
|
private Material m_ClearMat;
|
|
private readonly VectorArray<CachedEntry>.Cleanup m_Cleanup;
|
|
private readonly VectorArray<CachedEntry>.Comparator<Key> m_Comparator;
|
|
public bool captureFrame { get; set; }
|
|
|
|
private CustomSampler m_SamplerShadowMaps = CustomSampler.Create("ShadowMaps");
|
|
|
|
protected struct Key
|
|
{
|
|
public int id;
|
|
public uint faceIdx;
|
|
public int visibleIdx;
|
|
public uint shadowDataIdx;
|
|
}
|
|
|
|
protected struct Data
|
|
{
|
|
public FrameId frameId;
|
|
public int contentHash;
|
|
public GPUShadowAlgorithm shadowAlgo;
|
|
public uint slice;
|
|
public Rect viewport;
|
|
public Matrix4x4 view;
|
|
public Matrix4x4 proj;
|
|
public Vector4 lightDir;
|
|
public ShadowSplitData splitData;
|
|
|
|
public bool IsValid() { return viewport.width > 0 && viewport.height > 0; }
|
|
}
|
|
protected class CachedEntry : IComparable<CachedEntry>
|
|
{
|
|
public Key key;
|
|
public Data current;
|
|
public Data previous;
|
|
public bool zclip;
|
|
|
|
public int CompareTo(CachedEntry other)
|
|
{
|
|
if (current.viewport.height != other.current.viewport.height)
|
|
return current.viewport.height > other.current.viewport.height ? -1 : 1;
|
|
if (current.viewport.width != other.current.viewport.width)
|
|
return current.viewport.width > other.current.viewport.width ? -1 : 1;
|
|
if (key.id != other.key.id)
|
|
return key.id < other.key.id ? -1 : 1;
|
|
|
|
return key.faceIdx != other.key.faceIdx ? (key.faceIdx < other.key.faceIdx ? -1 : 1) : 0;
|
|
}
|
|
};
|
|
|
|
public struct AtlasInit
|
|
{
|
|
public BaseInit baseInit; // the base class's initializer
|
|
public string shaderKeyword; // the global shader keyword to use when rendering the shadowmap
|
|
public Shader shadowClearShader;
|
|
public ComputeShader shadowBlurMoments;
|
|
}
|
|
|
|
// UI stuff
|
|
protected struct ValRange
|
|
{
|
|
#if UNITY_EDITOR
|
|
GUIContent Name;
|
|
float ValMin;
|
|
float ValMax;
|
|
#endif
|
|
float ValDef;
|
|
float ValScale;
|
|
|
|
public ValRange(string name, float valMin, float valDef, float valMax, float valScale)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Name = new GUIContent(name);
|
|
ValMin = valMin;
|
|
ValMax = valMax;
|
|
#endif
|
|
ValDef = valDef;
|
|
ValScale = valScale;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public void Slider(ref int currentVal) { currentVal = ShadowUtils.Asint(ValScale * UnityEditor.EditorGUILayout.Slider(Name, ShadowUtils.Asfloat(currentVal) / ValScale, ValMin, ValMax)); }
|
|
#else
|
|
public void Slider(ref int currentVal) {}
|
|
#endif
|
|
public int Default() { return ShadowUtils.Asint(ValScale * ValDef); }
|
|
}
|
|
readonly ValRange m_DefPCF_DepthBias = new ValRange("Depth Bias", 0.0f, 0.0f, 1.0f, 000.1f);
|
|
readonly ValRange m_DefPCF_FilterSize = new ValRange("Filter Size", 1.0f, 1.0f, 10.0f, 1.0f);
|
|
|
|
readonly ValRange m_DefPCSS_ShadowSoftness = new ValRange("Shadow Softness", 0.0f, 0.5f, 1.0f, 0.01f);
|
|
readonly ValRange m_DefPCSS_SampleCount = new ValRange("Sample Count", 1, 32, 64, 1);
|
|
|
|
|
|
public ShadowAtlas(ref AtlasInit init) : base(ref init.baseInit)
|
|
{
|
|
m_ClearMat = CoreUtils.CreateEngineMaterial(init.shadowClearShader);
|
|
|
|
m_Cleanup = (CachedEntry entry) => { Free(entry); };
|
|
m_Comparator = (ref Key k, ref CachedEntry entry) => { return k.id == entry.key.id && k.faceIdx == entry.key.faceIdx; };
|
|
|
|
m_ZClipId = Shader.PropertyToID("_ZClip");
|
|
if (!IsNativeDepth())
|
|
{
|
|
m_TempDepthId = Shader.PropertyToID("Temporary Shadowmap Depth");
|
|
}
|
|
|
|
Initialize(init);
|
|
}
|
|
|
|
public override void CreateShadowmap()
|
|
{
|
|
m_Shadowmap = new RenderTexture((int)m_Width, (int)m_Height, (int)m_ShadowmapBits, m_ShadowmapFormat, RenderTextureReadWrite.Linear);
|
|
CreateShadowmap(m_Shadowmap);
|
|
m_Shadowmap.Create();
|
|
#if false && UNITY_PS4 && !UNITY_EDITOR
|
|
if (m_Shadowmap != null)
|
|
UnityEngine.PS4.RenderSettings.DisableDepthBufferCompression(m_Shadowmap);
|
|
#endif
|
|
}
|
|
|
|
virtual protected void CreateShadowmap(RenderTexture shadowmap)
|
|
{
|
|
m_Shadowmap.hideFlags = HideFlags.DontSaveInEditor | HideFlags.DontSaveInBuild;
|
|
m_Shadowmap.dimension = TextureDimension.Tex2DArray;
|
|
m_Shadowmap.volumeDepth = (int)m_Slices;
|
|
m_Shadowmap.name = CoreUtils.GetRenderTargetAutoName(shadowmap.width, shadowmap.height, 1, shadowmap.format, "Shadow", mips: shadowmap.useMipMap);
|
|
|
|
m_ShadowmapId = new RenderTargetIdentifier(m_Shadowmap);
|
|
}
|
|
|
|
override protected void Register(GPUShadowType type, ShadowRegistry registry)
|
|
{
|
|
ShadowPrecision precision = m_ShadowmapBits == 32 ? ShadowPrecision.High : ShadowPrecision.Low;
|
|
m_SupportedAlgorithms.Reserve(6);
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.PCF, ShadowVariant.V0, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.PCF, ShadowVariant.V1, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.PCF, ShadowVariant.V2, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.PCF, ShadowVariant.V3, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.PCF, ShadowVariant.V4, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.PCSS, ShadowVariant.V0, precision));
|
|
|
|
ShadowRegistry.VariantDelegate del = (Light l, ShadowAlgorithm dataAlgorithm, ShadowVariant dataVariant, ShadowPrecision dataPrecision, ref int[] dataBlock) =>
|
|
{
|
|
CheckDataIntegrity(dataAlgorithm, dataVariant, dataPrecision, ref dataBlock);
|
|
|
|
m_DefPCF_DepthBias.Slider(ref dataBlock[0]);
|
|
if (dataVariant == ShadowVariant.V1)
|
|
m_DefPCF_FilterSize.Slider(ref dataBlock[1]);
|
|
};
|
|
|
|
ShadowRegistry.VariantDelegate pcssDel = (Light l, ShadowAlgorithm dataAlgorithm, ShadowVariant dataVariant, ShadowPrecision dataPrecision, ref int[] dataBlock) =>
|
|
{
|
|
CheckDataIntegrity(dataAlgorithm, dataVariant, dataPrecision, ref dataBlock);
|
|
|
|
m_DefPCSS_ShadowSoftness.Slider(ref dataBlock[0]);
|
|
m_DefPCSS_SampleCount.Slider(ref dataBlock[1]);
|
|
dataBlock[1] = ShadowUtils.Asint(Mathf.RoundToInt(ShadowUtils.Asfloat(dataBlock[1])));
|
|
};
|
|
|
|
registry.Register(type, precision, ShadowAlgorithm.PCF, "Percentage Closer Filtering (PCF)",
|
|
new ShadowVariant[] { ShadowVariant.V0, ShadowVariant.V1, ShadowVariant.V2, ShadowVariant.V3, ShadowVariant.V4 },
|
|
new string[] {"1 tap", "9 tap adaptive", "tent 3x3 (4 taps)", "tent 5x5 (9 taps)", "tent 7x7 (16 taps)" },
|
|
new ShadowRegistry.VariantDelegate[] { del, del, del, del, del });
|
|
|
|
registry.Register(type, precision, ShadowAlgorithm.PCSS, "Percentage Closer Soft Shadows (PCSS)",
|
|
new ShadowVariant[] { ShadowVariant.V0 },
|
|
new string[] { "poisson 64" },
|
|
new ShadowRegistry.VariantDelegate[] { pcssDel });
|
|
}
|
|
|
|
// returns true if the original data passed integrity checks, false if the data had to be modified
|
|
virtual protected bool CheckDataIntegrity(ShadowAlgorithm algorithm, ShadowVariant variant, ShadowPrecision precision, ref int[] dataBlock)
|
|
{
|
|
if ((algorithm != ShadowAlgorithm.PCF && algorithm != ShadowAlgorithm.PCSS) ||
|
|
(variant != ShadowVariant.V0 &&
|
|
variant != ShadowVariant.V1 &&
|
|
variant != ShadowVariant.V2 &&
|
|
variant != ShadowVariant.V3 &&
|
|
variant != ShadowVariant.V4))
|
|
return true;
|
|
|
|
switch (algorithm)
|
|
{
|
|
case ShadowAlgorithm.PCF:
|
|
const int k_PcfBlockSize = 2;
|
|
if (dataBlock == null || dataBlock.Length != k_PcfBlockSize)
|
|
{
|
|
// set defaults
|
|
dataBlock = new int[k_PcfBlockSize];
|
|
dataBlock[0] = m_DefPCF_DepthBias.Default();
|
|
dataBlock[1] = m_DefPCF_FilterSize.Default();
|
|
return false;
|
|
}
|
|
break;
|
|
case ShadowAlgorithm.PCSS:
|
|
const int k_PcssBlockSize = 2;
|
|
if (dataBlock == null || dataBlock.Length != k_PcssBlockSize)
|
|
{
|
|
// set defaults
|
|
dataBlock = new int[k_PcssBlockSize];
|
|
dataBlock[0] = m_DefPCSS_ShadowSoftness.Default();
|
|
dataBlock[1] = m_DefPCSS_SampleCount.Default();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void Initialize(AtlasInit init)
|
|
{
|
|
m_ShaderKeyword = init.shaderKeyword;
|
|
}
|
|
|
|
override public void ReserveSlots(ShadowContextStorage sc)
|
|
{
|
|
m_TexSlot = sc.RequestTex2DArraySlot();
|
|
m_SampSlot = IsNativeDepth() ? sc.RequestSamplerSlot(m_CompSamplerState) : sc.RequestSamplerSlot(m_SamplerState);
|
|
}
|
|
|
|
override public void Fill(ShadowContextStorage cs)
|
|
{
|
|
cs.SetTex2DArraySlot(m_TexSlot, m_ShadowmapId);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (m_Shadowmap != null)
|
|
m_Shadowmap.Release();
|
|
|
|
CoreUtils.Destroy(m_ClearMat);
|
|
}
|
|
|
|
override public bool Reserve(FrameId frameId, Camera camera, bool cameraRelativeRendering, ref ShadowData shadowData, ShadowRequest sr, uint width, uint height, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payload, List<VisibleLight> lights)
|
|
{
|
|
for (uint i = 0, cnt = sr.facecount; i < cnt; ++i)
|
|
{
|
|
m_TmpWidths[i] = width;
|
|
m_TmpHeights[i] = height;
|
|
}
|
|
return Reserve(frameId, camera, cameraRelativeRendering, ref shadowData, sr, m_TmpWidths, m_TmpHeights, ref entries, ref payload, lights);
|
|
}
|
|
|
|
override public bool Reserve(FrameId frameId, Camera camera, bool cameraRelativeRendering, ref ShadowData shadowData, ShadowRequest sr, uint[] widths, uint[] heights, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payload, List<VisibleLight> lights)
|
|
{
|
|
if (m_FrameId.frameCount != frameId.frameCount)
|
|
m_ActiveEntriesCount = 0;
|
|
|
|
m_FrameId = frameId;
|
|
|
|
uint algoIdx;
|
|
int shadowAlgo = (int)sr.shadowAlgorithm;
|
|
if (!m_SupportedAlgorithms.FindFirst(out algoIdx, ref shadowAlgo))
|
|
return false;
|
|
|
|
ShadowData sd = shadowData;
|
|
ShadowData dummy = new ShadowData();
|
|
|
|
if (sr.shadowType != GPUShadowType.Point && sr.shadowType != GPUShadowType.Spot && sr.shadowType != GPUShadowType.Directional)
|
|
return false;
|
|
|
|
if (sr.shadowType == GPUShadowType.Directional)
|
|
{
|
|
for (uint i = 0; i < k_MaxCascadesInShader; ++i)
|
|
m_TmpSplits[i].Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity);
|
|
}
|
|
|
|
Key key;
|
|
key.id = sr.instanceId;
|
|
key.faceIdx = 0;
|
|
key.visibleIdx = (int)sr.index;
|
|
key.shadowDataIdx = entries.Count();
|
|
|
|
uint originalEntryCount = entries.Count();
|
|
uint originalPayloadCount = payload.Count();
|
|
uint originalActiveEntries = m_ActiveEntriesCount;
|
|
|
|
uint facecnt = sr.facecount;
|
|
uint facemask = sr.facemask;
|
|
uint bit = 1;
|
|
int resIdx = 0;
|
|
bool multiFace = sr.shadowType != GPUShadowType.Spot;
|
|
|
|
const uint k_MaxShadowDatasPerLight = 7; // 1 shared ShadowData and up to 6 faces for point lights
|
|
entries.Reserve(k_MaxShadowDatasPerLight);
|
|
|
|
float nearPlaneOffset = QualitySettings.shadowNearPlaneOffset;
|
|
|
|
GPUShadowAlgorithm sanitizedAlgo = ShadowUtils.ClearPrecision(sr.shadowAlgorithm);
|
|
AdditionalShadowData asd = lights[sr.index].light.GetComponent<AdditionalShadowData>();
|
|
if (!asd)
|
|
return false;
|
|
|
|
|
|
int cascadeCnt = 0;
|
|
float[] cascadeRatios = null;
|
|
float[] cascadeBorders = null;
|
|
if (sr.shadowType == GPUShadowType.Directional)
|
|
{
|
|
asd.GetShadowCascades(out cascadeCnt, out cascadeRatios, out cascadeBorders);
|
|
for (int i = 0; i < m_TmpSplits.Length; i++)
|
|
m_TmpSplits[i].w = -1.0f;
|
|
}
|
|
|
|
|
|
uint multiFaceIdx = key.shadowDataIdx;
|
|
if (multiFace)
|
|
{
|
|
entries.AddUnchecked(sd);
|
|
key.shadowDataIdx++;
|
|
}
|
|
payload.Resize(payload.Count() + ReservePayload(sr));
|
|
|
|
while (facecnt > 0)
|
|
{
|
|
if ((bit & facemask) != 0)
|
|
{
|
|
uint width = widths[resIdx];
|
|
uint height = heights[resIdx];
|
|
uint ceIdx;
|
|
if (!Alloc(frameId, key, width, height, out ceIdx, payload))
|
|
{
|
|
entries.Purge(entries.Count() - originalEntryCount);
|
|
payload.Purge(payload.Count() - originalPayloadCount);
|
|
uint added = m_ActiveEntriesCount - originalActiveEntries;
|
|
for (uint i = originalActiveEntries; i < m_ActiveEntriesCount; ++i)
|
|
m_EntryCache.Swap(i, m_EntryCache.Count() - i - 1);
|
|
m_EntryCache.Purge(added, m_Cleanup);
|
|
m_ActiveEntriesCount = originalActiveEntries;
|
|
return false;
|
|
}
|
|
|
|
// read
|
|
CachedEntry ce = m_EntryCache[ceIdx];
|
|
ce.zclip = sr.shadowType != GPUShadowType.Directional;
|
|
|
|
// modify
|
|
Matrix4x4 vp, invvp, devproj;
|
|
if (sr.shadowType == GPUShadowType.Point)
|
|
{
|
|
// calculate the fov bias
|
|
float guardAngle = ShadowUtils.CalcGuardAnglePerspective(90.0f, ce.current.viewport.width, GetFilterWidthInTexels(sr, asd), asd.normalBiasMax, 79.0f);
|
|
vp = ShadowUtils.ExtractPointLightMatrix(lights[sr.index], key.faceIdx, guardAngle, out ce.current.view, out ce.current.proj, out devproj, out invvp, out ce.current.lightDir, out ce.current.splitData);
|
|
}
|
|
else if (sr.shadowType == GPUShadowType.Spot)
|
|
{
|
|
float spotAngle = lights[sr.index].spotAngle;
|
|
float guardAngle = ShadowUtils.CalcGuardAnglePerspective(spotAngle, ce.current.viewport.width, GetFilterWidthInTexels(sr, asd), asd.normalBiasMax, 180.0f - spotAngle);
|
|
vp = ShadowUtils.ExtractSpotLightMatrix(lights[sr.index], guardAngle, out ce.current.view, out ce.current.proj, out devproj, out invvp, out ce.current.lightDir, out ce.current.splitData);
|
|
}
|
|
else if (sr.shadowType == GPUShadowType.Directional)
|
|
{
|
|
vp = ShadowUtils.ExtractDirectionalLightMatrix(lights[sr.index], key.faceIdx, cascadeCnt, cascadeRatios, nearPlaneOffset, width, height, out ce.current.view, out ce.current.proj, out devproj, out invvp, out ce.current.lightDir, out ce.current.splitData, m_CullResults, (int)sr.index);
|
|
m_TmpSplits[key.faceIdx] = ce.current.splitData.cullingSphere;
|
|
if (ce.current.splitData.cullingSphere.w != float.NegativeInfinity)
|
|
{
|
|
int face = (int)key.faceIdx;
|
|
m_TmpBorders[face] = cascadeBorders[face];
|
|
m_TmpSplits[key.faceIdx].w *= ce.current.splitData.cullingSphere.w;
|
|
}
|
|
}
|
|
else
|
|
vp = invvp = devproj = Matrix4x4.identity; // should never happen, though
|
|
|
|
if (cameraRelativeRendering)
|
|
{
|
|
Vector3 camPosWS = camera.transform.position;
|
|
Matrix4x4 translation = Matrix4x4.Translate(camPosWS);
|
|
ce.current.view *= translation;
|
|
vp *= translation;
|
|
translation.SetColumn(3, -camPosWS);
|
|
translation[15] = 1.0f;
|
|
invvp = translation * invvp;
|
|
if (sr.shadowType == GPUShadowType.Directional)
|
|
{
|
|
m_TmpSplits[key.faceIdx].x -= camPosWS.x;
|
|
m_TmpSplits[key.faceIdx].y -= camPosWS.y;
|
|
m_TmpSplits[key.faceIdx].z -= camPosWS.z;
|
|
}
|
|
}
|
|
|
|
// extract texel size in world space
|
|
int flags = 0;
|
|
flags |= asd.sampleBiasScale ? (1 << 0) : 0;
|
|
flags |= asd.edgeLeakFixup ? (1 << 1) : 0;
|
|
flags |= asd.edgeToleranceNormal ? (1 << 2) : 0;
|
|
sd.edgeTolerance = asd.edgeTolerance;
|
|
sd.viewBias = new Vector4(asd.viewBiasMin, asd.viewBiasMax, asd.viewBiasScale, 2.0f / ce.current.proj.m00 / ce.current.viewport.width * 1.4142135623730950488016887242097f);
|
|
sd.normalBias = new Vector4(asd.normalBiasMin, asd.normalBiasMax, asd.normalBiasScale, ShadowUtils.Asfloat(flags));
|
|
|
|
// write :(
|
|
ce.current.shadowAlgo = (GPUShadowAlgorithm)shadowAlgo;
|
|
m_EntryCache[ceIdx] = ce;
|
|
|
|
if (sr.shadowType == GPUShadowType.Directional)
|
|
sd.pos = new Vector3(ce.current.view.m03, ce.current.view.m13, ce.current.view.m23);
|
|
else
|
|
sd.pos = cameraRelativeRendering ? (lights[sr.index].light.transform.position - camera.transform.position) : lights[sr.index].light.transform.position;
|
|
|
|
sd.shadowToWorld = invvp.transpose;
|
|
sd.proj = new Vector4(devproj.m00, devproj.m11, devproj.m22, devproj.m23);
|
|
sd.rot0 = new Vector3(ce.current.view.m00, ce.current.view.m01, ce.current.view.m02);
|
|
sd.rot1 = new Vector3(ce.current.view.m10, ce.current.view.m11, ce.current.view.m12);
|
|
sd.rot2 = new Vector3(ce.current.view.m20, ce.current.view.m21, ce.current.view.m22);
|
|
sd.scaleOffset = new Vector4(ce.current.viewport.width * m_WidthRcp, ce.current.viewport.height * m_HeightRcp, ce.current.viewport.x, ce.current.viewport.y);
|
|
sd.textureSize = new Vector4(m_Width, m_Height, ce.current.viewport.width, ce.current.viewport.height);
|
|
sd.texelSizeRcp = new Vector4(m_WidthRcp, m_HeightRcp, 1.0f / ce.current.viewport.width, 1.0f / ce.current.viewport.height);
|
|
sd.PackShadowmapId(m_TexSlot, m_SampSlot);
|
|
sd.slice = ce.current.slice;
|
|
sd.PackShadowType(sr.shadowType, sanitizedAlgo);
|
|
sd.payloadOffset = originalPayloadCount;
|
|
entries.AddUnchecked(sd);
|
|
|
|
if (multiFace)
|
|
{
|
|
entries[multiFaceIdx] = sd;
|
|
multiFace = false;
|
|
}
|
|
resIdx++;
|
|
facecnt--;
|
|
key.shadowDataIdx++;
|
|
}
|
|
else
|
|
{
|
|
// we push a dummy face in, otherwise we'd need a mapping from face index to shadowData in the shader as well
|
|
entries.AddUnchecked(dummy);
|
|
}
|
|
key.faceIdx++;
|
|
bit <<= 1;
|
|
}
|
|
|
|
WritePerLightPayload(lights, sr, ref sd, ref payload, ref originalPayloadCount);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns how many entries will be written into the payload buffer per light.
|
|
virtual protected uint ReservePayload(ShadowRequest sr)
|
|
{
|
|
uint payloadSize = sr.shadowType == GPUShadowType.Directional ? (1 + k_MaxCascadesInShader + ((uint)m_TmpBorders.Length / 4)) : 0;
|
|
|
|
switch (ShadowUtils.ExtractAlgorithm(sr.shadowAlgorithm))
|
|
{
|
|
case ShadowAlgorithm.PCF:
|
|
payloadSize += 1;
|
|
break;
|
|
case ShadowAlgorithm.PCSS:
|
|
payloadSize += 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return payloadSize;
|
|
}
|
|
|
|
virtual protected float GetFilterWidthInTexels(ShadowRequest sr, AdditionalShadowData asd)
|
|
{
|
|
ShadowAlgorithm algo;
|
|
ShadowVariant vari;
|
|
ShadowPrecision prec;
|
|
ShadowUtils.Unpack(sr.shadowAlgorithm, out algo, out vari, out prec);
|
|
|
|
if (algo != ShadowAlgorithm.PCF)
|
|
return 1.0f;
|
|
|
|
switch (vari)
|
|
{
|
|
case ShadowVariant.V0: return 1;
|
|
case ShadowVariant.V1:
|
|
{
|
|
int shadowDataFormat;
|
|
int[] shadowData = asd.GetShadowData(out shadowDataFormat);
|
|
return ShadowUtils.Asfloat(shadowData[1]);
|
|
}
|
|
case ShadowVariant.V2: return 3;
|
|
case ShadowVariant.V3: return 5;
|
|
case ShadowVariant.V4: return 7;
|
|
default: return 1.0f;
|
|
}
|
|
}
|
|
|
|
// Writes additional per light data into the payload vector. Make sure to call base.WritePerLightPayload first.
|
|
virtual protected void WritePerLightPayload(List<VisibleLight> lights, ShadowRequest sr, ref ShadowData sd, ref ShadowPayloadVector payload, ref uint payloadOffset)
|
|
{
|
|
ShadowPayload sp = new ShadowPayload();
|
|
if (sr.shadowType == GPUShadowType.Directional)
|
|
{
|
|
uint first = k_MaxCascadesInShader, second = k_MaxCascadesInShader;
|
|
for (uint i = 0; i < k_MaxCascadesInShader; i++, payloadOffset++)
|
|
{
|
|
first = (first == k_MaxCascadesInShader && m_TmpSplits[i].w > 0.0f) ? i : first;
|
|
second = ((second == k_MaxCascadesInShader || second == first) && m_TmpSplits[i].w > 0.0f) ? i : second;
|
|
sp.Set(m_TmpSplits[i]);
|
|
payload[payloadOffset] = sp;
|
|
}
|
|
if (second != k_MaxCascadesInShader)
|
|
sp.Set((m_TmpSplits[second] - m_TmpSplits[first]).normalized);
|
|
else
|
|
sp.Set(0.0f, 0.0f, 0.0f, 0.0f);
|
|
sp.p3 = (int)sr.facecount;
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
|
|
for (int i = 0; i < m_TmpBorders.Length; i += 4)
|
|
{
|
|
sp.Set(m_TmpBorders[i + 0], m_TmpBorders[i + 1], m_TmpBorders[i + 2], m_TmpBorders[i + 3]);
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
}
|
|
}
|
|
ShadowAlgorithm algo; ShadowVariant vari; ShadowPrecision prec;
|
|
ShadowUtils.Unpack(sr.shadowAlgorithm, out algo, out vari, out prec);
|
|
if (algo == ShadowAlgorithm.PCF || algo == ShadowAlgorithm.PCSS)
|
|
{
|
|
AdditionalShadowData asd = lights[sr.index].light.GetComponent<AdditionalShadowData>();
|
|
if (!asd)
|
|
return;
|
|
|
|
int shadowDataFormat;
|
|
int[] shadowData = asd.GetShadowData(out shadowDataFormat);
|
|
if (!CheckDataIntegrity(algo, vari, prec, ref shadowData))
|
|
{
|
|
asd.SetShadowAlgorithm((int)algo, (int)vari, (int)prec, shadowDataFormat, shadowData);
|
|
Debug.Log("Fixed up shadow data for algorithm " + algo + ", variant " + vari);
|
|
}
|
|
|
|
if (algo == ShadowAlgorithm.PCF)
|
|
{
|
|
switch (vari)
|
|
{
|
|
case ShadowVariant.V0:
|
|
case ShadowVariant.V1:
|
|
case ShadowVariant.V2:
|
|
case ShadowVariant.V3:
|
|
case ShadowVariant.V4:
|
|
{
|
|
sp.Set(shadowData[0] | (SystemInfo.usesReversedZBuffer ? 1 : 0), shadowData[1], 0, 0);
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if (algo == ShadowAlgorithm.PCSS)
|
|
{
|
|
sp.Set(shadowData[0], shadowData[1], 0, 0);
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
override public bool ReserveFinalize(FrameId frameId, ref VectorArray<ShadowData> entries, ref VectorArray<ShadowPayload> payload)
|
|
{
|
|
if (Layout())
|
|
{
|
|
// patch up the shadow data contents with the result of the layouting step
|
|
for (uint i = 0; i < m_ActiveEntriesCount; ++i)
|
|
{
|
|
CachedEntry ce = m_EntryCache[i];
|
|
|
|
ShadowData sd = entries[ce.key.shadowDataIdx];
|
|
// update the shadow data with the actual result of the layouting step
|
|
sd.scaleOffset = new Vector4(ce.current.viewport.width * m_WidthRcp, ce.current.viewport.height * m_HeightRcp, ce.current.viewport.x * m_WidthRcp, ce.current.viewport.y * m_HeightRcp);
|
|
sd.PackShadowmapId(m_TexSlot, m_SampSlot);
|
|
sd.slice = ce.current.slice;
|
|
// write back the correct results
|
|
entries[ce.key.shadowDataIdx] = sd;
|
|
}
|
|
m_EntryCache.Purge(m_EntryCache.Count() - m_ActiveEntriesCount, m_Cleanup);
|
|
return true;
|
|
}
|
|
m_ActiveEntriesCount = 0;
|
|
m_EntryCache.Reset(m_Cleanup);
|
|
return false;
|
|
}
|
|
|
|
virtual protected void PreUpdate(FrameId frameId, CommandBuffer cb, uint rendertargetSlice)
|
|
{
|
|
cb.SetRenderTarget(m_ShadowmapId, 0, (CubemapFace)0, (int)rendertargetSlice);
|
|
if (!IsNativeDepth())
|
|
{
|
|
cb.GetTemporaryRT(m_TempDepthId, (int)m_Width, (int)m_Height, (int)m_ShadowmapBits, FilterMode.Bilinear, RenderTextureFormat.Shadowmap, RenderTextureReadWrite.Default);
|
|
cb.SetRenderTarget(new RenderTargetIdentifier(m_TempDepthId));
|
|
}
|
|
}
|
|
|
|
override public void Update(FrameId frameId, ScriptableRenderContext renderContext, CommandBuffer cmd, CullResults cullResults, List<VisibleLight> lights)
|
|
{
|
|
if (m_ActiveEntriesCount == 0)
|
|
return;
|
|
string sLabel = captureFrame ? string.Format("Shadowmap{0}", m_TexSlot) : "Shadowmap";
|
|
var profilingSample = new ProfilingSample(cmd, sLabel, m_SamplerShadowMaps);
|
|
|
|
string cbName = "";
|
|
if (!string.IsNullOrEmpty(m_ShaderKeyword))
|
|
{
|
|
cbName = "Shadowmap.EnableShadowKeyword";
|
|
cmd.BeginSample(cbName);
|
|
cmd.EnableShaderKeyword(m_ShaderKeyword);
|
|
cmd.EndSample(cbName);
|
|
}
|
|
|
|
// loop for generating each individual shadowmap
|
|
uint curSlice = uint.MaxValue;
|
|
Bounds bounds;
|
|
DrawShadowsSettings dss = new DrawShadowsSettings(cullResults, 0);
|
|
for (uint i = 0; i < m_ActiveEntriesCount; ++i)
|
|
{
|
|
if (!cullResults.GetShadowCasterBounds(m_EntryCache[i].key.visibleIdx, out bounds))
|
|
continue;
|
|
|
|
uint entrySlice = m_EntryCache[i].current.slice;
|
|
if (entrySlice != curSlice)
|
|
{
|
|
Debug.Assert(curSlice == uint.MaxValue || entrySlice >= curSlice, "Entries in the entry cache are not ordered in slice order.");
|
|
cbName = captureFrame ? string.Format("Shadowmap.Update.Slice{0}", entrySlice) : "Shadowmap.Update.Slice";
|
|
cmd.BeginSample(cbName);
|
|
|
|
if (curSlice != uint.MaxValue)
|
|
{
|
|
PostUpdate(frameId, cmd, curSlice, lights);
|
|
}
|
|
curSlice = entrySlice;
|
|
PreUpdate(frameId, cmd, curSlice);
|
|
|
|
cmd.EndSample(cbName);
|
|
}
|
|
|
|
if (captureFrame)
|
|
{
|
|
cbName = string.Format("Shadowmap.Update - slice: {0}, vp.x: {1}, vp.y: {2}, vp.w: {3}, vp.h: {4}", curSlice, m_EntryCache[i].current.viewport.x, m_EntryCache[i].current.viewport.y, m_EntryCache[i].current.viewport.width, m_EntryCache[i].current.viewport.height);
|
|
cmd.BeginSample(cbName);
|
|
}
|
|
|
|
cmd.SetViewport(m_EntryCache[i].current.viewport);
|
|
cbName = "Shadowmap.ClearRect";
|
|
cmd.BeginSample(cbName);
|
|
CoreUtils.DrawFullScreen(cmd, m_ClearMat, null, 0);
|
|
cmd.EndSample(cbName);
|
|
cmd.SetViewProjectionMatrices(m_EntryCache[i].current.view, m_EntryCache[i].current.proj);
|
|
cmd.SetGlobalVector("g_vLightDirWs", m_EntryCache[i].current.lightDir);
|
|
cmd.SetGlobalFloat(m_ZClipId, m_EntryCache[i].zclip ? 1.0f : 0.0f);
|
|
if (captureFrame)
|
|
cmd.EndSample(cbName);
|
|
|
|
dss.lightIndex = m_EntryCache[i].key.visibleIdx;
|
|
dss.splitData = m_EntryCache[i].current.splitData;
|
|
|
|
// This is done here because DrawRenderers API lives outside command buffers so we need to make sur eto call this before doing any DrawRenders
|
|
renderContext.ExecuteCommandBuffer(cmd);
|
|
cmd.Clear();
|
|
|
|
renderContext.DrawShadows(ref dss); // <- if this was a call on the commandbuffer we would get away with using just once commandbuffer for the entire shadowmap, instead of one per face
|
|
}
|
|
|
|
// post update
|
|
PostUpdate(frameId, cmd, curSlice, lights);
|
|
if (!string.IsNullOrEmpty(m_ShaderKeyword))
|
|
{
|
|
cmd.BeginSample("Shadowmap.DisableShaderKeyword");
|
|
cmd.DisableShaderKeyword(m_ShaderKeyword);
|
|
cmd.EndSample("Shadowmap.DisableShaderKeyword");
|
|
}
|
|
|
|
cmd.SetGlobalFloat(m_ZClipId, 1.0f); // Re-enable zclip globally
|
|
m_ActiveEntriesCount = 0;
|
|
|
|
profilingSample.Dispose();
|
|
}
|
|
|
|
virtual protected void PostUpdate(FrameId frameId, CommandBuffer cb, uint rendertargetSlice, List<VisibleLight> lights)
|
|
{
|
|
if (!IsNativeDepth())
|
|
cb.ReleaseTemporaryRT(m_TempDepthId);
|
|
}
|
|
|
|
protected bool Alloc(FrameId frameId, Key key, uint width, uint height, out uint cachedEntryIdx, VectorArray<ShadowPayload> payload)
|
|
{
|
|
CachedEntry ce = null;
|
|
if (m_EntryPool.Count() > 0)
|
|
{
|
|
ce = m_EntryPool[m_EntryPool.Count() - 1];
|
|
m_EntryPool.Purge(1);
|
|
}
|
|
if (ce == null)
|
|
ce = new CachedEntry();
|
|
|
|
ce.key = key;
|
|
ce.current.frameId = frameId;
|
|
ce.current.contentHash = -1;
|
|
ce.current.slice = 0;
|
|
ce.current.viewport = new Rect(0, 0, width, height);
|
|
|
|
uint idx;
|
|
if (m_EntryCache.FindFirst(out idx, ref key, m_Comparator))
|
|
{
|
|
if (m_EntryCache[idx].current.viewport.width == width && m_EntryCache[idx].current.viewport.height == height)
|
|
{
|
|
ce.previous = m_EntryCache[idx].current;
|
|
m_EntryPool.Add(m_EntryCache[idx]);
|
|
m_EntryCache[idx] = ce;
|
|
cachedEntryIdx = m_ActiveEntriesCount;
|
|
m_EntryCache.SwapUnchecked(m_ActiveEntriesCount++, idx);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
m_EntryCache.SwapUnchecked(idx, m_EntryCache.Count() - 1);
|
|
m_EntryCache.Purge(1, m_Cleanup);
|
|
}
|
|
}
|
|
|
|
idx = m_EntryCache.Count();
|
|
m_EntryCache.Add(ce);
|
|
cachedEntryIdx = m_ActiveEntriesCount;
|
|
m_EntryCache.SwapUnchecked(m_ActiveEntriesCount++, idx);
|
|
return true;
|
|
}
|
|
|
|
protected bool Layout()
|
|
{
|
|
VectorArray<CachedEntry> tmp = m_EntryCache.Subrange(0, m_ActiveEntriesCount);
|
|
tmp.Sort();
|
|
|
|
float curx = 0, cury = 0, curh = 0, xmax = m_Width, ymax = m_Height;
|
|
uint curslice = 0;
|
|
|
|
for (uint i = 0; i < m_ActiveEntriesCount; ++i)
|
|
{
|
|
// shadow atlas layouting
|
|
CachedEntry ce = m_EntryCache[i];
|
|
Rect vp = ce.current.viewport;
|
|
curh = curh >= vp.height ? curh : vp.height;
|
|
|
|
if (curx + vp.width > xmax)
|
|
{
|
|
curx = 0;
|
|
cury += curh;
|
|
curh = vp.height;
|
|
}
|
|
if (curx + vp.width > xmax || cury + curh > ymax)
|
|
{
|
|
curslice++;
|
|
curx = 0;
|
|
cury = 0;
|
|
curh = vp.height;
|
|
}
|
|
if (curx + vp.width > xmax || cury + curh > ymax || curslice == m_Slices)
|
|
{
|
|
Debug.LogWarning("Shadow atlasing has failed.");
|
|
return false;
|
|
}
|
|
vp.x = curx;
|
|
vp.y = cury;
|
|
ce.current.viewport = vp;
|
|
ce.current.slice = curslice;
|
|
m_EntryCache[i] = ce;
|
|
curx += vp.width;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected void Free(CachedEntry ce)
|
|
{
|
|
m_EntryPool.Add(ce);
|
|
}
|
|
|
|
override public void DisplayShadowMap(CommandBuffer debugCB, Material debugMaterial, Vector4 scaleBias, uint slice, float screenX, float screenY, float screenSizeX, float screenSizeY, float minValue, float maxValue, bool flipY)
|
|
{
|
|
Vector4 validRange = new Vector4(minValue, 1.0f / (maxValue - minValue));
|
|
|
|
MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
|
|
propertyBlock.SetTexture("_AtlasTexture", m_Shadowmap);
|
|
propertyBlock.SetVector("_TextureScaleBias", scaleBias);
|
|
propertyBlock.SetFloat("_TextureSlice", (float)slice);
|
|
propertyBlock.SetVector("_ValidRange", validRange);
|
|
propertyBlock.SetFloat("_RequireToFlipInputTexture", flipY ? 1.0f : 0.0f);
|
|
debugCB.SetViewport(new Rect(screenX, screenY, screenSizeX, screenSizeY));
|
|
debugCB.DrawProcedural(Matrix4x4.identity, debugMaterial, debugMaterial.FindPass("REGULARSHADOW"), MeshTopology.Triangles, 3, 1, propertyBlock);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------------------------------
|
|
//
|
|
// ShadowVariance
|
|
//
|
|
// -------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Shadowmap supporting various flavors of variance shadow maps.
|
|
public class ShadowVariance : ShadowAtlas
|
|
{
|
|
protected const int k_MomentBlurThreadsPerWorkgroup = 16;
|
|
protected const int k_BlurKernelMinSize = 3;
|
|
protected const int k_BlurKernelDefSize = (7 - k_BlurKernelMinSize) / 2;
|
|
protected const int k_BlurKernelMaxSize = 17;
|
|
protected const int k_BlurKernelCount = k_BlurKernelMaxSize / 2;
|
|
protected const int k_MaxSampleCount = 8;
|
|
protected ComputeShader m_MomentBlurCS;
|
|
protected int[] m_KernelVSM = new int[k_BlurKernelCount];
|
|
protected int[] m_KernelEVSM_2 = new int[k_BlurKernelCount];
|
|
protected int[] m_KernelEVSM_4 = new int[k_BlurKernelCount];
|
|
protected int[] m_KernelMSM = new int[k_BlurKernelCount];
|
|
protected float[][] m_BlurWeights = new float[k_BlurKernelCount][];
|
|
protected int m_SampleCount;
|
|
|
|
[Flags]
|
|
protected enum Flags
|
|
{
|
|
bpp_16 = 1 << 0,
|
|
channels_2 = 1 << 1,
|
|
reversed_z = 1 << 2
|
|
}
|
|
protected readonly Flags m_Flags;
|
|
|
|
// default values
|
|
readonly ValRange m_DefVSM_LightLeakBias = new ValRange("Light leak bias" , 0.0f, 0.5f , 0.99f , 1.0f);
|
|
readonly ValRange m_DefVSM_VarianceBias = new ValRange("Variance bias" , 0.0f, 0.1f , 1.0f , 0.01f);
|
|
readonly ValRange m_DefEVSM_LightLeakBias = new ValRange("Light leak bias" , 0.0f, 0.0f , 0.99f , 1.0f);
|
|
readonly ValRange m_DefEVSM_VarianceBias = new ValRange("Variance bias" , 0.0f, 0.1f , 1.0f , 0.01f);
|
|
readonly ValRange m_DefEVSM_PosExponent_32 = new ValRange("Positive Exponent" , 1.0f, 1.0f , 42.0f , 1.0f);
|
|
readonly ValRange m_DefEVSM_NegExponent_32 = new ValRange("Negative Exponent" , 1.0f, 1.0f , 42.0f , 1.0f);
|
|
readonly ValRange m_DefEVSM_PosExponent_16 = new ValRange("Positive Exponent" , 1.0f, 1.0f , 5.54f , 1.0f);
|
|
readonly ValRange m_DefEVSM_NegExponent_16 = new ValRange("Negative Exponent" , 1.0f, 1.0f , 5.54f , 1.0f);
|
|
readonly ValRange m_DefMSM_LightLeakBias = new ValRange("Light leak bias" , 0.0f, 0.5f , 0.99f , 1.0f);
|
|
readonly ValRange m_DefMSM_MomentBias = new ValRange("Moment Bias" , 0.0f, 0.0f , 1.0f , 0.0001f);
|
|
readonly ValRange m_DefMSM_DepthBias = new ValRange("Depth Bias" , 0.0f, 0.1f , 1.0f , 0.1f);
|
|
|
|
public static RenderTextureFormat GetFormat(bool use_16_BitsPerChannel, bool use_2_Channels, bool use_MSM)
|
|
{
|
|
Debug.Assert(!use_2_Channels || !use_MSM); // MSM always requires 4 channels
|
|
if (use_16_BitsPerChannel)
|
|
{
|
|
return use_2_Channels ? RenderTextureFormat.RGHalf : (use_MSM ? RenderTextureFormat.ARGB64 : RenderTextureFormat.ARGBHalf);
|
|
}
|
|
else
|
|
{
|
|
return use_2_Channels ? RenderTextureFormat.RGFloat : RenderTextureFormat.ARGBFloat;
|
|
}
|
|
}
|
|
|
|
public ShadowVariance(ref AtlasInit init, int sampleCount) : base(ref init)
|
|
{
|
|
m_Flags |= (base.m_ShadowmapFormat == RenderTextureFormat.ARGBHalf || base.m_ShadowmapFormat == RenderTextureFormat.RGHalf || base.m_ShadowmapFormat == RenderTextureFormat.ARGB64) ? Flags.bpp_16 : 0;
|
|
m_Flags |= (base.m_ShadowmapFormat == RenderTextureFormat.RGFloat || base.m_ShadowmapFormat == RenderTextureFormat.RGHalf) ? Flags.channels_2 : 0;
|
|
m_Flags |= SystemInfo.usesReversedZBuffer ? Flags.reversed_z : 0;
|
|
|
|
m_SampleCount = sampleCount <= 0 ? 1 : (sampleCount > k_MaxSampleCount ? k_MaxSampleCount : sampleCount);
|
|
m_MomentBlurCS = init.shadowBlurMoments;
|
|
|
|
if (m_MomentBlurCS)
|
|
{
|
|
for (int i = 0, blurSize = 3; i < k_BlurKernelCount; ++i, blurSize += 2)
|
|
{
|
|
m_KernelVSM[i] = m_MomentBlurCS.FindKernel("main" + (m_SampleCount > 1 ? "_MSAA_" : "_") + "VSM_" + blurSize);
|
|
m_KernelEVSM_2[i] = m_MomentBlurCS.FindKernel("main" + (m_SampleCount > 1 ? "_MSAA_" : "_") + "EVSM_2_" + blurSize);
|
|
m_KernelEVSM_4[i] = m_MomentBlurCS.FindKernel("main" + (m_SampleCount > 1 ? "_MSAA_" : "_") + "EVSM_4_" + blurSize);
|
|
m_KernelMSM[i] = m_MomentBlurCS.FindKernel("main" + (m_SampleCount > 1 ? "_MSAA_" : "_") + "MSM_" + blurSize);
|
|
|
|
m_BlurWeights[i] = new float[2 + i];
|
|
FillBlurWeights(i);
|
|
}
|
|
}
|
|
|
|
// normalize blur weights
|
|
for (int i = 0; i < k_BlurKernelCount; ++i)
|
|
{
|
|
float weightSum = 0.0f;
|
|
for (int j = 0; j < m_BlurWeights[i].Length; ++j)
|
|
weightSum += m_BlurWeights[i][j];
|
|
weightSum = 1.0f / (2.0f * weightSum - m_BlurWeights[i][0]);
|
|
for (int j = 0; j < m_BlurWeights[i].Length; ++j)
|
|
m_BlurWeights[i][j] *= weightSum;
|
|
}
|
|
}
|
|
|
|
public ShadowVariance(ref AtlasInit init) : this(ref init, 1) {}
|
|
|
|
override protected void CreateShadowmap(RenderTexture shadowmap)
|
|
{
|
|
shadowmap.enableRandomWrite = true;
|
|
base.CreateShadowmap(shadowmap);
|
|
}
|
|
|
|
private void FillBlurWeights(int idx)
|
|
{
|
|
if (idx == 0)
|
|
{
|
|
m_BlurWeights[0][0] = 2.0f;
|
|
m_BlurWeights[0][1] = 1.0f;
|
|
return;
|
|
}
|
|
|
|
float[] prev = m_BlurWeights[idx - 1];
|
|
float[] cur = m_BlurWeights[idx];
|
|
int prevSize = prev.Length;
|
|
for (int i = 0; i < prevSize; ++i)
|
|
{
|
|
cur[i] = prev[Math.Abs(i - 1)] + prev[i] * 2.0f + (i == (prevSize - 1) ? 0.0f : prev[i + 1]);
|
|
}
|
|
cur[cur.Length - 1] = 1.0f;
|
|
}
|
|
|
|
private void BlurSlider(ref int currentVal)
|
|
{
|
|
#if UNITY_EDITOR
|
|
currentVal = k_BlurKernelMinSize + currentVal * 2;
|
|
currentVal = (int)Math.Round(UnityEditor.EditorGUILayout.Slider("Blur Size", currentVal, k_BlurKernelMinSize, k_BlurKernelMaxSize));
|
|
currentVal = (currentVal - k_BlurKernelMinSize) / 2;
|
|
#endif
|
|
}
|
|
|
|
override protected void Register(GPUShadowType type, ShadowRegistry registry)
|
|
{
|
|
bool supports_VSM = m_ShadowmapFormat != RenderTextureFormat.ARGB64;
|
|
bool supports_EVSM_2 = m_ShadowmapFormat != RenderTextureFormat.ARGB64;
|
|
bool supports_EVSM_4 = m_ShadowmapFormat != RenderTextureFormat.ARGB64 && (m_Flags & Flags.channels_2) == 0;
|
|
bool supports_MSM = (m_Flags & Flags.channels_2) == 0 && ((m_Flags & Flags.bpp_16) == 0 || m_ShadowmapFormat == RenderTextureFormat.ARGB64);
|
|
|
|
ShadowRegistry.VariantDelegate vsmDel = (Light l, ShadowAlgorithm dataAlgorithm, ShadowVariant dataVariant, ShadowPrecision dataPrecision, ref int[] dataBlock) =>
|
|
{
|
|
CheckDataIntegrity(dataAlgorithm, dataVariant, dataPrecision, ref dataBlock);
|
|
|
|
m_DefVSM_LightLeakBias.Slider(ref dataBlock[0]);
|
|
m_DefVSM_VarianceBias.Slider(ref dataBlock[1]);
|
|
BlurSlider(ref dataBlock[2]);
|
|
};
|
|
|
|
ShadowRegistry.VariantDelegate evsmDel = (Light l, ShadowAlgorithm dataAlgorithm, ShadowVariant dataVariant, ShadowPrecision dataPrecision, ref int[] dataBlock) =>
|
|
{
|
|
CheckDataIntegrity(dataAlgorithm, dataVariant, dataPrecision, ref dataBlock);
|
|
|
|
m_DefEVSM_LightLeakBias.Slider(ref dataBlock[0]);
|
|
m_DefEVSM_VarianceBias.Slider(ref dataBlock[1]);
|
|
if ((m_Flags & Flags.bpp_16) != 0)
|
|
m_DefEVSM_PosExponent_16.Slider(ref dataBlock[2]);
|
|
else
|
|
m_DefEVSM_PosExponent_32.Slider(ref dataBlock[2]);
|
|
if (dataVariant == ShadowVariant.V1)
|
|
{
|
|
if ((m_Flags & Flags.bpp_16) != 0)
|
|
m_DefEVSM_NegExponent_16.Slider(ref dataBlock[3]);
|
|
else
|
|
m_DefEVSM_NegExponent_32.Slider(ref dataBlock[3]);
|
|
}
|
|
BlurSlider(ref dataBlock[4]);
|
|
};
|
|
|
|
ShadowRegistry.VariantDelegate msmDel = (Light l, ShadowAlgorithm dataAlgorithm, ShadowVariant dataVariant, ShadowPrecision dataPrecision, ref int[] dataBlock) =>
|
|
{
|
|
CheckDataIntegrity(dataAlgorithm, dataVariant, dataPrecision, ref dataBlock);
|
|
|
|
m_DefMSM_LightLeakBias.Slider(ref dataBlock[0]);
|
|
m_DefMSM_MomentBias.Slider(ref dataBlock[1]);
|
|
m_DefMSM_DepthBias.Slider(ref dataBlock[2]);
|
|
BlurSlider(ref dataBlock[3]);
|
|
};
|
|
|
|
ShadowPrecision precision = (m_Flags & Flags.bpp_16) == 0 ? ShadowPrecision.High : ShadowPrecision.Low;
|
|
m_SupportedAlgorithms.Reserve(5);
|
|
if (supports_VSM)
|
|
{
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.VSM, ShadowVariant.V0, precision));
|
|
registry.Register(type, precision, ShadowAlgorithm.VSM, "Variance shadow map (VSM)",
|
|
new ShadowVariant[] { ShadowVariant.V0 }, new string[] { "2 moments" }, new ShadowRegistry.VariantDelegate[] { vsmDel });
|
|
}
|
|
if (supports_EVSM_2 && !supports_EVSM_4)
|
|
{
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.EVSM, ShadowVariant.V0, precision));
|
|
registry.Register(type, precision, ShadowAlgorithm.EVSM, "Exponential variance shadow map (EVSM)",
|
|
new ShadowVariant[] { ShadowVariant.V0 }, new string[] { "2 moments" }, new ShadowRegistry.VariantDelegate[] { evsmDel });
|
|
}
|
|
if (supports_EVSM_4)
|
|
{
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.EVSM, ShadowVariant.V0, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.EVSM, ShadowVariant.V1, precision));
|
|
registry.Register(type, precision, ShadowAlgorithm.EVSM, "Exponential variance shadow map (EVSM)",
|
|
new ShadowVariant[] { ShadowVariant.V0, ShadowVariant.V1 }, new string[] { "2 moments", "4 moments" }, new ShadowRegistry.VariantDelegate[] { evsmDel, evsmDel });
|
|
}
|
|
if (supports_MSM)
|
|
{
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.MSM, ShadowVariant.V0, precision));
|
|
m_SupportedAlgorithms.AddUniqueUnchecked((int)ShadowUtils.Pack(ShadowAlgorithm.MSM, ShadowVariant.V1, precision));
|
|
registry.Register(type, precision, ShadowAlgorithm.MSM, "Moment shadow map (MSM)",
|
|
new ShadowVariant[] { ShadowVariant.V0, ShadowVariant.V1 }, new string[] { "Hamburg", "Hausdorff" }, new ShadowRegistry.VariantDelegate[] { msmDel, msmDel });
|
|
}
|
|
}
|
|
|
|
override protected bool CheckDataIntegrity(ShadowAlgorithm algorithm, ShadowVariant variant, ShadowPrecision precision, ref int[] dataBlock)
|
|
{
|
|
switch (algorithm)
|
|
{
|
|
case ShadowAlgorithm.VSM:
|
|
{
|
|
const int k_BlockSize = 3;
|
|
if (dataBlock == null || dataBlock.Length != k_BlockSize)
|
|
{
|
|
// set defaults
|
|
dataBlock = new int[k_BlockSize];
|
|
dataBlock[0] = m_DefVSM_LightLeakBias.Default();
|
|
dataBlock[1] = m_DefVSM_VarianceBias.Default();
|
|
dataBlock[2] = k_BlurKernelDefSize;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
case ShadowAlgorithm.EVSM:
|
|
{
|
|
const int k_BlockSize = 5;
|
|
if (dataBlock == null || dataBlock.Length != k_BlockSize)
|
|
{
|
|
// set defaults
|
|
dataBlock = new int[k_BlockSize];
|
|
dataBlock[0] = m_DefEVSM_LightLeakBias.Default();
|
|
dataBlock[1] = m_DefEVSM_VarianceBias.Default();
|
|
dataBlock[2] = (m_Flags & Flags.bpp_16) != 0 ? m_DefEVSM_PosExponent_16.Default() : m_DefEVSM_PosExponent_32.Default();
|
|
dataBlock[3] = (m_Flags & Flags.bpp_16) != 0 ? m_DefEVSM_NegExponent_16.Default() : m_DefEVSM_NegExponent_32.Default();
|
|
dataBlock[4] = k_BlurKernelDefSize;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
case ShadowAlgorithm.MSM:
|
|
{
|
|
const int k_BlockSize = 4;
|
|
if (dataBlock == null || dataBlock.Length != k_BlockSize)
|
|
{
|
|
// set defaults
|
|
dataBlock = new int[k_BlockSize];
|
|
dataBlock[0] = m_DefMSM_LightLeakBias.Default();
|
|
dataBlock[1] = m_DefMSM_MomentBias.Default();
|
|
dataBlock[2] = m_DefMSM_DepthBias.Default();
|
|
dataBlock[3] = k_BlurKernelDefSize;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
default: return base.CheckDataIntegrity(algorithm, variant, precision, ref dataBlock);
|
|
}
|
|
}
|
|
|
|
override protected uint ReservePayload(ShadowRequest sr)
|
|
{
|
|
uint cnt = base.ReservePayload(sr);
|
|
switch (ShadowUtils.ExtractAlgorithm(sr.shadowAlgorithm))
|
|
{
|
|
case ShadowAlgorithm.VSM: return cnt + 1;
|
|
case ShadowAlgorithm.EVSM: return cnt + 1;
|
|
case ShadowAlgorithm.MSM: return cnt + 1;
|
|
default: return cnt;
|
|
}
|
|
}
|
|
|
|
override protected float GetFilterWidthInTexels(ShadowRequest sr, AdditionalShadowData asd)
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
// Writes additional per light data into the payload vector. Make sure to call base.WritePerLightPayload first.
|
|
override protected void WritePerLightPayload(List<VisibleLight> lights, ShadowRequest sr, ref ShadowData sd, ref ShadowPayloadVector payload, ref uint payloadOffset)
|
|
{
|
|
base.WritePerLightPayload(lights, sr, ref sd, ref payload, ref payloadOffset);
|
|
|
|
AdditionalShadowData asd = lights[sr.index].light.GetComponent<AdditionalShadowData>();
|
|
if (!asd)
|
|
return;
|
|
|
|
ShadowPayload sp = new ShadowPayload();
|
|
int shadowDataFormat;
|
|
int[] shadowData = asd.GetShadowData(out shadowDataFormat);
|
|
if (shadowData == null)
|
|
return;
|
|
|
|
ShadowAlgorithm algo;
|
|
ShadowVariant vari;
|
|
ShadowPrecision prec;
|
|
ShadowUtils.Unpack(sr.shadowAlgorithm, out algo, out vari, out prec);
|
|
CheckDataIntegrity(algo, vari, prec, ref shadowData);
|
|
|
|
switch (algo)
|
|
{
|
|
case ShadowAlgorithm.VSM:
|
|
{
|
|
sp.p0 = shadowData[0];
|
|
sp.p1 = shadowData[1];
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
}
|
|
break;
|
|
case ShadowAlgorithm.EVSM:
|
|
{
|
|
sp.p0 = shadowData[0];
|
|
sp.p1 = shadowData[1];
|
|
sp.p2 = shadowData[2];
|
|
sp.p3 = shadowData[3];
|
|
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
}
|
|
break;
|
|
case ShadowAlgorithm.MSM:
|
|
{
|
|
sp.Set(shadowData[0], shadowData[1], shadowData[2] | (SystemInfo.usesReversedZBuffer ? 1 : 0), (m_Flags & Flags.bpp_16) != 0 ? 0x3f800000 : 0);
|
|
payload[payloadOffset] = sp;
|
|
payloadOffset++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
override protected void PreUpdate(FrameId frameId, CommandBuffer cb, uint rendertargetSlice)
|
|
{
|
|
cb.SetRenderTarget(m_ShadowmapId, 0, (CubemapFace)0, (int)rendertargetSlice);
|
|
|
|
RenderTextureDescriptor desc = new RenderTextureDescriptor((int)m_Width, (int)m_Height, RenderTextureFormat.Shadowmap, (int)m_ShadowmapBits);
|
|
desc.autoGenerateMips = false;
|
|
desc.enableRandomWrite = false;
|
|
desc.msaaSamples = m_SampleCount;
|
|
desc.shadowSamplingMode = ShadowSamplingMode.RawDepth;
|
|
desc.useMipMap = false;
|
|
desc.bindMS = true;
|
|
cb.GetTemporaryRT(m_TempDepthId, desc);
|
|
cb.SetRenderTarget(new RenderTargetIdentifier(m_TempDepthId));
|
|
cb.ClearRenderTarget(true, true, m_ClearColor);
|
|
}
|
|
|
|
protected override void PostUpdate(FrameId frameId, CommandBuffer cb, uint rendertargetSlice, List<VisibleLight> lights)
|
|
{
|
|
if (rendertargetSlice == uint.MaxValue)
|
|
{
|
|
base.PostUpdate(frameId, cb, rendertargetSlice, lights);
|
|
return;
|
|
}
|
|
|
|
uint cnt = m_EntryCache.Count();
|
|
uint i = 0;
|
|
while (i < cnt && m_EntryCache[i].current.slice < rendertargetSlice)
|
|
{
|
|
i++;
|
|
}
|
|
if (i >= cnt || m_EntryCache[i].current.slice > rendertargetSlice)
|
|
return;
|
|
|
|
|
|
cb.BeginSample("VSM conversion");
|
|
|
|
int kernelIdx = 2;
|
|
int currentKernel = 0;
|
|
|
|
for (int j = 0; j < k_BlurKernelCount; ++j)
|
|
{
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelVSM[j] , "depthTex" , new RenderTargetIdentifier(m_TempDepthId));
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelVSM[j] , "outputTex", m_ShadowmapId);
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelEVSM_2[j], "depthTex" , new RenderTargetIdentifier(m_TempDepthId));
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelEVSM_2[j], "outputTex", m_ShadowmapId);
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelEVSM_4[j], "depthTex" , new RenderTargetIdentifier(m_TempDepthId));
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelEVSM_4[j], "outputTex", m_ShadowmapId);
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelMSM[j] , "depthTex" , new RenderTargetIdentifier(m_TempDepthId));
|
|
cb.SetComputeTextureParam(m_MomentBlurCS, m_KernelMSM[j] , "outputTex", m_ShadowmapId);
|
|
}
|
|
|
|
while (i < cnt && m_EntryCache[i].current.slice == rendertargetSlice)
|
|
{
|
|
AdditionalShadowData asd = lights[m_EntryCache[i].key.visibleIdx].light.GetComponent<AdditionalShadowData>();
|
|
int shadowDataFormat;
|
|
int[] shadowData = asd.GetShadowData(out shadowDataFormat);
|
|
|
|
ShadowAlgorithm algo;
|
|
ShadowVariant vari;
|
|
ShadowPrecision prec;
|
|
ShadowUtils.Unpack(m_EntryCache[i].current.shadowAlgo, out algo, out vari, out prec);
|
|
switch (algo)
|
|
{
|
|
case ShadowAlgorithm.VSM:
|
|
{
|
|
kernelIdx = shadowData[2];
|
|
currentKernel = m_KernelVSM[kernelIdx];
|
|
}
|
|
break;
|
|
case ShadowAlgorithm.EVSM:
|
|
{
|
|
if (vari == ShadowVariant.V0)
|
|
{
|
|
float evsmExponent1 = ShadowUtils.Asfloat(shadowData[2]);
|
|
cb.SetComputeFloatParam(m_MomentBlurCS, "evsmExponent", evsmExponent1);
|
|
kernelIdx = shadowData[4];
|
|
currentKernel = m_KernelEVSM_2[kernelIdx];
|
|
}
|
|
else
|
|
{
|
|
float evsmExponent1 = ShadowUtils.Asfloat(shadowData[2]);
|
|
float evsmExponent2 = ShadowUtils.Asfloat(shadowData[3]);
|
|
cb.SetComputeFloatParams(m_MomentBlurCS, "evsmExponents", new float[] { evsmExponent1, evsmExponent2 });
|
|
kernelIdx = shadowData[4];
|
|
currentKernel = m_KernelEVSM_4[kernelIdx];
|
|
}
|
|
}
|
|
break;
|
|
case ShadowAlgorithm.MSM:
|
|
{
|
|
kernelIdx = shadowData[3];
|
|
currentKernel = m_KernelMSM[kernelIdx];
|
|
}
|
|
break;
|
|
default: Debug.LogError("Unknown shadow algorithm selected for moment type shadow maps."); break;
|
|
}
|
|
// TODO: Need a check here whether the shadowmap actually got updated, but right now that's queried on the cullResults.
|
|
Rect r = m_EntryCache[i].current.viewport;
|
|
cb.SetComputeIntParams(m_MomentBlurCS, "srcRect", new int[] { (int)r.x, (int)r.y, (int)r.width, (int)r.height });
|
|
cb.SetComputeIntParams(m_MomentBlurCS, "dstRect", new int[] { (int)r.x, (int)r.y, (int)rendertargetSlice, (int)m_Flags });
|
|
cb.SetComputeFloatParams(m_MomentBlurCS, "blurWeightsStorage", m_BlurWeights[kernelIdx]);
|
|
cb.DispatchCompute(m_MomentBlurCS, currentKernel, ((int)r.width) / k_MomentBlurThreadsPerWorkgroup, (int)r.height / k_MomentBlurThreadsPerWorkgroup, 1);
|
|
i++;
|
|
}
|
|
base.PostUpdate(frameId, cb, rendertargetSlice, lights);
|
|
cb.EndSample("VSM conversion");
|
|
}
|
|
|
|
override public void DisplayShadowMap(CommandBuffer debugCB, Material debugMaterial, Vector4 scaleBias, uint slice, float screenX, float screenY, float screenSizeX, float screenSizeY, float minValue, float maxValue, bool flipY)
|
|
{
|
|
Vector4 validRange = new Vector4(minValue, 1.0f / (maxValue - minValue));
|
|
|
|
MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
|
|
propertyBlock.SetTexture("_AtlasTexture", m_Shadowmap);
|
|
propertyBlock.SetVector("_TextureScaleBias", scaleBias);
|
|
propertyBlock.SetFloat("_TextureSlice", (float)slice);
|
|
propertyBlock.SetVector("_ValidRange", validRange);
|
|
propertyBlock.SetFloat("_RequireToFlipInputTexture", flipY ? 1.0f : 0.0f);
|
|
debugCB.SetViewport(new Rect(screenX, screenY, screenSizeX, screenSizeY));
|
|
debugCB.DrawProcedural(Matrix4x4.identity, debugMaterial, debugMaterial.FindPass("VARIANCESHADOW"), MeshTopology.Triangles, 3, 1, propertyBlock);
|
|
}
|
|
}
|
|
// -------------------------------------------------------------------------------------------------------------------------------------------------
|
|
//
|
|
// ShadowManager
|
|
//
|
|
// -------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
// Standard shadow manager
|
|
public class ShadowManager : ShadowManagerBase
|
|
{
|
|
protected class ShadowContextAccess : ShadowContext
|
|
{
|
|
public ShadowContextAccess(ref ShadowContext.CtxtInit initializer) : base(ref initializer) {}
|
|
// unfortunately ref returns are only a C# 7.0 feature
|
|
public VectorArray<ShadowData> shadowDatas { get { return m_ShadowDatas; } set { m_ShadowDatas = value; } }
|
|
public VectorArray<ShadowPayload> payloads { get { return m_Payloads; } set { m_Payloads = value; } }
|
|
}
|
|
|
|
public struct ShadowBudgets
|
|
{
|
|
public int maxPointLights;
|
|
public int maxSpotLights;
|
|
public int maxDirectionalLights;
|
|
}
|
|
|
|
private const int k_MaxShadowmapPerType = 4;
|
|
private ShadowSettings m_ShadowSettings;
|
|
private ShadowmapBase[] m_Shadowmaps;
|
|
private ShadowmapBase[,] m_ShadowmapsPerType = new ShadowmapBase[(int)GPUShadowType.MAX, k_MaxShadowmapPerType];
|
|
private ShadowContextAccess m_ShadowCtxt;
|
|
private int[,] m_MaxShadows = new int[(int)GPUShadowType.MAX, 2];
|
|
// The following vectors are just temporary helpers to avoid reallocation each frame. Contents are not stable.
|
|
private VectorArray<long> m_TmpSortKeys = new VectorArray<long>(0, false);
|
|
private ShadowRequestVector m_TmpRequests = new ShadowRequestVector(0, false);
|
|
// The following vector holds data that are returned to the caller so it can be sent to GPU memory in some form. Contents are stable in between calls to ProcessShadowRequests.
|
|
private ShadowIndicesVector m_ShadowIndices = new ShadowIndicesVector(0, false);
|
|
private CustomSampler m_SamplerRenderShadows = CustomSampler.Create("RenderShadows");
|
|
|
|
public override uint GetShadowMapCount()
|
|
{
|
|
return (uint)m_Shadowmaps.Length;
|
|
}
|
|
|
|
public override uint GetShadowMapSliceCount(uint shadowMapIndex)
|
|
{
|
|
if (shadowMapIndex >= m_Shadowmaps.Length)
|
|
return 0;
|
|
|
|
return m_Shadowmaps[shadowMapIndex].slices;
|
|
}
|
|
|
|
public override uint GetShadowRequestCount()
|
|
{
|
|
return m_TmpRequests.Count();
|
|
}
|
|
|
|
public override uint GetShadowRequestFaceCount(uint requestIndex)
|
|
{
|
|
if (requestIndex >= (int)m_TmpRequests.Count())
|
|
return 0;
|
|
else
|
|
return m_TmpRequests[requestIndex].facecount;
|
|
}
|
|
|
|
public override int GetShadowRequestIndex(Light light)
|
|
{
|
|
for (int i = 0; i < m_TmpRequests.Count(); ++i)
|
|
{
|
|
if (m_TmpRequests[(uint)i].instanceId == light.GetInstanceID())
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public ShadowManager(ShadowSettings shadowSettings, ref ShadowContext.CtxtInit ctxtInitializer, ShadowmapBase[] shadowmaps)
|
|
{
|
|
m_ShadowSettings = shadowSettings;
|
|
m_ShadowCtxt = new ShadowContextAccess(ref ctxtInitializer);
|
|
|
|
Debug.Assert(shadowmaps != null && shadowmaps.Length > 0);
|
|
m_Shadowmaps = shadowmaps;
|
|
foreach (var sm in shadowmaps)
|
|
{
|
|
sm.CreateShadowmap();
|
|
sm.Register(this);
|
|
sm.ReserveSlots(m_ShadowCtxt);
|
|
ShadowmapBase.ShadowSupport smsupport = sm.QueryShadowSupport();
|
|
for (int i = 0, bit = 1; i < (int)GPUShadowType.MAX; ++i, bit <<= 1)
|
|
{
|
|
if (((int)smsupport & bit) == 0)
|
|
continue;
|
|
|
|
for (int idx = 0; i < k_MaxShadowmapPerType; ++idx)
|
|
{
|
|
if (m_ShadowmapsPerType[i, idx] == null)
|
|
{
|
|
m_ShadowmapsPerType[i, idx] = sm;
|
|
break;
|
|
}
|
|
}
|
|
Debug.Assert(m_ShadowmapsPerType[i, k_MaxShadowmapPerType - 1] == null || m_ShadowmapsPerType[i, k_MaxShadowmapPerType - 1] == sm,
|
|
"Only up to " + k_MaxShadowmapPerType + " are allowed per light type. If more are needed then increase ShadowManager.k_MaxShadowmapPerType");
|
|
}
|
|
}
|
|
|
|
m_MaxShadows[(int)GPUShadowType.Point , 0] = m_MaxShadows[(int)GPUShadowType.Point , 1] = 4;
|
|
m_MaxShadows[(int)GPUShadowType.Spot , 0] = m_MaxShadows[(int)GPUShadowType.Spot , 1] = 8;
|
|
m_MaxShadows[(int)GPUShadowType.Directional, 0] = m_MaxShadows[(int)GPUShadowType.Directional , 1] = 2;
|
|
|
|
#if UNITY_EDITOR
|
|
// and register itself
|
|
AdditionalShadowDataEditor.SetRegistry(this);
|
|
#endif
|
|
}
|
|
|
|
public ShadowManager(ShadowSettings shadowSettings, ref ShadowContext.CtxtInit ctxtInitializer, ref ShadowBudgets budgets, ShadowmapBase[] shadowmaps) : this(shadowSettings, ref ctxtInitializer, shadowmaps)
|
|
{
|
|
SetPerFrameBudgets(ref budgets);
|
|
}
|
|
|
|
public void SetPerFrameBudgets(ref ShadowBudgets budgets)
|
|
{
|
|
m_MaxShadows[(int)GPUShadowType.Point , 0] = m_MaxShadows[(int)GPUShadowType.Point , 1] = budgets.maxPointLights;
|
|
m_MaxShadows[(int)GPUShadowType.Spot , 0] = m_MaxShadows[(int)GPUShadowType.Spot , 1] = budgets.maxSpotLights;
|
|
m_MaxShadows[(int)GPUShadowType.Directional, 0] = m_MaxShadows[(int)GPUShadowType.Directional , 1] = budgets.maxDirectionalLights;
|
|
}
|
|
|
|
public override void UpdateCullingParameters(ref ScriptableCullingParameters cullingParams)
|
|
{
|
|
cullingParams.shadowDistance = Mathf.Min(m_ShadowSettings.maxShadowDistance, cullingParams.shadowDistance);
|
|
}
|
|
|
|
public override void ProcessShadowRequests(FrameId frameId, CullResults cullResults, Camera camera, bool cameraRelativeRendering, List<VisibleLight> lights, ref uint shadowRequestsCount, int[] shadowRequests, out int[] shadowDataIndices)
|
|
{
|
|
shadowDataIndices = null;
|
|
|
|
// TODO:
|
|
// Cached the cullResults here so we don't need to pass them around.
|
|
// Allocate needs to pass them to the shadowmaps, as the ShadowUtil functions calculating view/proj matrices need them to call into C++ land.
|
|
// Ideally we can get rid of that at some point, then we wouldn't need to cache them here, anymore.
|
|
foreach (var sm in m_Shadowmaps)
|
|
{
|
|
sm.Assign(cullResults);
|
|
}
|
|
|
|
if (shadowRequestsCount == 0 || lights == null || shadowRequests == null)
|
|
{
|
|
shadowRequestsCount = 0;
|
|
return;
|
|
}
|
|
|
|
// first sort the shadow casters according to some priority
|
|
PrioritizeShadowCasters(camera, lights, shadowRequestsCount, shadowRequests);
|
|
|
|
// next prune them based on some logic
|
|
VectorArray<int> requestedShadows = new VectorArray<int>(shadowRequests, 0, shadowRequestsCount, false);
|
|
m_TmpRequests.Reset(shadowRequestsCount);
|
|
uint totalGranted;
|
|
PruneShadowCasters(camera, lights, ref requestedShadows, ref m_TmpRequests, out totalGranted);
|
|
|
|
// if there are no shadow casters at this point -> bail
|
|
if (totalGranted == 0)
|
|
{
|
|
shadowRequestsCount = 0;
|
|
return;
|
|
}
|
|
|
|
// TODO: Now would be a good time to kick off the culling jobs for the granted requests - but there's no way to control that at the moment.
|
|
|
|
// finally go over the lights deemed shadow casters and try to fit them into the shadow map
|
|
// shadowmap allocation must succeed at this point.
|
|
m_ShadowCtxt.ClearData();
|
|
ShadowDataVector shadowVector = m_ShadowCtxt.shadowDatas;
|
|
ShadowPayloadVector payloadVector = m_ShadowCtxt.payloads;
|
|
m_ShadowIndices.Reset(m_TmpRequests.Count());
|
|
if (!AllocateShadows(frameId, camera, cameraRelativeRendering, lights, totalGranted, ref m_TmpRequests, ref m_ShadowIndices, ref shadowVector, ref payloadVector))
|
|
{
|
|
shadowRequestsCount = 0;
|
|
return;
|
|
}
|
|
Debug.Assert(m_TmpRequests.Count() == m_ShadowIndices.Count());
|
|
m_ShadowCtxt.shadowDatas = shadowVector;
|
|
m_ShadowCtxt.payloads = payloadVector;
|
|
|
|
// and set the output parameters
|
|
uint offset;
|
|
shadowDataIndices = m_ShadowIndices.AsArray(out offset, out shadowRequestsCount);
|
|
}
|
|
|
|
public class SortReverter : System.Collections.Generic.IComparer<long>
|
|
{
|
|
public int Compare(long lhs, long rhs)
|
|
{
|
|
return rhs.CompareTo(lhs);
|
|
}
|
|
}
|
|
|
|
private readonly SortReverter m_SortReverter = new SortReverter();
|
|
private readonly VectorArray<long>.Extractor<int> m_Extractor = delegate(long key) { return (int)(key & 0xffffffff); };
|
|
|
|
protected override void PrioritizeShadowCasters(Camera camera, List<VisibleLight> lights, uint shadowRequestsCount, int[] shadowRequests)
|
|
{
|
|
// this function simply looks at the projected area on the screen, ignoring all light types and shapes
|
|
m_TmpSortKeys.Reset(shadowRequestsCount);
|
|
|
|
for (int i = 0; i < shadowRequestsCount; ++i)
|
|
{
|
|
int vlidx = shadowRequests[i];
|
|
VisibleLight vl = lights[vlidx];
|
|
Light l = vl.light;
|
|
|
|
// use the screen rect as a measure of importance
|
|
float area = vl.screenRect.width * vl.screenRect.height;
|
|
long val = ShadowUtils.Asint(area);
|
|
val <<= 32;
|
|
val |= (uint)vlidx;
|
|
m_TmpSortKeys.AddUnchecked(val);
|
|
}
|
|
|
|
m_TmpSortKeys.Sort(m_SortReverter);
|
|
m_TmpSortKeys.ExtractTo(shadowRequests, 0, out shadowRequestsCount, m_Extractor);
|
|
}
|
|
|
|
protected override void PruneShadowCasters(Camera camera, List<VisibleLight> lights, ref VectorArray<int> shadowRequests, ref ShadowRequestVector requestsGranted, out uint totalRequestCount)
|
|
{
|
|
Debug.Assert(shadowRequests.Count() > 0);
|
|
// at this point the array is sorted in order of some importance determined by the prioritize function
|
|
requestsGranted.Reserve(shadowRequests.Count());
|
|
totalRequestCount = 0;
|
|
Vector3 campos = new Vector3(camera.transform.position.x, camera.transform.position.y, camera.transform.position.z);
|
|
|
|
ShadowmapBase.ShadowRequest sreq = new ShadowmapBase.ShadowRequest();
|
|
uint totalSlots = ResetMaxShadows();
|
|
// there's a 1:1 mapping between the index in the shadowRequests array and the element in requestsGranted at the same index.
|
|
// if the prune function skips requests it must make sure that the array is still compact
|
|
m_TmpSortKeys.Reset(shadowRequests.Count());
|
|
for (uint i = 0, count = shadowRequests.Count(); i < count && totalSlots > 0; ++i)
|
|
{
|
|
int requestIdx = shadowRequests[i];
|
|
VisibleLight vl = lights[requestIdx];
|
|
int facecount = 0;
|
|
GPUShadowType shadowType = GetShadowLightType(vl.light);
|
|
|
|
AdditionalShadowData asd = vl.light.GetComponent<AdditionalShadowData>();
|
|
Vector3 lpos = vl.light.transform.position;
|
|
float distToCam = (campos - lpos).magnitude;
|
|
bool add = shadowType != GPUShadowType.Unknown && (distToCam < asd.shadowFadeDistance || vl.lightType == LightType.Directional) && m_ShadowSettings.enabled;
|
|
|
|
if (add)
|
|
{
|
|
switch (vl.lightType)
|
|
{
|
|
case LightType.Directional:
|
|
add = --m_MaxShadows[(int)GPUShadowType.Directional, 0] >= 0;
|
|
facecount = asd.cascadeCount;
|
|
break;
|
|
case LightType.Point:
|
|
add = --m_MaxShadows[(int)GPUShadowType.Point, 0] >= 0;
|
|
facecount = 6;
|
|
break;
|
|
case LightType.Spot:
|
|
add = --m_MaxShadows[(int)GPUShadowType.Spot, 0] >= 0;
|
|
facecount = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (add)
|
|
{
|
|
sreq.instanceId = vl.light.GetInstanceID();
|
|
sreq.index = requestIdx;
|
|
sreq.facemask = (uint)(1 << facecount) - 1;
|
|
sreq.shadowType = shadowType;
|
|
|
|
int sa, sv, sp;
|
|
asd.GetShadowAlgorithm(out sa, out sv, out sp);
|
|
GPUShadowAlgorithm packed_algo = ShadowUtils.Pack((ShadowAlgorithm)sa, (ShadowVariant)sv, (ShadowPrecision)sp);
|
|
GetGlobalShadowOverride(shadowType, ref packed_algo);
|
|
sreq.shadowAlgorithm = packed_algo;
|
|
totalRequestCount += (uint)facecount;
|
|
requestsGranted.AddUnchecked(sreq);
|
|
totalSlots--;
|
|
}
|
|
else
|
|
m_TmpSortKeys.AddUnchecked(requestIdx);
|
|
}
|
|
// make sure that shadowRequests contains all light indices that are going to cast a shadow first, then the rest
|
|
shadowRequests.Reset();
|
|
requestsGranted.ExtractTo(ref shadowRequests, (ShadowmapBase.ShadowRequest request) => { return (int)request.index; });
|
|
m_TmpSortKeys.ExtractTo(ref shadowRequests, (long idx) => { return (int)idx; });
|
|
}
|
|
|
|
protected override bool AllocateShadows(FrameId frameId, Camera camera, bool cameraRelativeRendering, List<VisibleLight> lights, uint totalGranted, ref ShadowRequestVector grantedRequests, ref ShadowIndicesVector shadowIndices, ref ShadowDataVector shadowDatas, ref ShadowPayloadVector shadowmapPayload)
|
|
{
|
|
ShadowData sd = new ShadowData();
|
|
shadowDatas.Reserve(totalGranted);
|
|
shadowIndices.Reserve(grantedRequests.Count());
|
|
for (uint i = 0, cnt = grantedRequests.Count(); i < cnt; ++i)
|
|
{
|
|
Light l = lights[grantedRequests[i].index].light;
|
|
AdditionalShadowData asd = l.GetComponent<AdditionalShadowData>();
|
|
|
|
// set light specific values that are not related to the shadowmap
|
|
GPUShadowType shadowtype = GetShadowLightType(l);
|
|
|
|
shadowIndices.AddUnchecked((int)shadowDatas.Count());
|
|
|
|
int smidx = 0;
|
|
while (smidx < k_MaxShadowmapPerType)
|
|
{
|
|
if (m_ShadowmapsPerType[(int)shadowtype, smidx] != null && m_ShadowmapsPerType[(int)shadowtype, smidx].Reserve(frameId, camera, cameraRelativeRendering, ref sd, grantedRequests[i], (uint)asd.shadowResolution, (uint)asd.shadowResolution, ref shadowDatas, ref shadowmapPayload, lights))
|
|
break;
|
|
smidx++;
|
|
}
|
|
if (smidx == k_MaxShadowmapPerType)
|
|
{
|
|
Debug.LogWarning("The requested shadows do not fit into any shadowmap.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// final step for shadowmaps that only gather data during the previous loop and do the actual allocation once they have all the data.
|
|
foreach (var sm in m_Shadowmaps)
|
|
{
|
|
if (!sm.ReserveFinalize(frameId, ref shadowDatas, ref shadowmapPayload))
|
|
{
|
|
Debug.LogWarning("Shadow allocation failed in the ReserveFinalize step.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void RenderShadows(FrameId frameId, ScriptableRenderContext renderContext, CommandBuffer cmd, CullResults cullResults, List<VisibleLight> lights)
|
|
{
|
|
using (new ProfilingSample(cmd, "Render Shadows", m_SamplerRenderShadows))
|
|
{
|
|
foreach (var sm in m_Shadowmaps)
|
|
{
|
|
sm.Update(frameId, renderContext, cmd, cullResults, lights);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void DisplayShadow(CommandBuffer cmd, Material debugMaterial, int shadowRequestIndex, uint faceIndex, float screenX, float screenY, float screenSizeX, float screenSizeY, float minValue, float maxValue, bool flipY)
|
|
{
|
|
if (m_ShadowIndices.Count() == 0)
|
|
return;
|
|
|
|
uint index = Math.Max(0, Math.Min((uint)(m_ShadowIndices.Count() - 1), (uint)shadowRequestIndex));
|
|
int offset = (m_TmpRequests[index].facecount > 1) ? 1 : 0;
|
|
VectorArray<ShadowData> shadowDatas = m_ShadowCtxt.shadowDatas;
|
|
ShadowData faceData = shadowDatas[(uint)(m_ShadowIndices[index] + offset + faceIndex)];
|
|
uint texID, samplerID;
|
|
faceData.UnpackShadowmapId(out texID, out samplerID);
|
|
m_Shadowmaps[texID].DisplayShadowMap(cmd, debugMaterial, faceData.scaleOffset, (uint)faceData.slice, screenX, screenY, screenSizeX, screenSizeY, minValue, maxValue, flipY);
|
|
}
|
|
|
|
public override void DisplayShadowMap(CommandBuffer cmd, Material debugMaterial, uint shadowMapIndex, uint sliceIndex, float screenX, float screenY, float screenSizeX, float screenSizeY, float minValue, float maxValue, bool flipY)
|
|
{
|
|
if (m_Shadowmaps.Length == 0)
|
|
return;
|
|
|
|
uint index = Math.Max(0, Math.Min((uint)(m_Shadowmaps.Length - 1), shadowMapIndex));
|
|
m_Shadowmaps[index].DisplayShadowMap(cmd, debugMaterial, new Vector4(1.0f, 1.0f, 0.0f, 0.0f), sliceIndex, screenX, screenY, screenSizeX, screenSizeY, minValue, maxValue, flipY);
|
|
}
|
|
|
|
public override void SyncData()
|
|
{
|
|
m_ShadowCtxt.SyncData();
|
|
}
|
|
|
|
public override void BindResources(CommandBuffer cmd, ComputeShader computeShader, int computeKernel)
|
|
{
|
|
foreach (var sm in m_Shadowmaps)
|
|
{
|
|
sm.Fill(m_ShadowCtxt);
|
|
}
|
|
cmd.BeginSample("Bind resources to GPU");
|
|
m_ShadowCtxt.BindResources(cmd, computeShader, computeKernel);
|
|
cmd.EndSample("Bind resources to GPU");
|
|
}
|
|
|
|
// resets the shadow slot counters and returns the sum of all slots
|
|
private uint ResetMaxShadows()
|
|
{
|
|
int total = 0;
|
|
for (int i = 0; i < (int)GPUShadowType.MAX; ++i)
|
|
{
|
|
m_MaxShadows[i, 0] = m_MaxShadows[i, 1];
|
|
total += m_MaxShadows[i, 1];
|
|
}
|
|
return total > 0 ? (uint)total : 0;
|
|
}
|
|
}
|
|
} // end of namespace UnityEngine.Experimental.ScriptableRenderLoop
|