using System; using System.Collections.Generic; using System.Linq; using System.Text; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule; namespace UnityEngine.Rendering.Universal.Internal { /// /// Computes and submits lighting data to the GPU. /// public class ForwardLights { static class LightConstantBuffer { public static int _MainLightPosition; // DeferredLights.LightConstantBuffer also refers to the same ShaderPropertyID - TODO: move this definition to a common location shared by other UniversalRP classes public static int _MainLightColor; // DeferredLights.LightConstantBuffer also refers to the same ShaderPropertyID - TODO: move this definition to a common location shared by other UniversalRP classes public static int _MainLightOcclusionProbesChannel; // Deferred? public static int _MainLightLayerMask; public static int _AdditionalLightsCount; public static int _AdditionalLightsPosition; public static int _AdditionalLightsColor; public static int _AdditionalLightsAttenuation; public static int _AdditionalLightsSpotDir; public static int _AdditionalLightOcclusionProbeChannel; public static int _AdditionalLightsLayerMasks; } int m_AdditionalLightsBufferId; int m_AdditionalLightsIndicesId; 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; Vector4[] m_AdditionalLightColors; Vector4[] m_AdditionalLightAttenuations; Vector4[] m_AdditionalLightSpotDirections; Vector4[] m_AdditionalLightOcclusionProbeChannels; float[] m_AdditionalLightsLayerMasks; // Unity has no support for binding uint arrays. We will use asuint() in the shader instead. bool m_UseStructuredBuffer; bool m_UseForwardPlus; int m_DirectionalLightCount; int m_ActualTileWidth; int2 m_TileResolution; JobHandle m_CullingHandle; NativeArray m_ZBins; GraphicsBuffer m_ZBinsBuffer; NativeArray m_TileMasks; GraphicsBuffer m_TileMasksBuffer; LightCookieManager m_LightCookieManager; ReflectionProbeManager m_ReflectionProbeManager; int m_WordsPerTile; float m_ZBinScale; float m_ZBinOffset; int m_LightCount; int m_BinCount; internal struct InitParams { public LightCookieManager lightCookieManager; public bool forwardPlus; static internal InitParams Create() { InitParams p; { var settings = LightCookieManager.Settings.Create(); var asset = UniversalRenderPipeline.asset; if (asset) { settings.atlas.format = asset.additionalLightsCookieFormat; settings.atlas.resolution = asset.additionalLightsCookieResolution; } p.lightCookieManager = new LightCookieManager(ref settings); p.forwardPlus = false; } return p; } } /// /// Creates a new ForwardLights instance. /// public ForwardLights() : this(InitParams.Create()) { } internal ForwardLights(InitParams initParams) { m_UseStructuredBuffer = RenderingUtils.useStructuredBuffer; m_UseForwardPlus = initParams.forwardPlus; LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition"); LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor"); LightConstantBuffer._MainLightOcclusionProbesChannel = Shader.PropertyToID("_MainLightOcclusionProbes"); LightConstantBuffer._MainLightLayerMask = Shader.PropertyToID("_MainLightLayerMask"); LightConstantBuffer._AdditionalLightsCount = Shader.PropertyToID("_AdditionalLightsCount"); if (m_UseStructuredBuffer) { m_AdditionalLightsBufferId = Shader.PropertyToID("_AdditionalLightsBuffer"); m_AdditionalLightsIndicesId = Shader.PropertyToID("_AdditionalLightsIndices"); } else { LightConstantBuffer._AdditionalLightsPosition = Shader.PropertyToID("_AdditionalLightsPosition"); LightConstantBuffer._AdditionalLightsColor = Shader.PropertyToID("_AdditionalLightsColor"); LightConstantBuffer._AdditionalLightsAttenuation = Shader.PropertyToID("_AdditionalLightsAttenuation"); LightConstantBuffer._AdditionalLightsSpotDir = Shader.PropertyToID("_AdditionalLightsSpotDir"); LightConstantBuffer._AdditionalLightOcclusionProbeChannel = Shader.PropertyToID("_AdditionalLightsOcclusionProbes"); LightConstantBuffer._AdditionalLightsLayerMasks = Shader.PropertyToID("_AdditionalLightsLayerMasks"); int maxLights = UniversalRenderPipeline.maxVisibleAdditionalLights; m_AdditionalLightPositions = new Vector4[maxLights]; m_AdditionalLightColors = new Vector4[maxLights]; m_AdditionalLightAttenuations = new Vector4[maxLights]; m_AdditionalLightSpotDirections = new Vector4[maxLights]; m_AdditionalLightOcclusionProbeChannels = new Vector4[maxLights]; m_AdditionalLightsLayerMasks = new float[maxLights]; } if (m_UseForwardPlus) { CreateForwardPlusBuffers(); m_ReflectionProbeManager = ReflectionProbeManager.Create(); } 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 = "URP Z-Bin Buffer"; m_TileMasks = new NativeArray(UniversalRenderPipeline.maxTileWords, Allocator.Persistent); m_TileMasksBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Constant, UniversalRenderPipeline.maxTileWords / 4, UnsafeUtility.SizeOf()); m_TileMasksBuffer.name = "URP Tile Buffer"; } internal ReflectionProbeManager reflectionProbeManager => m_ReflectionProbeManager; static int AlignByteCount(int count, int align) => align * ((count + align - 1) / align); // Calculate view planes and viewToViewportScaleBias. This handles projection center in case the projection is off-centered void GetViewParams(Camera camera, float4x4 viewToClip, out float viewPlaneBot, out float viewPlaneTop, out float4 viewToViewportScaleBias) { // We want to calculate `fovHalfHeight = tan(fov / 2)` // `projection[1][1]` contains `1 / tan(fov / 2)` var viewPlaneHalfSizeInv = math.float2(viewToClip[0][0], viewToClip[1][1]); var viewPlaneHalfSize = math.rcp(viewPlaneHalfSizeInv); var centerClipSpace = camera.orthographic ? -math.float2(viewToClip[3][0], viewToClip[3][1]): math.float2(viewToClip[2][0], viewToClip[2][1]); viewPlaneBot = centerClipSpace.y * viewPlaneHalfSize.y - viewPlaneHalfSize.y; viewPlaneTop = centerClipSpace.y * viewPlaneHalfSize.y + viewPlaneHalfSize.y; viewToViewportScaleBias = math.float4( viewPlaneHalfSizeInv * 0.5f, -centerClipSpace * 0.5f + 0.5f ); } internal void PreSetup(UniversalRenderingData renderingData, UniversalCameraData cameraData, UniversalLightData lightData) { if (m_UseForwardPlus) { using var _ = new ProfilingScope(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 = cameraData.camera; var screenResolution = math.int2(cameraData.pixelWidth, cameraData.pixelHeight); #if ENABLE_VR && ENABLE_XR_MODULE var viewCount = cameraData.xr.enabled && cameraData.xr.singlePassEnabled ? 2 : 1; #else var viewCount = 1; #endif m_LightCount = lightData.visibleLights.Length; var lightOffset = 0; while (lightOffset < m_LightCount && lightData.visibleLights[lightOffset].lightType == LightType.Directional) { lightOffset++; } m_LightCount -= lightOffset; m_DirectionalLightCount = lightOffset; if (lightData.mainLightIndex != -1 && m_DirectionalLightCount != 0) m_DirectionalLightCount -= 1; var visibleLights = lightData.visibleLights.GetSubArray(lightOffset, m_LightCount); var reflectionProbes = renderingData.cullResults.visibleReflectionProbes; var reflectionProbeCount = math.min(reflectionProbes.Length, UniversalRenderPipeline.maxVisibleReflectionProbes); var itemsPerTile = visibleLights.Length + reflectionProbeCount; m_WordsPerTile = (itemsPerTile + 31) / 32; m_ActualTileWidth = 8 >> 1; do { m_ActualTileWidth <<= 1; m_TileResolution = (screenResolution + m_ActualTileWidth - 1) / m_ActualTileWidth; } while ((m_TileResolution.x * m_TileResolution.y * m_WordsPerTile * viewCount) > UniversalRenderPipeline.maxTileWords); if (!camera.orthographic) { // Use to calculate binIndex = log2(z) * zBinScale + zBinOffset m_ZBinScale = (UniversalRenderPipeline.maxZBinWords / viewCount) / ((math.log2(camera.farClipPlane) - math.log2(camera.nearClipPlane)) * (2 + m_WordsPerTile)); m_ZBinOffset = -math.log2(camera.nearClipPlane) * m_ZBinScale; m_BinCount = (int)(math.log2(camera.farClipPlane) * m_ZBinScale + m_ZBinOffset); } else { // Use to calculate binIndex = z * zBinScale + zBinOffset m_ZBinScale = (UniversalRenderPipeline.maxZBinWords / viewCount) / ((camera.farClipPlane - camera.nearClipPlane) * (2 + m_WordsPerTile)); m_ZBinOffset = -camera.nearClipPlane * m_ZBinScale; m_BinCount = (int)(camera.farClipPlane * m_ZBinScale + m_ZBinOffset); } var worldToViews = new Fixed2(cameraData.GetViewMatrix(0), cameraData.GetViewMatrix(math.min(1, viewCount - 1))); var viewToClips = new Fixed2(cameraData.GetProjectionMatrix(0), cameraData.GetProjectionMatrix(math.min(1, viewCount - 1))); // Should probe come after otherProbe? static bool IsProbeGreater(VisibleReflectionProbe probe, VisibleReflectionProbe otherProbe) { return probe.importance < otherProbe.importance || (probe.importance == otherProbe.importance && probe.bounds.extents.sqrMagnitude > otherProbe.bounds.extents.sqrMagnitude); } for (var i = 1; i < reflectionProbeCount; i++) { var probe = reflectionProbes[i]; var j = i - 1; while (j >= 0 && IsProbeGreater(reflectionProbes[j], probe)) { reflectionProbes[j + 1] = reflectionProbes[j]; j--; } reflectionProbes[j + 1] = probe; } var minMaxZs = new NativeArray(itemsPerTile * viewCount, Allocator.TempJob); var lightMinMaxZJob = new LightMinMaxZJob { worldToViews = worldToViews, lights = visibleLights, minMaxZs = minMaxZs.GetSubArray(0, m_LightCount * viewCount) }; // Innerloop batch count of 32 is not special, just a handwavy amount to not have too much scheduling overhead nor too little parallelism. var lightMinMaxZHandle = lightMinMaxZJob.ScheduleParallel(m_LightCount * viewCount, 32, new JobHandle()); var reflectionProbeMinMaxZJob = new ReflectionProbeMinMaxZJob { worldToViews = worldToViews, reflectionProbes = reflectionProbes, minMaxZs = minMaxZs.GetSubArray(m_LightCount * viewCount, reflectionProbeCount * viewCount) }; var reflectionProbeMinMaxZHandle = reflectionProbeMinMaxZJob.ScheduleParallel(reflectionProbeCount * viewCount, 32, lightMinMaxZHandle); var zBinningBatchCount = (m_BinCount + ZBinningJob.batchSize - 1) / ZBinningJob.batchSize; var zBinningJob = new ZBinningJob { bins = m_ZBins, minMaxZs = minMaxZs, zBinScale = m_ZBinScale, zBinOffset = m_ZBinOffset, binCount = m_BinCount, wordsPerTile = m_WordsPerTile, lightCount = m_LightCount, reflectionProbeCount = reflectionProbeCount, batchCount = zBinningBatchCount, viewCount = viewCount, isOrthographic = camera.orthographic }; var zBinningHandle = zBinningJob.ScheduleParallel(zBinningBatchCount * viewCount, 1, reflectionProbeMinMaxZHandle); reflectionProbeMinMaxZHandle.Complete(); GetViewParams(camera, viewToClips[0], out float viewPlaneBottom0, out float viewPlaneTop0, out float4 viewToViewportScaleBias0); GetViewParams(camera, viewToClips[1], out float viewPlaneBottom1, out float viewPlaneTop1, out float4 viewToViewportScaleBias1); // Each light needs 1 range for Y, and a range per row. Align to 128-bytes to avoid false sharing. var rangesPerItem = AlignByteCount((1 + m_TileResolution.y) * UnsafeUtility.SizeOf(), 128) / UnsafeUtility.SizeOf(); var tileRanges = new NativeArray(rangesPerItem * itemsPerTile * viewCount, Allocator.TempJob); var tilingJob = new TilingJob { lights = visibleLights, reflectionProbes = reflectionProbes, tileRanges = tileRanges, itemsPerTile = itemsPerTile, rangesPerItem = rangesPerItem, worldToViews = worldToViews, tileScale = (float2)screenResolution / m_ActualTileWidth, tileScaleInv = m_ActualTileWidth / (float2)screenResolution, viewPlaneBottoms = new Fixed2(viewPlaneBottom0, viewPlaneBottom1), viewPlaneTops = new Fixed2(viewPlaneTop0, viewPlaneTop1), viewToViewportScaleBiases = new Fixed2(viewToViewportScaleBias0, viewToViewportScaleBias1), tileCount = m_TileResolution, near = camera.nearClipPlane, isOrthographic = camera.orthographic }; var tileRangeHandle = tilingJob.ScheduleParallel(itemsPerTile * viewCount, 1, reflectionProbeMinMaxZHandle); var expansionJob = new TileRangeExpansionJob { tileRanges = tileRanges, tileMasks = m_TileMasks, rangesPerItem = rangesPerItem, itemsPerTile = itemsPerTile, wordsPerTile = m_WordsPerTile, tileResolution = m_TileResolution, }; var tilingHandle = expansionJob.ScheduleParallel(m_TileResolution.y * viewCount, 1, tileRangeHandle); m_CullingHandle = JobHandle.CombineDependencies( minMaxZs.Dispose(zBinningHandle), tileRanges.Dispose(tilingHandle)); JobHandle.ScheduleBatchedJobs(); } } /// /// Sets up the keywords and data for forward lighting. /// /// /// public void Setup(ScriptableRenderContext context, ref RenderingData renderingData) { ContextContainer frameData = renderingData.frameData; UniversalRenderingData universalRenderingData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); UniversalLightData lightData = frameData.Get(); SetupLights(CommandBufferHelpers.GetUnsafeCommandBuffer(renderingData.commandBuffer), universalRenderingData, cameraData, lightData); } static ProfilingSampler s_SetupForwardLights = new ProfilingSampler("Setup Forward Lights"); private class SetupLightPassData { internal UniversalRenderingData renderingData; internal UniversalCameraData cameraData; internal UniversalLightData lightData; internal ForwardLights forwardLights; }; /// /// Sets up the ForwardLight data for RenderGraph execution /// internal void SetupRenderGraphLights(RenderGraph renderGraph, UniversalRenderingData renderingData, UniversalCameraData cameraData, UniversalLightData lightData) { using (var builder = renderGraph.AddUnsafePass(s_SetupForwardLights.name, out var passData, s_SetupForwardLights)) { passData.renderingData = renderingData; passData.cameraData = cameraData; passData.lightData = lightData; passData.forwardLights = this; builder.AllowPassCulling(false); builder.SetRenderFunc((SetupLightPassData data, UnsafeGraphContext rgContext) => { data.forwardLights.SetupLights(rgContext.cmd, data.renderingData, data.cameraData, data.lightData); }); } } internal void SetupLights(UnsafeCommandBuffer cmd, UniversalRenderingData renderingData, UniversalCameraData cameraData, UniversalLightData lightData) { int additionalLightsCount = lightData.additionalLightsCount; bool additionalLightsPerVertex = lightData.shadeAdditionalLightsPerVertex; using (new ProfilingScope(m_ProfilingSampler)) { if (m_UseForwardPlus) { m_ReflectionProbeManager.UpdateGpuData(CommandBufferHelpers.GetNativeCommandBuffer(cmd), ref renderingData.cullResults); using (new ProfilingScope(m_ProfilingSamplerFPComplete)) { m_CullingHandle.Complete(); } using (new ProfilingScope(m_ProfilingSamplerFPUpload)) { m_ZBinsBuffer.SetData(m_ZBins.Reinterpret(UnsafeUtility.SizeOf())); m_TileMasksBuffer.SetData(m_TileMasks.Reinterpret(UnsafeUtility.SizeOf())); cmd.SetGlobalConstantBuffer(m_ZBinsBuffer, "urp_ZBinBuffer", 0, UniversalRenderPipeline.maxZBinWords * 4); cmd.SetGlobalConstantBuffer(m_TileMasksBuffer, "urp_TileBuffer", 0, UniversalRenderPipeline.maxTileWords * 4); } cmd.SetGlobalVector("_FPParams0", math.float4(m_ZBinScale, m_ZBinOffset, m_LightCount, m_DirectionalLightCount)); cmd.SetGlobalVector("_FPParams1", math.float4(cameraData.pixelRect.size / m_ActualTileWidth, m_TileResolution.x, m_WordsPerTile)); cmd.SetGlobalVector("_FPParams2", math.float4(m_BinCount, m_TileResolution.x * m_TileResolution.y, 0, 0)); } SetupShaderLightConstants(cmd, ref renderingData.cullResults, lightData); bool lightCountCheck = (cameraData.renderer.stripAdditionalLightOffVariants && lightData.supportsAdditionalLights) || additionalLightsCount > 0; cmd.SetKeyword(ShaderGlobalKeywords.AdditionalLightsVertex, lightCountCheck && additionalLightsPerVertex && !m_UseForwardPlus); cmd.SetKeyword(ShaderGlobalKeywords.AdditionalLightsPixel, lightCountCheck && !additionalLightsPerVertex && !m_UseForwardPlus); cmd.SetKeyword(ShaderGlobalKeywords.ForwardPlus, m_UseForwardPlus); bool isShadowMask = lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.ShadowMask; bool isShadowMaskAlways = isShadowMask && QualitySettings.shadowmaskMode == ShadowmaskMode.Shadowmask; bool isSubtractive = lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.Subtractive; cmd.SetKeyword(ShaderGlobalKeywords.LightmapShadowMixing, isSubtractive || isShadowMaskAlways); cmd.SetKeyword(ShaderGlobalKeywords.ShadowsShadowMask, isShadowMask); cmd.SetKeyword(ShaderGlobalKeywords.MixedLightingSubtractive, isSubtractive); // Backward compatibility cmd.SetKeyword(ShaderGlobalKeywords.ReflectionProbeBlending, lightData.reflectionProbeBlending); cmd.SetKeyword(ShaderGlobalKeywords.ReflectionProbeBoxProjection, lightData.reflectionProbeBoxProjection); var asset = UniversalRenderPipeline.asset; bool apvIsEnabled = asset != null && asset.lightProbeSystem == LightProbeSystem.ProbeVolumes; ProbeVolumeSHBands probeVolumeSHBands = asset.probeVolumeSHBands; cmd.SetKeyword(ShaderGlobalKeywords.ProbeVolumeL1, apvIsEnabled && probeVolumeSHBands == ProbeVolumeSHBands.SphericalHarmonicsL1); cmd.SetKeyword(ShaderGlobalKeywords.ProbeVolumeL2, apvIsEnabled && probeVolumeSHBands == ProbeVolumeSHBands.SphericalHarmonicsL2); // TODO: If we can robustly detect LIGHTMAP_ON, we can skip SH logic. var shMode = PlatformAutoDetect.ShAutoDetect(asset.shEvalMode); cmd.SetKeyword(ShaderGlobalKeywords.EVALUATE_SH_MIXED, shMode == ShEvalMode.Mixed); cmd.SetKeyword(ShaderGlobalKeywords.EVALUATE_SH_VERTEX, shMode == ShEvalMode.PerVertex); var stack = VolumeManager.instance.stack; bool enableProbeVolumes = ProbeReferenceVolume.instance.UpdateShaderVariablesProbeVolumes( CommandBufferHelpers.GetNativeCommandBuffer(cmd), stack.GetComponent(), cameraData.IsTemporalAAEnabled() ? Time.frameCount : 0, lightData.supportsLightLayers); cmd.SetGlobalInt("_EnableProbeVolumes", enableProbeVolumes ? 1 : 0); cmd.SetKeyword(ShaderGlobalKeywords.LightLayers, lightData.supportsLightLayers && !CoreUtils.IsSceneLightingDisabled(cameraData.camera)); if (m_LightCookieManager != null) { m_LightCookieManager.Setup(CommandBufferHelpers.GetNativeCommandBuffer(cmd), lightData); } else { cmd.SetKeyword(ShaderGlobalKeywords.LightCookies, false); } } } internal void Cleanup() { if (m_UseForwardPlus) { m_CullingHandle.Complete(); m_ZBins.Dispose(); m_TileMasks.Dispose(); m_ZBinsBuffer.Dispose(); m_ZBinsBuffer = null; m_TileMasksBuffer.Dispose(); m_TileMasksBuffer = null; m_ReflectionProbeManager.Dispose(); } } void InitializeLightConstants(NativeArray lights, int lightIndex, bool supportsLightLayers, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightAttenuation, out Vector4 lightSpotDir, out Vector4 lightOcclusionProbeChannel, out uint lightLayerMask, out bool isSubtractive) { UniversalRenderPipeline.InitializeLightConstants_Common(lights, lightIndex, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir, out lightOcclusionProbeChannel); lightLayerMask = 0; isSubtractive = false; // When no lights are visible, main light will be set to -1. // In this case we initialize it to default values and return if (lightIndex < 0) return; ref VisibleLight lightData = ref lights.UnsafeElementAtMutable(lightIndex); Light light = lightData.light; var lightBakingOutput = light.bakingOutput; isSubtractive = lightBakingOutput.isBaked && lightBakingOutput.lightmapBakeType == LightmapBakeType.Mixed && lightBakingOutput.mixedLightingMode == MixedLightingMode.Subtractive; if (light == null) return; if (lightBakingOutput.lightmapBakeType == LightmapBakeType.Mixed && lightData.light.shadows != LightShadows.None && m_MixedLightingSetup == MixedLightingSetup.None) { switch (lightBakingOutput.mixedLightingMode) { case MixedLightingMode.Subtractive: m_MixedLightingSetup = MixedLightingSetup.Subtractive; break; case MixedLightingMode.Shadowmask: m_MixedLightingSetup = MixedLightingSetup.ShadowMask; break; } } if (supportsLightLayers) { var additionalLightData = light.GetUniversalAdditionalLightData(); lightLayerMask = RenderingLayerUtils.ToValidRenderingLayers(additionalLightData.renderingLayers); } } void SetupShaderLightConstants(UnsafeCommandBuffer cmd, ref CullingResults cullResults, UniversalLightData lightData) { m_MixedLightingSetup = MixedLightingSetup.None; // Main light has an optimized shader path for main light. This will benefit games that only care about a single light. // Universal pipeline also supports only a single shadow light, if available it will be the main light. SetupMainLightConstants(cmd, lightData); SetupAdditionalLightConstants(cmd, ref cullResults, lightData); } void SetupMainLightConstants(UnsafeCommandBuffer cmd, UniversalLightData lightData) { Vector4 lightPos, lightColor, lightAttenuation, lightSpotDir, lightOcclusionChannel; bool supportsLightLayers = lightData.supportsLightLayers; uint lightLayerMask; bool isSubtractive; InitializeLightConstants(lightData.visibleLights, lightData.mainLightIndex, supportsLightLayers, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir, out lightOcclusionChannel, out lightLayerMask, out isSubtractive); lightColor.w = isSubtractive ? 0f : 1f; cmd.SetGlobalVector(LightConstantBuffer._MainLightPosition, lightPos); cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lightColor); cmd.SetGlobalVector(LightConstantBuffer._MainLightOcclusionProbesChannel, lightOcclusionChannel); if (supportsLightLayers) cmd.SetGlobalInt(LightConstantBuffer._MainLightLayerMask, (int)lightLayerMask); } void SetupAdditionalLightConstants(UnsafeCommandBuffer cmd, ref CullingResults cullResults, UniversalLightData lightData) { bool supportsLightLayers = lightData.supportsLightLayers; var lights = lightData.visibleLights; int maxAdditionalLightsCount = UniversalRenderPipeline.maxVisibleAdditionalLights; int additionalLightsCount = SetupPerObjectLightIndices(cullResults, lightData); if (additionalLightsCount > 0) { if (m_UseStructuredBuffer) { NativeArray additionalLightsData = new NativeArray(additionalLightsCount, Allocator.Temp); for (int i = 0, lightIter = 0; i < lights.Length && lightIter < maxAdditionalLightsCount; ++i) { if (lightData.mainLightIndex != i) { ShaderInput.LightData data; InitializeLightConstants(lights, i, supportsLightLayers, out data.position, out data.color, out data.attenuation, out data.spotDirection, out data.occlusionProbeChannels, out data.layerMask, out _); additionalLightsData[lightIter] = data; lightIter++; } } var lightDataBuffer = ShaderData.instance.GetLightDataBuffer(additionalLightsCount); lightDataBuffer.SetData(additionalLightsData); int lightIndices = cullResults.lightAndReflectionProbeIndexCount; var lightIndicesBuffer = ShaderData.instance.GetLightIndicesBuffer(lightIndices); cmd.SetGlobalBuffer(m_AdditionalLightsBufferId, lightDataBuffer); cmd.SetGlobalBuffer(m_AdditionalLightsIndicesId, lightIndicesBuffer); additionalLightsData.Dispose(); } else { for (int i = 0, lightIter = 0; i < lights.Length && lightIter < maxAdditionalLightsCount; ++i) { if (lightData.mainLightIndex != i) { InitializeLightConstants( lights, i, supportsLightLayers, out m_AdditionalLightPositions[lightIter], out m_AdditionalLightColors[lightIter], out m_AdditionalLightAttenuations[lightIter], out m_AdditionalLightSpotDirections[lightIter], out m_AdditionalLightOcclusionProbeChannels[lightIter], out uint lightLayerMask, out var isSubtractive); if (supportsLightLayers) m_AdditionalLightsLayerMasks[lightIter] = math.asfloat(lightLayerMask); m_AdditionalLightColors[lightIter].w = isSubtractive ? 1f : 0f; lightIter++; } } cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsPosition, m_AdditionalLightPositions); cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsColor, m_AdditionalLightColors); cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsAttenuation, m_AdditionalLightAttenuations); cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsSpotDir, m_AdditionalLightSpotDirections); cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightOcclusionProbeChannel, m_AdditionalLightOcclusionProbeChannels); if (supportsLightLayers) cmd.SetGlobalFloatArray(LightConstantBuffer._AdditionalLightsLayerMasks, m_AdditionalLightsLayerMasks); } cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, new Vector4(lightData.maxPerObjectAdditionalLightsCount, 0.0f, 0.0f, 0.0f)); } else { cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, Vector4.zero); } } int SetupPerObjectLightIndices(CullingResults cullResults, UniversalLightData lightData) { if (lightData.additionalLightsCount == 0 || m_UseForwardPlus) return lightData.additionalLightsCount; var perObjectLightIndexMap = cullResults.GetLightIndexMap(Allocator.Temp); int globalDirectionalLightsCount = 0; int additionalLightsCount = 0; // Disable all directional lights from the perobject light indices // Pipeline handles main light globally and there's no support for additional directional lights atm. int maxVisibleAdditionalLightsCount = UniversalRenderPipeline.maxVisibleAdditionalLights; int len = lightData.visibleLights.Length; for (int i = 0; i < len; ++i) { if (additionalLightsCount >= maxVisibleAdditionalLightsCount) break; if (i == lightData.mainLightIndex) { perObjectLightIndexMap[i] = -1; ++globalDirectionalLightsCount; } else { if (lightData.visibleLights[i].lightType == LightType.Directional || lightData.visibleLights[i].lightType == LightType.Spot || lightData.visibleLights[i].lightType == LightType.Point) { // Light type is supported perObjectLightIndexMap[i] -= globalDirectionalLightsCount; } else { // Light type is not supported. Skip the light. perObjectLightIndexMap[i] = -1; } ++additionalLightsCount; } } // Disable all remaining lights we cannot fit into the global light buffer. for (int i = globalDirectionalLightsCount + additionalLightsCount; i < perObjectLightIndexMap.Length; ++i) perObjectLightIndexMap[i] = -1; cullResults.SetLightIndexMap(perObjectLightIndexMap); if (m_UseStructuredBuffer && additionalLightsCount > 0) { int lightAndReflectionProbeIndices = cullResults.lightAndReflectionProbeIndexCount; Assertions.Assert.IsTrue(lightAndReflectionProbeIndices > 0, "Pipelines configures additional lights but per-object light and probe indices count is zero."); cullResults.FillLightAndReflectionProbeIndices(ShaderData.instance.GetLightIndicesBuffer(lightAndReflectionProbeIndices)); } perObjectLightIndexMap.Dispose(); return additionalLightsCount; } } }