No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ReflectionProbeManager.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.Mathematics;
  4. using UnityEngine.Experimental.Rendering;
  5. namespace UnityEngine.Rendering.Universal
  6. {
  7. struct ReflectionProbeManager : IDisposable
  8. {
  9. int2 m_Resolution;
  10. RenderTexture m_AtlasTexture0;
  11. RenderTexture m_AtlasTexture1;
  12. RTHandle m_AtlasTexture0Handle;
  13. BuddyAllocator m_AtlasAllocator;
  14. Dictionary<int, CachedProbe> m_Cache;
  15. Dictionary<int, int> m_WarningCache;
  16. List<int> m_NeedsUpdate;
  17. List<int> m_NeedsRemove;
  18. // Pre-allocated arrays for filling constant buffers
  19. Vector4[] m_BoxMax;
  20. Vector4[] m_BoxMin;
  21. Vector4[] m_ProbePosition;
  22. Vector4[] m_MipScaleOffset;
  23. // There is a global max of 7 mips in Unity.
  24. const int k_MaxMipCount = 7;
  25. const string k_ReflectionProbeAtlasName = "URP Reflection Probe Atlas";
  26. unsafe struct CachedProbe
  27. {
  28. public uint updateCount;
  29. public Hash128 imageContentsHash;
  30. public int size;
  31. public int mipCount;
  32. // One for each mip.
  33. public fixed int dataIndices[k_MaxMipCount];
  34. public fixed int levels[k_MaxMipCount];
  35. public Texture texture;
  36. public int lastUsed;
  37. public Vector4 hdrData;
  38. }
  39. static class ShaderProperties
  40. {
  41. public static readonly int BoxMin = Shader.PropertyToID("urp_ReflProbes_BoxMin");
  42. public static readonly int BoxMax = Shader.PropertyToID("urp_ReflProbes_BoxMax");
  43. public static readonly int ProbePosition = Shader.PropertyToID("urp_ReflProbes_ProbePosition");
  44. public static readonly int MipScaleOffset = Shader.PropertyToID("urp_ReflProbes_MipScaleOffset");
  45. public static readonly int Count = Shader.PropertyToID("urp_ReflProbes_Count");
  46. public static readonly int Atlas = Shader.PropertyToID("urp_ReflProbes_Atlas");
  47. }
  48. public RenderTexture atlasRT => m_AtlasTexture0;
  49. public RTHandle atlasRTHandle => m_AtlasTexture0Handle;
  50. public static ReflectionProbeManager Create()
  51. {
  52. var instance = new ReflectionProbeManager();
  53. instance.Init();
  54. return instance;
  55. }
  56. void Init()
  57. {
  58. var maxProbes = UniversalRenderPipeline.maxVisibleReflectionProbes;
  59. // m_Resolution = math.min((int)reflectionProbeResolution, SystemInfo.maxTextureSize);
  60. m_Resolution = 1;
  61. var format = GraphicsFormat.B10G11R11_UFloatPack32;
  62. if (!SystemInfo.IsFormatSupported(format, GraphicsFormatUsage.Render)) { format = GraphicsFormat.R16G16B16A16_SFloat; }
  63. m_AtlasTexture0 = new RenderTexture(new RenderTextureDescriptor
  64. {
  65. width = m_Resolution.x,
  66. height = m_Resolution.y,
  67. volumeDepth = 1,
  68. dimension = TextureDimension.Tex2D,
  69. graphicsFormat = format,
  70. useMipMap = false,
  71. msaaSamples = 1
  72. });
  73. m_AtlasTexture0.name = k_ReflectionProbeAtlasName;
  74. m_AtlasTexture0.filterMode = FilterMode.Bilinear;
  75. m_AtlasTexture0.hideFlags = HideFlags.HideAndDontSave;
  76. m_AtlasTexture0.Create();
  77. m_AtlasTexture0Handle = RTHandles.Alloc(m_AtlasTexture0, transferOwnership: true);
  78. m_AtlasTexture1 = new RenderTexture(m_AtlasTexture0.descriptor);
  79. m_AtlasTexture1.name = k_ReflectionProbeAtlasName;
  80. m_AtlasTexture1.filterMode = FilterMode.Bilinear;
  81. m_AtlasTexture1.hideFlags = HideFlags.HideAndDontSave;
  82. // The smallest allocatable resolution we want is 4x4. We calculate the number of levels as:
  83. // log2(max) - log2(4) = log2(max) - 2
  84. m_AtlasAllocator = new BuddyAllocator(math.floorlog2(SystemInfo.maxTextureSize) - 2, 2);
  85. m_Cache = new Dictionary<int, CachedProbe>(maxProbes);
  86. m_WarningCache = new Dictionary<int, int>(maxProbes);
  87. m_NeedsUpdate = new List<int>(maxProbes);
  88. m_NeedsRemove = new List<int>(maxProbes);
  89. m_BoxMax = new Vector4[maxProbes];
  90. m_BoxMin = new Vector4[maxProbes];
  91. m_ProbePosition = new Vector4[maxProbes];
  92. m_MipScaleOffset = new Vector4[maxProbes * 7];
  93. }
  94. public unsafe void UpdateGpuData(CommandBuffer cmd, ref CullingResults cullResults)
  95. {
  96. var probes = cullResults.visibleReflectionProbes;
  97. var probeCount = math.min(probes.Length, UniversalRenderPipeline.maxVisibleReflectionProbes);
  98. var frameIndex = Time.renderedFrameCount;
  99. // Populate list of probes we need to remove to avoid modifying dictionary while iterating.
  100. foreach (var (id, cachedProbe) in m_Cache)
  101. {
  102. // Evict probe if not used for more than 1 frame, if the texture no longer exists, or if the size changed.
  103. if (Math.Abs(cachedProbe.lastUsed - frameIndex) > 1 ||
  104. !cachedProbe.texture ||
  105. cachedProbe.size != cachedProbe.texture.width)
  106. {
  107. m_NeedsRemove.Add(id);
  108. for (var i = 0; i < k_MaxMipCount; i++)
  109. {
  110. if (cachedProbe.dataIndices[i] != -1) m_AtlasAllocator.Free(new BuddyAllocation(cachedProbe.levels[i], cachedProbe.dataIndices[i]));
  111. }
  112. }
  113. }
  114. foreach (var probeIndex in m_NeedsRemove)
  115. {
  116. m_Cache.Remove(probeIndex);
  117. }
  118. m_NeedsRemove.Clear();
  119. foreach (var (id, lastUsed) in m_WarningCache)
  120. {
  121. if (Math.Abs(lastUsed - frameIndex) > 1)
  122. {
  123. m_NeedsRemove.Add(id);
  124. }
  125. }
  126. foreach (var probeIndex in m_NeedsRemove)
  127. {
  128. m_WarningCache.Remove(probeIndex);
  129. }
  130. m_NeedsRemove.Clear();
  131. var showFullWarning = false;
  132. var requiredAtlasSize = math.int2(0, 0);
  133. for (var probeIndex = 0; probeIndex < probeCount; probeIndex++)
  134. {
  135. var probe = probes[probeIndex];
  136. var texture = probe.texture;
  137. var id = probe.reflectionProbe.GetInstanceID();
  138. var wasCached = m_Cache.TryGetValue(id, out var cachedProbe);
  139. if (!texture)
  140. {
  141. continue;
  142. }
  143. if (!wasCached)
  144. {
  145. cachedProbe.size = texture.width;
  146. var mipCount = math.ceillog2(cachedProbe.size * 4) + 1;
  147. var level = m_AtlasAllocator.levelCount + 2 - mipCount;
  148. cachedProbe.mipCount = math.min(mipCount, k_MaxMipCount);
  149. cachedProbe.texture = texture;
  150. var mip = 0;
  151. for (; mip < cachedProbe.mipCount; mip++)
  152. {
  153. // Clamp to maximum level. This is relevant for 64x64 and lower, which will have valid content
  154. // in 1x1 mip. The octahedron size is double the face size, so that ends up at 2x2. Due to
  155. // borders the final mip must be 4x4 as that leaves 2x2 texels for the octahedron.
  156. var mipLevel = math.min(level + mip, m_AtlasAllocator.levelCount - 1);
  157. if (!m_AtlasAllocator.TryAllocate(mipLevel, out var allocation)) break;
  158. // We split up the allocation struct because C# cannot do struct fixed arrays :(
  159. cachedProbe.levels[mip] = allocation.level;
  160. cachedProbe.dataIndices[mip] = allocation.index;
  161. var scaleOffset = (int4)(GetScaleOffset(mipLevel, allocation.index, true, false) * m_Resolution.xyxy);
  162. requiredAtlasSize = math.max(requiredAtlasSize, scaleOffset.zw + scaleOffset.xy);
  163. }
  164. // Check if we ran out of space in the atlas.
  165. if (mip < cachedProbe.mipCount)
  166. {
  167. if (!m_WarningCache.ContainsKey(id)) showFullWarning = true;
  168. m_WarningCache[id] = frameIndex;
  169. for (var i = 0; i < mip; i++) m_AtlasAllocator.Free(new BuddyAllocation(cachedProbe.levels[i], cachedProbe.dataIndices[i]));
  170. for (var i = 0; i < k_MaxMipCount; i++) cachedProbe.dataIndices[i] = -1;
  171. continue;
  172. }
  173. for (; mip < k_MaxMipCount; mip++)
  174. {
  175. cachedProbe.dataIndices[mip] = -1;
  176. }
  177. }
  178. var needsUpdate = !wasCached || cachedProbe.updateCount != texture.updateCount;
  179. #if UNITY_EDITOR
  180. needsUpdate |= cachedProbe.imageContentsHash != texture.imageContentsHash;
  181. #endif
  182. needsUpdate |= cachedProbe.hdrData != probe.hdrData; // The probe needs update if the runtime intensity multiplier changes
  183. if (needsUpdate)
  184. {
  185. cachedProbe.updateCount = texture.updateCount;
  186. #if UNITY_EDITOR
  187. cachedProbe.imageContentsHash = texture.imageContentsHash;
  188. #endif
  189. m_NeedsUpdate.Add(id);
  190. }
  191. // If the probe is set to be updated every frame, we assign the last used frame to -1 so it's evicted in next frame.
  192. if (probe.reflectionProbe.refreshMode == ReflectionProbeRefreshMode.EveryFrame)
  193. cachedProbe.lastUsed = -1;
  194. else
  195. cachedProbe.lastUsed = frameIndex;
  196. cachedProbe.hdrData = probe.hdrData;
  197. m_Cache[id] = cachedProbe;
  198. }
  199. // Grow the atlas if it's not big enough to contain the current allocations.
  200. if (math.any(m_Resolution < requiredAtlasSize))
  201. {
  202. requiredAtlasSize = math.max(m_Resolution, math.ceilpow2(requiredAtlasSize));
  203. var desc = m_AtlasTexture0.descriptor;
  204. desc.width = requiredAtlasSize.x;
  205. desc.height = requiredAtlasSize.y;
  206. m_AtlasTexture1.width = requiredAtlasSize.x;
  207. m_AtlasTexture1.height = requiredAtlasSize.y;
  208. m_AtlasTexture1.Create();
  209. if (m_AtlasTexture0.width != 1)
  210. {
  211. if (SystemInfo.copyTextureSupport != CopyTextureSupport.None)
  212. {
  213. Graphics.CopyTexture(m_AtlasTexture0, 0, 0, 0, 0, m_Resolution.x, m_Resolution.y, m_AtlasTexture1, 0, 0, 0, 0);
  214. }
  215. else
  216. {
  217. Graphics.Blit(m_AtlasTexture0, m_AtlasTexture1, (float2)m_Resolution / requiredAtlasSize, Vector2.zero);
  218. }
  219. }
  220. m_AtlasTexture0.Release();
  221. (m_AtlasTexture0, m_AtlasTexture1) = (m_AtlasTexture1, m_AtlasTexture0);
  222. m_Resolution = requiredAtlasSize;
  223. }
  224. for (var probeIndex = 0; probeIndex < probeCount; probeIndex++)
  225. {
  226. var probe = probes[probeIndex];
  227. var id = probe.reflectionProbe.GetInstanceID();
  228. if (!m_Cache.TryGetValue(id, out var cachedProbe)) continue;
  229. m_BoxMax[probeIndex] = new Vector4(probe.bounds.max.x, probe.bounds.max.y, probe.bounds.max.z, probe.blendDistance);
  230. m_BoxMin[probeIndex] = new Vector4(probe.bounds.min.x, probe.bounds.min.y, probe.bounds.min.z, probe.importance);
  231. m_ProbePosition[probeIndex] = new Vector4(probe.localToWorldMatrix.m03, probe.localToWorldMatrix.m13, probe.localToWorldMatrix.m23, (probe.isBoxProjection ? 1 : -1) * (cachedProbe.mipCount));
  232. for (var i = 0; i < cachedProbe.mipCount; i++) m_MipScaleOffset[probeIndex * k_MaxMipCount + i] = GetScaleOffset(cachedProbe.levels[i], cachedProbe.dataIndices[i], false, false);
  233. }
  234. if (showFullWarning)
  235. {
  236. Debug.LogWarning("A number of reflection probes have been skipped due to the reflection probe atlas being full.\nTo fix this, you can decrease the number or resolution of probes.");
  237. }
  238. using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.UpdateReflectionProbeAtlas)))
  239. {
  240. cmd.SetRenderTarget(m_AtlasTexture0);
  241. foreach (var probeId in m_NeedsUpdate)
  242. {
  243. var cachedProbe = m_Cache[probeId];
  244. for (var mip = 0; mip < cachedProbe.mipCount; mip++)
  245. {
  246. var level = cachedProbe.levels[mip];
  247. var dataIndex = cachedProbe.dataIndices[mip];
  248. // If we need to y-flip we will instead flip the atlas since that is updated less frequent and then the lookup should be correct.
  249. // By doing this we won't have to y-flip the lookup in the shader code.
  250. var scaleBias = GetScaleOffset(level, dataIndex, true, !SystemInfo.graphicsUVStartsAtTop);
  251. var sizeWithoutPadding = (1 << (m_AtlasAllocator.levelCount + 1 - level)) - 2;
  252. Blitter.BlitCubeToOctahedral2DQuadWithPadding(cmd, cachedProbe.texture, new Vector2(sizeWithoutPadding, sizeWithoutPadding), scaleBias, mip, true, 2, cachedProbe.hdrData);
  253. }
  254. }
  255. cmd.SetGlobalVectorArray(ShaderProperties.BoxMin, m_BoxMin);
  256. cmd.SetGlobalVectorArray(ShaderProperties.BoxMax, m_BoxMax);
  257. cmd.SetGlobalVectorArray(ShaderProperties.ProbePosition, m_ProbePosition);
  258. cmd.SetGlobalVectorArray(ShaderProperties.MipScaleOffset, m_MipScaleOffset);
  259. cmd.SetGlobalFloat(ShaderProperties.Count, probeCount);
  260. cmd.SetGlobalTexture(ShaderProperties.Atlas, m_AtlasTexture0);
  261. }
  262. m_NeedsUpdate.Clear();
  263. }
  264. float4 GetScaleOffset(int level, int dataIndex, bool includePadding, bool yflip)
  265. {
  266. // level = m_AtlasAllocator.levelCount + 2 - (log2(size) + 1) <=>
  267. // log2(size) + 1 = m_AtlasAllocator.levelCount + 2 - level <=>
  268. // log2(size) = m_AtlasAllocator.levelCount + 1 - level <=>
  269. // size = 2^(m_AtlasAllocator.levelCount + 1 - level)
  270. var size = (1 << (m_AtlasAllocator.levelCount + 1 - level));
  271. var coordinate = SpaceFillingCurves.DecodeMorton2D((uint)dataIndex);
  272. var scale = (size - (includePadding ? 0 : 2)) / ((float2)m_Resolution);
  273. var bias = ((float2) coordinate * size + (includePadding ? 0 : 1)) / (m_Resolution);
  274. if (yflip) bias.y = 1.0f - bias.y - scale.y;
  275. return math.float4(scale, bias);
  276. }
  277. public void Dispose()
  278. {
  279. if (m_AtlasTexture0)
  280. {
  281. m_AtlasTexture0.Release();
  282. m_AtlasTexture0Handle.Release();
  283. }
  284. Object.DestroyImmediate(m_AtlasTexture0);
  285. Object.DestroyImmediate(m_AtlasTexture1);
  286. this = default;
  287. }
  288. }
  289. }