using UnityEngine.Rendering; using System.Collections.Generic; using System; namespace UnityEngine.Experimental.Rendering.HDPipeline { class ShadowSetup : IDisposable { // shadow related stuff const int k_MaxShadowDataSlots = 64; const int k_MaxPayloadSlotsPerShadowData = 4; ShadowmapBase[] m_Shadowmaps; ShadowManager m_ShadowMgr; static ComputeBuffer s_ShadowDataBuffer; static ComputeBuffer s_ShadowPayloadBuffer; public static GPUShadowType HDShadowLightType(Light l) { // We only process light with additional data var ald = l.GetComponent(); if (ald == null) { return ShadowRegistry.ShadowLightType(l); } GPUShadowType shadowType = GPUShadowType.Unknown; switch (ald.lightTypeExtent) { case LightTypeExtent.Punctual: shadowType = ShadowRegistry.ShadowLightType(l); break; // Area and projector not supported yet } return shadowType; } public ShadowSetup(ShadowInitParameters shadowInit, ShadowSettings shadowSettings, out IShadowManager shadowManager) { s_ShadowDataBuffer = new ComputeBuffer( k_MaxShadowDataSlots, System.Runtime.InteropServices.Marshal.SizeOf( typeof( ShadowData ) ) ); s_ShadowPayloadBuffer = new ComputeBuffer( k_MaxShadowDataSlots * k_MaxPayloadSlotsPerShadowData, System.Runtime.InteropServices.Marshal.SizeOf( typeof( ShadowPayload ) ) ); ShadowAtlas.AtlasInit atlasInit; atlasInit.baseInit.width = (uint)shadowInit.shadowAtlasWidth; atlasInit.baseInit.height = (uint)shadowInit.shadowAtlasHeight; atlasInit.baseInit.slices = 1; atlasInit.baseInit.shadowmapBits = 32; atlasInit.baseInit.shadowmapFormat = RenderTextureFormat.Shadowmap; atlasInit.baseInit.samplerState = SamplerState.Default(); atlasInit.baseInit.comparisonSamplerState = ComparisonSamplerState.Default(); atlasInit.baseInit.clearColor = new Vector4( 0.0f, 0.0f, 0.0f, 0.0f ); atlasInit.baseInit.maxPayloadCount = 0; atlasInit.baseInit.shadowSupport = ShadowmapBase.ShadowSupport.Directional | ShadowmapBase.ShadowSupport.Point | ShadowmapBase.ShadowSupport.Spot; atlasInit.shaderKeyword = null; var varianceInit = atlasInit; varianceInit.baseInit.shadowmapFormat = ShadowVariance.GetFormat( false, false, true ); var varianceInit2 = varianceInit; varianceInit2.baseInit.shadowmapFormat = ShadowVariance.GetFormat( true, true, false ); var varianceInit3 = varianceInit; varianceInit3.baseInit.shadowmapFormat = ShadowVariance.GetFormat( true, false, true ); m_Shadowmaps = new ShadowmapBase[] { new ShadowVariance( ref varianceInit ), new ShadowVariance( ref varianceInit2 ), new ShadowVariance( ref varianceInit3 ), new ShadowAtlas( ref atlasInit ) }; ShadowContext.SyncDel syncer = (ShadowContext sc) => { // update buffers uint offset, count; ShadowData[] sds; sc.GetShadowDatas(out sds, out offset, out count); Debug.Assert(offset == 0); s_ShadowDataBuffer.SetData(sds); // unfortunately we can't pass an offset or count to this function ShadowPayload[] payloads; sc.GetPayloads(out payloads, out offset, out count); Debug.Assert(offset == 0); s_ShadowPayloadBuffer.SetData(payloads); }; // binding code. This needs to be in sync with ShadowContext.hlsl ShadowContext.BindDel binder = (ShadowContext sc, CommandBuffer cb, ComputeShader computeShader, int computeKernel) => { uint offset, count; RenderTargetIdentifier[] tex; sc.GetTex2DArrays(out tex, out offset, out count); // bind buffers cb.SetGlobalBuffer(HDShaderIDs._ShadowDatasExp, s_ShadowDataBuffer); cb.SetGlobalBuffer(HDShaderIDs._ShadowPayloads, s_ShadowPayloadBuffer); // bind textures cb.SetGlobalTexture(HDShaderIDs._ShadowmapExp_VSM_0, tex[0]); cb.SetGlobalTexture(HDShaderIDs._ShadowmapExp_VSM_1, tex[1]); cb.SetGlobalTexture(HDShaderIDs._ShadowmapExp_VSM_2, tex[2]); cb.SetGlobalTexture(HDShaderIDs._ShadowmapExp_PCF, tex[3]); // TODO: Currently samplers are hard coded in ShadowContext.hlsl, so we can't really set them here }; ShadowContext.CtxtInit scInit; scInit.storage.maxShadowDataSlots = k_MaxShadowDataSlots; scInit.storage.maxPayloadSlots = k_MaxShadowDataSlots * k_MaxPayloadSlotsPerShadowData; scInit.storage.maxTex2DArraySlots = 4; scInit.storage.maxTexCubeArraySlots = 0; scInit.storage.maxComparisonSamplerSlots = 1; scInit.storage.maxSamplerSlots = 4; scInit.dataSyncer = syncer; scInit.resourceBinder = binder; m_ShadowMgr = new ShadowManager(shadowSettings, ref scInit, m_Shadowmaps); // set global overrides - these need to match the override specified in TilePass/Shadow.hlsl bool useGlobalOverrides = true; m_ShadowMgr.SetGlobalShadowOverride( GPUShadowType.Point , ShadowAlgorithm.PCF, ShadowVariant.V4, ShadowPrecision.High, useGlobalOverrides ); m_ShadowMgr.SetGlobalShadowOverride( GPUShadowType.Spot , ShadowAlgorithm.PCF, ShadowVariant.V4, ShadowPrecision.High, useGlobalOverrides ); m_ShadowMgr.SetGlobalShadowOverride( GPUShadowType.Directional , ShadowAlgorithm.PCF, ShadowVariant.V3, ShadowPrecision.High, useGlobalOverrides ); m_ShadowMgr.SetShadowLightTypeDelegate(HDShadowLightType); shadowManager = m_ShadowMgr; } public void Dispose() { if (m_Shadowmaps != null) { (m_Shadowmaps[0] as ShadowAtlas).Dispose(); (m_Shadowmaps[1] as ShadowAtlas).Dispose(); (m_Shadowmaps[2] as ShadowAtlas).Dispose(); (m_Shadowmaps[3] as ShadowAtlas).Dispose(); m_Shadowmaps = null; } m_ShadowMgr = null; if (s_ShadowDataBuffer != null) s_ShadowDataBuffer.Release(); if (s_ShadowPayloadBuffer != null) s_ShadowPayloadBuffer.Release(); } } namespace TilePass { //----------------------------------------------------------------------------- // structure definition //----------------------------------------------------------------------------- [GenerateHLSL] public enum LightVolumeType { Cone, Sphere, Box, Count } [GenerateHLSL] public enum LightCategory { Punctual, Area, Env, Count } [GenerateHLSL] public enum LightFeatureFlags { // Light bit mask must match LightDefinitions.s_LightFeatureMaskFlags value Punctual = 1 << 12, Area = 1 << 13, Directional = 1 << 14, Env = 1 << 15, Sky = 1 << 16, SSRefraction = 1 << 17, SSReflection = 1 << 18, // If adding more light be sure to not overflow LightDefinitions.s_LightFeatureMaskFlags } [GenerateHLSL] public class LightDefinitions { public static int s_MaxNrLightsPerCamera = 1024; public static int s_MaxNrBigTileLightsPlusOne = 512; // may be overkill but the footprint is 2 bits per pixel using uint16. public static float s_ViewportScaleZ = 1.0f; // enable unity's original left-hand shader camera space (right-hand internally in unity). public static int s_UseLeftHandCameraSpace = 1; public static int s_TileSizeFptl = 16; public static int s_TileSizeClustered = 32; // feature variants public static int s_NumFeatureVariants = 27; // Following define the maximum number of bits use in each feature category. public static uint s_LightFeatureMaskFlags = 0xFFF000; public static uint s_LightFeatureMaskFlagsOpaque = 0xFFF000 & ~((uint)LightFeatureFlags.SSRefraction); // Opaque don't support screen space refraction public static uint s_LightFeatureMaskFlagsTransparent = 0xFFF000 & ~((uint)LightFeatureFlags.SSReflection); // Transparent don't support screen space reflection public static uint s_MaterialFeatureMaskFlags = 0x000FFF; // don't use all bits just to be safe from signed and/or float conversions :/ } [GenerateHLSL] public struct SFiniteLightBound { public Vector3 boxAxisX; public Vector3 boxAxisY; public Vector3 boxAxisZ; public Vector3 center; // a center in camera space inside the bounding volume of the light source. public Vector2 scaleXY; public float radius; }; [GenerateHLSL] public struct LightVolumeData { public Vector3 lightPos; public uint lightVolume; public Vector3 lightAxisX; public uint lightCategory; public Vector3 lightAxisY; public float radiusSq; public Vector3 lightAxisZ; // spot +Z axis public float cotan; public Vector3 boxInnerDist; public uint featureFlags; public Vector3 boxInvRange; public float unused2; }; [Serializable] public class TileSettings { public bool enableTileAndCluster; // For debug / test public bool enableComputeLightEvaluation; public bool enableComputeLightVariants; public bool enableComputeMaterialVariants; // Deferred opaque always use FPTL, forward opaque can use FPTL or cluster, transparent always use cluster // When MSAA is enabled, we only support cluster (Fptl is too slow with MSAA), and we don't support MSAA for deferred path (mean it is ok to keep fptl) public bool enableFptlForForwardOpaque; // clustered light list specific buffers and data begin public bool enableBigTilePrepass; public enum TileClusterDebug : int { None, Tile, Cluster, FeatureVariants }; public enum TileClusterCategoryDebug : int { Punctual = 1, Area = 2, AreaAndPunctual = 3, Environment = 4, EnvironmentAndPunctual = 5, EnvironmentAndArea = 6, EnvironmentAndAreaAndPunctual = 7 }; public TileSettings() { enableTileAndCluster = true; enableComputeLightEvaluation = true; enableComputeLightVariants = true; enableComputeMaterialVariants = true; enableFptlForForwardOpaque = true; enableBigTilePrepass = true; } } public class LightLoop { public const int k_MaxDirectionalLightsOnScreen = 4; public const int k_MaxPunctualLightsOnScreen = 512; public const int k_MaxAreaLightsOnScreen = 64; public const int k_MaxLightsOnScreen = k_MaxDirectionalLightsOnScreen + k_MaxPunctualLightsOnScreen + k_MaxAreaLightsOnScreen; public const int k_MaxEnvLightsOnScreen = 64; public const int k_MaxShadowOnScreen = 16; public const int k_MaxCascadeCount = 4; //Should be not less than m_Settings.directionalLightCascadeCount; // Static keyword is required here else we get a "DestroyBuffer can only be call in main thread" static ComputeBuffer s_DirectionalLightDatas = null; static ComputeBuffer s_LightDatas = null; static ComputeBuffer s_EnvLightDatas = null; static ComputeBuffer s_shadowDatas = null; static Texture2DArray m_DefaultTexture2DArray; static Cubemap m_DefaultTextureCube; ReflectionProbeCache m_ReflectionProbeCache; int m_ReflectionProbeCacheSize = 128; TextureCache2D m_CookieTexArray; int m_CookieTexArraySize = 16; TextureCacheCubemap m_CubeCookieTexArray; int m_CubeCookieTexArraySize = 16; LightingSettings m_LightingSettings = new LightingSettings(); public class LightList { public List directionalLights; public List lights; public List envLights; public List shadows; public Vector4[] directionalShadowSplitSphereSqr; public List bounds; public List lightVolumes; public void Clear() { directionalLights.Clear(); lights.Clear(); envLights.Clear(); shadows.Clear(); bounds.Clear(); lightVolumes.Clear(); } public void Allocate() { directionalLights = new List(); lights = new List(); envLights = new List(); shadows = new List(); directionalShadowSplitSphereSqr = new Vector4[k_MaxCascadeCount]; bounds = new List(); lightVolumes = new List(); } } LightList m_lightList; int m_punctualLightCount = 0; int m_areaLightCount = 0; int m_lightCount = 0; bool m_enableBakeShadowMask = false; // Track if any light require shadow mask. In this case we will need to enable the keyword shadow mask float m_maxShadowDistance = 0.0f; // Save value from shadow settings private ComputeShader buildScreenAABBShader { get { return m_Resources.buildScreenAABBShader; } } private ComputeShader buildPerTileLightListShader { get { return m_Resources.buildPerTileLightListShader; } } private ComputeShader buildPerBigTileLightListShader { get { return m_Resources.buildPerBigTileLightListShader; } } private ComputeShader buildPerVoxelLightListShader { get { return m_Resources.buildPerVoxelLightListShader; } } private ComputeShader buildMaterialFlagsShader { get { return m_Resources.buildMaterialFlagsShader; } } private ComputeShader buildDispatchIndirectShader { get { return m_Resources.buildDispatchIndirectShader; } } private ComputeShader clearDispatchIndirectShader { get { return m_Resources.clearDispatchIndirectShader; } } private ComputeShader deferredComputeShader { get { return m_Resources.deferredComputeShader; } } private ComputeShader deferredDirectionalShadowComputeShader { get { return m_Resources.deferredDirectionalShadowComputeShader; } } static int s_GenAABBKernel; static int s_GenListPerTileKernel; static int s_GenListPerVoxelKernel; static int s_ClearVoxelAtomicKernel; static int s_ClearDispatchIndirectKernel; static int s_BuildDispatchIndirectKernel; static int s_BuildMaterialFlagsWriteKernel; static int s_BuildMaterialFlagsOrKernel; static int s_shadeOpaqueDirectFptlKernel; static int s_shadeOpaqueDirectFptlDebugDisplayKernel; static int s_shadeOpaqueDirectShadowMaskFptlKernel; static int s_shadeOpaqueDirectShadowMaskFptlDebugDisplayKernel; static int[] s_shadeOpaqueIndirectFptlKernels = new int[LightDefinitions.s_NumFeatureVariants]; static int[] s_shadeOpaqueIndirectShadowMaskFptlKernels = new int[LightDefinitions.s_NumFeatureVariants]; static int s_deferredDirectionalShadowKernel; static ComputeBuffer s_LightVolumeDataBuffer = null; static ComputeBuffer s_ConvexBoundsBuffer = null; static ComputeBuffer s_AABBBoundsBuffer = null; static ComputeBuffer s_LightList = null; static ComputeBuffer s_TileList = null; static ComputeBuffer s_TileFeatureFlags = null; static ComputeBuffer s_DispatchIndirectBuffer = null; static ComputeBuffer s_BigTileLightList = null; // used for pre-pass coarse culling on 64x64 tiles static int s_GenListPerBigTileKernel; const bool k_UseDepthBuffer = true; // only has an impact when EnableClustered is true (requires a depth-prepass) const bool k_UseAsyncCompute = true; // should not use on mobile const int k_Log2NumClusters = 6; // accepted range is from 0 to 6. NumClusters is 1< m_ShadowRequests = new List(); Dictionary m_ShadowIndices = new Dictionary(); void InitShadowSystem(ShadowInitParameters initParam, ShadowSettings shadowSettings) { m_ShadowSetup = new ShadowSetup(initParam, shadowSettings, out m_ShadowMgr); } void DeinitShadowSystem() { if (m_ShadowSetup != null) { m_ShadowSetup.Dispose(); m_ShadowSetup = null; m_ShadowMgr = null; } } int GetNumTileFtplX(Camera camera) { return (camera.pixelWidth + (LightDefinitions.s_TileSizeFptl - 1)) / LightDefinitions.s_TileSizeFptl; } int GetNumTileFtplY(Camera camera) { return (camera.pixelHeight + (LightDefinitions.s_TileSizeFptl - 1)) / LightDefinitions.s_TileSizeFptl; } int GetNumTileClusteredX(Camera camera) { return (camera.pixelWidth + (LightDefinitions.s_TileSizeClustered - 1)) / LightDefinitions.s_TileSizeClustered; } int GetNumTileClusteredY(Camera camera) { return (camera.pixelHeight + (LightDefinitions.s_TileSizeClustered - 1)) / LightDefinitions.s_TileSizeClustered; } public bool GetFeatureVariantsEnabled() { return m_isFptlEnabled && m_TileSettings.enableComputeLightEvaluation && (m_TileSettings.enableComputeLightVariants || m_TileSettings.enableComputeMaterialVariants); } TileSettings m_TileSettings = null; RenderPipelineResources m_Resources = null; public LightLoop() {} int GetDeferredLightingMaterialIndex(int outputSplitLighting, int lightLoopTilePass, int shadowMask, int debugDisplay) { return (outputSplitLighting) | (lightLoopTilePass << 1) | (shadowMask << 2) | (debugDisplay << 3); } public void Build( RenderPipelineResources renderPipelineResources, GlobalRenderingSettings renderingSettings, TileSettings tileSettings, GlobalTextureSettings textureSettings, ShadowInitParameters shadowInit, ShadowSettings shadowSettings, IBLFilterGGX iblFilterGGX) { // Deferred opaque are always using Fptl. Forward opaque can use Fptl or Cluster, transparent use cluster. // When MSAA is enabled we disable Fptl as it become expensive compare to cluster // In HD, MSAA is only supported for forward only rendering, no MSAA in deferred mode (for code complexity reasons) // If Deferred, enable Fptl. If we are forward renderer only and not using Fptl for forward opaque, disable Fptl m_isFptlEnabled = !renderingSettings.ShouldUseForwardRenderingOnly() || tileSettings.enableFptlForForwardOpaque; // TODO: Disable if MSAA m_isFptlEnabledForForwardOpaque = tileSettings.enableFptlForForwardOpaque; // TODO: Disable if MSAA m_Resources = renderPipelineResources; m_TileSettings = tileSettings; m_lightList = new LightList(); m_lightList.Allocate(); s_DirectionalLightDatas = new ComputeBuffer(k_MaxDirectionalLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(DirectionalLightData))); s_LightDatas = new ComputeBuffer(k_MaxPunctualLightsOnScreen + k_MaxAreaLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightData))); s_EnvLightDatas = new ComputeBuffer(k_MaxEnvLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(EnvLightData))); s_shadowDatas = new ComputeBuffer(k_MaxCascadeCount + k_MaxShadowOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(ShadowData))); m_CookieTexArray = new TextureCache2D(); m_CookieTexArray.AllocTextureArray(m_CookieTexArraySize, textureSettings.spotCookieSize, textureSettings.spotCookieSize, TextureFormat.RGBA32, true); m_CubeCookieTexArray = new TextureCacheCubemap(); m_CubeCookieTexArray.AllocTextureArray(m_CubeCookieTexArraySize, textureSettings.pointCookieSize, TextureFormat.RGBA32, true); TextureFormat probeCacheFormat = textureSettings.reflectionCacheCompressed ? TextureFormat.BC6H : TextureFormat.RGBAHalf; m_ReflectionProbeCache = new ReflectionProbeCache(iblFilterGGX, m_ReflectionProbeCacheSize, textureSettings.reflectionCubemapSize, probeCacheFormat, true); s_GenAABBKernel = buildScreenAABBShader.FindKernel("ScreenBoundsAABB"); if (GetFeatureVariantsEnabled()) { s_GenListPerTileKernel = buildPerTileLightListShader.FindKernel(m_TileSettings.enableBigTilePrepass ? "TileLightListGen_SrcBigTile_FeatureFlags" : "TileLightListGen_FeatureFlags"); } else { s_GenListPerTileKernel = buildPerTileLightListShader.FindKernel(m_TileSettings.enableBigTilePrepass ? "TileLightListGen_SrcBigTile" : "TileLightListGen"); } s_AABBBoundsBuffer = new ComputeBuffer(2 * k_MaxLightsOnScreen, 3 * sizeof(float)); s_ConvexBoundsBuffer = new ComputeBuffer(k_MaxLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(SFiniteLightBound))); s_LightVolumeDataBuffer = new ComputeBuffer(k_MaxLightsOnScreen, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightVolumeData))); s_DispatchIndirectBuffer = new ComputeBuffer(LightDefinitions.s_NumFeatureVariants * 3, sizeof(uint), ComputeBufferType.IndirectArguments); // Cluster { var kernelName = m_TileSettings.enableBigTilePrepass ? (k_UseDepthBuffer ? "TileLightListGen_DepthRT_SrcBigTile" : "TileLightListGen_NoDepthRT_SrcBigTile") : (k_UseDepthBuffer ? "TileLightListGen_DepthRT" : "TileLightListGen_NoDepthRT"); s_GenListPerVoxelKernel = buildPerVoxelLightListShader.FindKernel(kernelName); s_ClearVoxelAtomicKernel = buildPerVoxelLightListShader.FindKernel("ClearAtomic"); s_GlobalLightListAtomic = new ComputeBuffer(1, sizeof(uint)); } if (m_TileSettings.enableBigTilePrepass) { s_GenListPerBigTileKernel = buildPerBigTileLightListShader.FindKernel("BigTileLightListGen"); } s_BuildDispatchIndirectKernel = buildDispatchIndirectShader.FindKernel("BuildDispatchIndirect"); s_ClearDispatchIndirectKernel = clearDispatchIndirectShader.FindKernel("ClearDispatchIndirect"); s_BuildMaterialFlagsOrKernel = buildMaterialFlagsShader.FindKernel("MaterialFlagsGen_Or"); s_BuildMaterialFlagsWriteKernel = buildMaterialFlagsShader.FindKernel("MaterialFlagsGen_Write"); s_shadeOpaqueDirectFptlKernel = deferredComputeShader.FindKernel("Deferred_Direct_Fptl"); s_shadeOpaqueDirectFptlDebugDisplayKernel = deferredComputeShader.FindKernel("Deferred_Direct_Fptl_DebugDisplay"); s_shadeOpaqueDirectShadowMaskFptlKernel = deferredComputeShader.FindKernel("Deferred_Direct_ShadowMask_Fptl"); s_shadeOpaqueDirectShadowMaskFptlDebugDisplayKernel = deferredComputeShader.FindKernel("Deferred_Direct_ShadowMask_Fptl_DebugDisplay"); s_deferredDirectionalShadowKernel = deferredDirectionalShadowComputeShader.FindKernel("DeferredDirectionalShadow"); for (int variant = 0; variant < LightDefinitions.s_NumFeatureVariants; variant++) { s_shadeOpaqueIndirectFptlKernels[variant] = deferredComputeShader.FindKernel("Deferred_Indirect_Fptl_Variant" + variant); s_shadeOpaqueIndirectShadowMaskFptlKernels[variant] = deferredComputeShader.FindKernel("Deferred_Indirect_ShadowMask_Fptl_Variant" + variant); } s_LightList = null; s_TileList = null; s_TileFeatureFlags = null; // OUTPUT_SPLIT_LIGHTING - LIGHTLOOP_TILE_PASS - SHADOWS_SHADOWMASK - DEBUG_DISPLAY m_deferredLightingMaterial = new Material[16]; for (int outputSplitLighting = 0; outputSplitLighting < 2; ++outputSplitLighting) { for (int lightLoopTilePass = 0; lightLoopTilePass < 2; ++lightLoopTilePass) { for (int shadowMask = 0; shadowMask < 2; ++shadowMask) { for (int debugDisplay = 0; debugDisplay < 2; ++debugDisplay) { int index = GetDeferredLightingMaterialIndex(outputSplitLighting, lightLoopTilePass, shadowMask, debugDisplay); m_deferredLightingMaterial[index] = CoreUtils.CreateEngineMaterial(m_Resources.deferredShader); CoreUtils.SetKeyword(m_deferredLightingMaterial[index], "OUTPUT_SPLIT_LIGHTING", outputSplitLighting == 1); CoreUtils.SelectKeyword(m_deferredLightingMaterial[index], "LIGHTLOOP_TILE_PASS", "LIGHTLOOP_SINGLE_PASS", lightLoopTilePass == 1); CoreUtils.SetKeyword(m_deferredLightingMaterial[index], "SHADOWS_SHADOWMASK", shadowMask == 1); CoreUtils.SetKeyword(m_deferredLightingMaterial[index], "DEBUG_DISPLAY", debugDisplay == 1); m_deferredLightingMaterial[index].SetInt(HDShaderIDs._StencilRef, outputSplitLighting == 1 ? (int)StencilLightingUsage.SplitLighting : (int)StencilLightingUsage.RegularLighting); m_deferredLightingMaterial[index].SetInt(HDShaderIDs._StencilCmp, (int)CompareFunction.Equal); m_deferredLightingMaterial[index].SetInt(HDShaderIDs._SrcBlend, (int)BlendMode.One); m_deferredLightingMaterial[index].SetInt(HDShaderIDs._DstBlend, (int)BlendMode.Zero); } } } } m_DebugViewTilesMaterial = CoreUtils.CreateEngineMaterial(m_Resources.debugViewTilesShader); m_DefaultTexture2DArray = new Texture2DArray(1, 1, 1, TextureFormat.ARGB32, false); m_DefaultTexture2DArray.SetPixels32(new Color32[1] { new Color32(128, 128, 128, 128) }, 0); m_DefaultTexture2DArray.Apply(); m_DefaultTextureCube = new Cubemap(16, TextureFormat.ARGB32, false); m_DefaultTextureCube.Apply(); #if UNITY_EDITOR UnityEditor.SceneView.onSceneGUIDelegate -= OnSceneGUI; UnityEditor.SceneView.onSceneGUIDelegate += OnSceneGUI; #endif InitShadowSystem(shadowInit, shadowSettings); } public void Cleanup() { DeinitShadowSystem(); #if UNITY_EDITOR UnityEditor.SceneView.onSceneGUIDelegate -= OnSceneGUI; #endif CoreUtils.SafeRelease(s_DirectionalLightDatas); CoreUtils.SafeRelease(s_LightDatas); CoreUtils.SafeRelease(s_EnvLightDatas); CoreUtils.SafeRelease(s_shadowDatas); if (m_ReflectionProbeCache != null) { m_ReflectionProbeCache.Release(); m_ReflectionProbeCache = null; } if (m_CookieTexArray != null) { m_CookieTexArray.Release(); m_CookieTexArray = null; } if (m_CubeCookieTexArray != null) { m_CubeCookieTexArray.Release(); m_CubeCookieTexArray = null; } ReleaseResolutionDependentBuffers(); CoreUtils.SafeRelease(s_AABBBoundsBuffer); CoreUtils.SafeRelease(s_ConvexBoundsBuffer); CoreUtils.SafeRelease(s_LightVolumeDataBuffer); CoreUtils.SafeRelease(s_DispatchIndirectBuffer); // enableClustered CoreUtils.SafeRelease(s_GlobalLightListAtomic); for (int outputSplitLighting = 0; outputSplitLighting < 2; ++outputSplitLighting) { for (int lightLoopTilePass = 0; lightLoopTilePass < 2; ++lightLoopTilePass) { for (int shadowMask = 0; shadowMask < 2; ++shadowMask) { for (int debugDisplay = 0; debugDisplay < 2; ++debugDisplay) { int index = GetDeferredLightingMaterialIndex(outputSplitLighting, lightLoopTilePass, shadowMask, debugDisplay); CoreUtils.Destroy(m_deferredLightingMaterial[index]); } } } } CoreUtils.Destroy(m_DebugViewTilesMaterial); } public void NewFrame() { m_CookieTexArray.NewFrame(); m_CubeCookieTexArray.NewFrame(); m_ReflectionProbeCache.NewFrame(); } public bool NeedResize() { return s_LightList == null || s_TileList == null || s_TileFeatureFlags == null || (s_BigTileLightList == null && m_TileSettings.enableBigTilePrepass) || (s_PerVoxelLightLists == null); } public void ReleaseResolutionDependentBuffers() { CoreUtils.SafeRelease(s_LightList); CoreUtils.SafeRelease(s_TileList); CoreUtils.SafeRelease(s_TileFeatureFlags); // enableClustered CoreUtils.SafeRelease(s_PerVoxelLightLists); CoreUtils.SafeRelease(s_PerVoxelOffset); CoreUtils.SafeRelease(s_PerTileLogBaseTweak); // enableBigTilePrepass CoreUtils.SafeRelease(s_BigTileLightList); } int NumLightIndicesPerClusteredTile() { return 8 * (1 << k_Log2NumClusters); // total footprint for all layers of the tile (measured in light index entries) } public void AllocResolutionDependentBuffers(int width, int height) { var nrTilesX = (width + LightDefinitions.s_TileSizeFptl - 1) / LightDefinitions.s_TileSizeFptl; var nrTilesY = (height + LightDefinitions.s_TileSizeFptl - 1) / LightDefinitions.s_TileSizeFptl; var nrTiles = nrTilesX * nrTilesY; const int capacityUShortsPerTile = 32; const int dwordsPerTile = (capacityUShortsPerTile + 1) >> 1; // room for 31 lights and a nrLights value. s_LightList = new ComputeBuffer((int)LightCategory.Count * dwordsPerTile * nrTiles, sizeof(uint)); // enough list memory for a 4k x 4k display s_TileList = new ComputeBuffer((int)LightDefinitions.s_NumFeatureVariants * nrTiles, sizeof(uint)); s_TileFeatureFlags = new ComputeBuffer(nrTilesX * nrTilesY, sizeof(uint)); // Cluster { var nrClustersX = (width + LightDefinitions.s_TileSizeClustered - 1) / LightDefinitions.s_TileSizeClustered; var nrClustersY = (height + LightDefinitions.s_TileSizeClustered - 1) / LightDefinitions.s_TileSizeClustered; var nrClusterTiles = nrClustersX * nrClustersY; s_PerVoxelOffset = new ComputeBuffer((int)LightCategory.Count * (1 << k_Log2NumClusters) * nrClusterTiles, sizeof(uint)); s_PerVoxelLightLists = new ComputeBuffer(NumLightIndicesPerClusteredTile() * nrClusterTiles, sizeof(uint)); if (k_UseDepthBuffer) { s_PerTileLogBaseTweak = new ComputeBuffer(nrClusterTiles, sizeof(float)); } } if (m_TileSettings.enableBigTilePrepass) { var nrBigTilesX = (width + 63) / 64; var nrBigTilesY = (height + 63) / 64; var nrBigTiles = nrBigTilesX * nrBigTilesY; s_BigTileLightList = new ComputeBuffer(LightDefinitions.s_MaxNrBigTileLightsPlusOne * nrBigTiles, sizeof(uint)); } } static Matrix4x4 GetFlipMatrix() { Matrix4x4 flip = Matrix4x4.identity; bool isLeftHand = ((int)LightDefinitions.s_UseLeftHandCameraSpace) != 0; if (isLeftHand) flip.SetColumn(2, new Vector4(0.0f, 0.0f, -1.0f, 0.0f)); return flip; } static Matrix4x4 WorldToCamera(Camera camera) { return GetFlipMatrix() * camera.worldToCameraMatrix; } static Matrix4x4 CameraProjection(Camera camera) { return camera.projectionMatrix * GetFlipMatrix(); } public Vector3 GetLightColor(VisibleLight light) { return new Vector3(light.finalColor.r, light.finalColor.g, light.finalColor.b); } public bool GetDirectionalLightData(CommandBuffer cmd, ShadowSettings shadowSettings, GPULightType gpuLightType, VisibleLight light, HDAdditionalLightData additionalData, AdditionalShadowData additionalShadowData, int lightIndex) { var directionalLightData = new DirectionalLightData(); float diffuseDimmer = m_LightingSettings.diffuseGlobalDimmer * additionalData.lightDimmer; float specularDimmer = m_LightingSettings.specularGlobalDimmer * additionalData.lightDimmer; if (diffuseDimmer <= 0.0f && specularDimmer <= 0.0f) return false; // Light direction for directional is opposite to the forward direction directionalLightData.forward = light.light.transform.forward; // Rescale for cookies and windowing. directionalLightData.up = light.light.transform.up * 2 / additionalData.shapeWidth; directionalLightData.right = light.light.transform.right * 2 / additionalData.shapeLength; directionalLightData.positionWS = light.light.transform.position; directionalLightData.color = GetLightColor(light); directionalLightData.diffuseScale = additionalData.affectDiffuse ? diffuseDimmer : 0.0f; directionalLightData.specularScale = additionalData.affectSpecular ? specularDimmer : 0.0f; directionalLightData.shadowIndex = directionalLightData.cookieIndex = -1; if (light.light.cookie != null) { directionalLightData.tileCookie = light.light.cookie.wrapMode == TextureWrapMode.Repeat; directionalLightData.cookieIndex = m_CookieTexArray.FetchSlice(cmd, light.light.cookie); } // fix up shadow information int shadowIdx; if (m_ShadowIndices.TryGetValue(lightIndex, out shadowIdx)) { directionalLightData.shadowIndex = shadowIdx; m_CurrentSunLight = light.light; m_CurrentSunLightShadowIndex = shadowIdx; } // TODO: Currently m_maxShadowDistance is based on shadow settings, but this value is define for a whole level. We should be able to change this value during gameplay float scale; float bias; GetSCaleAndBiasForLinearDistanceFade(m_maxShadowDistance, out scale, out bias); directionalLightData.fadeDistanceScaleAndBias = new Vector2(scale, bias); directionalLightData.shadowMaskSelector = Vector4.zero; if (IsBakedShadowMaskLight(light.light)) { directionalLightData.shadowMaskSelector[light.light.bakingOutput.occlusionMaskChannel] = 1.0f; // TODO: make this option per light, not global directionalLightData.dynamicShadowCasterOnly = QualitySettings.shadowmaskMode == ShadowmaskMode.Shadowmask; } else { // use -1 to say that we don't use shadow mask directionalLightData.shadowMaskSelector.x = -1.0f; directionalLightData.dynamicShadowCasterOnly = false; } m_CurrentSunLight = m_CurrentSunLight == null ? light.light : m_CurrentSunLight; m_lightList.directionalLights.Add(directionalLightData); return true; } void GetSCaleAndBiasForLinearDistanceFade(float fadeDistance, out float scale, out float bias) { // Fade with distance calculation is just a linear fade from 90% of fade distance to fade distance. 90% arbitrarily chosen but should work well enough. float distanceFadeNear = 0.9f * fadeDistance; scale = 1.0f / (fadeDistance - distanceFadeNear); bias = - distanceFadeNear / (fadeDistance - distanceFadeNear); } float ComputeLinearDistanceFade(float distanceToCamera, float fadeDistance) { float scale; float bias; GetSCaleAndBiasForLinearDistanceFade(fadeDistance, out scale, out bias); return 1.0f - Mathf.Clamp01(distanceToCamera * scale + bias); } public bool GetLightData(CommandBuffer cmd, ShadowSettings shadowSettings, Camera camera, GPULightType gpuLightType, VisibleLight light, HDAdditionalLightData additionalLightData, AdditionalShadowData additionalshadowData, int lightIndex) { var lightData = new LightData(); lightData.lightType = gpuLightType; lightData.positionWS = light.light.transform.position; // Setting 0 for invSqrAttenuationRadius mean we have no range attenuation, but still have inverse square attenuation. lightData.invSqrAttenuationRadius = additionalLightData.applyRangeAttenuation ? 1.0f / (light.range * light.range) : 0.0f; lightData.color = GetLightColor(light); lightData.forward = light.light.transform.forward; // Note: Light direction is oriented backward (-Z) lightData.up = light.light.transform.up; lightData.right = light.light.transform.right; lightData.size = new Vector2(additionalLightData.shapeLength, additionalLightData.shapeWidth); if (lightData.lightType == GPULightType.ProjectorBox) { // Rescale for cookies and windowing. lightData.right *= 2.0f / additionalLightData.shapeLength; lightData.up *= 2.0f / additionalLightData.shapeWidth; } else if (lightData.lightType == GPULightType.ProjectorPyramid) { // Get width and height for the current frustum var spotAngle = light.spotAngle; float frustumHeight; float frustumWidth; if (additionalLightData.aspectRatio >= 1.0f) { frustumHeight = 2.0f * Mathf.Tan(spotAngle * 0.5f * Mathf.Deg2Rad); frustumWidth = frustumHeight * additionalLightData.aspectRatio; } else { frustumWidth = 2.0f * Mathf.Tan(spotAngle * 0.5f * Mathf.Deg2Rad); frustumHeight = frustumWidth / additionalLightData.aspectRatio; } lightData.size = new Vector2(frustumWidth, frustumHeight); // Rescale for cookies and windowing. lightData.right *= 2.0f / frustumWidth; lightData.up *= 2.0f / frustumHeight; } if (lightData.lightType == GPULightType.Spot) { var spotAngle = light.spotAngle; var innerConePercent = additionalLightData.GetInnerSpotPercent01(); var cosSpotOuterHalfAngle = Mathf.Clamp(Mathf.Cos(spotAngle * 0.5f * Mathf.Deg2Rad), 0.0f, 1.0f); var sinSpotOuterHalfAngle = Mathf.Sqrt(1.0f - cosSpotOuterHalfAngle * cosSpotOuterHalfAngle); var cosSpotInnerHalfAngle = Mathf.Clamp(Mathf.Cos(spotAngle * 0.5f * innerConePercent * Mathf.Deg2Rad), 0.0f, 1.0f); // inner cone var val = Mathf.Max(0.001f, (cosSpotInnerHalfAngle - cosSpotOuterHalfAngle)); lightData.angleScale = 1.0f / val; lightData.angleOffset = -cosSpotOuterHalfAngle * lightData.angleScale; // Rescale for cookies and windowing. float cotOuterHalfAngle = cosSpotOuterHalfAngle / sinSpotOuterHalfAngle; lightData.up *= cotOuterHalfAngle; lightData.right *= cotOuterHalfAngle; } else { // These are the neutral values allowing GetAngleAnttenuation in shader code to return 1.0 lightData.angleScale = 0.0f; lightData.angleOffset = 1.0f; } float distanceToCamera = (lightData.positionWS - camera.transform.position).magnitude; float distanceFade = ComputeLinearDistanceFade(distanceToCamera, additionalLightData.fadeDistance); float lightScale = additionalLightData.lightDimmer * distanceFade; lightData.diffuseScale = additionalLightData.affectDiffuse ? lightScale * m_LightingSettings.diffuseGlobalDimmer : 0.0f; lightData.specularScale = additionalLightData.affectSpecular ? lightScale * m_LightingSettings.specularGlobalDimmer : 0.0f; if (lightData.diffuseScale <= 0.0f && lightData.specularScale <= 0.0f) return false; lightData.cookieIndex = -1; lightData.shadowIndex = -1; if (light.light.cookie != null) { // TODO: add texture atlas support for cookie textures. switch (light.lightType) { case LightType.Spot: lightData.cookieIndex = m_CookieTexArray.FetchSlice(cmd, light.light.cookie); break; case LightType.Point: lightData.cookieIndex = m_CubeCookieTexArray.FetchSlice(cmd, light.light.cookie); break; } } else if (light.lightType == LightType.Spot && additionalLightData.spotLightShape != SpotLightShape.Cone) { // Projectors lights must always have a cookie texture. lightData.cookieIndex = m_CookieTexArray.FetchSlice(cmd, Texture2D.whiteTexture); } if (additionalshadowData) { float shadowDistanceFade = ComputeLinearDistanceFade(distanceToCamera, additionalshadowData.shadowFadeDistance); lightData.shadowDimmer = additionalshadowData.shadowDimmer * shadowDistanceFade; } else { lightData.shadowDimmer = 1.0f; } // fix up shadow information int shadowIdx; if (m_ShadowIndices.TryGetValue(lightIndex, out shadowIdx)) { lightData.shadowIndex = shadowIdx; } // Value of max smoothness is from artists point of view, need to convert from perceptual smoothness to roughness lightData.minRoughness = (1.0f - additionalLightData.maxSmoothness) * (1.0f - additionalLightData.maxSmoothness); lightData.shadowMaskSelector = Vector4.zero; if (IsBakedShadowMaskLight(light.light)) { lightData.shadowMaskSelector[light.light.bakingOutput.occlusionMaskChannel] = 1.0f; // TODO: make this option per light, not global lightData.dynamicShadowCasterOnly = QualitySettings.shadowmaskMode == ShadowmaskMode.Shadowmask; } else { // use -1 to say that we don't use shadow mask lightData.shadowMaskSelector.x = -1.0f; lightData.dynamicShadowCasterOnly = false; } m_lightList.lights.Add(lightData); return true; } // TODO: we should be able to do this calculation only with LightData without VisibleLight light, but for now pass both public void GetLightVolumeDataAndBound(LightCategory lightCategory, GPULightType gpuLightType, LightVolumeType lightVolumeType, VisibleLight light, LightData lightData, Matrix4x4 worldToView) { // Then Culling side var range = light.range; var lightToWorld = light.localToWorld; Vector3 positionWS = lightData.positionWS; Vector3 positionVS = worldToView.MultiplyPoint(positionWS); Matrix4x4 lightToView = worldToView * lightToWorld; Vector3 xAxisVS = lightToView.GetColumn(0); Vector3 yAxisVS = lightToView.GetColumn(1); Vector3 zAxisVS = lightToView.GetColumn(2); // Fill bounds var bound = new SFiniteLightBound(); var lightVolumeData = new LightVolumeData(); lightVolumeData.lightCategory = (uint)lightCategory; lightVolumeData.lightVolume = (uint)lightVolumeType; if (gpuLightType == GPULightType.Spot || gpuLightType == GPULightType.ProjectorPyramid) { Vector3 lightDir = lightToWorld.GetColumn(2); // represents a left hand coordinate system in world space since det(worldToView)<0 Vector3 vx = xAxisVS; Vector3 vy = yAxisVS; Vector3 vz = zAxisVS; const float pi = 3.1415926535897932384626433832795f; const float degToRad = (float)(pi / 180.0); var sa = light.light.spotAngle; var cs = Mathf.Cos(0.5f * sa * degToRad); var si = Mathf.Sin(0.5f * sa * degToRad); if (gpuLightType == GPULightType.ProjectorPyramid) { Vector3 lightPosToProjWindowCorner = (0.5f * lightData.size.x) * vx + (0.5f * lightData.size.y) * vy + 1.0f * vz; cs = Vector3.Dot(vz, Vector3.Normalize(lightPosToProjWindowCorner)); si = Mathf.Sqrt(1.0f - cs * cs); } const float FltMax = 3.402823466e+38F; var ta = cs > 0.0f ? (si / cs) : FltMax; var cota = si > 0.0f ? (cs / si) : FltMax; //const float cotasa = l.GetCotanHalfSpotAngle(); // apply nonuniform scale to OBB of spot light var squeeze = true;//sa < 0.7f * 90.0f; // arb heuristic var fS = squeeze ? ta : si; bound.center = worldToView.MultiplyPoint(positionWS + ((0.5f * range) * lightDir)); // use mid point of the spot as the center of the bounding volume for building screen-space AABB for tiled lighting. // scale axis to match box or base of pyramid bound.boxAxisX = (fS * range) * vx; bound.boxAxisY = (fS * range) * vy; bound.boxAxisZ = (0.5f * range) * vz; // generate bounding sphere radius var fAltDx = si; var fAltDy = cs; fAltDy = fAltDy - 0.5f; //if(fAltDy<0) fAltDy=-fAltDy; fAltDx *= range; fAltDy *= range; // Handle case of pyramid with this select (currently unused) var altDist = Mathf.Sqrt(fAltDy * fAltDy + (true ? 1.0f : 2.0f) * fAltDx * fAltDx); bound.radius = altDist > (0.5f * range) ? altDist : (0.5f * range); // will always pick fAltDist bound.scaleXY = squeeze ? new Vector2(0.01f, 0.01f) : new Vector2(1.0f, 1.0f); lightVolumeData.lightAxisX = vx; lightVolumeData.lightAxisY = vy; lightVolumeData.lightAxisZ = vz; lightVolumeData.lightPos = positionVS; lightVolumeData.radiusSq = range * range; lightVolumeData.cotan = cota; lightVolumeData.featureFlags = (uint)LightFeatureFlags.Punctual; } else if (gpuLightType == GPULightType.Point) { Vector3 vx = xAxisVS; Vector3 vy = yAxisVS; Vector3 vz = zAxisVS; bound.center = positionVS; bound.boxAxisX = vx * range; bound.boxAxisY = vy * range; bound.boxAxisZ = vz * range; bound.scaleXY.Set(1.0f, 1.0f); bound.radius = range; // fill up ldata lightVolumeData.lightAxisX = vx; lightVolumeData.lightAxisY = vy; lightVolumeData.lightAxisZ = vz; lightVolumeData.lightPos = bound.center; lightVolumeData.radiusSq = range * range; lightVolumeData.featureFlags = (uint)LightFeatureFlags.Punctual; } else if (gpuLightType == GPULightType.Line) { Vector3 dimensions = new Vector3(lightData.size.x + 2 * range, 2 * range, 2 * range); // Omni-directional Vector3 extents = 0.5f * dimensions; bound.center = positionVS; bound.boxAxisX = extents.x * xAxisVS; bound.boxAxisY = extents.y * yAxisVS; bound.boxAxisZ = extents.z * zAxisVS; bound.scaleXY.Set(1.0f, 1.0f); bound.radius = extents.magnitude; lightVolumeData.lightPos = positionVS; lightVolumeData.lightAxisX = xAxisVS; lightVolumeData.lightAxisY = yAxisVS; lightVolumeData.lightAxisZ = zAxisVS; lightVolumeData.boxInnerDist = new Vector3(lightData.size.x, 0, 0); lightVolumeData.boxInvRange.Set(1.0f / range, 1.0f / range, 1.0f / range); lightVolumeData.featureFlags = (uint)LightFeatureFlags.Area; } else if (gpuLightType == GPULightType.Rectangle) { Vector3 dimensions = new Vector3(lightData.size.x + 2 * range, lightData.size.y + 2 * range, range); // One-sided Vector3 extents = 0.5f * dimensions; Vector3 centerVS = positionVS + extents.z * zAxisVS; bound.center = centerVS; bound.boxAxisX = extents.x * xAxisVS; bound.boxAxisY = extents.y * yAxisVS; bound.boxAxisZ = extents.z * zAxisVS; bound.scaleXY.Set(1.0f, 1.0f); bound.radius = extents.magnitude; lightVolumeData.lightPos = centerVS; lightVolumeData.lightAxisX = xAxisVS; lightVolumeData.lightAxisY = yAxisVS; lightVolumeData.lightAxisZ = zAxisVS; lightVolumeData.boxInnerDist = extents; lightVolumeData.boxInvRange.Set(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity); lightVolumeData.featureFlags = (uint)LightFeatureFlags.Area; } else if (gpuLightType == GPULightType.ProjectorBox) { Vector3 dimensions = new Vector3(lightData.size.x, lightData.size.y, range); // One-sided Vector3 extents = 0.5f * dimensions; Vector3 centerVS = positionVS + extents.z * zAxisVS; bound.center = centerVS; bound.boxAxisX = extents.x * xAxisVS; bound.boxAxisY = extents.y * yAxisVS; bound.boxAxisZ = extents.z * zAxisVS; bound.radius = extents.magnitude; bound.scaleXY.Set(1.0f, 1.0f); lightVolumeData.lightPos = centerVS; lightVolumeData.lightAxisX = xAxisVS; lightVolumeData.lightAxisY = yAxisVS; lightVolumeData.lightAxisZ = zAxisVS; lightVolumeData.boxInnerDist = extents; lightVolumeData.boxInvRange.Set(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity); lightVolumeData.featureFlags = (uint)LightFeatureFlags.Punctual; } else { Debug.Assert(false, "TODO: encountered an unknown GPULightType."); } m_lightList.bounds.Add(bound); m_lightList.lightVolumes.Add(lightVolumeData); } public bool GetEnvLightData(CommandBuffer cmd, Camera camera, VisibleReflectionProbe probe) { // For now we won't display real time probe when rendering one. // TODO: We may want to display last frame result but in this case we need to be careful not to update the atlas before all realtime probes are rendered (for frame coherency). // Unfortunately we don't have this information at the moment. if (probe.probe.mode == ReflectionProbeMode.Realtime && camera.cameraType == CameraType.Reflection) return false; int envIndex = m_ReflectionProbeCache.FetchSlice(cmd, probe.texture); // -1 means that the texture is not ready yet (ie not convolved/compressed yet) if (envIndex == -1) return false; var envLightData = new EnvLightData(); // CAUTION: localToWorld is the transform for the widget of the reflection probe. i.e the world position of the point use to do the cubemap capture (mean it include the local offset) envLightData.positionWS = probe.localToWorld.GetColumn(3); // TODO: Support sphere influence in UI if (probe.boxProjection == 0) { envLightData.envShapeType = EnvShapeType.Box; // If user request to have no projection, then setup a high number for minProjectionDistance // this will mimic infinite shape projection envLightData.minProjectionDistance = 65504.0f; } else { envLightData.envShapeType = EnvShapeType.Box; envLightData.minProjectionDistance = 0.0f; } // remove scale from the matrix (Scale in this matrix is use to scale the widget) envLightData.right = probe.localToWorld.GetColumn(0); envLightData.right.Normalize(); envLightData.up = probe.localToWorld.GetColumn(1); envLightData.up.Normalize(); envLightData.forward = probe.localToWorld.GetColumn(2); envLightData.forward.Normalize(); // Artists prefer to have blend distance inside the volume! // So we let the current UI but we assume blendDistance is an inside factor instead // Blend distance can't be larger than the max radius // probe.bounds.extents is BoxSize / 2 float maxBlendDist = Mathf.Min(probe.bounds.extents.x, Mathf.Min(probe.bounds.extents.y, probe.bounds.extents.z)); float blendDistance = Mathf.Min(maxBlendDist, probe.blendDistance); envLightData.innerDistance = probe.bounds.extents - new Vector3(blendDistance, blendDistance, blendDistance); envLightData.envIndex = envIndex; envLightData.offsetLS = probe.center; // center is misnamed, it is the offset (in local space) from center of the bounding box to the cubemap capture point envLightData.blendDistance = blendDistance; m_lightList.envLights.Add(envLightData); return true; } public void GetEnvLightVolumeDataAndBound(VisibleReflectionProbe probe, LightVolumeType lightVolumeType, Matrix4x4 worldToView) { var bound = new SFiniteLightBound(); var lightVolumeData = new LightVolumeData(); var bnds = probe.bounds; var boxOffset = probe.center; // reflection volume offset relative to cube map capture point var blendDistance = probe.blendDistance; var mat = probe.localToWorld; Vector3 vx = mat.GetColumn(0); Vector3 vy = mat.GetColumn(1); Vector3 vz = mat.GetColumn(2); Vector3 vw = mat.GetColumn(3); vx.Normalize(); // Scale shouldn't affect the probe or its bounds vy.Normalize(); vz.Normalize(); // C is reflection volume center in world space (NOT same as cube map capture point) var e = bnds.extents; // 0.5f * Vector3.Max(-boxSizes[p], boxSizes[p]); var C = vx * boxOffset.x + vy * boxOffset.y + vz * boxOffset.z + vw; var combinedExtent = e + new Vector3(blendDistance, blendDistance, blendDistance); // transform to camera space (becomes a left hand coordinate frame in Unity since Determinant(worldToView)<0) vx = worldToView.MultiplyVector(vx); vy = worldToView.MultiplyVector(vy); vz = worldToView.MultiplyVector(vz); var Cw = worldToView.MultiplyPoint(C); bound.center = Cw; bound.boxAxisX = combinedExtent.x * vx; bound.boxAxisY = combinedExtent.y * vy; bound.boxAxisZ = combinedExtent.z * vz; bound.scaleXY.Set(1.0f, 1.0f); bound.radius = combinedExtent.magnitude; lightVolumeData.lightCategory = (uint)LightCategory.Env; lightVolumeData.lightVolume = (uint)lightVolumeType; lightVolumeData.featureFlags = (uint)LightFeatureFlags.Env; lightVolumeData.lightPos = Cw; lightVolumeData.lightAxisX = vx; lightVolumeData.lightAxisY = vy; lightVolumeData.lightAxisZ = vz; var delta = combinedExtent - e; lightVolumeData.boxInnerDist = e; lightVolumeData.boxInvRange.Set(1.0f / delta.x, 1.0f / delta.y, 1.0f / delta.z); m_lightList.bounds.Add(bound); m_lightList.lightVolumes.Add(lightVolumeData); } public int GetCurrentShadowCount() { return m_ShadowRequests.Count; } public int GetShadowAtlasCount() { return (int)m_ShadowMgr.GetShadowMapCount(); } public void UpdateCullingParameters(ref ScriptableCullingParameters cullingParams) { m_ShadowMgr.UpdateCullingParameters( ref cullingParams ); } public bool IsBakedShadowMaskLight(Light light) { return light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed && light.bakingOutput.mixedLightingMode == MixedLightingMode.Shadowmask && light.bakingOutput.occlusionMaskChannel != -1; // We need to have an occlusion mask channel assign, else we have no shadow mask } // Return true if BakedShadowMask are enabled public bool PrepareLightsForGPU(CommandBuffer cmd, ShadowSettings shadowSettings, CullResults cullResults, Camera camera) { using (new ProfilingSample(cmd, "Prepare Lights For GPU")) { // If any light require it, we need to enabled bake shadow mask feature m_enableBakeShadowMask = false; m_lightList.Clear(); Vector3 camPosWS = camera.transform.position; // Note: Light with null intensity/Color are culled by the C++, no need to test it here if (cullResults.visibleLights.Count != 0 || cullResults.visibleReflectionProbes.Count != 0) { // 0. deal with shadows { m_FrameId.frameCount++; // get the indices for all lights that want to have shadows m_ShadowRequests.Clear(); m_ShadowRequests.Capacity = cullResults.visibleLights.Count; int lcnt = cullResults.visibleLights.Count; for (int i = 0; i < lcnt; ++i) { VisibleLight vl = cullResults.visibleLights[i]; if (vl.light.shadows == LightShadows.None) continue; AdditionalShadowData asd = vl.light.GetComponent(); if (asd != null && asd.shadowDimmer > 0.0f) m_ShadowRequests.Add(i); } // pass this list to a routine that assigns shadows based on some heuristic uint shadowRequestCount = (uint)m_ShadowRequests.Count; //TODO: Do not call ToArray here to avoid GC, refactor API int[] shadowRequests = m_ShadowRequests.ToArray(); int[] shadowDataIndices; m_ShadowMgr.ProcessShadowRequests(m_FrameId, cullResults, camera, ShaderConfig.s_CameraRelativeRendering != 0, cullResults.visibleLights, ref shadowRequestCount, shadowRequests, out shadowDataIndices); // update the visibleLights with the shadow information m_ShadowIndices.Clear(); for (uint i = 0; i < shadowRequestCount; i++) { m_ShadowIndices.Add(shadowRequests[i], shadowDataIndices[i]); } } float oldSpecularGlobalDimmer = m_LightingSettings.specularGlobalDimmer; // Change some parameters in case of "special" rendering (can be preview, reflection, etc.) if (camera.cameraType == CameraType.Reflection) { m_LightingSettings.specularGlobalDimmer = 0.0f; } // 1. Count the number of lights and sort all lights by category, type and volume - This is required for the fptl/cluster shader code // If we reach maximum of lights available on screen, then we discard the light. // Lights are processed in order, so we don't discards light based on their importance but based on their ordering in visible lights list. int directionalLightcount = 0; int punctualLightcount = 0; int areaLightCount = 0; int lightCount = Math.Min(cullResults.visibleLights.Count, k_MaxLightsOnScreen); var sortKeys = new uint[lightCount]; int sortCount = 0; for (int lightIndex = 0, numLights = cullResults.visibleLights.Count; (lightIndex < numLights) && (sortCount < lightCount); ++lightIndex) { var light = cullResults.visibleLights[lightIndex]; // We only process light with additional data var additionalData = light.light.GetComponent(); // Debug.Assert(additionalData == null, "Missing HDAdditionalData on a light - Should have been create by HDLightEditor"); if (additionalData == null) return false; LightCategory lightCategory = LightCategory.Count; GPULightType gpuLightType = GPULightType.Point; LightVolumeType lightVolumeType = LightVolumeType.Count; if (additionalData.lightTypeExtent == LightTypeExtent.Punctual) { lightCategory = LightCategory.Punctual; switch (light.lightType) { case LightType.Spot: if (punctualLightcount >= k_MaxPunctualLightsOnScreen) continue; switch (additionalData.spotLightShape) { case SpotLightShape.Cone: gpuLightType = GPULightType.Spot; lightVolumeType = LightVolumeType.Cone; break; case SpotLightShape.Pyramid: gpuLightType = GPULightType.ProjectorPyramid; lightVolumeType = LightVolumeType.Cone; break; case SpotLightShape.Box: gpuLightType = GPULightType.ProjectorBox; lightVolumeType = LightVolumeType.Box; break; default: Debug.Assert(false, "Encountered an unknown SpotLightShape."); break; } break; case LightType.Directional: if (directionalLightcount >= k_MaxDirectionalLightsOnScreen) continue; gpuLightType = GPULightType.Directional; // No need to add volume, always visible lightVolumeType = LightVolumeType.Count; // Count is none break; case LightType.Point: if (punctualLightcount >= k_MaxPunctualLightsOnScreen) continue; gpuLightType = GPULightType.Point; lightVolumeType = LightVolumeType.Sphere; break; default: Debug.Assert(false, "Encountered an unknown LightType."); break; } } else { lightCategory = LightCategory.Area; switch (additionalData.lightTypeExtent) { case LightTypeExtent.Rectangle: if (areaLightCount >= k_MaxAreaLightsOnScreen) continue; gpuLightType = GPULightType.Rectangle; lightVolumeType = LightVolumeType.Box; break; case LightTypeExtent.Line: if (areaLightCount >= k_MaxAreaLightsOnScreen) continue; gpuLightType = GPULightType.Line; lightVolumeType = LightVolumeType.Box; break; default: Debug.Assert(false, "Encountered an unknown LightType."); break; } } uint shadow = m_ShadowIndices.ContainsKey(lightIndex) ? 1u : 0; // 5 bit (0x1F) light category, 5 bit (0x1F) GPULightType, 5 bit (0x1F) lightVolume, 1 bit for shadow casting, 16 bit index sortKeys[sortCount++] = (uint)lightCategory << 27 | (uint)gpuLightType << 22 | (uint)lightVolumeType << 17 | shadow << 16 | (uint)lightIndex; } CoreUtils.QuickSort(sortKeys, 0, sortCount - 1); // Call our own quicksort instead of Array.Sort(sortKeys, 0, sortCount) so we don't allocate memory (note the SortCount-1 that is different from original call). // TODO: Refactor shadow management // The good way of managing shadow: // Here we sort everyone and we decide which light is important or not (this is the responsibility of the lightloop) // we allocate shadow slot based on maximum shadow allowed on screen and attribute slot by bigger solid angle // THEN we ask to the ShadowRender to render the shadow, not the reverse as it is today (i.e render shadow than expect they // will be use...) // The lightLoop is in charge, not the shadow pass. // For now we will still apply the maximum of shadow here but we don't apply the sorting by priority + slot allocation yet m_CurrentSunLight = null; m_CurrentSunLightShadowIndex = -1; // 2. Go through all lights, convert them to GPU format. // Create simultaneously data for culling (LigthVolumeData and rendering) var worldToView = WorldToCamera(camera); for (int sortIndex = 0; sortIndex < sortCount; ++sortIndex) { // In 1. we have already classify and sorted the light, we need to use this sorted order here uint sortKey = sortKeys[sortIndex]; LightCategory lightCategory = (LightCategory)((sortKey >> 27) & 0x1F); GPULightType gpuLightType = (GPULightType)((sortKey >> 22) & 0x1F); LightVolumeType lightVolumeType = (LightVolumeType)((sortKey >> 17) & 0x1F); int lightIndex = (int)(sortKey & 0xFFFF); var light = cullResults.visibleLights[lightIndex]; m_enableBakeShadowMask = m_enableBakeShadowMask || IsBakedShadowMaskLight(light.light); var additionalLightData = light.light.GetComponent(); var additionalShadowData = light.light.GetComponent(); // Can be null // Directional rendering side, it is separated as it is always visible so no volume to handle here if (gpuLightType == GPULightType.Directional) { if (GetDirectionalLightData(cmd, shadowSettings, gpuLightType, light, additionalLightData, additionalShadowData, lightIndex)) { directionalLightcount++; // We make the light position camera-relative as late as possible in order // to allow the preceding code to work with the absolute world space coordinates. if (ShaderConfig.s_CameraRelativeRendering != 0) { // Caution: 'DirectionalLightData.positionWS' is camera-relative after this point. int n = m_lightList.directionalLights.Count; DirectionalLightData lightData = m_lightList.directionalLights[n - 1]; lightData.positionWS -= camPosWS; m_lightList.directionalLights[n - 1] = lightData; } } continue; } // Punctual, area, projector lights - the rendering side. if (GetLightData(cmd, shadowSettings, camera, gpuLightType, light, additionalLightData, additionalShadowData, lightIndex)) { switch (lightCategory) { case LightCategory.Punctual: punctualLightcount++; break; case LightCategory.Area: areaLightCount++; break; default: Debug.Assert(false, "TODO: encountered an unknown LightCategory."); break; } // Then culling side. Must be call in this order as we pass the created Light data to the function GetLightVolumeDataAndBound(lightCategory, gpuLightType, lightVolumeType, light, m_lightList.lights[m_lightList.lights.Count - 1], worldToView); // We make the light position camera-relative as late as possible in order // to allow the preceding code to work with the absolute world space coordinates. if (ShaderConfig.s_CameraRelativeRendering != 0) { // Caution: 'LightData.positionWS' is camera-relative after this point. int n = m_lightList.lights.Count; LightData lightData = m_lightList.lights[n - 1]; lightData.positionWS -= camPosWS; m_lightList.lights[n - 1] = lightData; } } } // Sanity check Debug.Assert(m_lightList.directionalLights.Count == directionalLightcount); Debug.Assert(m_lightList.lights.Count == areaLightCount + punctualLightcount); m_punctualLightCount = punctualLightcount; m_areaLightCount = areaLightCount; // Redo everything but this time with envLights int envLightCount = 0; int probeCount = Math.Min(cullResults.visibleReflectionProbes.Count, k_MaxEnvLightsOnScreen); sortKeys = new uint[probeCount]; sortCount = 0; for (int probeIndex = 0, numProbes = cullResults.visibleReflectionProbes.Count; (probeIndex < numProbes) && (sortCount < probeCount); probeIndex++) { VisibleReflectionProbe probe = cullResults.visibleReflectionProbes[probeIndex]; // probe.texture can be null when we are adding a reflection probe in the editor if (probe.texture == null || envLightCount >= k_MaxEnvLightsOnScreen) continue; // Work around the culling issues. TODO: fix culling in C++. if (probe.probe == null || !probe.probe.isActiveAndEnabled) continue; // Work around the data issues. if (probe.localToWorld.determinant == 0) { Debug.LogError("Reflection probe " + probe.probe.name + " has an invalid local frame and needs to be fixed."); continue; } // TODO: Support LightVolumeType.Sphere, currently in UI there is no way to specify a sphere influence volume LightVolumeType lightVolumeType = probe.boxProjection != 0 ? LightVolumeType.Box : LightVolumeType.Box; ++envLightCount; float boxVolume = 8 * probe.bounds.extents.x * probe.bounds.extents.y * probe.bounds.extents.z; float logVolume = Mathf.Clamp(256 + Mathf.Log(boxVolume, 1.1f), 0, 8191); // Allow for negative exponents // 13 bit volume, 3 bit LightVolumeType, 16 bit index sortKeys[sortCount++] = (uint)logVolume << 19 | (uint)lightVolumeType << 16 | ((uint)probeIndex & 0xFFFF); // Sort by volume } // Not necessary yet but call it for future modification with sphere influence volume CoreUtils.QuickSort(sortKeys, 0, sortCount - 1); // Call our own quicksort instead of Array.Sort(sortKeys, 0, sortCount) so we don't allocate memory (note the SortCount-1 that is different from original call). for (int sortIndex = 0; sortIndex < sortCount; ++sortIndex) { // In 1. we have already classify and sorted the light, we need to use this sorted order here uint sortKey = sortKeys[sortIndex]; LightVolumeType lightVolumeType = (LightVolumeType)((sortKey >> 16) & 0x3); int probeIndex = (int)(sortKey & 0xFFFF); VisibleReflectionProbe probe = cullResults.visibleReflectionProbes[probeIndex]; if (GetEnvLightData(cmd, camera, probe)) { GetEnvLightVolumeDataAndBound(probe, lightVolumeType, worldToView); // We make the light position camera-relative as late as possible in order // to allow the preceding code to work with the absolute world space coordinates. if (ShaderConfig.s_CameraRelativeRendering != 0) { // Caution: 'EnvLightData.positionWS' is camera-relative after this point. int n = m_lightList.envLights.Count; EnvLightData envLightData = m_lightList.envLights[n - 1]; envLightData.positionWS -= camPosWS; m_lightList.envLights[n - 1] = envLightData; } } } // Restore values after "special rendering" m_LightingSettings.specularGlobalDimmer = oldSpecularGlobalDimmer; } m_lightCount = m_lightList.lights.Count + m_lightList.envLights.Count; Debug.Assert(m_lightList.bounds.Count == m_lightCount); Debug.Assert(m_lightList.lightVolumes.Count == m_lightCount); UpdateDataBuffers(); m_maxShadowDistance = shadowSettings.maxShadowDistance; return m_enableBakeShadowMask; } } void VoxelLightListGeneration(CommandBuffer cmd, Camera camera, Matrix4x4 projscr, Matrix4x4 invProjscr, RenderTargetIdentifier cameraDepthBufferRT) { // clear atomic offset index cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_ClearVoxelAtomicKernel, HDShaderIDs.g_LayeredSingleIdxBuffer, s_GlobalLightListAtomic); cmd.DispatchCompute(buildPerVoxelLightListShader, s_ClearVoxelAtomicKernel, 1, 1, 1); bool isOrthographic = camera.orthographic; cmd.SetComputeIntParam(buildPerVoxelLightListShader, HDShaderIDs.g_isOrthographic, isOrthographic ? 1 : 0); cmd.SetComputeIntParam(buildPerVoxelLightListShader, HDShaderIDs._EnvLightIndexShift, m_lightList.lights.Count); cmd.SetComputeIntParam(buildPerVoxelLightListShader, HDShaderIDs.g_iNrVisibLights, m_lightCount); cmd.SetComputeMatrixParam(buildPerVoxelLightListShader, HDShaderIDs.g_mScrProjection, projscr); cmd.SetComputeMatrixParam(buildPerVoxelLightListShader, HDShaderIDs.g_mInvScrProjection, invProjscr); cmd.SetComputeIntParam(buildPerVoxelLightListShader, HDShaderIDs.g_iLog2NumClusters, k_Log2NumClusters); //Vector4 v2_near = invProjscr * new Vector4(0.0f, 0.0f, 0.0f, 1.0f); //Vector4 v2_far = invProjscr * new Vector4(0.0f, 0.0f, 1.0f, 1.0f); //float nearPlane2 = -(v2_near.z/v2_near.w); //float farPlane2 = -(v2_far.z/v2_far.w); var nearPlane = camera.nearClipPlane; var farPlane = camera.farClipPlane; cmd.SetComputeFloatParam(buildPerVoxelLightListShader, HDShaderIDs.g_fNearPlane, nearPlane); cmd.SetComputeFloatParam(buildPerVoxelLightListShader, HDShaderIDs.g_fFarPlane, farPlane); const float C = (float)(1 << k_Log2NumClusters); var geomSeries = (1.0 - Mathf.Pow(k_ClustLogBase, C)) / (1 - k_ClustLogBase); // geometric series: sum_k=0^{C-1} base^k m_ClustScale = (float)(geomSeries / (farPlane - nearPlane)); cmd.SetComputeFloatParam(buildPerVoxelLightListShader, HDShaderIDs.g_fClustScale, m_ClustScale); cmd.SetComputeFloatParam(buildPerVoxelLightListShader, HDShaderIDs.g_fClustBase, k_ClustLogBase); cmd.SetComputeTextureParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_depth_tex, cameraDepthBufferRT); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_vLayeredLightList, s_PerVoxelLightLists); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_LayeredOffset, s_PerVoxelOffset); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_LayeredSingleIdxBuffer, s_GlobalLightListAtomic); if (m_TileSettings.enableBigTilePrepass) cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_vBigTileLightList, s_BigTileLightList); if (k_UseDepthBuffer) { cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_logBaseBuffer, s_PerTileLogBaseTweak); } cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_vBoundsBuffer, s_AABBBoundsBuffer); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs._LightVolumeData, s_LightVolumeDataBuffer); cmd.SetComputeBufferParam(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, HDShaderIDs.g_data, s_ConvexBoundsBuffer); var numTilesX = GetNumTileClusteredX(camera); var numTilesY = GetNumTileClusteredY(camera); cmd.DispatchCompute(buildPerVoxelLightListShader, s_GenListPerVoxelKernel, numTilesX, numTilesY, 1); } public void BuildGPULightLists(Camera camera, CommandBuffer cmd, RenderTargetIdentifier cameraDepthBufferRT, RenderTargetIdentifier stencilTextureRT) { cmd.BeginSample("Build Light List"); var w = camera.pixelWidth; var h = camera.pixelHeight; var numBigTilesX = (w + 63) / 64; var numBigTilesY = (h + 63) / 64; // camera to screen matrix (and it's inverse) var proj = CameraProjection(camera); var temp = new Matrix4x4(); temp.SetRow(0, new Vector4(0.5f * w, 0.0f, 0.0f, 0.5f * w)); temp.SetRow(1, new Vector4(0.0f, 0.5f * h, 0.0f, 0.5f * h)); temp.SetRow(2, new Vector4(0.0f, 0.0f, 0.5f, 0.5f)); temp.SetRow(3, new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); var projscr = temp * proj; var invProjscr = projscr.inverse; bool isOrthographic = camera.orthographic; cmd.SetRenderTarget(BuiltinRenderTextureType.None); // generate screen-space AABBs (used for both fptl and clustered). if (m_lightCount != 0) { temp.SetRow(0, new Vector4(1.0f, 0.0f, 0.0f, 0.0f)); temp.SetRow(1, new Vector4(0.0f, 1.0f, 0.0f, 0.0f)); temp.SetRow(2, new Vector4(0.0f, 0.0f, 0.5f, 0.5f)); temp.SetRow(3, new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); var projh = temp * proj; var invProjh = projh.inverse; cmd.SetComputeIntParam(buildScreenAABBShader, HDShaderIDs.g_isOrthographic, isOrthographic ? 1 : 0); cmd.SetComputeIntParam(buildScreenAABBShader, HDShaderIDs.g_iNrVisibLights, m_lightCount); cmd.SetComputeBufferParam(buildScreenAABBShader, s_GenAABBKernel, HDShaderIDs.g_data, s_ConvexBoundsBuffer); cmd.SetComputeMatrixParam(buildScreenAABBShader, HDShaderIDs.g_mProjection, projh); cmd.SetComputeMatrixParam(buildScreenAABBShader, HDShaderIDs.g_mInvProjection, invProjh); cmd.SetComputeBufferParam(buildScreenAABBShader, s_GenAABBKernel, HDShaderIDs.g_vBoundsBuffer, s_AABBBoundsBuffer); cmd.DispatchCompute(buildScreenAABBShader, s_GenAABBKernel, (m_lightCount + 7) / 8, 1, 1); } // enable coarse 2D pass on 64x64 tiles (used for both fptl and clustered). if (m_TileSettings.enableBigTilePrepass) { cmd.SetComputeIntParam(buildPerBigTileLightListShader, HDShaderIDs.g_isOrthographic, isOrthographic ? 1 : 0); cmd.SetComputeIntParams(buildPerBigTileLightListShader, HDShaderIDs.g_viDimensions, w, h); cmd.SetComputeIntParam(buildPerBigTileLightListShader, HDShaderIDs._EnvLightIndexShift, m_lightList.lights.Count); cmd.SetComputeIntParam(buildPerBigTileLightListShader, HDShaderIDs.g_iNrVisibLights, m_lightCount); cmd.SetComputeMatrixParam(buildPerBigTileLightListShader, HDShaderIDs.g_mScrProjection, projscr); cmd.SetComputeMatrixParam(buildPerBigTileLightListShader, HDShaderIDs.g_mInvScrProjection, invProjscr); cmd.SetComputeFloatParam(buildPerBigTileLightListShader, HDShaderIDs.g_fNearPlane, camera.nearClipPlane); cmd.SetComputeFloatParam(buildPerBigTileLightListShader, HDShaderIDs.g_fFarPlane, camera.farClipPlane); cmd.SetComputeBufferParam(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, HDShaderIDs.g_vLightList, s_BigTileLightList); cmd.SetComputeBufferParam(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, HDShaderIDs.g_vBoundsBuffer, s_AABBBoundsBuffer); cmd.SetComputeBufferParam(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, HDShaderIDs._LightVolumeData, s_LightVolumeDataBuffer); cmd.SetComputeBufferParam(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, HDShaderIDs.g_data, s_ConvexBoundsBuffer); cmd.DispatchCompute(buildPerBigTileLightListShader, s_GenListPerBigTileKernel, numBigTilesX, numBigTilesY, 1); } var numTilesX = GetNumTileFtplX(camera); var numTilesY = GetNumTileFtplY(camera); var numTiles = numTilesX * numTilesY; bool enableFeatureVariants = GetFeatureVariantsEnabled(); // optimized for opaques only if (m_isFptlEnabled) { cmd.SetComputeIntParam(buildPerTileLightListShader, HDShaderIDs.g_isOrthographic, isOrthographic ? 1 : 0); cmd.SetComputeIntParams(buildPerTileLightListShader, HDShaderIDs.g_viDimensions, w, h); cmd.SetComputeIntParam(buildPerTileLightListShader, HDShaderIDs._EnvLightIndexShift, m_lightList.lights.Count); cmd.SetComputeIntParam(buildPerTileLightListShader, HDShaderIDs.g_iNrVisibLights, m_lightCount); cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs.g_vBoundsBuffer, s_AABBBoundsBuffer); cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs._LightVolumeData, s_LightVolumeDataBuffer); cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs.g_data, s_ConvexBoundsBuffer); cmd.SetComputeMatrixParam(buildPerTileLightListShader, HDShaderIDs.g_mScrProjection, projscr); cmd.SetComputeMatrixParam(buildPerTileLightListShader, HDShaderIDs.g_mInvScrProjection, invProjscr); cmd.SetComputeTextureParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs.g_depth_tex, cameraDepthBufferRT); cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs.g_vLightList, s_LightList); if (m_TileSettings.enableBigTilePrepass) cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs.g_vBigTileLightList, s_BigTileLightList); if (enableFeatureVariants) { uint baseFeatureFlags = 0; if (m_lightList.directionalLights.Count > 0) { baseFeatureFlags |= (uint)LightFeatureFlags.Directional; } if (Shader.GetGlobalInt(HDShaderIDs._EnvLightSkyEnabled) != 0) { baseFeatureFlags |= (uint)LightFeatureFlags.Sky; } if (!m_TileSettings.enableComputeMaterialVariants) { baseFeatureFlags |= LightDefinitions.s_MaterialFeatureMaskFlags; } cmd.SetComputeIntParam(buildPerTileLightListShader, HDShaderIDs.g_BaseFeatureFlags, (int)baseFeatureFlags); cmd.SetComputeBufferParam(buildPerTileLightListShader, s_GenListPerTileKernel, HDShaderIDs.g_TileFeatureFlags, s_TileFeatureFlags); } cmd.DispatchCompute(buildPerTileLightListShader, s_GenListPerTileKernel, numTilesX, numTilesY, 1); } // Cluster VoxelLightListGeneration(cmd, camera, projscr, invProjscr, cameraDepthBufferRT); if (enableFeatureVariants) { // material classification if (m_TileSettings.enableComputeMaterialVariants) { int buildMaterialFlagsKernel = s_BuildMaterialFlagsOrKernel; uint baseFeatureFlags = 0; if (!m_TileSettings.enableComputeLightVariants) { buildMaterialFlagsKernel = s_BuildMaterialFlagsWriteKernel; baseFeatureFlags |= LightDefinitions.s_LightFeatureMaskFlags; } cmd.SetComputeIntParam(buildMaterialFlagsShader, HDShaderIDs.g_BaseFeatureFlags, (int)baseFeatureFlags); cmd.SetComputeIntParams(buildMaterialFlagsShader, HDShaderIDs.g_viDimensions, w, h); cmd.SetComputeBufferParam(buildMaterialFlagsShader, buildMaterialFlagsKernel, HDShaderIDs.g_TileFeatureFlags, s_TileFeatureFlags); cmd.SetComputeTextureParam(buildMaterialFlagsShader, buildMaterialFlagsKernel, HDShaderIDs._StencilTexture, stencilTextureRT); cmd.DispatchCompute(buildMaterialFlagsShader, buildMaterialFlagsKernel, numTilesX, numTilesY, 1); } // clear dispatch indirect buffer cmd.SetComputeBufferParam(clearDispatchIndirectShader, s_ClearDispatchIndirectKernel, HDShaderIDs.g_DispatchIndirectBuffer, s_DispatchIndirectBuffer); cmd.DispatchCompute(clearDispatchIndirectShader, s_ClearDispatchIndirectKernel, 1, 1, 1); // add tiles to indirect buffer cmd.SetComputeBufferParam(buildDispatchIndirectShader, s_BuildDispatchIndirectKernel, HDShaderIDs.g_DispatchIndirectBuffer, s_DispatchIndirectBuffer); cmd.SetComputeBufferParam(buildDispatchIndirectShader, s_BuildDispatchIndirectKernel, HDShaderIDs.g_TileList, s_TileList); cmd.SetComputeBufferParam(buildDispatchIndirectShader, s_BuildDispatchIndirectKernel, HDShaderIDs.g_TileFeatureFlags, s_TileFeatureFlags); cmd.SetComputeIntParam(buildDispatchIndirectShader, HDShaderIDs.g_NumTiles, numTiles); cmd.SetComputeIntParam(buildDispatchIndirectShader, HDShaderIDs.g_NumTilesX, numTilesX); cmd.DispatchCompute(buildDispatchIndirectShader, s_BuildDispatchIndirectKernel, (numTiles + 63) / 64, 1, 1); } cmd.EndSample("Build Light List"); PushGlobalParams(camera, cmd); } private void UpdateDataBuffers() { s_DirectionalLightDatas.SetData(m_lightList.directionalLights); s_LightDatas.SetData(m_lightList.lights); s_EnvLightDatas.SetData(m_lightList.envLights); s_shadowDatas.SetData(m_lightList.shadows); // These two buffers have been set in Rebuild() s_ConvexBoundsBuffer.SetData(m_lightList.bounds); s_LightVolumeDataBuffer.SetData(m_lightList.lightVolumes); } void PushGlobalParams(Camera camera, CommandBuffer cmd) { using (new ProfilingSample(cmd, "Push Global Parameters", HDRenderPipeline.GetSampler(CustomSamplerId.TPPushGlobalParameters))) { // Shadows m_ShadowMgr.SyncData(); m_ShadowMgr.BindResources(cmd, null, 0); cmd.SetGlobalTexture(HDShaderIDs._CookieTextures, m_CookieTexArray.GetTexCache()); cmd.SetGlobalTexture(HDShaderIDs._CookieCubeTextures, m_CubeCookieTexArray.GetTexCache()); cmd.SetGlobalTexture(HDShaderIDs._EnvTextures, m_ReflectionProbeCache.GetTexCache()); cmd.SetGlobalBuffer(HDShaderIDs._DirectionalLightDatas, s_DirectionalLightDatas); cmd.SetGlobalInt(HDShaderIDs._DirectionalLightCount, m_lightList.directionalLights.Count); cmd.SetGlobalBuffer(HDShaderIDs._LightDatas, s_LightDatas); cmd.SetGlobalInt(HDShaderIDs._PunctualLightCount, m_punctualLightCount); cmd.SetGlobalInt(HDShaderIDs._AreaLightCount, m_areaLightCount); cmd.SetGlobalBuffer(HDShaderIDs._EnvLightDatas, s_EnvLightDatas); cmd.SetGlobalInt(HDShaderIDs._EnvLightCount, m_lightList.envLights.Count); cmd.SetGlobalBuffer(HDShaderIDs._ShadowDatas, s_shadowDatas); cmd.SetGlobalVectorArray(HDShaderIDs._DirShadowSplitSpheres, m_lightList.directionalShadowSplitSphereSqr); cmd.SetGlobalInt(HDShaderIDs._NumTileFtplX, GetNumTileFtplX(camera)); cmd.SetGlobalInt(HDShaderIDs._NumTileFtplY, GetNumTileFtplY(camera)); cmd.SetGlobalInt(HDShaderIDs._NumTileClusteredX, GetNumTileClusteredX(camera)); cmd.SetGlobalInt(HDShaderIDs._NumTileClusteredY, GetNumTileClusteredY(camera)); if (m_TileSettings.enableBigTilePrepass) cmd.SetGlobalBuffer(HDShaderIDs.g_vBigTileLightList, s_BigTileLightList); // Cluster { cmd.SetGlobalFloat(HDShaderIDs.g_fClustScale, m_ClustScale); cmd.SetGlobalFloat(HDShaderIDs.g_fClustBase, k_ClustLogBase); cmd.SetGlobalFloat(HDShaderIDs.g_fNearPlane, camera.nearClipPlane); cmd.SetGlobalFloat(HDShaderIDs.g_fFarPlane, camera.farClipPlane); cmd.SetGlobalInt(HDShaderIDs.g_iLog2NumClusters, k_Log2NumClusters); cmd.SetGlobalInt(HDShaderIDs.g_isLogBaseBufferEnabled, k_UseDepthBuffer ? 1 : 0); cmd.SetGlobalBuffer(HDShaderIDs.g_vLayeredOffsetsBuffer, s_PerVoxelOffset); if (k_UseDepthBuffer) { cmd.SetGlobalBuffer(HDShaderIDs.g_logBaseBuffer, s_PerTileLogBaseTweak); } } } } #if UNITY_EDITOR private Vector2 m_mousePosition = Vector2.zero; private void OnSceneGUI(UnityEditor.SceneView sceneview) { m_mousePosition = Event.current.mousePosition; } #endif public void RenderShadows(ScriptableRenderContext renderContext, CommandBuffer cmd, CullResults cullResults) { // kick off the shadow jobs here m_ShadowMgr.RenderShadows(m_FrameId, renderContext, cmd, cullResults, cullResults.visibleLights); } public struct LightingPassOptions { public bool outputSplitLighting; } public void RenderDeferredDirectionalShadow(HDCamera hdCamera, RenderTargetIdentifier deferredShadowRT, RenderTargetIdentifier depthTexture, CommandBuffer cmd) { if (m_CurrentSunLight == null) return; using (new ProfilingSample(cmd, "Deferred Directional Shadow", HDRenderPipeline.GetSampler(CustomSamplerId.TPDeferredDirectionalShadow))) { m_ShadowMgr.BindResources(cmd, deferredDirectionalShadowComputeShader, s_deferredDirectionalShadowKernel); cmd.SetComputeFloatParam(deferredDirectionalShadowComputeShader, HDShaderIDs._DirectionalShadowIndex, (float)m_CurrentSunLightShadowIndex); cmd.SetComputeTextureParam(deferredDirectionalShadowComputeShader, s_deferredDirectionalShadowKernel, HDShaderIDs._DeferredShadowTextureUAV, deferredShadowRT); cmd.SetComputeTextureParam(deferredDirectionalShadowComputeShader, s_deferredDirectionalShadowKernel, HDShaderIDs._MainDepthTexture, depthTexture); int deferredShadowTileSize = 16; // Must match DeferreDirectionalShadow.compute int numTilesX = (hdCamera.camera.pixelWidth + (deferredShadowTileSize - 1)) / deferredShadowTileSize; int numTilesY = (hdCamera.camera.pixelHeight + (deferredShadowTileSize - 1)) / deferredShadowTileSize; cmd.DispatchCompute(deferredDirectionalShadowComputeShader, s_deferredDirectionalShadowKernel, numTilesX, numTilesY, 1); } } public void RenderDeferredLighting( HDCamera hdCamera, CommandBuffer cmd, DebugDisplaySettings debugDisplaySettings, RenderTargetIdentifier[] colorBuffers, RenderTargetIdentifier depthStencilBuffer, RenderTargetIdentifier depthTexture, LightingPassOptions options) { cmd.SetGlobalBuffer(HDShaderIDs.g_vLightListGlobal, s_LightList); if (m_TileSettings.enableTileAndCluster && m_TileSettings.enableComputeLightEvaluation && options.outputSplitLighting) { // The CS is always in the MRT mode. Do not execute the same shader twice. return; } // Predeclared to reduce GC pressure string tilePassName = "TilePass - Deferred Lighting Pass"; string tilePassMRTName = "TilePass - Deferred Lighting Pass MRT"; string singlePassName = "SinglePass - Deferred Lighting Pass"; string SinglePassMRTName = "SinglePass - Deferred Lighting Pass MRT"; // TODO: Check if we can remove this, when I test I can't Texture skyTexture = Shader.GetGlobalTexture(HDShaderIDs._SkyTexture); float skyTextureMipCount = Shader.GetGlobalFloat(HDShaderIDs._SkyTextureMipCount); string sLabel = m_TileSettings.enableTileAndCluster ? (options.outputSplitLighting ? tilePassMRTName : tilePassName) : (options.outputSplitLighting ? SinglePassMRTName : singlePassName); using (new ProfilingSample(cmd, sLabel, HDRenderPipeline.GetSampler(CustomSamplerId.TPRenderDeferredLighting))) { var camera = hdCamera.camera; // Compute path if (m_TileSettings.enableTileAndCluster && m_TileSettings.enableComputeLightEvaluation) { int w = camera.pixelWidth; int h = camera.pixelHeight; int numTilesX = (w + 15) / 16; int numTilesY = (h + 15) / 16; int numTiles = numTilesX * numTilesY; bool enableFeatureVariants = GetFeatureVariantsEnabled() && !debugDisplaySettings.IsDebugDisplayEnabled(); int numVariants = 1; if (enableFeatureVariants) numVariants = LightDefinitions.s_NumFeatureVariants; for (int variant = 0; variant < numVariants; variant++) { int kernel; if (enableFeatureVariants) { if (m_enableBakeShadowMask) kernel = s_shadeOpaqueIndirectShadowMaskFptlKernels[variant]; else kernel = s_shadeOpaqueIndirectFptlKernels[variant]; } else { if (m_enableBakeShadowMask) { kernel = debugDisplaySettings.IsDebugDisplayEnabled() ? s_shadeOpaqueDirectShadowMaskFptlDebugDisplayKernel : s_shadeOpaqueDirectShadowMaskFptlKernel; } else { kernel = debugDisplaySettings.IsDebugDisplayEnabled() ? s_shadeOpaqueDirectFptlDebugDisplayKernel : s_shadeOpaqueDirectFptlKernel; } } cmd.SetComputeTextureParam(deferredComputeShader, kernel, HDShaderIDs._MainDepthTexture, depthTexture); // TODO: Should remove this setting but can't remove it else get error: Property (_AmbientOcclusionTexture) at kernel index (32) is not set. Check why cmd.SetComputeTextureParam(deferredComputeShader, kernel, HDShaderIDs._AmbientOcclusionTexture, HDShaderIDs._AmbientOcclusionTexture); // TODO: Is it possible to setup this outside the loop ? Can figure out how, get this: Property (specularLightingUAV) at kernel index (21) is not set cmd.SetComputeTextureParam(deferredComputeShader, kernel, HDShaderIDs.specularLightingUAV, colorBuffers[0]); cmd.SetComputeTextureParam(deferredComputeShader, kernel, HDShaderIDs.diffuseLightingUAV, colorBuffers[1]); // TODO: Check if we can remove this, when I test I can't cmd.SetComputeTextureParam(deferredComputeShader, kernel, HDShaderIDs._SkyTexture, skyTexture ? skyTexture : m_DefaultTextureCube); cmd.SetComputeFloatParam(deferredComputeShader, HDShaderIDs._SkyTextureMipCount, skyTextureMipCount); // always do deferred lighting in blocks of 16x16 (not same as tiled light size) if (enableFeatureVariants) { cmd.SetComputeIntParam(deferredComputeShader, HDShaderIDs.g_TileListOffset, variant * numTiles); cmd.SetComputeBufferParam(deferredComputeShader, kernel, HDShaderIDs.g_TileList, s_TileList); cmd.DispatchCompute(deferredComputeShader, kernel, s_DispatchIndirectBuffer, (uint)variant * 3 * sizeof(uint)); } else { cmd.DispatchCompute(deferredComputeShader, kernel, numTilesX, numTilesY, 1); } } } else // Pixel shader evaluation { int index = GetDeferredLightingMaterialIndex( options.outputSplitLighting ? 1 : 0, m_TileSettings.enableTileAndCluster ? 1 : 0, m_enableBakeShadowMask ? 1 : 0, debugDisplaySettings.IsDebugDisplayEnabled() ? 1 : 0); Material currentLightingMaterial = m_deferredLightingMaterial[index]; if (options.outputSplitLighting) { CoreUtils.DrawFullScreen(cmd, currentLightingMaterial, colorBuffers, depthStencilBuffer); } else { // If SSS is disable, do lighting for both split lighting and no split lighting // This is for debug purpose, so fine to use immediate material mode here to modify render state if (!debugDisplaySettings.renderingDebugSettings.enableSSSAndTransmission) { currentLightingMaterial.SetInt(HDShaderIDs._StencilRef, (int)StencilLightingUsage.NoLighting); currentLightingMaterial.SetInt(HDShaderIDs._StencilCmp, (int)CompareFunction.NotEqual); } else { currentLightingMaterial.SetInt(HDShaderIDs._StencilRef, (int)StencilLightingUsage.RegularLighting); currentLightingMaterial.SetInt(HDShaderIDs._StencilCmp, (int)CompareFunction.Equal); } CoreUtils.DrawFullScreen(cmd, currentLightingMaterial, colorBuffers[0], depthStencilBuffer); } } } // End profiling } public void RenderForward(Camera camera, CommandBuffer cmd, bool renderOpaque) { // Note: SHADOWS_SHADOWMASK keyword is enabled in HDRenderPipeline.cs ConfigureForShadowMask // Note: if we use render opaque with deferred tiling we need to render a opaque depth pass for these opaque objects if (!m_TileSettings.enableTileAndCluster) { using (new ProfilingSample(cmd, "Forward pass", HDRenderPipeline.GetSampler(CustomSamplerId.TPForwardPass))) { cmd.EnableShaderKeyword("LIGHTLOOP_SINGLE_PASS"); cmd.DisableShaderKeyword("LIGHTLOOP_TILE_PASS"); } } else { // Only opaques can use FPTL, transparent must use clustered! bool useFptl = renderOpaque && m_isFptlEnabledForForwardOpaque; using (new ProfilingSample(cmd, useFptl ? "Forward Tiled pass" : "Forward Clustered pass", HDRenderPipeline.GetSampler(CustomSamplerId.TPForwardTiledClusterpass))) { // say that we want to use tile of single loop cmd.EnableShaderKeyword("LIGHTLOOP_TILE_PASS"); cmd.DisableShaderKeyword("LIGHTLOOP_SINGLE_PASS"); CoreUtils.SetKeyword(cmd, "USE_FPTL_LIGHTLIST", useFptl); CoreUtils.SetKeyword(cmd, "USE_CLUSTERED_LIGHTLIST", !useFptl); cmd.SetGlobalBuffer(HDShaderIDs.g_vLightListGlobal, useFptl ? s_LightList : s_PerVoxelLightLists); } } } public void RenderDebugOverlay(HDCamera hdCamera, CommandBuffer cmd, DebugDisplaySettings debugDisplaySettings, ref float x, ref float y, float overlaySize, float width) { LightingDebugSettings lightingDebug = debugDisplaySettings.lightingDebugSettings; using (new ProfilingSample(cmd, "Tiled/cluster Lighting Debug", HDRenderPipeline.GetSampler(CustomSamplerId.TPTiledLightingDebug))) { if (lightingDebug.tileClusterDebug != TileSettings.TileClusterDebug.None) { int w = hdCamera.camera.pixelWidth; int h = hdCamera.camera.pixelHeight; int numTilesX = (w + 15) / 16; int numTilesY = (h + 15) / 16; int numTiles = numTilesX * numTilesY; Vector2 mousePixelCoord = Input.mousePosition; #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) { mousePixelCoord = m_mousePosition; mousePixelCoord.y = (hdCamera.screenSize.y - 1.0f) - mousePixelCoord.y; } #endif // Debug tiles if (lightingDebug.tileClusterDebug == TileSettings.TileClusterDebug.FeatureVariants) { if (GetFeatureVariantsEnabled()) { // featureVariants m_DebugViewTilesMaterial.SetInt(HDShaderIDs._NumTiles, numTiles); m_DebugViewTilesMaterial.SetInt(HDShaderIDs._ViewTilesFlags, (int)lightingDebug.tileClusterDebugByCategory); m_DebugViewTilesMaterial.SetVector(HDShaderIDs._MousePixelCoord, mousePixelCoord); m_DebugViewTilesMaterial.SetBuffer(HDShaderIDs.g_TileList, s_TileList); m_DebugViewTilesMaterial.SetBuffer(HDShaderIDs.g_DispatchIndirectBuffer, s_DispatchIndirectBuffer); m_DebugViewTilesMaterial.EnableKeyword("USE_FPTL_LIGHTLIST"); m_DebugViewTilesMaterial.DisableKeyword("USE_CLUSTERED_LIGHTLIST"); m_DebugViewTilesMaterial.DisableKeyword("SHOW_LIGHT_CATEGORIES"); m_DebugViewTilesMaterial.EnableKeyword("SHOW_FEATURE_VARIANTS"); cmd.DrawProcedural(Matrix4x4.identity, m_DebugViewTilesMaterial, 0, MeshTopology.Triangles, numTiles * 6); } } else // tile or cluster { bool bUseClustered = lightingDebug.tileClusterDebug == TileSettings.TileClusterDebug.Cluster; // lightCategories m_DebugViewTilesMaterial.SetInt(HDShaderIDs._ViewTilesFlags, (int)lightingDebug.tileClusterDebugByCategory); m_DebugViewTilesMaterial.SetVector(HDShaderIDs._MousePixelCoord, mousePixelCoord); m_DebugViewTilesMaterial.SetBuffer(HDShaderIDs.g_vLightListGlobal, bUseClustered ? s_PerVoxelLightLists : s_LightList); m_DebugViewTilesMaterial.EnableKeyword(bUseClustered ? "USE_CLUSTERED_LIGHTLIST" : "USE_FPTL_LIGHTLIST"); m_DebugViewTilesMaterial.DisableKeyword(!bUseClustered ? "USE_CLUSTERED_LIGHTLIST" : "USE_FPTL_LIGHTLIST"); m_DebugViewTilesMaterial.EnableKeyword("SHOW_LIGHT_CATEGORIES"); m_DebugViewTilesMaterial.DisableKeyword("SHOW_FEATURE_VARIANTS"); CoreUtils.DrawFullScreen(cmd, m_DebugViewTilesMaterial, 0); } } } using (new ProfilingSample(cmd, "Display Shadows", HDRenderPipeline.GetSampler(CustomSamplerId.TPDisplayShadows))) { if (lightingDebug.shadowDebugMode == ShadowMapDebugMode.VisualizeShadowMap) { int index = (int)lightingDebug.shadowMapIndex; #if UNITY_EDITOR if(lightingDebug.shadowDebugUseSelection) { index = -1; if (UnityEditor.Selection.activeObject is GameObject) { GameObject go = UnityEditor.Selection.activeObject as GameObject; Light light = go.GetComponent(); if (light != null) { index = m_ShadowMgr.GetShadowRequestIndex(light); } } } #endif if(index != -1) { uint faceCount = m_ShadowMgr.GetShadowRequestFaceCount((uint)index); for (uint i = 0; i < faceCount; ++i) { m_ShadowMgr.DisplayShadow(cmd, index, i, x, y, overlaySize, overlaySize, lightingDebug.shadowMinValue, lightingDebug.shadowMaxValue); HDUtils.NextOverlayCoord(ref x, ref y, overlaySize, overlaySize, hdCamera.camera.pixelWidth); } } } else if (lightingDebug.shadowDebugMode == ShadowMapDebugMode.VisualizeAtlas) { m_ShadowMgr.DisplayShadowMap(cmd, lightingDebug.shadowAtlasIndex, 0, x, y, overlaySize, overlaySize, lightingDebug.shadowMinValue, lightingDebug.shadowMaxValue); HDUtils.NextOverlayCoord(ref x, ref y, overlaySize, overlaySize, hdCamera.camera.pixelWidth); } } } } } }