From b15539875e9879392c30bcc4dcaa1bf9ea34618f Mon Sep 17 00:00:00 2001 From: Peter Bay Bastian Date: Mon, 23 May 2022 12:00:47 +0000 Subject: [PATCH] URP Forward+ Improved Tiling This PR revamps the tiling math and algorithm used for URP Forward+, and makes it available for general use. This has far tighter bounds than previously, while keeping CPU usage reasonable. It also streamlines a number of things. Some algorithmic explanations in TDD, but feel free to throw me any questions :) https://docs.google.com/document/d/1-NA5XUqAboVAB4fsMfV9O3eQOrC0RKOvPT9_SGjgwMc/edit#heading=h.wo67r6cr8k6c Part of the following initiatives: https://jira.unity3d.com/browse/RND-406 https://jira.unity3d.com/browse/RND-177 --- .../Targets/UniversalDecalSubTarget.cs | 4 +- .../Targets/UniversalLitSubTarget.cs | 2 +- .../ShaderGraph/Targets/UniversalTarget.cs | 6 +- .../Editor/ShaderPreprocessor.cs | 45 +- .../Editor/UniversalRendererDataEditor.cs | 21 +- .../VFXGraph/Shaders/VFXPasses.template | 10 +- .../Runtime/ForwardLights.cs | 330 ++++++---- .../Runtime/Memory.meta | 3 + .../Runtime/Memory/PinnedArray.cs | 36 + .../Runtime/Memory/PinnedArray.cs.meta | 3 + .../Runtime/ScriptableRenderer.cs | 2 +- .../Runtime/Tiling/InclusiveRange.cs | 45 ++ .../Runtime/Tiling/InclusiveRange.cs.meta | 3 + .../Runtime/Tiling/LightExtractionJob.cs | 35 - .../Runtime/Tiling/LightExtractionJob.cs.meta | 11 - .../Runtime/Tiling/SliceCombineJob.cs | 39 -- .../Runtime/Tiling/SliceCombineJob.cs.meta | 11 - .../Runtime/Tiling/SliceCullingJob.cs | 165 ----- .../Runtime/Tiling/SliceCullingJob.cs.meta | 11 - .../Runtime/Tiling/TileRangeExpansionJob.cs | 61 ++ .../Tiling/TileRangeExpansionJob.cs.meta | 3 + .../Runtime/Tiling/TilingJob.cs | 617 ++++++++++++++++++ .../Runtime/Tiling/TilingJob.cs.meta | 3 + .../Runtime/Tiling/ZBinningJob.cs | 59 +- .../Runtime/UniversalRenderPipeline.cs | 11 +- .../Runtime/UniversalRenderPipelineCore.cs | 2 +- .../Runtime/UniversalRenderer.cs | 23 +- .../Runtime/UniversalRendererData.cs | 35 - .../ShaderLibrary/Clustering.hlsl | 115 ++-- .../ShaderLibrary/Core.hlsl | 6 +- .../ShaderLibrary/Debug/Debugging3D.hlsl | 10 + .../ShaderLibrary/Input.hlsl | 32 +- .../ShaderLibrary/Lighting.hlsl | 4 +- .../ShaderLibrary/RealtimeLights.hlsl | 14 +- .../ShaderVariablesFunctions.hlsl | 30 + .../Shaders/ComplexLit.shader | 4 +- .../Shaders/Lit.shader | 4 +- .../Shaders/Nature/SpeedTree7.shader | 2 +- .../Shaders/Nature/SpeedTree7Billboard.shader | 2 +- .../Shaders/Nature/SpeedTree8.shader | 2 +- .../Shaders/Particles/ParticlesLit.shader | 2 +- .../Particles/ParticlesSimpleLit.shader | 2 +- .../Shaders/SimpleLit.shader | 4 +- .../Shaders/Terrain/TerrainDetailLit.shader | 2 +- .../Shaders/Terrain/TerrainLit.shader | 2 +- .../Shaders/Terrain/TerrainLitAdd.shader | 2 +- .../Shaders/Terrain/TerrainLitBase.shader | 2 +- .../Shaders/Terrain/WavingGrass.shader | 2 +- .../Terrain/WavingGrassBillboard.shader | 2 +- 49 files changed, 1192 insertions(+), 649 deletions(-) create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Memory.meta create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs.meta create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs.meta delete mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs delete mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs.meta delete mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs delete mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs.meta delete mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs delete mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs.meta create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs.meta create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs create mode 100644 Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs.meta diff --git a/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalDecalSubTarget.cs b/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalDecalSubTarget.cs index 2bdd75d1f58..d6486159559 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalDecalSubTarget.cs +++ b/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalDecalSubTarget.cs @@ -1026,7 +1026,7 @@ static class Descriptors { CoreKeywordDescriptors.ShadowsSoft }, { CoreKeywordDescriptors.LightmapShadowMixing }, { CoreKeywordDescriptors.ShadowsShadowmask }, - { CoreKeywordDescriptors.ClusteredRendering }, + { CoreKeywordDescriptors.ForwardPlus }, { Descriptors.DecalsNormalBlend }, { CoreKeywordDescriptors.LODFadeCrossFade, new FieldCondition(Fields.LodCrossFade, true) }, }; @@ -1044,7 +1044,7 @@ static class Descriptors { CoreKeywordDescriptors.AdditionalLights }, { CoreKeywordDescriptors.AdditionalLightShadows }, { CoreKeywordDescriptors.ShadowsSoft }, - { CoreKeywordDescriptors.ClusteredRendering }, + { CoreKeywordDescriptors.ForwardPlus }, { Descriptors.DecalsNormalBlend }, }; diff --git a/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalLitSubTarget.cs b/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalLitSubTarget.cs index e3c47382234..75c388570fa 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalLitSubTarget.cs +++ b/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalLitSubTarget.cs @@ -887,7 +887,7 @@ static class LitKeywords { CoreKeywordDescriptors.LightLayers }, { CoreKeywordDescriptors.DebugDisplay }, { CoreKeywordDescriptors.LightCookies }, - { CoreKeywordDescriptors.ClusteredRendering }, + { CoreKeywordDescriptors.ForwardPlus }, }; public static readonly KeywordCollection DOTSForward = new KeywordCollection diff --git a/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalTarget.cs b/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalTarget.cs index 6cb64454388..f578a46f975 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalTarget.cs +++ b/Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Targets/UniversalTarget.cs @@ -1973,10 +1973,10 @@ static class CoreKeywordDescriptors stages = KeywordShaderStage.Fragment, }; - public static readonly KeywordDescriptor ClusteredRendering = new KeywordDescriptor() + public static readonly KeywordDescriptor ForwardPlus = new KeywordDescriptor() { - displayName = "Clustered Rendering", - referenceName = "_CLUSTERED_RENDERING", + displayName = "Forward+", + referenceName = "_FORWARD_PLUS", type = KeywordType.Boolean, definition = KeywordDefinition.MultiCompile, scope = KeywordScope.Global, diff --git a/Packages/com.unity.render-pipelines.universal/Editor/ShaderPreprocessor.cs b/Packages/com.unity.render-pipelines.universal/Editor/ShaderPreprocessor.cs index 8f089e29e49..5a4493a11f4 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/ShaderPreprocessor.cs +++ b/Packages/com.unity.render-pipelines.universal/Editor/ShaderPreprocessor.cs @@ -44,7 +44,7 @@ enum ShaderFeatures : long DecalNormalBlendLow = (1 << 21), DecalNormalBlendMedium = (1 << 22), DecalNormalBlendHigh = (1 << 23), - ClusteredRendering = (1 << 24), + ForwardPlus = (1 << 24), RenderPassEnabled = (1 << 25), MainLightShadowsCascade = (1 << 26), DrawProcedural = (1 << 27), @@ -112,7 +112,7 @@ internal class ShaderPreprocessor : IShaderVariantStripper, IShaderVariantStripp LocalKeyword m_DecalNormalBlendLow; LocalKeyword m_DecalNormalBlendMedium; LocalKeyword m_DecalNormalBlendHigh; - LocalKeyword m_ClusteredRendering; + LocalKeyword m_ForwardPlus; LocalKeyword m_EditorVisualization; LocalKeyword m_LODFadeCrossFade; @@ -179,7 +179,7 @@ public void InitializeLocalShaderKeywords([DisallowNull] Shader shader) m_DecalNormalBlendLow = TryGetLocalKeyword(shader, ShaderKeywordStrings.DecalNormalBlendLow); m_DecalNormalBlendMedium = TryGetLocalKeyword(shader, ShaderKeywordStrings.DecalNormalBlendMedium); m_DecalNormalBlendHigh = TryGetLocalKeyword(shader, ShaderKeywordStrings.DecalNormalBlendHigh); - m_ClusteredRendering = TryGetLocalKeyword(shader, ShaderKeywordStrings.ClusteredRendering); + m_ForwardPlus = TryGetLocalKeyword(shader, ShaderKeywordStrings.ForwardPlus); m_EditorVisualization = TryGetLocalKeyword(shader, ShaderKeywordStrings.EDITOR_VISUALIZATION); m_LODFadeCrossFade = TryGetLocalKeyword(shader, ShaderKeywordStrings.LOD_FADE_CROSSFADE); @@ -470,7 +470,7 @@ bool StripUnusedFeatures(ShaderFeatures features, Shader shader, ShaderSnippetDa return true; } - if (stripTool.StripMultiCompile(m_ClusteredRendering, ShaderFeatures.ClusteredRendering)) + if (stripTool.StripMultiCompile(m_ForwardPlus, ShaderFeatures.ForwardPlus)) return true; // Screen Space Occlusion @@ -652,7 +652,7 @@ bool StripInvalidVariants(ShaderCompilerData compilerData) bool isMainShadow = isMainShadowNoCascades || isMainShadowCascades || isMainShadowScreen; bool isAdditionalShadow = compilerData.shaderKeywordSet.IsEnabled(m_AdditionalLightShadows); - if (isAdditionalShadow && !(compilerData.shaderKeywordSet.IsEnabled(m_AdditionalLightsPixel) || compilerData.shaderKeywordSet.IsEnabled(m_ClusteredRendering) || compilerData.shaderKeywordSet.IsEnabled(m_DeferredStencil))) + if (isAdditionalShadow && !(compilerData.shaderKeywordSet.IsEnabled(m_AdditionalLightsPixel) || compilerData.shaderKeywordSet.IsEnabled(m_ForwardPlus) || compilerData.shaderKeywordSet.IsEnabled(m_DeferredStencil))) return true; bool isShadowVariant = isMainShadow || isAdditionalShadow; @@ -915,11 +915,6 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline shaderFeatures |= ShaderFeatures.AdditionalLights; } - bool anyShadows = pipelineAsset.supportsMainLightShadows || - (shaderFeatures & ShaderFeatures.AdditionalLightShadows) != 0; - if (pipelineAsset.supportsSoftShadows && anyShadows) - shaderFeatures |= ShaderFeatures.SoftShadows; - if (pipelineAsset.supportsMixedLighting) shaderFeatures |= ShaderFeatures.MixedLighting; @@ -939,8 +934,7 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline bool hasScreenSpaceOcclusion = false; bool hasDeferredRenderer = false; bool accurateGbufferNormals = false; - bool clusteredRendering = false; - bool onlyClusteredRendering = false; + bool forwardPlus = false; bool usesRenderPass = false; { @@ -967,8 +961,6 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline if (!renderer.stripAdditionalLightOffVariants) shaderFeatures |= ShaderFeatures.AdditionalLightsKeepOffVariants; - var rendererClustered = false; - ScriptableRendererData rendererData = pipelineAsset.m_RendererDataList[rendererIndex]; if (rendererData != null) { @@ -1013,8 +1005,7 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline if (rendererData is UniversalRendererData universalRendererData) { - rendererClustered = universalRendererData.renderingMode == RenderingMode.Forward && - universalRendererData.clusteredRendering; + forwardPlus = universalRendererData.renderingMode == RenderingMode.ForwardPlus; if (RenderingLayerUtils.RequireRenderingLayers(universalRendererData, pipelineAsset.msaaSampleCount, @@ -1043,9 +1034,6 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline #endif } } - - clusteredRendering |= rendererClustered; - onlyClusteredRendering &= rendererClustered; } if (hasDeferredRenderer) @@ -1069,17 +1057,15 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline if (pipelineAsset.reflectionProbeBoxProjection) shaderFeatures |= ShaderFeatures.ReflectionProbeBoxProjection; - if (clusteredRendering) + if (forwardPlus) { - shaderFeatures |= ShaderFeatures.ClusteredRendering; - } - - if (onlyClusteredRendering) - { - shaderFeatures &= ~(ShaderFeatures.AdditionalLights | ShaderFeatures.VertexLighting); + shaderFeatures |= ShaderFeatures.ForwardPlus; + { + shaderFeatures &= ~(ShaderFeatures.AdditionalLights | ShaderFeatures.VertexLighting); + } } - if (pipelineAsset.additionalLightsRenderingMode == LightRenderingMode.PerPixel || clusteredRendering) + if (pipelineAsset.additionalLightsRenderingMode == LightRenderingMode.PerPixel || forwardPlus) { if (pipelineAsset.supportsAdditionalLightShadows) { @@ -1087,6 +1073,11 @@ private static ShaderFeatures GetSupportedShaderFeatures(UniversalRenderPipeline } } + bool anyShadows = pipelineAsset.supportsMainLightShadows || + (shaderFeatures & ShaderFeatures.AdditionalLightShadows) != 0; + if (pipelineAsset.supportsSoftShadows && anyShadows) + shaderFeatures |= ShaderFeatures.SoftShadows; + return shaderFeatures; } diff --git a/Packages/com.unity.render-pipelines.universal/Editor/UniversalRendererDataEditor.cs b/Packages/com.unity.render-pipelines.universal/Editor/UniversalRendererDataEditor.cs index c0a4b08157c..aab47044174 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/UniversalRendererDataEditor.cs +++ b/Packages/com.unity.render-pipelines.universal/Editor/UniversalRendererDataEditor.cs @@ -36,7 +36,6 @@ private static class Styles public static readonly GUIContent defaultStencilStateLabel = EditorGUIUtility.TrTextContent("Default Stencil State", "Configure the stencil state for the opaque and transparent render passes."); public static readonly GUIContent shadowTransparentReceiveLabel = EditorGUIUtility.TrTextContent("Transparent Receive Shadows", "When disabled, none of the transparent objects will receive shadows."); public static readonly GUIContent invalidStencilOverride = EditorGUIUtility.TrTextContent("Error: When using the deferred rendering path, the Renderer requires the control over the 4 highest bits of the stencil buffer to store Material types. The current combination of the stencil override options prevents the Renderer from controlling the required bits. Try changing one of the options to Replace."); - public static readonly GUIContent clusteredRenderingLabel = EditorGUIUtility.TrTextContent("Clustered (experimental)", "(Experimental) Enables clustered rendering, allowing for more lights per object and more accurate light cullling."); public static readonly GUIContent intermediateTextureMode = EditorGUIUtility.TrTextContent("Intermediate Texture", "Controls when URP renders via an intermediate texture."); } @@ -46,8 +45,6 @@ private static class Styles SerializedProperty m_DepthPrimingMode; SerializedProperty m_CopyDepthMode; SerializedProperty m_AccurateGbufferNormals; - SerializedProperty m_ClusteredRendering; - SerializedProperty m_TileSize; SerializedProperty m_UseNativeRenderPass; SerializedProperty m_DefaultStencilState; SerializedProperty m_PostProcessData; @@ -55,12 +52,6 @@ private static class Styles SerializedProperty m_ShadowTransparentReceiveProp; SerializedProperty m_IntermediateTextureMode; -#if URP_ENABLE_CLUSTERED_UI - static bool s_EnableClusteredUI => true; -#else - static bool s_EnableClusteredUI => false; -#endif - private void OnEnable() { m_OpaqueLayerMask = serializedObject.FindProperty("m_OpaqueLayerMask"); @@ -69,8 +60,6 @@ private void OnEnable() m_DepthPrimingMode = serializedObject.FindProperty("m_DepthPrimingMode"); m_CopyDepthMode = serializedObject.FindProperty("m_CopyDepthMode"); m_AccurateGbufferNormals = serializedObject.FindProperty("m_AccurateGbufferNormals"); - m_ClusteredRendering = serializedObject.FindProperty("m_ClusteredRendering"); - m_TileSize = serializedObject.FindProperty("m_TileSize"); m_UseNativeRenderPass = serializedObject.FindProperty("m_UseNativeRenderPass"); m_DefaultStencilState = serializedObject.FindProperty("m_DefaultStencilState"); m_PostProcessData = serializedObject.FindProperty("postProcessData"); @@ -102,18 +91,10 @@ public override void OnInspectorGUI() EditorGUI.indentLevel--; } - if (m_RenderingMode.intValue == (int)RenderingMode.Forward) + if (m_RenderingMode.intValue == (int)RenderingMode.Forward || m_RenderingMode.intValue == (int)RenderingMode.ForwardPlus) { EditorGUI.indentLevel++; - if (s_EnableClusteredUI) - { - EditorGUILayout.PropertyField(m_ClusteredRendering, Styles.clusteredRenderingLabel); - EditorGUI.BeginDisabledGroup(!m_ClusteredRendering.boolValue); - EditorGUILayout.PropertyField(m_TileSize); - EditorGUI.EndDisabledGroup(); - } - EditorGUILayout.PropertyField(m_DepthPrimingMode, Styles.DepthPrimingModeLabel); if (m_DepthPrimingMode.intValue != (int)DepthPrimingMode.Disabled) { diff --git a/Packages/com.unity.render-pipelines.universal/Editor/VFXGraph/Shaders/VFXPasses.template b/Packages/com.unity.render-pipelines.universal/Editor/VFXGraph/Shaders/VFXPasses.template index 1a7d65cb600..94d55136c8a 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/VFXGraph/Shaders/VFXPasses.template +++ b/Packages/com.unity.render-pipelines.universal/Editor/VFXGraph/Shaders/VFXPasses.template @@ -28,7 +28,7 @@ ${VFXBegin:VFXPassForwardLitAdditionalPragma} #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES -#pragma multi_compile _ _CLUSTERED_RENDERING +#pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fog #pragma multi_compile _ DEBUG_DISPLAY ${VFXEnd} @@ -100,7 +100,7 @@ void frag(ps_input i UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); VFXTransformPSInputs(i); ${VFXComputeNormalWS} - + #ifdef VFX_SHADERGRAPH ${VFXAdditionalInterpolantsPreparation} @@ -112,15 +112,15 @@ void frag(ps_input i float alpha = OUTSG.${SHADERGRAPH_PARAM_ALPHA}; #else - + float alpha = VFXGetFragmentColor(i).a; #if URP_USE_BASE_COLOR_MAP_ALPHA alpha *= VFXGetTextureColor(VFX_SAMPLER(baseColorMap),i).a; #endif #endif - + VFXClipFragmentColor(alpha,i); - + #if defined(WRITE_NORMAL_BUFFER) #ifdef VFX_SHADERGRAPH #if HAS_SHADERGRAPH_PARAM_NORMALTS diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/ForwardLights.cs b/Packages/com.unity.render-pipelines.universal/Runtime/ForwardLights.cs index 543367cdb3d..4ceff14d933 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/ForwardLights.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/ForwardLights.cs @@ -1,10 +1,9 @@ +using System; +using System.Collections.Generic; using Unity.Collections; -using UnityEngine.PlayerLoop; using Unity.Jobs; -using UnityEngine.Assertions; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; -using UnityEditor; namespace UnityEngine.Rendering.Universal.Internal { @@ -34,6 +33,9 @@ static class LightConstantBuffer const string k_SetupLightConstants = "Setup Light Constants"; private static readonly ProfilingSampler m_ProfilingSampler = new ProfilingSampler(k_SetupLightConstants); + private static readonly ProfilingSampler m_ProfilingSamplerFPSetup = new ProfilingSampler("Forward+ Setup"); + private static readonly ProfilingSampler m_ProfilingSamplerFPComplete = new ProfilingSampler("Forward+ Complete"); + private static readonly ProfilingSampler m_ProfilingSamplerFPUpload = new ProfilingSampler("Forward+ Upload"); MixedLightingSetup m_MixedLightingSetup; Vector4[] m_AdditionalLightPositions; @@ -45,28 +47,29 @@ static class LightConstantBuffer bool m_UseStructuredBuffer; - bool m_UseClusteredRendering; + bool m_UseForwardPlus; int m_DirectionalLightCount; int m_ActualTileWidth; int2 m_TileResolution; - int m_RequestedTileWidth; - float m_ZBinFactor; - int m_ZBinOffset; JobHandle m_CullingHandle; - NativeArray m_ZBins; - NativeArray m_TileLightMasks; - - ComputeBuffer m_ZBinBuffer; - ComputeBuffer m_TileBuffer; + NativeArray m_ZBins; + GraphicsBuffer m_ZBinsBuffer; + NativeArray m_TileMasks; + GraphicsBuffer m_TileMasksBuffer; private LightCookieManager m_LightCookieManager; + int m_WordsPerTile; + float m_ZBinScale; + float m_ZBinOffset; + Dictionary m_OrthographicWarningShown = new Dictionary(8); + Dictionary m_XrWarningShown = new Dictionary(8); + List m_KeysToRemove = new List(8); internal struct InitParams { public LightCookieManager lightCookieManager; - public bool clusteredRendering; - public int tileSize; + public bool forwardPlus; static internal InitParams Create() { @@ -81,8 +84,7 @@ static internal InitParams Create() } p.lightCookieManager = new LightCookieManager(ref settings); - p.clusteredRendering = false; - p.tileSize = 32; + p.forwardPlus = false; } return p; } @@ -95,9 +97,8 @@ public ForwardLights() : this(InitParams.Create()) { } internal ForwardLights(InitParams initParams) { - if (initParams.clusteredRendering) Assert.IsTrue(math.ispow2(initParams.tileSize)); m_UseStructuredBuffer = RenderingUtils.useStructuredBuffer; - m_UseClusteredRendering = initParams.clusteredRendering; + m_UseForwardPlus = initParams.forwardPlus; LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition"); LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor"); @@ -128,21 +129,116 @@ internal ForwardLights(InitParams initParams) m_AdditionalLightsLayerMasks = new float[maxLights]; } - m_LightCookieManager = initParams.lightCookieManager; - - if (m_UseClusteredRendering) + if (m_UseForwardPlus) { - m_ZBinBuffer = new ComputeBuffer(UniversalRenderPipeline.maxZBins / 4, UnsafeUtility.SizeOf(), ComputeBufferType.Constant, ComputeBufferMode.Dynamic); - m_TileBuffer = new ComputeBuffer(UniversalRenderPipeline.maxTileVec4s, UnsafeUtility.SizeOf(), ComputeBufferType.Constant, ComputeBufferMode.Dynamic); - m_RequestedTileWidth = initParams.tileSize; + CreateForwardPlusBuffers(); } + + m_LightCookieManager = initParams.lightCookieManager; } + void CreateForwardPlusBuffers() + { + m_ZBins = new NativeArray(UniversalRenderPipeline.maxZBinWords, Allocator.Persistent); + m_ZBinsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Constant, UniversalRenderPipeline.maxZBinWords / 4, UnsafeUtility.SizeOf()); + m_ZBinsBuffer.name = "AdditionalLightsZBins"; + m_TileMasks = new NativeArray(UniversalRenderPipeline.maxTileWords, Allocator.Persistent); + m_TileMasksBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Constant, UniversalRenderPipeline.maxTileWords / 4, UnsafeUtility.SizeOf()); + m_TileMasksBuffer.name = "AdditionalLightsTiles"; + } + + static int AlignByteCount(int count, int align) => align * ((count + align - 1) / align); + internal void ProcessLights(ref RenderingData renderingData) { - if (m_UseClusteredRendering) + if (m_UseForwardPlus) { + using var _ = new ProfilingScope(null, m_ProfilingSamplerFPSetup); + + if (!m_CullingHandle.IsCompleted) + { + throw new InvalidOperationException("Forward+ jobs have not completed yet."); + } + + if (m_TileMasks.Length != UniversalRenderPipeline.maxTileWords) + { + m_ZBins.Dispose(); + m_ZBinsBuffer.Dispose(); + m_TileMasks.Dispose(); + m_TileMasksBuffer.Dispose(); + CreateForwardPlusBuffers(); + } + else + { + unsafe + { + UnsafeUtility.MemClear(m_ZBins.GetUnsafePtr(), m_ZBins.Length * sizeof(uint)); + UnsafeUtility.MemClear(m_TileMasks.GetUnsafePtr(), m_TileMasks.Length * sizeof(uint)); + } + } + var camera = renderingData.cameraData.camera; + + var frameIndex = Time.renderedFrameCount; + + if (m_OrthographicWarningShown.Count > 0) + { + foreach (var (cameraId, lastFrameIndex) in m_OrthographicWarningShown) + { + if (math.abs(frameIndex - lastFrameIndex) > 2) + { + m_KeysToRemove.Add(cameraId); + } + } + + foreach (var cameraId in m_KeysToRemove) + { + m_OrthographicWarningShown.Remove(cameraId); + } + + m_KeysToRemove.Clear(); + } + + if (m_XrWarningShown.Count > 0) + { + foreach (var (cameraId, lastFrameIndex) in m_XrWarningShown) + { + if (math.abs(frameIndex - lastFrameIndex) > 2) + { + m_KeysToRemove.Add(cameraId); + } + } + + foreach (var cameraId in m_KeysToRemove) + { + m_XrWarningShown.Remove(cameraId); + } + + m_KeysToRemove.Clear(); + } + + if (camera.orthographic) + { + var cameraId = camera.GetInstanceID(); + if (!m_OrthographicWarningShown.ContainsKey(cameraId)) + { + Debug.LogWarning("Orthographic projection is not supported when using Forward+."); + } + + m_OrthographicWarningShown[cameraId] = frameIndex; + } + + if (renderingData.cameraData.xrRendering) + { + var cameraId = camera.GetInstanceID(); + if (!m_XrWarningShown.ContainsKey(cameraId)) + { + Debug.LogWarning("XR rendering is not supported when using Forward+."); + } + + m_XrWarningShown[cameraId] = frameIndex; + } + var screenResolution = math.int2(renderingData.cameraData.pixelWidth, renderingData.cameraData.pixelHeight); var lightCount = renderingData.lightData.visibleLights.Length; @@ -151,41 +247,35 @@ internal void ProcessLights(ref RenderingData renderingData) { lightOffset++; } - if (lightOffset == lightCount) lightOffset = 0; lightCount -= lightOffset; m_DirectionalLightCount = lightOffset; - if (renderingData.lightData.mainLightIndex != -1) m_DirectionalLightCount -= 1; + if (renderingData.lightData.mainLightIndex != -1 && m_DirectionalLightCount != 0) m_DirectionalLightCount -= 1; var visibleLights = renderingData.lightData.visibleLights.GetSubArray(lightOffset, lightCount); - var lightsPerTile = UniversalRenderPipeline.lightsPerTile; - var wordsPerTile = lightsPerTile / 32; + var lightsPerTile = visibleLights.Length; + m_WordsPerTile = (lightsPerTile + 31) / 32; - m_ActualTileWidth = m_RequestedTileWidth >> 1; + m_ActualTileWidth = 8 >> 1; do { - m_ActualTileWidth = m_ActualTileWidth << 1; + m_ActualTileWidth <<= 1; m_TileResolution = (screenResolution + m_ActualTileWidth - 1) / m_ActualTileWidth; } - while ((m_TileResolution.x * m_TileResolution.y * wordsPerTile) > (UniversalRenderPipeline.maxTileVec4s * 4)); + while ((m_TileResolution.x * m_TileResolution.y * m_WordsPerTile) > UniversalRenderPipeline.maxTileWords); var fovHalfHeight = math.tan(math.radians(camera.fieldOfView * 0.5f)); - // TODO: Make this work with VR - var fovHalfWidth = fovHalfHeight * (float)screenResolution.x / (float)screenResolution.y; - - var maxZFactor = (float)UniversalRenderPipeline.maxZBins / (math.sqrt(camera.farClipPlane) - math.sqrt(camera.nearClipPlane)); - m_ZBinFactor = maxZFactor; - m_ZBinOffset = (int)(math.sqrt(camera.nearClipPlane) * m_ZBinFactor); - var binCount = (int)(math.sqrt(camera.farClipPlane) * m_ZBinFactor) - m_ZBinOffset; - // Must be a multiple of 4 to be able to alias to vec4 - binCount = ((binCount + 3) / 4) * 4; - binCount = math.min(UniversalRenderPipeline.maxZBins, binCount); - m_ZBins = new NativeArray(binCount, Allocator.TempJob); - Assert.AreEqual(UnsafeUtility.SizeOf(), UnsafeUtility.SizeOf()); - - using var minMaxZs = new NativeArray(lightCount, Allocator.TempJob); + // binIndex = log2(z) * zBinScale + zBinOffset + m_ZBinScale = 1f / math.log2(1f + 2f * fovHalfHeight / m_TileResolution.y); + m_ZBinOffset = -math.log2(camera.nearClipPlane) * m_ZBinScale; + var binCount = (int)(math.log2(camera.farClipPlane) * m_ZBinScale + m_ZBinOffset); + // Clamp the bin count to stay within memory budget. + // words = binCount * (1 + m_WordsPerTile) => binCount = words / (1 + m_WordsPerTile) + binCount = math.min(UniversalRenderPipeline.maxZBinWords, binCount * (1 + m_WordsPerTile)) / (1 + m_WordsPerTile); + + var minMaxZs = new NativeArray(lightCount, Allocator.TempJob); // We allocate double array length because the sorting algorithm needs swap space to work in. - using var meanZs = new NativeArray(lightCount * 2, Allocator.TempJob); + var meanZs = new NativeArray(lightCount * 2, Allocator.TempJob); Matrix4x4 worldToViewMatrix = renderingData.cameraData.GetViewMatrix(); var minMaxZJob = new MinMaxZJob @@ -199,7 +289,7 @@ internal void ProcessLights(ref RenderingData renderingData) var minMaxZHandle = minMaxZJob.ScheduleParallel(lightCount, 32, new JobHandle()); // We allocate double array length because the sorting algorithm needs swap space to work in. - using var indices = new NativeArray(lightCount * 2, Allocator.TempJob); + var indices = new NativeArray(lightCount * 2, Allocator.TempJob); var radixSortJob = new RadixSortJob { // Floats can be sorted bitwise with no special handling if positive floats only @@ -224,73 +314,54 @@ internal void ProcessLights(ref RenderingData renderingData) JobHandle.ScheduleBatchedJobs(); - LightExtractionJob lightExtractionJob; - lightExtractionJob.lights = reorderedLights; - var lightTypes = lightExtractionJob.lightTypes = new NativeArray(lightCount, Allocator.TempJob); - var radiuses = lightExtractionJob.radiuses = new NativeArray(lightCount, Allocator.TempJob); - var directions = lightExtractionJob.directions = new NativeArray(lightCount, Allocator.TempJob); - var positions = lightExtractionJob.positions = new NativeArray(lightCount, Allocator.TempJob); - var coneRadiuses = lightExtractionJob.coneRadiuses = new NativeArray(lightCount, Allocator.TempJob); - var lightExtractionHandle = lightExtractionJob.ScheduleParallel(lightCount, 32, reorderHandle); - var zBinningJob = new ZBinningJob { bins = m_ZBins, minMaxZs = reorderedMinMaxZs, - binOffset = m_ZBinOffset, - zFactor = m_ZBinFactor + zBinScale = m_ZBinScale, + zBinOffset = m_ZBinOffset, + binCount = binCount, + wordsPerTile = m_WordsPerTile }; var zBinningHandle = zBinningJob.ScheduleParallel((binCount + ZBinningJob.batchCount - 1) / ZBinningJob.batchCount, 1, reorderHandle); - reorderedMinMaxZs.Dispose(zBinningHandle); - // Must be a multiple of 4 to be able to alias to vec4 - var lightMasksLength = (((wordsPerTile) * m_TileResolution + 3) / 4) * 4; - var horizontalLightMasks = new NativeArray(lightMasksLength.y, Allocator.TempJob); - var verticalLightMasks = new NativeArray(lightMasksLength.x, Allocator.TempJob); - - // Vertical slices along the x-axis - var verticalJob = new SliceCullingJob + // Each light needs 1 range for Y, and a range per row. Align to 128-bytes to avoid false sharing. + var itemsPerLight = AlignByteCount((1 + m_TileResolution.y) * UnsafeUtility.SizeOf(), 128) / UnsafeUtility.SizeOf(); + var tileRanges = new NativeArray(itemsPerLight * lightCount, Allocator.TempJob); + var tilingJob = new TilingJob { - scale = (float)m_ActualTileWidth / (float)screenResolution.x, - viewOrigin = camera.transform.position, - viewForward = camera.transform.forward, - viewRight = camera.transform.right * fovHalfWidth, - viewUp = camera.transform.up * fovHalfHeight, - lightTypes = lightTypes, - radiuses = radiuses, - directions = directions, - positions = positions, - coneRadiuses = coneRadiuses, - lightsPerTile = lightsPerTile, - sliceLightMasks = verticalLightMasks + lights = reorderedLights, + tileRanges = tileRanges, + itemsPerLight = itemsPerLight, + worldToViewMatrix = worldToViewMatrix, + tileScale = (float2)screenResolution / m_ActualTileWidth, + tileScaleInv = m_ActualTileWidth / (float2)screenResolution, + viewPlaneHalfSize = fovHalfHeight * math.float2(camera.aspect, 1), + viewPlaneHalfSizeInv = math.rcp(fovHalfHeight * math.float2(camera.aspect, 1)), + tileCount = m_TileResolution, + near = camera.nearClipPlane, }; - var verticalHandle = verticalJob.ScheduleParallel(m_TileResolution.x, 1, lightExtractionHandle); - // Horizontal slices along the y-axis - var horizontalJob = verticalJob; - horizontalJob.scale = (float)m_ActualTileWidth / (float)screenResolution.y; - horizontalJob.viewRight = camera.transform.up * fovHalfHeight; - horizontalJob.viewUp = -camera.transform.right * fovHalfWidth; - horizontalJob.sliceLightMasks = horizontalLightMasks; - var horizontalHandle = horizontalJob.ScheduleParallel(m_TileResolution.y, 1, lightExtractionHandle); + var tileRangeHandle = tilingJob.ScheduleParallel(lightCount, 1, reorderHandle); - var slicesHandle = JobHandle.CombineDependencies(horizontalHandle, verticalHandle); - - m_TileLightMasks = new NativeArray(((m_TileResolution.x * m_TileResolution.y * (wordsPerTile) + 3) / 4) * 4, Allocator.TempJob); - var sliceCombineJob = new SliceCombineJob + var expansionJob = new TileRangeExpansionJob { + tileRanges = tileRanges, + lightMasks = m_TileMasks, + itemsPerLight = itemsPerLight, + lightCount = lightCount, + wordsPerTile = m_WordsPerTile, tileResolution = m_TileResolution, - wordsPerTile = wordsPerTile, - sliceLightMasksH = horizontalLightMasks, - sliceLightMasksV = verticalLightMasks, - lightMasks = m_TileLightMasks }; - var sliceCombineHandle = sliceCombineJob.ScheduleParallel(m_TileResolution.y, 1, slicesHandle); - m_CullingHandle = JobHandle.CombineDependencies(sliceCombineHandle, zBinningHandle); + var tilingHandle = expansionJob.ScheduleParallel(m_TileResolution.y, 1, tileRangeHandle); + + m_CullingHandle = JobHandle.CombineDependencies(tilingHandle, zBinningHandle); reorderHandle.Complete(); - NativeArray.Copy(reorderedLights, 0, renderingData.lightData.visibleLights, lightOffset, lightCount); + minMaxZs.Dispose(); + meanZs.Dispose(); + if (lightCount > 0) NativeArray.Copy(reorderedLights, 0, renderingData.lightData.visibleLights, lightOffset, lightCount); var tempBias = new NativeArray(lightCount, Allocator.Temp); var tempResolution = new NativeArray(lightCount, Allocator.Temp); @@ -303,6 +374,8 @@ internal void ProcessLights(ref RenderingData renderingData) tempIndices[indices[i]] = lightOffset + i; } + indices.Dispose(); + for (var i = 0; i < lightCount; i++) { renderingData.shadowData.bias[i + lightOffset] = tempBias[i]; @@ -313,15 +386,10 @@ internal void ProcessLights(ref RenderingData renderingData) tempBias.Dispose(); tempResolution.Dispose(); tempIndices.Dispose(); - - lightTypes.Dispose(m_CullingHandle); - radiuses.Dispose(m_CullingHandle); - directions.Dispose(m_CullingHandle); - positions.Dispose(m_CullingHandle); - coneRadiuses.Dispose(m_CullingHandle); - reorderedLights.Dispose(m_CullingHandle); - horizontalLightMasks.Dispose(m_CullingHandle); - verticalLightMasks.Dispose(m_CullingHandle); + m_CullingHandle = JobHandle.CombineDependencies( + reorderedMinMaxZs.Dispose(zBinningHandle), + tileRanges.Dispose(tilingHandle), + reorderedLights.Dispose(m_CullingHandle)); JobHandle.ScheduleBatchedJobs(); } } @@ -338,36 +406,37 @@ public void Setup(ScriptableRenderContext context, ref RenderingData renderingDa var cmd = renderingData.commandBuffer; using (new ProfilingScope(null, m_ProfilingSampler)) { - var useClusteredRendering = m_UseClusteredRendering; - if (useClusteredRendering) + if (m_UseForwardPlus) { - m_CullingHandle.Complete(); + using (new ProfilingScope(null, m_ProfilingSamplerFPComplete)) + { + m_CullingHandle.Complete(); + } - m_ZBinBuffer.SetData(m_ZBins.Reinterpret(UnsafeUtility.SizeOf()), 0, 0, m_ZBins.Length / 4); - m_TileBuffer.SetData(m_TileLightMasks.Reinterpret(UnsafeUtility.SizeOf()), 0, 0, m_TileLightMasks.Length / 4); + using (new ProfilingScope(null, m_ProfilingSamplerFPUpload)) + { + m_ZBinsBuffer.SetData(m_ZBins.Reinterpret(UnsafeUtility.SizeOf())); + m_TileMasksBuffer.SetData(m_TileMasks.Reinterpret(UnsafeUtility.SizeOf())); + cmd.SetGlobalConstantBuffer(m_ZBinsBuffer, "AdditionalLightsZBins", 0, UniversalRenderPipeline.maxZBinWords * 4); + cmd.SetGlobalConstantBuffer(m_TileMasksBuffer, "AdditionalLightsTiles", 0, UniversalRenderPipeline.maxTileWords * 4); + } cmd.SetGlobalInteger("_AdditionalLightsDirectionalCount", m_DirectionalLightCount); - cmd.SetGlobalInteger("_AdditionalLightsZBinOffset", m_ZBinOffset); - cmd.SetGlobalFloat("_AdditionalLightsZBinScale", m_ZBinFactor); + cmd.SetGlobalVector("_AdditionalLightsParams0", new Vector4(m_ZBinScale, m_ZBinOffset)); cmd.SetGlobalVector("_AdditionalLightsTileScale", renderingData.cameraData.pixelRect.size / (float)m_ActualTileWidth); cmd.SetGlobalInteger("_AdditionalLightsTileCountX", m_TileResolution.x); - - cmd.SetGlobalConstantBuffer(m_ZBinBuffer, "AdditionalLightsZBins", 0, m_ZBins.Length * 4); - cmd.SetGlobalConstantBuffer(m_TileBuffer, "AdditionalLightsTiles", 0, m_TileLightMasks.Length * 4); - - m_ZBins.Dispose(); - m_TileLightMasks.Dispose(); + cmd.SetGlobalInteger("_AdditionalLightsWordsPerTile", m_WordsPerTile); } SetupShaderLightConstants(cmd, ref renderingData); bool lightCountCheck = (renderingData.cameraData.renderer.stripAdditionalLightOffVariants && renderingData.lightData.supportsAdditionalLights) || additionalLightsCount > 0; CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsVertex, - lightCountCheck && additionalLightsPerVertex && !useClusteredRendering); + lightCountCheck && additionalLightsPerVertex && !m_UseForwardPlus); CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsPixel, - lightCountCheck && !additionalLightsPerVertex && !useClusteredRendering); - CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.ClusteredRendering, - useClusteredRendering); + lightCountCheck && !additionalLightsPerVertex && !m_UseForwardPlus); + CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.ForwardPlus, + m_UseForwardPlus); bool isShadowMask = renderingData.lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.ShadowMask; bool isShadowMaskAlways = isShadowMask && QualitySettings.shadowmaskMode == ShadowmaskMode.Shadowmask; @@ -390,10 +459,15 @@ public void Setup(ScriptableRenderContext context, ref RenderingData renderingDa internal void Cleanup() { - if (m_UseClusteredRendering) + if (m_UseForwardPlus) { - m_ZBinBuffer.Dispose(); - m_TileBuffer.Dispose(); + m_CullingHandle.Complete(); + m_ZBins.Dispose(); + m_TileMasks.Dispose(); + m_ZBinsBuffer.Dispose(); + m_ZBinsBuffer = null; + m_TileMasksBuffer.Dispose(); + m_TileMasksBuffer = null; } } @@ -530,7 +604,7 @@ void SetupAdditionalLightConstants(CommandBuffer cmd, ref RenderingData renderin int SetupPerObjectLightIndices(CullingResults cullResults, ref LightData lightData) { - if (lightData.additionalLightsCount == 0) + if (lightData.additionalLightsCount == 0 || m_UseForwardPlus) return lightData.additionalLightsCount; var visibleLights = lightData.visibleLights; diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Memory.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Memory.meta new file mode 100644 index 00000000000..497686c0843 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Memory.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: db16a3790436426e8ea186e7918ea4cc +timeCreated: 1651845409 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs new file mode 100644 index 00000000000..4f207e56977 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace UnityEngine.Rendering.Universal +{ + unsafe struct PinnedArray : IDisposable where T : struct + { + public T[] managedArray; + public GCHandle handle; + public NativeArray nativeArray; + + public int length => managedArray != null ? managedArray.Length : 0; + + public PinnedArray(int length) + { + managedArray = new T[length]; + handle = GCHandle.Alloc(managedArray, GCHandleType.Pinned); + nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray((void*)handle.AddrOfPinnedObject(), length, Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); +#endif + } + + public void Dispose() + { + if (managedArray == null) return; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.Release(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(nativeArray)); +#endif + handle.Free(); + this = default; + } + } +} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs.meta new file mode 100644 index 00000000000..dfdd3e90ff1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Memory/PinnedArray.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8e137341e98d40119699d4357b618a46 +timeCreated: 1651845438 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs b/Packages/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs index ab89f621a98..e40feeea658 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs @@ -1331,7 +1331,7 @@ static void ClearRenderingState(CommandBuffer cmd) cmd.DisableShaderKeyword(ShaderKeywordStrings.MainLightShadowCascades); cmd.DisableShaderKeyword(ShaderKeywordStrings.AdditionalLightsVertex); cmd.DisableShaderKeyword(ShaderKeywordStrings.AdditionalLightsPixel); - cmd.DisableShaderKeyword(ShaderKeywordStrings.ClusteredRendering); + cmd.DisableShaderKeyword(ShaderKeywordStrings.ForwardPlus); cmd.DisableShaderKeyword(ShaderKeywordStrings.AdditionalLightShadows); cmd.DisableShaderKeyword(ShaderKeywordStrings.ReflectionProbeBlending); cmd.DisableShaderKeyword(ShaderKeywordStrings.ReflectionProbeBoxProjection); diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs new file mode 100644 index 00000000000..a080d4877b5 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs @@ -0,0 +1,45 @@ +using System; + +namespace UnityEngine.Rendering.Universal +{ + struct InclusiveRange + { + public short start; + public short end; + + public InclusiveRange(short startEnd) + { + this.start = startEnd; + this.end = startEnd; + } + + public InclusiveRange(short start, short end) + { + this.start = start; + this.end = end; + } + + public void Expand(short index) + { + start = Math.Min(start, index); + end = Math.Max(end, index); + } + + public void Clamp(short min, short max) + { + start = Math.Max(min, start); + end = Math.Min(max, end); + } + + public bool isEmpty => end < start; + + public bool Contains(short index) + { + return index >= start && index <= end; + } + + public static InclusiveRange Merge(InclusiveRange a, InclusiveRange b) => new(Math.Min(a.start, b.start), Math.Max(a.end, b.end)); + + public static InclusiveRange empty => new InclusiveRange(short.MaxValue, short.MinValue); + } +} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs.meta new file mode 100644 index 00000000000..236a525e433 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/InclusiveRange.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 527aa596a18643c2879c1723a3d3b64a +timeCreated: 1639995854 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs deleted file mode 100644 index a66966ac43e..00000000000 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace UnityEngine.Rendering.Universal -{ - [BurstCompile] - struct LightExtractionJob : IJobFor - { - [ReadOnly] - public NativeArray lights; - - public NativeArray lightTypes; - - public NativeArray radiuses; - - public NativeArray directions; - - public NativeArray positions; - - public NativeArray coneRadiuses; - - public void Execute(int index) - { - var light = lights[index]; - var localToWorldMatrix = (float4x4)light.localToWorldMatrix; - lightTypes[index] = light.lightType; - radiuses[index] = light.range; - directions[index] = localToWorldMatrix.c2.xyz; - positions[index] = localToWorldMatrix.c3.xyz; - coneRadiuses[index] = math.tan(math.radians(light.spotAngle * 0.5f)) * light.range; - } - } -} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs.meta deleted file mode 100644 index a78d7936cb2..00000000000 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/LightExtractionJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7a39173dadb4c402d9d337545c6e8bd0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs deleted file mode 100644 index 73546c8045f..00000000000 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace UnityEngine.Rendering.Universal -{ - [BurstCompile] - unsafe struct SliceCombineJob : IJobFor - { - public int2 tileResolution; - - public int wordsPerTile; - - [ReadOnly] - public NativeArray sliceLightMasksH; - - [ReadOnly] - public NativeArray sliceLightMasksV; - - [NativeDisableParallelForRestriction] - public NativeArray lightMasks; - - public void Execute(int idY) - { - var baseIndexH = idY * wordsPerTile; - var baseIndexRow = baseIndexH * tileResolution.x; - for (var idX = 0; idX < tileResolution.x; idX++) - { - var baseIndexV = idX * wordsPerTile; - var baseIndexTile = baseIndexRow + baseIndexV; - for (var wordIndex = 0; wordIndex < wordsPerTile; wordIndex++) - { - lightMasks[baseIndexTile + wordIndex] = sliceLightMasksH[baseIndexH + wordIndex] & sliceLightMasksV[baseIndexV + wordIndex]; - } - } - } - } -} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs.meta deleted file mode 100644 index 7e047415e6c..00000000000 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCombineJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5df5478e52f204fdea0fbfc8ab3d22de -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs deleted file mode 100644 index a91c55017c5..00000000000 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Runtime.CompilerServices; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace UnityEngine.Rendering.Universal -{ - // Culls slices along one axis of the screen. - [BurstCompile] - unsafe struct SliceCullingJob : IJobFor - { - public float scale; - public float3 viewOrigin; - public float3 viewForward; - public float3 viewRight; - public float3 viewUp; - - [ReadOnly] - public NativeArray lightTypes; - - [ReadOnly] - public NativeArray radiuses; - - [ReadOnly] - public NativeArray directions; - - [ReadOnly] - public NativeArray positions; - - [ReadOnly] - public NativeArray coneRadiuses; - - public int lightsPerTile; - - [NativeDisableParallelForRestriction] - public NativeArray sliceLightMasks; - - public void Execute(int index) - { - var leftX = (((float)index) * scale) * 2f - 1f; - var rightX = (((float)index + 1f) * scale) * 2f - 1f; - var leftPlane = ComputePlane(viewOrigin, - viewOrigin + viewForward + viewRight * leftX + viewUp, - viewOrigin + viewForward + viewRight * leftX - viewUp); - var rightPlane = ComputePlane(viewOrigin, - viewOrigin + viewForward + viewRight * rightX - viewUp, - viewOrigin + viewForward + viewRight * rightX + viewUp); - - var lightCount = lightTypes.Length; - var lightWordCount = (lightCount + 31) / 32; - - var sectionOffset = index * lightsPerTile / 32; - - // Handle lights in multiples of 32 - for (var lightWordIndex = 0; lightWordIndex < lightWordCount; lightWordIndex++) - { - var wordLightMask = 0u; - var lightsInWord = math.min(32, lightCount - lightWordIndex * 32); - for (var bitIndex = 0; bitIndex < lightsInWord; bitIndex++) - { - var lightIndex = lightWordIndex * 32 + bitIndex; - if (ContainsLight(leftPlane, rightPlane, lightIndex)) - { - wordLightMask |= 1u << bitIndex; - } - } - - var wordIndex = sectionOffset + lightWordIndex; - sliceLightMasks[wordIndex] = sliceLightMasks[wordIndex] | wordLightMask; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - bool ContainsLight(Plane leftPlane, Plane rightPlane, int lightIndex) - { - var hit = true; - - var sphere = new Sphere - { - center = positions[lightIndex], - radius = radiuses[lightIndex] - }; - - if (SphereBehindPlane(sphere, leftPlane) || SphereBehindPlane(sphere, rightPlane)) - { - hit = false; - } - - if (hit && lightTypes[lightIndex] == LightType.Spot) - { - var cone = new Cone - { - tip = sphere.center, - direction = directions[lightIndex], - height = radiuses[lightIndex], - radius = coneRadiuses[lightIndex] - }; - if (ConeBehindPlane(cone, leftPlane) || ConeBehindPlane(cone, rightPlane)) - { - hit = false; - } - } - - return hit; - } - - struct Cone - { - public float3 tip; - public float3 direction; - public float height; - public float radius; - } - - struct Sphere - { - public float3 center; - public float radius; - } - - struct Plane - { - public float3 normal; - public float distanceToOrigin; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Plane ComputePlane(float3 p0, float3 p1, float3 p2) - { - Plane plane; - - float3 v0 = p1 - p0; - float3 v2 = p2 - p0; - - plane.normal = math.normalize(math.cross(v0, v2)); - - // Compute the distance to the origin using p0. - plane.distanceToOrigin = math.dot(plane.normal, p0); - - return plane; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool SphereBehindPlane(Sphere sphere, Plane plane) - { - float dist = math.dot(sphere.center, plane.normal) - plane.distanceToOrigin; - return dist < -sphere.radius; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool PointBehindPlane(float3 p, Plane plane) - { - return math.dot(plane.normal, p) - plane.distanceToOrigin < 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool ConeBehindPlane(Cone cone, Plane plane) - { - float3 furthestPointDirection = math.cross(math.cross(plane.normal, cone.direction), cone.direction); - float3 furthestPointOnCircle = cone.tip + cone.direction * cone.height - furthestPointDirection * cone.radius; - return PointBehindPlane(cone.tip, plane) && PointBehindPlane(furthestPointOnCircle, plane); - } - } -} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs.meta deleted file mode 100644 index ca84a8dca61..00000000000 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/SliceCullingJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bc45902ab38cd4ebe8e33c7792204ded -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs new file mode 100644 index 00000000000..c222b6de3e7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs @@ -0,0 +1,61 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace UnityEngine.Rendering.Universal +{ + [BurstCompile(FloatMode = FloatMode.Fast, DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)] + struct TileRangeExpansionJob : IJobFor + { + [ReadOnly] + public NativeArray tileRanges; + + [NativeDisableParallelForRestriction] + public NativeArray lightMasks; + + public int itemsPerLight; + public int lightCount; + public int wordsPerTile; + public int2 tileResolution; + + public void Execute(int rowIndex) + { + var compactCount = 0; + var lightIndices = new NativeArray(lightCount, Allocator.Temp); + var lightRanges = new NativeArray(lightCount, Allocator.Temp); + + // Compact the light ranges for the current row. + for (var lightIndex = 0; lightIndex < lightCount; lightIndex++) + { + var range = tileRanges[lightIndex * itemsPerLight + 1 + rowIndex]; + if (!range.isEmpty) + { + lightIndices[compactCount] = (byte)lightIndex; + lightRanges[compactCount] = range; + compactCount++; + } + } + + var rowBaseMaskIndex = rowIndex * wordsPerTile * tileResolution.x; + for (var tileIndex = 0; tileIndex < tileResolution.x; tileIndex++) + { + var tileBaseIndex = rowBaseMaskIndex + tileIndex * wordsPerTile; + for (var i = 0; i < compactCount; i++) + { + var lightIndex = (int)lightIndices[i]; + var wordIndex = lightIndex / 32; + var lightMask = 1u << (lightIndex % 32); + var range = lightRanges[i]; + if (range.Contains((short)tileIndex)) + { + lightMasks[tileBaseIndex + wordIndex] |= lightMask; + } + } + } + + lightIndices.Dispose(); + lightRanges.Dispose(); + } + } +} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs.meta new file mode 100644 index 00000000000..ac607ce8c84 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TileRangeExpansionJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a6bc23d6f2eb4e398389d1954b4aafae +timeCreated: 1643968292 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs new file mode 100644 index 00000000000..c58d4aa5b39 --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs @@ -0,0 +1,617 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace UnityEngine.Rendering.Universal +{ + [BurstCompile(FloatMode = FloatMode.Fast, DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)] + struct TilingJob : IJobFor + { + [ReadOnly] + public NativeArray lights; + + [NativeDisableParallelForRestriction] + public NativeArray tileRanges; + + public int itemsPerLight; + + public float4x4 worldToViewMatrix; + + public float2 tileScale; + public float2 tileScaleInv; + public float2 viewPlaneHalfSize; + public float2 viewPlaneHalfSizeInv; + public int2 tileCount; + public float near; + + InclusiveRange m_TileYRange; + int m_Offset; + + public void Execute(int lightIndex) + { + m_Offset = lightIndex * itemsPerLight; + var light = lights[lightIndex]; + var near = this.near; + + m_TileYRange = new InclusiveRange(short.MaxValue, short.MinValue); + + for (var i = 0; i < itemsPerLight; i++) + { + tileRanges[m_Offset + i] = new InclusiveRange(short.MaxValue, short.MinValue); + } + + var lightToWorld = (float4x4)light.localToWorldMatrix; + var lightPositionVS = math.mul(worldToViewMatrix, math.float4(lightToWorld.c3.xyz, 1)).xyz; + lightPositionVS.z *= -1; + if (lightPositionVS.z >= near) ExpandY(lightPositionVS); + var lightDirectionVS = math.normalize(math.mul(worldToViewMatrix, math.float4(lightToWorld.c2.xyz, 0)).xyz); + lightDirectionVS.z *= -1; + + var halfAngle = math.radians(light.spotAngle * 0.5f); + var range = light.range; + var rangesq = square(range); + var rangeinv = math.rcp(range); + var cosHalfAngle = math.cos(halfAngle); + var coneHeight = cosHalfAngle * range; + + // Radius of circle formed by intersection of sphere and near plane. + // Found using Pythagoras with a right triangle formed by three points: + // (a) light position + // (b) light position projected to near plane + // (c) a point on the near plane at a distance `range` from the light position + // (i.e. lies both on the sphere and the near plane) + // Thus the hypotenuse is formed by (a) and (c) with length `range`, and the known side is formed + // by (a) and (b) with length equal to the distance between the near plane and the light position. + // The remaining unknown side is formed by (b) and (c) with length equal to the radius of the circle. + // m_ClipCircleRadius = sqrt(pow2(light.range) - pow2(abs(m_Near - m_LightPosition.z))); + var sphereClipRadius = math.sqrt(rangesq - square(math.abs(near - lightPositionVS.z))); + + // Assumes a point on the sphere, i.e. at distance `range` from the light position. + // If spot light, we check the angle between the direction vector from the light position and the light direction vector. + // Note that division by range is to normalize the vector, as we know that the resulting vector will have length `range`. + bool SpherePointIsValid(float3 p) => light.lightType == LightType.Point || + math.dot(math.normalize(p - lightPositionVS), lightDirectionVS) >= cosHalfAngle; + + // Project light sphere onto YZ plane, find the horizon points, and re-construct view space position of found points. + // CalculateSphereYBounds(lightPositionVS, range, near, sphereClipRadius, out var sphereBoundY0, out var sphereBoundY1); + GetSphereHorizon(lightPositionVS.yz, range, near, sphereClipRadius, out var sphereBoundYZ0, out var sphereBoundYZ1); + var sphereBoundY0 = math.float3(lightPositionVS.x, sphereBoundYZ0); + var sphereBoundY1 = math.float3(lightPositionVS.x, sphereBoundYZ1); + if (SpherePointIsValid(sphereBoundY0)) ExpandY(sphereBoundY0); + if (SpherePointIsValid(sphereBoundY1)) ExpandY(sphereBoundY1); + + // Project light sphere onto XZ plane, find the horizon points, and re-construct view space position of found points. + GetSphereHorizon(lightPositionVS.xz, range, near, sphereClipRadius, out var sphereBoundXZ0, out var sphereBoundXZ1); + var sphereBoundX0 = math.float3(sphereBoundXZ0.x, lightPositionVS.y, sphereBoundXZ0.y); + var sphereBoundX1 = math.float3(sphereBoundXZ1.x, lightPositionVS.y, sphereBoundXZ1.y); + if (SpherePointIsValid(sphereBoundX0)) ExpandY(sphereBoundX0); + if (SpherePointIsValid(sphereBoundX1)) ExpandY(sphereBoundX1); + // UnityEditor.TransformWorldPlacementJSON:{"position":{"x":-1.3365650177001954,"y":-30.506000518798829,"z":5.571170330047607},"rotation":{"x":0.9168553948402405,"y":-0.04080447554588318,"z":0.3962773084640503,"w":-0.025992946699261667},"scale":{"x":0.9999998807907105,"y":1.0,"z":0.9999998807907105}} + + if (light.lightType == LightType.Spot) + { + // Cone base + var baseRadius = math.sqrt(range * range - coneHeight * coneHeight); + var baseCenter = lightPositionVS + lightDirectionVS * coneHeight; + + // Project cone base (a circle) into the YZ plane, find the horizon points, and re-construct view space position of found points. + // When projecting a circle to a plane, it becomes an ellipse where the major axis is parallel to the line + // of intersection of the projection plane and the circle plane. We can get this by taking the cross product + // of the two plane normals, as the line of intersection will have to be a vector in both planes, and thus + // orthogonal to both normals. + // If the two plane normals are parallel, the cross product would return 0. In that case, the circle will + // project to a line segment, so we pick a vector in the plane pointing in the direction we're interested + // in finding horizon points in. + var baseUY = math.abs(math.abs(lightDirectionVS.x) - 1) < 1e-6f ? math.float3(0, 1, 0) : math.normalize(math.cross(lightDirectionVS, math.float3(1, 0, 0))); + var baseVY = math.cross(lightDirectionVS, baseUY); + GetProjectedCircleHorizon(baseCenter.yz, baseRadius, baseUY.yz, baseVY.yz, out var baseY1UV, out var baseY2UV); + var baseY1 = baseCenter + baseY1UV.x * baseUY + baseY1UV.y * baseVY; + var baseY2 = baseCenter + baseY2UV.x * baseUY + baseY2UV.y * baseVY; + if (baseY1.z >= near) ExpandY(baseY1); + if (baseY2.z >= near) ExpandY(baseY2); + + // Project cone base into the XZ plane, find the horizon points, and re-construct view space position of found points. + // See comment for YZ plane for details. + var baseUX = math.abs(math.abs(lightDirectionVS.y) - 1) < 1e-6f ? math.float3(1, 0, 0) : math.normalize(math.cross(lightDirectionVS, math.float3(0, 1, 0))); + var baseVX = math.cross(lightDirectionVS, baseUX); + GetProjectedCircleHorizon(baseCenter.xz, baseRadius, baseUX.xz, baseVX.xz, out var baseX1UV, out var baseX2UV); + var baseX1 = baseCenter + baseX1UV.x * baseUX + baseX1UV.y * baseVX; + var baseX2 = baseCenter + baseX2UV.x * baseUX + baseX2UV.y * baseVX; + if (baseX1.z >= near) ExpandY(baseX1); + if (baseX2.z >= near) ExpandY(baseX2); + + // Handle base circle clipping by intersecting it with the near-plane if needed. + if (GetCircleClipPoints(baseCenter, lightDirectionVS, baseRadius, near, out var baseClip0, out var baseClip1)) + { + ExpandY(baseClip0); + ExpandY(baseClip1); + } + + bool ConicPointIsValid(float3 p) => + math.dot(math.normalize(p - lightPositionVS), lightDirectionVS) >= 0 && + math.dot(p - lightPositionVS, lightDirectionVS) <= coneHeight; + + // Calculate Z bounds of cone and check if it's overlapping with the near plane. + // From https://www.iquilezles.org/www/articles/diskbbox/diskbbox.htm + var baseExtentZ = baseRadius * math.sqrt(1.0f - square(lightDirectionVS.z)); + var coneIsClipping = near >= math.min(baseCenter.z - baseExtentZ, lightPositionVS.z) && near <= math.max(baseCenter.z + baseExtentZ, lightPositionVS.z); + + if (coneIsClipping) + { + var r = baseRadius / coneHeight; + + // Find the Y bounds of the near-plane cone intersection, i.e. where y' = 0 + var thetaY = FindNearConicTangentTheta(lightPositionVS.yz, lightDirectionVS.yz, r, baseUX.yz, baseVX.yz); + var p0Y = EvaluateNearConic(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, thetaY.x); + var p1Y = EvaluateNearConic(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, thetaY.y); + if (ConicPointIsValid(p0Y)) ExpandY(p0Y); + if (ConicPointIsValid(p1Y)) ExpandY(p1Y); + + // Find the X bounds of the near-plane cone intersection, i.e. where x' = 0 + var thetaX = FindNearConicTangentTheta(lightPositionVS.xz, lightDirectionVS.xz, r, baseUX.xz, baseVX.xz); + var p0X = EvaluateNearConic(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, thetaX.x); + var p1X = EvaluateNearConic(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, thetaX.y); + if (ConicPointIsValid(p0X)) ExpandY(p0X); + if (ConicPointIsValid(p1X)) ExpandY(p1X); + } + + // Calculate the lines making up the sides of the cone as seen from the camera. `l1` and `l2` form lines + // from the light position. + GetConeSideTangentPoints(lightPositionVS, lightDirectionVS, cosHalfAngle, baseRadius, coneHeight, range, baseUY, baseVY, out var l1, out var l2); + + { + var planeNormal = math.float3(0, 1, -viewPlaneHalfSize.y); + var l1t = math.dot(-lightPositionVS, planeNormal) / math.dot(l1, planeNormal); + var l1x = lightPositionVS + l1 * l1t; + if (l1t >= 0 && l1t <= 1 && l1x.z >= near) ExpandY(l1x); + } + { + var planeNormal = math.float3(0, 1, viewPlaneHalfSize.y); + var l1t = math.dot(-lightPositionVS, planeNormal) / math.dot(l1, planeNormal); + var l1x = lightPositionVS + l1 * l1t; + if (l1t >= 0 && l1t <= 1 && l1x.z >= near) ExpandY(l1x); + } + + m_TileYRange.Clamp(0, (short)(tileCount.y - 1)); + + // Calculate tile plane ranges for cone. + for (var planeIndex = m_TileYRange.start + 1; planeIndex <= m_TileYRange.end; planeIndex++) + { + var planeRange = InclusiveRange.empty; + + // Y-position on the view plane (Z=1) + var planeY = math.lerp(-viewPlaneHalfSize.y, viewPlaneHalfSize.y, planeIndex * tileScaleInv.y); + + var planeNormal = math.float3(0, 1, -planeY); + + // Intersect lines with y-plane and clip if needed. + var l1t = math.dot(-lightPositionVS, planeNormal) / math.dot(l1, planeNormal); + var l1x = lightPositionVS + l1 * l1t; + if (l1t >= 0 && l1t <= 1 && l1x.z >= near) planeRange.Expand((short)math.clamp(ViewToTileSpace(l1x).x, 0, tileCount.x - 1)); + + var l2t = math.dot(-lightPositionVS, planeNormal) / math.dot(l2, planeNormal); + var l2x = lightPositionVS + l2 * l2t; + if (l2t >= 0 && l2t <= 1 && l2x.z >= near) planeRange.Expand((short)math.clamp(ViewToTileSpace(l2x).x, 0, tileCount.x - 1)); + + if (IntersectCircleYPlane(planeY, baseCenter, lightDirectionVS, baseUY, baseVY, baseRadius, out var circleTile0, out var circleTile1)) + { + if (circleTile0.z >= near) planeRange.Expand((short)math.clamp(ViewToTileSpace(circleTile0).x, 0, tileCount.x - 1)); + if (circleTile1.z >= near) planeRange.Expand((short)math.clamp(ViewToTileSpace(circleTile1).x, 0, tileCount.x - 1)); + } + + if (coneIsClipping) + { + var y = planeY * near; + var r = baseRadius / coneHeight; + var theta = FindNearConicYTheta(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, y); + var p0 = math.float3(EvaluateNearConic(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, theta.x).x, y, near); + var p1 = math.float3(EvaluateNearConic(near, lightPositionVS, lightDirectionVS, r, baseUX, baseVX, theta.y).x, y, near); + if (ConicPointIsValid(p0)) planeRange.Expand((short)math.clamp(ViewToTileSpace(p0).x, 0, tileCount.x - 1)); + if (ConicPointIsValid(p1)) planeRange.Expand((short)math.clamp(ViewToTileSpace(p1).x, 0, tileCount.x - 1)); + } + + // Write to tile ranges above and below the plane. Note that at `m_Offset` we store Y-range. + var tileIndex = m_Offset + 1 + planeIndex; + tileRanges[tileIndex] = InclusiveRange.Merge(tileRanges[tileIndex], planeRange); + tileRanges[tileIndex - 1] = InclusiveRange.Merge(tileRanges[tileIndex - 1], planeRange); + } + } + + m_TileYRange.Clamp(0, (short)(tileCount.y - 1)); + + // Calculate tile plane ranges for sphere. + for (var planeIndex = m_TileYRange.start + 1; planeIndex <= m_TileYRange.end; planeIndex++) + { + var planeRange = InclusiveRange.empty; + + var planeY = math.lerp(-viewPlaneHalfSize.y, viewPlaneHalfSize.y, planeIndex * tileScaleInv.y); + GetSphereYPlaneHorizon(lightPositionVS, range, near, sphereClipRadius, planeY, out var sphereTile0, out var sphereTile1); + if (SpherePointIsValid(sphereTile0)) planeRange.Expand((short)math.clamp(ViewToTileSpace(sphereTile0).x, 0, tileCount.x - 1)); + if (SpherePointIsValid(sphereTile1)) planeRange.Expand((short)math.clamp(ViewToTileSpace(sphereTile1).x, 0, tileCount.x - 1)); + + var tileIndex = m_Offset + 1 + planeIndex; + tileRanges[tileIndex] = InclusiveRange.Merge(tileRanges[tileIndex], planeRange); + tileRanges[tileIndex - 1] = InclusiveRange.Merge(tileRanges[tileIndex - 1], planeRange); + } + + tileRanges[m_Offset] = m_TileYRange; + } + + /// + /// Project onto Z=1, scale and offset into [0, tileCount] + /// + float2 ViewToTileSpace(float3 positionVS) + { + return (positionVS.xy / positionVS.z * viewPlaneHalfSizeInv * 0.5f + 0.5f) * tileScale; + } + + /// + /// Expands the tile Y range and the X range in the row containing the position. + /// + void ExpandY(float3 positionVS) + { + // var positionTS = math.clamp(ViewToTileSpace(positionVS), 0, tileCount - 1); + var positionTS = ViewToTileSpace(positionVS); + var tileY = (int)positionTS.y; + var tileX = (int)positionTS.x; + m_TileYRange.Expand((short)math.clamp(tileY, 0, tileCount.y - 1)); + if (tileY >= 0 && tileY < tileCount.y && tileX >= 0 && tileX < tileCount.x) + { + var rowXRange = tileRanges[m_Offset + 1 + tileY]; + rowXRange.Expand((short)tileX); + tileRanges[m_Offset + 1 + tileY] = rowXRange; + } + } + + /// + /// Expands the X range in the row containing the position. + /// + void ExpandX(float3 positionVS) + { + var positionTS = math.clamp(ViewToTileSpace(positionVS), 0, tileCount - 1); + var tileY = (short)positionTS.y; + if (tileY >= 0 && tileY < tileCount.y) + { + var rangeIndex = m_Offset + 1 + tileY; + var rowXRange = tileRanges[rangeIndex]; + rowXRange.Expand((short)positionTS.x); + tileRanges[rangeIndex] = rowXRange; + } + } + + static float square(float x) => x * x; + + /// + /// Finds the two horizon points seen from (0, 0) of a sphere projected onto either XZ or YZ. Takes clipping into account. + /// + static void GetSphereHorizon(float2 center, float radius, float near, float clipRadius, out float2 p0, out float2 p1) + { + var direction = math.normalize(center); + + // Distance from camera to center of sphere + var d = math.length(center); + + // Distance from camera to sphere horizon edge + var l = math.sqrt(d * d - radius * radius); + + // Height of circle horizon + var h = l * radius / d; + + // Center of circle horizon + var c = direction * (l * h / radius); + + // Circle horizon points + p0 = c + math.float2(-direction.y, direction.x) * h; + p1 = c + math.float2(direction.y, -direction.x) * h; + + // Handle clipping + if (square(d) < square(radius) || p0.y < near) p0 = math.float2(center.x + clipRadius, near); + if (square(d) < square(radius) || p1.y < near) p1 = math.float2(center.x - clipRadius, near); + } + + static void GetSphereYPlaneHorizon(float3 center, float sphereRadius, float near, float clipRadius, float y, out float3 left, out float3 right) + { + // Note: The y-plane is the plane that is determined by `y` in that it contains the vector (1, 0, 0) + // and goes through the points (0, y, 1) and (0, 0, 0). This would become a straight line in screen-space, and so it + // represents the boundary between two rows of tiles. + + // Near-plane clipping - will get overwritten if no clipping is needed. + // `y` is given for the view plane (Z=1), scale it so that it is on the near plane instead. + var yNear = y * near; + // Find the two points of intersection between the clip circle of the sphere and the y-plane. + // Found using Pythagoras with a right triangle formed by three points: + // (a) center of the clip circle + // (b) a point straight above the clip circle center on the y-plane + // (c) a point that is both on the circle and the y-plane (this is the point we want to find in the end) + // The hypotenuse is formed by (a) and (c) with length equal to the clip radius. The known side is + // formed by (a) and (b) and is simply the distance from the center to the y-plane along the y-axis. + // The remaining side gives us the x-displacement needed to find the intersection points. + var clipHalfWidth = math.sqrt(square(clipRadius) - square(yNear - center.y)); + left = math.float3(center.x - clipHalfWidth, yNear, near); + right = math.float3(center.x + clipHalfWidth, yNear, near); + + // Basis vectors in the y-plane for being able to parameterize the plane. + var planeU = math.normalize(math.float3(0, y, 1)); + var planeV = math.float3(1, 0, 0); + + // Calculate the normal of the y-plane. Found from: (0, y, 1) × (1, 0, 0) = (0, 1, -y) + // This is used to represent the plane along with the origin, which is just 0 and thus doesn't show up + // in the calculations. + var normal = math.normalize(math.float3(0, 1, -y)); + + // We want to first find the circle from the intersection of the y-plane and the sphere. + + // The shortest distance from the sphere center and the y-plane. The sign determines which side of the plane + // the center is on. + var signedDistance = math.dot(normal, center); + + // Unsigned shortest distance from the sphere center to the plane. + var distanceToPlane = math.abs(signedDistance); + + // The center of the intersection circle in the y-plane, which is the point on the plane closest to the + // sphere center. I.e. this is at `distanceToPlane` from the center. + var centerOnPlane = math.float2(math.dot(center, planeU), math.dot(center, planeV)); + + // Distance from origin to the circle center. + var distanceInPlane = math.length(centerOnPlane); + + // Direction from origin to the circle center. + var directionPS = centerOnPlane / distanceInPlane; + + // Calculate the radius of the circle using Pythagoras. We know that any point on the circle is a point on + // the sphere. Thus we can construct a triangle with the sphere center, circle center, and a point on the + // circle. We then want to find its distance to the circle center, as that will be equal to the radius. As + // the point is on the sphere, it must be `sphereRadius` from the sphere center, forming the hypotenuse. The + // other side is between the sphere and circle centers, which we've already calculated to be + // `distanceToPlane`. + var circleRadius = math.sqrt(square(sphereRadius) - square(distanceToPlane)); + + // Now that we have the circle, we can find the horizon points. Since we've parametrized the plane, we can + // just do this in 2D. + + // Any of these conditions will yield NaN due to negative square roots. They are signs that clipping is needed, + // so we fallback on the already calculated values in that case. + if (square(distanceToPlane) <= square(sphereRadius) && square(circleRadius) <= square(distanceInPlane)) + { + // Distance from origin to circle horizon edge. + var l = math.sqrt(square(distanceInPlane) - square(circleRadius)); + + // Height of circle horizon. + var h = l * circleRadius / distanceInPlane; + + // Center of circle horizon. + var c = directionPS * (l * h / circleRadius); + + // Calculate the horizon points in the plane. + var leftOnPlane = c + math.float2(directionPS.y, -directionPS.x) * h; + var rightOnPlane = c + math.float2(-directionPS.y, directionPS.x) * h; + + // Transform horizon points to view space and use if not clipped. + var leftCandidate = leftOnPlane.x * planeU + leftOnPlane.y * planeV; + if (leftCandidate.z >= near) left = leftCandidate; + + var rightCandidate = rightOnPlane.x * planeU + rightOnPlane.y * planeV; + if (rightCandidate.z >= near) right = rightCandidate; + } + } + + /// + /// Finds the two points of intersection of a 3D circle and the near plane. + /// + static bool GetCircleClipPoints(float3 circleCenter, float3 circleNormal, float circleRadius, float near, out float3 p0, out float3 p1) + { + // The intersection of two planes is a line where the direction is the cross product of the two plane normals. + // In this case, it is the plane containing the circle, and the near plane. + var lineDirection = math.normalize(math.cross(circleNormal, math.float3(0, 0, 1))); + + // Find a direction on the circle plane towards the nearest point on the intersection line. + // It has to be perpendicular to the circle normal to be in the circle plane. The direction to the closest + // point on a line is perpendicular to the line direction. Thus this is given by the cross product of the + // line direction and the circle normal, as this gives us a vector that is perpendicular to both of those. + var nearestDirection = math.cross(lineDirection, circleNormal); + + // Distance from circle center to the intersection line along `nearestDirection`. + // This is done using a ray-plane intersection, where the plane is the near plane. + // ({0, 0, near} - circleCenter) . {0, 0, 1} / (nearestDirection . {0, 0, 1}) + var distance = (near - circleCenter.z) / nearestDirection.z; + + // The point on the line nearest to the circle center when traveling only in the circle plane. + var nearestPoint = circleCenter + nearestDirection * distance; + + // Any line through a circle makes a chord where the endpoints are the intersections with the circle. + // The half length of the circle chord can be found by constructing a right triangle from three points: + // (a) The circle center. + // (b) The nearest point. + // (c) A point that is on circle and the intersection line. + // The hypotenuse is formed by (a) and (c) and will have length `circleRadius` as it is on the circle. + // The known side if formed by (a) and (b), which we have already calculated the distance of in `distance`. + // The unknown side formed by (b) and (c) is then found using Pythagoras. + var chordHalfLength = math.sqrt(square(circleRadius) - square(distance)); + p0 = nearestPoint + lineDirection * chordHalfLength; + p1 = nearestPoint - lineDirection * chordHalfLength; + + return math.abs(distance) <= circleRadius; + } + + static (float, float) IntersectEllipseLine(float a, float b, float3 line) + { + // The line is represented as a homogenous 2D line {u, v, w} such that ux + vy + w = 0. + // The ellipse is represented by the implicit equation x^2/a^2 + y^2/b^2 = 1. + // We solve the line equation for y: y = (ux + w) / v + // We then substitute this into the ellipse equation and expand and re-arrange a bit: + // x^2/a^2 + ((ux + w) / v)^2/b^2 = 1 => + // x^2/a^2 + ((ux + w)^2 / v^2)/b^2 = 1 => + // x^2/a^2 + (ux + w)^2/(v^2 b^2) = 1 => + // x^2/a^2 + (u^2 x^2 + w^2 + 2 u x w)/(v^2 b^2) = 1 => + // x^2/a^2 + x^2 u^2 / (v^2 b^2) + w^2/(v^2 b^2) + x 2 u w / (v^2 b^2) = 1 => + // x^2 (1/a^2 + u^2 / (v^2 b^2)) + x 2 u w / (v^2 b^2) + w^2 / (v^2 b^2) - 1 = 0 + // We now have a quadratic equation with: + // a = 1/a^2 + u^2 / (v^2 b^2) + // b = 2 u w / (v^2 b^2) + // c = w^2 / (v^2 b^2) - 1 + var div = math.rcp(square(line.y) * square(b)); + var qa = 1f / square(a) + square(line.x) * div; + var qb = 2f * line.x * line.z * div; + var qc = square(line.z) * div - 1f; + var sqrtD = math.sqrt(qb * qb - 4f * qa * qc); + var x1 = (-qb + sqrtD) / (2f * qa); + var x2 = (-qb - sqrtD) / (2f * qa); + return (x1, x2); + } + + /// + /// Calculates the horizon of a circle orthogonally projected to a plane as seen from the origin on the plane. + /// + /// The center of the circle projected onto the plane. + /// The radius of the circle. + /// The major axis of the ellipse formed by the projection of the circle. + /// The minor axis of the ellipse formed by the projection of the circle. + /// The first horizon point expressed as factors of and . + /// The second horizon point expressed as factors of and . + static void GetProjectedCircleHorizon(float2 center, float radius, float2 U, float2 V, out float2 uv1, out float2 uv2) + { + // U is assumed to be constructed such that it is never 0, but V can be if the circle projects to a line segment. + // In that case, the solution can be trivially found using U only. + var vl = math.length(V); + if (vl < 1e-6f) + { + uv1 = math.float2(radius, 0); + uv2 = math.float2(-radius, 0); + } + else + { + var ul = math.length(U); + var ulinv = math.rcp(ul); + var vlinv = math.rcp(vl); + + // Normalize U and V in the plane. + var u = U * ulinv; + var v = V * vlinv; + + // Major and minor axis of the ellipse. + var a = ul * radius; + var b = vl * radius; + + // Project the camera position into a 2D coordinate system with the circle at (0, 0) and + // the ellipse major and minor axes as the coordinate system axes. This allows us to use the standard + // form of the ellipse equation, greatly simplifying the calculations. + var cameraUV = math.float2(math.dot(-center, u), math.dot(-center, v)); + + // Find the polar line of the camera position in the normalized UV coordinate system. + var polar = math.float3(cameraUV.x / square(a), cameraUV.y / square(b), -1); + var (t1, t2) = IntersectEllipseLine(a, b, polar); + + // Find Y by putting polar into line equation and solving. Denormalize by dividing by U and V lengths. + uv1 = math.float2(t1 * ulinv, (-polar.x / polar.y * t1 - polar.z / polar.y) * vlinv); + uv2 = math.float2(t2 * ulinv, (-polar.x / polar.y * t2 - polar.z / polar.y) * vlinv); + } + } + + static bool IntersectCircleYPlane( + float y, float3 circleCenter, float3 circleNormal, float3 circleU, float3 circleV, float circleRadius, + out float3 p1, out float3 p2) + { + p1 = p2 = 0; + + // Intersecting a circle with a plane yields 2 points, or the whole circle if the plane and the plane of the + // circle are the same, or nothing if the planes are parallel but offset. We're only interested in the first + // case. Our other tests will catch the other cases. + + // The two points will be on the line of intersection of the two planes. Thus we first have to find that line. + + // Shoot 2 rays along the y-plane and intersect the circle plane. We then transform them into the circle + // plane, so that we can work in 2D. + var CdotN = math.dot(circleCenter, circleNormal); + var h1v = math.float3(1, y, 1) * CdotN / math.dot(math.float3(1, y, 1), circleNormal) - circleCenter; + var h1 = math.float2(math.dot(h1v, circleU), math.dot(h1v, circleV)); + var h2v = math.float3(-1, y, 1) * CdotN / math.dot(math.float3(-1, y, 1), circleNormal) - circleCenter; + var h2 = math.float2(math.dot(h2v, circleU), math.dot(h2v, circleV)); + + var lineDirection = math.normalize(h2 - h1); + // We now have the direction of the line, and would like to find the point on it that is closest to the + // circle center. A line in 2D is similar to a plane in 3D. So we can calculate a normal, which is just a + // perpendicular/orthogonal direction, and then take the dot product to find the distance. This is similar + // to when calculating the d-term for a plane in 3D, which is also just calculating the closest distance + // from the origin to the plane. + var lineNormal = math.float2(lineDirection.y, -lineDirection.x); + var distToLine = math.dot(h1, lineNormal); + // We can then get that point on the line by following our normal with the distance we just calculated. + var lineCenter = lineNormal * distToLine; + + // Avoid negative square roots, as this means we've hit one of the cases that we do not care about. + if (distToLine > circleRadius) return false; + + // What's left now is to intersect the line with the circle. We can do so with Pythagoras. Our triangle + // is made up of `lineCenter`, the circle center and one of the intersection points. + // We know the distance from `lineCenter` to the circle center (`distToLine`), and the distance from + // the circle center to one of the intersection points must be the circle radius, as it lies on the + // circle, forming the hypotenuse. + var l = math.sqrt(circleRadius * circleRadius - distToLine * distToLine); + + // What we found above is the distance from `lineCenter` to each of the intersection points. So we just + // scrub along the line in both directions using the found distance, and then transform back into view + // space. + var x1 = lineCenter + l * lineDirection; + var x2 = lineCenter - l * lineDirection; + p1 = circleCenter + x1.x * circleU + x1.y * circleV; + p2 = circleCenter + x2.x * circleU + x2.y * circleV; + + return true; + } + + static void GetConeSideTangentPoints(float3 vertex, float3 axis, float cosHalfAngle, float circleRadius, float coneHeight, float range, float3 circleU, float3 circleV, out float3 l1, out float3 l2) + { + l1 = l2 = 0; + + if (math.dot(math.normalize(-vertex), axis) >= cosHalfAngle) + { + return; + } + + var d = -math.dot(vertex, axis); + var sign = d < 0 ? -1f : 1f; + // sign *= vertex.z < 0 ? -1f : 1f; + var origin = vertex + axis * d; + var radius = math.max(math.abs(d), 1e-6f) * circleRadius / coneHeight; + var cameraUV = math.float2(math.dot(circleU, -origin), math.dot(circleV, -origin)); + var polar = math.float3(cameraUV, -square(radius)); + var p1 = math.float2(-1, -polar.x / polar.y * (-1) - polar.z / polar.y); + var p2 = math.float2(1, -polar.x / polar.y * 1 - polar.z / polar.y); + var lineDirection = math.normalize(p2 - p1); + var lineNormal = math.float2(lineDirection.y, -lineDirection.x); + var distToLine = math.dot(p1, lineNormal); + var lineCenter = lineNormal * distToLine; + var l = math.sqrt(radius * radius - distToLine * distToLine); + var x1UV = lineCenter + l * lineDirection; + var x2UV = lineCenter - l * lineDirection; + var dir1 = math.normalize((origin + x1UV.x * circleU + x1UV.y * circleV) - vertex) * sign; + var dir2 = math.normalize((origin + x2UV.x * circleU + x2UV.y * circleV) - vertex) * sign; + l1 = dir1 * range; + l2 = dir2 * range; + } + + static float3 EvaluateNearConic(float near, float3 o, float3 d, float r, float3 u, float3 v, float theta) + { + var h = (near - o.z) / (d.z + r * u.z * math.cos(theta) + r * v.z * math.sin(theta)); + return math.float3(o.xy + h * (d.xy + r * u.xy * math.cos(theta) + r * v.xy * math.sin(theta)), near); + } + + // o, d, u and v are expected to contain {x or y, z}. I.e. pass in x values to find tangents where x' = 0 + // Returns the two theta values as a float2. + static float2 FindNearConicTangentTheta(float2 o, float2 d, float r, float2 u, float2 v) + { + var sqrt = math.sqrt(square(d.x) * square(u.y) + square(d.x) * square(v.y) - 2f * d.x * d.y * u.x * u.y - 2f * d.x * d.y * v.x * v.y + square(d.y) * square(u.x) + square(d.y) * square(v.x) - square(r) * square(u.x) * square(v.y) + 2f * square(r) * u.x * u.y * v.x * v.y - square(r) * square(u.y) * square(v.x)); + var denom = d.x * v.y - d.y * v.x - r * u.x * v.y + r * u.y * v.x; + return 2 * math.atan((-d.x * u.y + d.y * u.x + math.float2(1, -1) * sqrt) / denom); + } + + static float2 FindNearConicYTheta(float near, float3 o, float3 d, float r, float3 u, float3 v, float y) + { + var sqrt = math.sqrt(-square(d.y) * square(o.z) + 2 * square(d.y) * o.z * near - square(d.y) * square(near) + 2 * d.y * d.z * o.y * o.z - 2 * d.y * d.z * o.y * near - 2 * d.y * d.z * o.z * y + 2 * d.y * d.z * y * near - square(d.z) * square(o.y) + 2 * square(d.z) * o.y * y - square(d.z) * square(y) + square(o.y) * square(r) * square(u.z) + square(o.y) * square(r) * square(v.z) - 2 * o.y * o.z * square(r) * u.y * u.z - 2 * o.y * o.z * square(r) * v.y * v.z - 2 * o.y * y * square(r) * square(u.z) - 2 * o.y * y * square(r) * square(v.z) + 2 * o.y * square(r) * u.y * u.z * near + 2 * o.y * square(r) * v.y * v.z * near + square(o.z) * square(r) * square(u.y) + square(o.z) * square(r) * square(v.y) + 2 * o.z * y * square(r) * u.y * u.z + 2 * o.z * y * square(r) * v.y * v.z - 2 * o.z * square(r) * square(u.y) * near - 2 * o.z * square(r) * square(v.y) * near + square(y) * square(r) * square(u.z) + square(y) * square(r) * square(v.z) - 2 * y * square(r) * u.y * u.z * near - 2 * y * square(r) * v.y * v.z * near + square(r) * square(u.y) * square(near) + square(r) * square(v.y) * square(near)); + var denom = d.y * o.z - d.y * near - d.z * o.y + d.z * y + o.y * r * u.z - o.z * r * u.y - y * r * u.z + r * u.y * near; + return 2 * math.atan((r * (o.y * v.z - o.z * v.y - y * v.z + v.y * near) + math.float2(1, -1) * sqrt) / denom); + } + } +} diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs.meta b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs.meta new file mode 100644 index 00000000000..5aba07b243a --- /dev/null +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/TilingJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b1021b0cb21b45b7b3e8e22788703471 +timeCreated: 1639996762 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/ZBinningJob.cs b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/ZBinningJob.cs index cee33dc97a5..8bbd15d7c6d 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/ZBinningJob.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/Tiling/ZBinningJob.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections; using Unity.Jobs; @@ -7,53 +6,63 @@ namespace UnityEngine.Rendering.Universal { - [StructLayout(LayoutKind.Sequential)] - struct ZBin - { - public ushort minIndex; - public ushort maxIndex; - } - - [BurstCompile] - unsafe struct ZBinningJob : IJobFor + [BurstCompile(FloatMode = FloatMode.Fast, DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)] + struct ZBinningJob : IJobFor { // Do not use this for the innerloopBatchCount (use 1 for that). Use for dividing the arrayLength when scheduling. - public const int batchCount = 64; + public const int batchCount = 128; [NativeDisableParallelForRestriction] - public NativeArray bins; + public NativeArray bins; [ReadOnly] public NativeArray minMaxZs; - public int binOffset; + public float zBinScale; + + public float zBinOffset; - public float zFactor; + public int binCount; + + public int wordsPerTile; + + static uint EncodeHeader(uint min, uint max) + { + return (min & 0xFFFF) | ((max & 0xFFFF) << 16); + } + + static (uint, uint) DecodeHeader(uint zBin) + { + return (zBin & 0xFFFF, (zBin >> 16) & 0xFFFF); + } public void Execute(int index) { var binsStart = batchCount * index; - var binsEnd = math.min(binsStart + batchCount, bins.Length) - 1; + var binsEnd = math.min(binsStart + batchCount, binCount) - 1; - for (var i = binsStart; i <= binsEnd; i++) + for (var binIndex = binsStart; binIndex <= binsEnd; binIndex++) { - bins[i] = new ZBin { minIndex = ushort.MaxValue, maxIndex = ushort.MaxValue }; + bins[binIndex * (1 + wordsPerTile)] = EncodeHeader(ushort.MaxValue, ushort.MinValue); } - for (var lightIndex = 0; lightIndex < minMaxZs.Length; lightIndex++) { - var ushortLightIndex = (ushort)lightIndex; var minMax = minMaxZs[lightIndex]; - var minBin = math.max((int)(math.sqrt(minMax.minZ) * zFactor) - binOffset, binsStart); - var maxBin = math.min((int)(math.sqrt(minMax.maxZ) * zFactor) - binOffset, binsEnd); + var minBin = math.max((int)(math.log2(minMax.minZ) * zBinScale + zBinOffset), binsStart); + var maxBin = math.min((int)(math.log2(minMax.maxZ) * zBinScale + zBinOffset), binsEnd); + + var wordIndex = lightIndex / 32; + var bitMask = 1u << (lightIndex % 32); for (var binIndex = minBin; binIndex <= maxBin; binIndex++) { - var bin = bins[binIndex]; - bin.minIndex = Math.Min(bin.minIndex, ushortLightIndex); + var baseIndex = binIndex * (1 + wordsPerTile); + var (minIndex, maxIndex) = DecodeHeader(bins[baseIndex]); + minIndex = math.min(minIndex, (uint)wordIndex); // This will always be the largest light index this bin has seen due to light iteration order. - bin.maxIndex = ushortLightIndex; - bins[binIndex] = bin; + maxIndex = math.max(maxIndex, (uint)wordIndex); + bins[baseIndex] = EncodeHeader(minIndex, maxIndex); + bins[baseIndex + 1 + wordIndex] |= bitMask; } } } diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs index 8d17c8f67e2..4b37aa704e1 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs @@ -144,8 +144,8 @@ public static int maxVisibleAdditionalLights // Match with values in Input.hlsl internal static int lightsPerTile => ((maxVisibleAdditionalLights + 31) / 32) * 32; - internal static int maxZBins => 1024 * 4; - internal static int maxTileVec4s => 4096; + internal static int maxZBinWords => 1024 * 4; + internal static int maxTileWords => (maxVisibleAdditionalLights <= 32 ? 1024 : 4096) * 4; internal const int k_DefaultRenderingLayerMask = 0x00000001; private readonly DebugDisplaySettingsUI m_DebugDisplaySettingsUI = new DebugDisplaySettingsUI(); @@ -300,6 +300,7 @@ protected override void Render(ScriptableRenderContext renderContext, Camera[] c #endif { var camera = cameras[i]; + camera.allowDynamicResolution = false; if (IsGameCamera(camera)) { RenderCameraStack(renderContext, camera); @@ -1096,7 +1097,7 @@ static void InitializeRenderingData(UniversalRenderPipelineAsset settings, ref C InitializeShadowData(settings, visibleLights, mainLightCastShadows, additionalLightsCastShadows && !renderingData.lightData.shadeAdditionalLightsPerVertex, out renderingData.shadowData); InitializePostProcessingData(settings, out renderingData.postProcessingData); renderingData.supportsDynamicBatching = settings.supportsDynamicBatching; - renderingData.perObjectData = GetPerObjectLightFlags(renderingData.lightData.additionalLightsCount); + renderingData.perObjectData = GetPerObjectLightFlags(renderingData.lightData.additionalLightsCount, ((settings.scriptableRendererData as UniversalRendererData)?.renderingMode ?? RenderingMode.Forward) == RenderingMode.ForwardPlus); renderingData.postProcessingEnabled = anyPostProcessingEnabled; renderingData.commandBuffer = cmd; @@ -1258,13 +1259,13 @@ static void UpdateCameraStereoMatrices(Camera camera, XRPass xr) #endif } - static PerObjectData GetPerObjectLightFlags(int additionalLightsCount) + static PerObjectData GetPerObjectLightFlags(int additionalLightsCount, bool clustering) { using var profScope = new ProfilingScope(null, Profiling.Pipeline.getPerObjectLightFlags); var configuration = PerObjectData.ReflectionProbes | PerObjectData.Lightmaps | PerObjectData.LightProbe | PerObjectData.LightData | PerObjectData.OcclusionProbe | PerObjectData.ShadowMask; - if (additionalLightsCount > 0) + if (additionalLightsCount > 0 && !clustering) { configuration |= PerObjectData.LightData; diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipelineCore.cs b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipelineCore.cs index 9595a06885d..4e7b5d9b7bb 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipelineCore.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipelineCore.cs @@ -607,7 +607,7 @@ public static class ShaderKeywordStrings public static readonly string CastingPunctualLightShadow = "_CASTING_PUNCTUAL_LIGHT_SHADOW"; // This is used during shadow map generation to differentiate between directional and punctual light shadows, as they use different formulas to apply Normal Bias public static readonly string AdditionalLightsVertex = "_ADDITIONAL_LIGHTS_VERTEX"; public static readonly string AdditionalLightsPixel = "_ADDITIONAL_LIGHTS"; - internal static readonly string ClusteredRendering = "_CLUSTERED_RENDERING"; + internal static readonly string ForwardPlus = "_FORWARD_PLUS"; public static readonly string AdditionalLightShadows = "_ADDITIONAL_LIGHT_SHADOWS"; public static readonly string ReflectionProbeBoxProjection = "_REFLECTION_PROBE_BOX_PROJECTION"; public static readonly string ReflectionProbeBlending = "_REFLECTION_PROBE_BLENDING"; diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs index 8cd61361872..febad2e5b2a 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs @@ -10,9 +10,12 @@ namespace UnityEngine.Rendering.Universal public enum RenderingMode { /// Render all objects and lighting in one pass, with a hard limit on the number of lights that can be applied on an object. - Forward, + Forward = 0, + /// Render all objects and lighting in one pass using a clustered data structure to access lighting data. + [InspectorName("Forward+")] + ForwardPlus = 2, /// Render all objects first in a g-buffer pass, then apply all lighting in a separate pass using deferred shading. - Deferred + Deferred = 1 }; /// @@ -57,6 +60,7 @@ public override int SupportedCameraStackingTypes() switch (m_RenderingMode) { case RenderingMode.Forward: + case RenderingMode.ForwardPlus: return 1 << (int)CameraRenderType.Base | 1 << (int)CameraRenderType.Overlay; case RenderingMode.Deferred: return 1 << (int)CameraRenderType.Base; @@ -69,10 +73,12 @@ public override int SupportedCameraStackingTypes() internal RenderingMode renderingModeRequested => m_RenderingMode; // Actual rendering mode, which may be different (ex: wireframe rendering, hardware not capable of deferred rendering). - internal RenderingMode renderingModeActual => (GL.wireframe || (DebugHandler != null && DebugHandler.IsActiveModeUnsupportedForDeferred) || m_DeferredLights == null || !m_DeferredLights.IsRuntimeSupportedThisFrame() || m_DeferredLights.IsOverlay) + internal RenderingMode renderingModeActual => renderingModeRequested == RenderingMode.Deferred && (GL.wireframe || (DebugHandler != null && DebugHandler.IsActiveModeUnsupportedForDeferred) || m_DeferredLights == null || !m_DeferredLights.IsRuntimeSupportedThisFrame() || m_DeferredLights.IsOverlay) ? RenderingMode.Forward : this.renderingModeRequested; + bool m_Clustering; + internal bool accurateGbufferNormals => m_DeferredLights != null ? m_DeferredLights.AccurateGbufferNormals : false; #if ADAPTIVE_PERFORMANCE_2_1_0_OR_NEWER @@ -188,8 +194,8 @@ public UniversalRenderer(UniversalRendererData data) : base(data) ForwardLights.InitParams forwardInitParams; forwardInitParams.lightCookieManager = m_LightCookieManager; - forwardInitParams.clusteredRendering = data.clusteredRendering; - forwardInitParams.tileSize = (int)data.tileSize; + forwardInitParams.forwardPlus = data.renderingMode == RenderingMode.ForwardPlus; + m_Clustering = data.renderingMode == RenderingMode.ForwardPlus; m_ForwardLights = new ForwardLights(forwardInitParams); //m_DeferredLights.LightCulling = data.lightCulling; this.m_RenderingMode = data.renderingMode; @@ -217,7 +223,7 @@ public UniversalRenderer(UniversalRendererData data) : base(data) m_DepthNormalPrepass = new DepthNormalOnlyPass(RenderPassEvent.BeforeRenderingPrePasses, RenderQueueRange.opaque, data.opaqueLayerMask); m_MotionVectorPass = new MotionVectorRenderPass(m_CameraMotionVecMaterial, m_ObjectMotionVecMaterial); - if (this.renderingModeRequested == RenderingMode.Forward) + if (renderingModeRequested == RenderingMode.Forward || renderingModeRequested == RenderingMode.ForwardPlus) { m_PrimedDepthCopyPass = new CopyDepthPass(RenderPassEvent.AfterRenderingPrePasses, m_CopyDepthMaterial, true); } @@ -444,7 +450,8 @@ public override void Setup(ScriptableRenderContext context, ref RenderingData re // Assign the camera color target early in case it is needed during AddRenderPasses. bool isPreviewCamera = cameraData.isPreviewCamera; - var createColorTexture = (rendererFeatures.Count != 0 && m_IntermediateTextureMode == IntermediateTextureMode.Always) && !isPreviewCamera; + var createColorTexture = ((rendererFeatures.Count != 0 && m_IntermediateTextureMode == IntermediateTextureMode.Always) && !isPreviewCamera) || + (Application.isEditor && m_Clustering); // Gather render passe input requirements RenderPassInputSummary renderPassInputs = GetRenderPassInputs(ref renderingData); @@ -583,7 +590,7 @@ public override void Setup(ScriptableRenderContext context, ref RenderingData re createColorTexture |= createDepthTexture; #endif bool useDepthPriming = IsDepthPrimingEnabled(); - useDepthPriming &= requiresDepthPrepass && (createDepthTexture || createColorTexture) && m_RenderingMode == RenderingMode.Forward && (cameraData.renderType == CameraRenderType.Base || cameraData.clearDepth); + useDepthPriming &= requiresDepthPrepass && (createDepthTexture || createColorTexture) && (m_RenderingMode == RenderingMode.Forward || m_RenderingMode == RenderingMode.ForwardPlus) && (cameraData.renderType == CameraRenderType.Base || cameraData.clearDepth); if (useRenderPassEnabled || useDepthPriming) createColorTexture |= createDepthTexture; diff --git a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRendererData.cs b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRendererData.cs index 8a1643eaf91..96faf8f955b 100644 --- a/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRendererData.cs +++ b/Packages/com.unity.render-pipelines.universal/Runtime/UniversalRendererData.cs @@ -148,9 +148,6 @@ public sealed class ShaderResources [SerializeField] DepthPrimingMode m_DepthPrimingMode = DepthPrimingMode.Disabled; // Default disabled because there are some outstanding issues with Text Mesh rendering. [SerializeField] CopyDepthMode m_CopyDepthMode = CopyDepthMode.AfterTransparents; [SerializeField] bool m_AccurateGbufferNormals = false; - [SerializeField] bool m_ClusteredRendering = false; - const TileSize k_DefaultTileSize = TileSize._32; - [SerializeField] TileSize m_TileSize = k_DefaultTileSize; [SerializeField] IntermediateTextureMode m_IntermediateTextureMode = IntermediateTextureMode.Always; protected override ScriptableRenderer Create() @@ -264,27 +261,6 @@ public bool accurateGbufferNormals } } - internal bool clusteredRendering - { - get => m_ClusteredRendering; - set - { - SetDirty(); - m_ClusteredRendering = value; - } - } - - internal TileSize tileSize - { - get => m_TileSize; - set - { - Assert.IsTrue(value.IsValid()); - SetDirty(); - m_TileSize = value; - } - } - /// /// Controls when URP renders via an intermediate texture. /// @@ -298,17 +274,6 @@ public IntermediateTextureMode intermediateTextureMode } } - /// - protected override void OnValidate() - { - base.OnValidate(); - if (!m_TileSize.IsValid()) - { - m_TileSize = k_DefaultTileSize; - } - } - - /// protected override void OnEnable() { base.OnEnable(); diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Clustering.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Clustering.hlsl index f448ccd57bd..a711bdf257d 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Clustering.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Clustering.hlsl @@ -3,101 +3,70 @@ #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl" -#if USE_CLUSTERED_LIGHTING - -// TODO: Remove after PR #4039 is merged -// Select uint4 component by index. -// Helper to improve codegen for 2d indexing (data[x][y]) -// Replace: -// data[i / 4][i % 4]; -// with: -// select4(data[i / 4], i % 4); -uint ClusteringSelect4(uint4 v, uint i) -{ - // x = 0 = 00 - // y = 1 = 01 - // z = 2 = 10 - // w = 3 = 11 - uint mask0 = uint(int(i << 31) >> 31); - uint mask1 = uint(int(i << 30) >> 31); - return - (((v.w & mask0) | (v.z & ~mask0)) & mask1) | - (((v.y & mask0) | (v.x & ~mask0)) & ~mask1); -} +#if USE_FORWARD_PLUS struct ClusteredLightLoop { - uint baseIndex; + uint tileOffset; + uint zBinOffset; uint tileMask; - uint wordIndex; - uint bitIndex; - uint zBinMinMask; - uint zBinMaxMask; -#if LIGHTS_PER_TILE > 32 - uint wordMin; - uint wordMax; -#endif + // Stores the word index in first 16 bits, and the max word index in the last 16 bits. + uint wordIndexMax; }; ClusteredLightLoop ClusteredLightLoopInit(float2 normalizedScreenSpaceUV, float3 positionWS) { ClusteredLightLoop state = (ClusteredLightLoop)0; + uint2 tileId = uint2(normalizedScreenSpaceUV * _AdditionalLightsTileScale); - state.baseIndex = (tileId.y * _AdditionalLightsTileCountX + tileId.x) * (LIGHTS_PER_TILE / 32); + state.tileOffset = (tileId.y * _AdditionalLightsTileCountX + tileId.x) * _AdditionalLightsWordsPerTile; + float viewZ = dot(GetViewForwardDir(), positionWS - GetCameraPositionWS()); - uint zBinIndex = min(4*MAX_ZBIN_VEC4S - 1, (uint)(sqrt(viewZ) * _AdditionalLightsZBinScale) - _AdditionalLightsZBinOffset); - uint zBinData = ClusteringSelect4(asuint(_AdditionalLightsZBins[zBinIndex / 4]), zBinIndex % 4); - uint2 zBin = min(uint2(zBinData & 0xFFFF, (zBinData >> 16) & 0xFFFF), MAX_VISIBLE_LIGHTS - 1); - uint2 zBinWords = zBin / 32; - state.zBinMinMask = 0xFFFFFFFF << (zBin.x & 0x1F); - state.zBinMaxMask = 0xFFFFFFFF >> (31 - (zBin.y & 0x1F)); -#if LIGHTS_PER_TILE > 32 - state.wordMin = zBinWords.x; - state.wordMax = zBinWords.y; - state.wordIndex = zBinWords.x; -#endif -#if SHADER_TARGET < 45 - state.bitIndex = zBin.x & 0x1F; + uint zBinHeaderIndex = min(4*MAX_ZBIN_VEC4S - 1, (uint)(log2(viewZ) * URP_ADDITIONAL_LIGHTS_ZBIN_SCALE + URP_ADDITIONAL_LIGHTS_ZBIN_OFFSET)) * (1 + _AdditionalLightsWordsPerTile); + state.zBinOffset = zBinHeaderIndex + 1; + +#if MAX_LIGHTS_PER_TILE > 32 + state.wordIndexMax = Select4(asuint(_AdditionalLightsZBins[zBinHeaderIndex / 4]), zBinHeaderIndex % 4); +#else + uint tileIndex = state.tileOffset; + uint zBinIndex = state.zBinOffset; + if (_AdditionalLightsWordsPerTile > 0) + { + state.tileMask = + Select4(asuint(_AdditionalLightsTiles[tileIndex / 4]), tileIndex % 4) & + Select4(asuint(_AdditionalLightsZBins[zBinIndex / 4]), zBinIndex % 4); + } #endif + return state; } -bool ClusteredLightLoopNextWord(inout ClusteredLightLoop state) +bool ClusteredLightLoopNext(inout ClusteredLightLoop state) { -#if LIGHTS_PER_TILE > 32 - uint wordMin = state.wordMin; - uint wordMax = state.wordMax; -#else - uint wordMin = 0; - uint wordMax = 0; -#endif - if (state.wordIndex > wordMax) return false; - uint index = state.baseIndex + state.wordIndex; - state.tileMask = ClusteringSelect4(asuint(_AdditionalLightsTiles[index / 4]), index % 4); - if (state.wordIndex == wordMin) state.tileMask &= state.zBinMinMask; - if (state.wordIndex == wordMax) state.tileMask &= state.zBinMaxMask; - state.wordIndex++; -#if SHADER_TARGET < 45 - state.bitIndex = 0; +#if MAX_LIGHTS_PER_TILE > 32 + uint wordMax = state.wordIndexMax >> 16; + while (state.tileMask == 0 && (state.wordIndexMax & 0xFFFF) <= wordMax) + { + uint tileIndex = state.tileOffset + (state.wordIndexMax & 0xFFFF); + uint zBinIndex = state.zBinOffset + (state.wordIndexMax & 0xFFFF); + state.tileMask = + Select4(asuint(_AdditionalLightsTiles[tileIndex / 4]), tileIndex % 4) & + Select4(asuint(_AdditionalLightsZBins[zBinIndex / 4]), zBinIndex % 4); + state.wordIndexMax++; + } #endif - return true; + return state.tileMask != 0; } -bool ClusteredLightLoopNextLight(inout ClusteredLightLoop state) +uint ClusteredLightLoopGetLightIndex(inout ClusteredLightLoop state) { - if (state.tileMask == 0) return false; -#if SHADER_TARGET < 45 - while ((state.tileMask & (1 << state.bitIndex)) == 0) state.bitIndex++; + uint bitIndex = FIRST_BIT_LOW(state.tileMask); + state.tileMask ^= (1 << bitIndex); +#if MAX_LIGHTS_PER_TILE > 32 + return _AdditionalLightsDirectionalCount + ((state.wordIndexMax & 0xFFFF) - 1) * 32 + bitIndex; #else - state.bitIndex = firstbitlow(state.tileMask); + return _AdditionalLightsDirectionalCount + bitIndex; #endif - state.tileMask ^= (1 << state.bitIndex); - return true; -} - -uint ClusteredLightLoopGetLightIndex(ClusteredLightLoop state) -{ - return _AdditionalLightsDirectionalCount + (state.wordIndex - 1) * 32 + state.bitIndex; } #endif diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl index d4cdf47d973..f13af461c3c 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl @@ -5,12 +5,12 @@ // node work by falling to regular texture sampling. #define FORCE_VIRTUAL_TEXTURING_OFF 1 -#if defined(_CLUSTERED_RENDERING) +#if defined(_FORWARD_PLUS) #define _ADDITIONAL_LIGHTS 1 #undef _ADDITIONAL_LIGHTS_VERTEX -#define USE_CLUSTERED_LIGHTING 1 +#define USE_FORWARD_PLUS 1 #else -#define USE_CLUSTERED_LIGHTING 0 +#define USE_FORWARD_PLUS 0 #endif #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/Debugging3D.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/Debugging3D.hlsl index 4ec0e626a35..212780ae556 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/Debugging3D.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/Debugging3D.hlsl @@ -199,8 +199,18 @@ half3 CalculateDebugShadowCascadeColor(in InputData inputData) half4 CalculateDebugLightingComplexityColor(in InputData inputData, in SurfaceData surfaceData) { +#if USE_FORWARD_PLUS + int numLights = _AdditionalLightsDirectionalCount; + ClusteredLightLoop cll = ClusteredLightLoopInit(inputData.normalizedScreenSpaceUV, inputData.positionWS); + [loop] while (ClusteredLightLoopNext(cll)) + { + ClusteredLightLoopGetLightIndex(cll); + numLights++; + } +#else // Assume a main light and add 1 to the additional lights. int numLights = GetAdditionalLightsCount() + 1; +#endif const uint2 tileSize = uint2(32,32); const uint maxLights = 9; diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl index 1dd5c483e9b..bb747110199 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl @@ -21,11 +21,12 @@ // Match with values in UniversalRenderPipeline.cs #define MAX_ZBIN_VEC4S 1024 -#define MAX_TILE_VEC4S 4096 -#if MAX_VISIBLE_LIGHTS < 32 - #define LIGHTS_PER_TILE 32 +#if MAX_VISIBLE_LIGHTS <= 32 + #define MAX_LIGHTS_PER_TILE 32 + #define MAX_TILE_VEC4S 1024 #else - #define LIGHTS_PER_TILE MAX_VISIBLE_LIGHTS + #define MAX_LIGHTS_PER_TILE MAX_VISIBLE_LIGHTS + #define MAX_TILE_VEC4S 4096 #endif struct InputData @@ -100,17 +101,19 @@ half4 _AdditionalLightsCount; uint _RenderingLayerMaxInt; float _RenderingLayerRcpMaxInt; -#if USE_CLUSTERED_LIGHTING +#if USE_FORWARD_PLUS // Directional lights would be in all clusters, so they don't go into the cluster structure. // Instead, they are stored first in the light buffer. uint _AdditionalLightsDirectionalCount; -// The number of Z-bins to skip based on near plane distance. -uint _AdditionalLightsZBinOffset; -// Scale from view-space Z to Z-bin. -float _AdditionalLightsZBinScale; // Scale from screen-space UV [0, 1] to tile coordinates [0, tile resolution]. float2 _AdditionalLightsTileScale; uint _AdditionalLightsTileCountX; +uint _AdditionalLightsWordsPerTile; +float4 _AdditionalLightsParams0; + +#define URP_ADDITIONAL_LIGHTS_ZBIN_SCALE _AdditionalLightsParams0.x +#define URP_ADDITIONAL_LIGHTS_ZBIN_OFFSET _AdditionalLightsParams0.y + #endif #if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA @@ -132,13 +135,14 @@ CBUFFER_END #endif #endif -#if USE_CLUSTERED_LIGHTING - CBUFFER_START(AdditionalLightsZBins) +#if USE_FORWARD_PLUS + +CBUFFER_START(AdditionalLightsZBins) float4 _AdditionalLightsZBins[MAX_ZBIN_VEC4S]; - CBUFFER_END - CBUFFER_START(AdditionalLightsTiles) +CBUFFER_END +CBUFFER_START(AdditionalLightsTiles) float4 _AdditionalLightsTiles[MAX_TILE_VEC4S]; - CBUFFER_END +CBUFFER_END #endif #define UNITY_MATRIX_M unity_ObjectToWorld diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl index b74fc381e30..406388e860d 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl @@ -293,7 +293,7 @@ half4 UniversalFragmentPBR(InputData inputData, SurfaceData surfaceData) #if defined(_ADDITIONAL_LIGHTS) uint pixelLightCount = GetAdditionalLightsCount(); - #if USE_CLUSTERED_LIGHTING + #if USE_FORWARD_PLUS for (uint lightIndex = 0; lightIndex < min(_AdditionalLightsDirectionalCount, MAX_VISIBLE_LIGHTS); lightIndex++) { Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor); @@ -384,7 +384,7 @@ half4 UniversalFragmentBlinnPhong(InputData inputData, SurfaceData surfaceData) #if defined(_ADDITIONAL_LIGHTS) uint pixelLightCount = GetAdditionalLightsCount(); - #if USE_CLUSTERED_LIGHTING + #if USE_FORWARD_PLUS for (uint lightIndex = 0; lightIndex < min(_AdditionalLightsDirectionalCount, MAX_VISIBLE_LIGHTS); lightIndex++) { Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor); diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl index 85ea6451e90..fb1d97a6d2d 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl @@ -26,12 +26,12 @@ struct Light #define _USE_WEBGL1_LIGHTS 0 #endif -#if USE_CLUSTERED_LIGHTING +#if USE_FORWARD_PLUS #define LIGHT_LOOP_BEGIN(lightCount) \ ClusteredLightLoop cll = ClusteredLightLoopInit(inputData.normalizedScreenSpaceUV, inputData.positionWS); \ - while (ClusteredLightLoopNextWord(cll)) { while (ClusteredLightLoopNextLight(cll)) { \ + [loop] while (ClusteredLightLoopNext(cll)) { \ uint lightIndex = ClusteredLightLoopGetLightIndex(cll); - #define LIGHT_LOOP_END } } + #define LIGHT_LOOP_END } #elif !_USE_WEBGL1_LIGHTS #define LIGHT_LOOP_BEGIN(lightCount) \ for (uint lightIndex = 0u; lightIndex < lightCount; ++lightIndex) { @@ -90,7 +90,7 @@ Light GetMainLight() { Light light; light.direction = half3(_MainLightPosition.xyz); -#if USE_CLUSTERED_LIGHTING +#if USE_FORWARD_PLUS light.distanceAttenuation = 1.0; #else light.distanceAttenuation = unity_LightData.z; // unity_LightData.z is 1 when not culled by the culling mask, otherwise 0. @@ -236,7 +236,7 @@ int GetPerObjectLightIndex(uint index) // index to a perObjectLightIndex Light GetAdditionalLight(uint i, float3 positionWS) { -#if USE_CLUSTERED_LIGHTING +#if USE_FORWARD_PLUS int lightIndex = i; #else int lightIndex = GetPerObjectLightIndex(i); @@ -246,7 +246,7 @@ Light GetAdditionalLight(uint i, float3 positionWS) Light GetAdditionalLight(uint i, float3 positionWS, half4 shadowMask) { -#if USE_CLUSTERED_LIGHTING +#if USE_FORWARD_PLUS int lightIndex = i; #else int lightIndex = GetPerObjectLightIndex(i); @@ -283,7 +283,7 @@ Light GetAdditionalLight(uint i, InputData inputData, half4 shadowMask, AmbientO int GetAdditionalLightsCount() { -#if USE_CLUSTERED_LIGHTING +#if USE_FORWARD_PLUS // Counting the number of lights in clustered requires traversing the bit list, and is not needed up front. return 0; #else diff --git a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl index 9fb8743a0d4..8838b5664be 100644 --- a/Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl +++ b/Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl @@ -382,6 +382,36 @@ float2 GetNormalizedScreenSpaceUV(float4 positionCS) return GetNormalizedScreenSpaceUV(positionCS.xy); } +// Select uint4 component by index. +// Helper to improve codegen for 2d indexing (data[x][y]) +// Replace: +// data[i / 4][i % 4]; +// with: +// select4(data[i / 4], i % 4); +uint Select4(uint4 v, uint i) +{ + // x = 0 = 00 + // y = 1 = 01 + // z = 2 = 10 + // w = 3 = 11 + uint mask0 = uint(int(i << 31) >> 31); + uint mask1 = uint(int(i << 30) >> 31); + return + (((v.w & mask0) | (v.z & ~mask0)) & mask1) | + (((v.y & mask0) | (v.x & ~mask0)) & ~mask1); +} + +#if SHADER_TARGET < 45 +uint URP_FirstBitLow(uint m) +{ + // http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightFloatCast + return (asuint((float)(m & asuint(-asint(m)))) >> 23) - 0x7F; +} +#define FIRST_BIT_LOW URP_FirstBitLow +#else +#define FIRST_BIT_LOW firstbitlow +#endif + #if defined(UNITY_SINGLE_PASS_STEREO) float2 TransformStereoScreenSpaceTex(float2 uv, float w) { diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/ComplexLit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/ComplexLit.shader index 2ac376b6568..e2293a2010b 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/ComplexLit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/ComplexLit.shader @@ -127,7 +127,7 @@ Shader "Universal Render Pipeline/Complex Lit" #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3 #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fragment _ _WRITE_RENDERING_LAYERS // ------------------------------------- @@ -380,7 +380,7 @@ Shader "Universal Render Pipeline/Complex Lit" #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader index 21dfd870d75..2bbd13fe953 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader @@ -126,7 +126,7 @@ Shader "Universal Render Pipeline/Lit" #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3 #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fragment _ _WRITE_RENDERING_LAYERS // ------------------------------------- @@ -455,7 +455,7 @@ Shader "Universal Render Pipeline/Lit" #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7.shader index 6f3ddfb61d6..5b6c73c2d4c 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7.shader @@ -46,7 +46,7 @@ Shader "Universal Render Pipeline/Nature/SpeedTree7" #pragma multi_compile_vertex LOD_FADE_PERCENTAGE #pragma multi_compile_fragment _ DEBUG_DISPLAY #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fog diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7Billboard.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7Billboard.shader index 349ad68e1a1..a127da7db5a 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7Billboard.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree7Billboard.shader @@ -37,7 +37,7 @@ Shader "Universal Render Pipeline/Nature/SpeedTree7 Billboard" #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _SHADOWS_SOFT - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile __ BILLBOARD_FACE_CAMERA_POS #pragma multi_compile __ LOD_FADE_CROSSFADE #pragma multi_compile_fragment _ _LIGHT_COOKIES diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree8.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree8.shader index fa534aa0ea0..668aeada86a 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree8.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Nature/SpeedTree8.shader @@ -57,7 +57,7 @@ Shader "Universal Render Pipeline/Nature/SpeedTree8" #pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION #pragma multi_compile_fragment _ _SHADOWS_SOFT - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fragment _ LOD_FADE_CROSSFADE #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesLit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesLit.shader index 0ad6c5f40df..8188eaf2119 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesLit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesLit.shader @@ -133,7 +133,7 @@ Shader "Universal Render Pipeline/Particles/Lit" #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesSimpleLit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesSimpleLit.shader index 0e8fd97216f..c86e16ad63f 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesSimpleLit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Particles/ParticlesSimpleLit.shader @@ -123,7 +123,7 @@ Shader "Universal Render Pipeline/Particles/Simple Lit" #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/SimpleLit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/SimpleLit.shader index 4df20760e9e..c0f8c54f256 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/SimpleLit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/SimpleLit.shader @@ -91,7 +91,7 @@ Shader "Universal Render Pipeline/Simple Lit" #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3 #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fragment _ _WRITE_RENDERING_LAYERS // ------------------------------------- @@ -382,7 +382,7 @@ Shader "Universal Render Pipeline/Simple Lit" #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _WRITE_RENDERING_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainDetailLit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainDetailLit.shader index 1bbfce5ef73..6284d55db55 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainDetailLit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainDetailLit.shader @@ -27,7 +27,7 @@ Shader "Hidden/TerrainEngine/Details/UniversalPipeline/Vertexlit" #pragma multi_compile _ SHADOWS_SHADOWMASK #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLit.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLit.shader index fadb0eff5a6..6e41ed494b1 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLit.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLit.shader @@ -76,7 +76,7 @@ Shader "Universal Render Pipeline/Terrain/Lit" #pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3 #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS #pragma multi_compile_fragment _ _WRITE_RENDERING_LAYERS // ------------------------------------- diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitAdd.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitAdd.shader index 7944f1afc19..8030873a304 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitAdd.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitAdd.shader @@ -69,7 +69,7 @@ Shader "Hidden/Universal Render Pipeline/Terrain/Lit (Add Pass)" #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitBase.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitBase.shader index bd7f18a1f11..85353244810 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitBase.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/TerrainLitBase.shader @@ -48,7 +48,7 @@ Shader "Hidden/Universal Render Pipeline/Terrain/Lit (Base Pass)" #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrass.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrass.shader index 00a26314eac..c4dc72a0e22 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrass.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrass.shader @@ -30,7 +30,7 @@ Shader "Hidden/TerrainEngine/Details/UniversalPipeline/WavingDoublePass" #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION #pragma multi_compile_fragment _ _LIGHT_LAYERS #pragma multi_compile_fragment _ _LIGHT_COOKIES - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords diff --git a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrassBillboard.shader b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrassBillboard.shader index c29646ab149..734faccb678 100644 --- a/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrassBillboard.shader +++ b/Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrassBillboard.shader @@ -27,7 +27,7 @@ Shader "Hidden/TerrainEngine/Details/UniversalPipeline/BillboardWavingDoublePass #pragma multi_compile_fragment _ _SHADOWS_SOFT #pragma multi_compile _ LIGHTMAP_SHADOW_MIXING #pragma multi_compile _ SHADOWS_SHADOWMASK - #pragma multi_compile _ _CLUSTERED_RENDERING + #pragma multi_compile _ _FORWARD_PLUS // ------------------------------------- // Unity defined keywords