using System; using System.Collections.Generic; using Unity.Mathematics; using System.Runtime.InteropServices; using UnityEngine.Rendering; using UnityEngine.Rendering.UnifiedRayTracing; namespace UnityEngine.PathTracing.Core { using InstanceHandle = Handle; using InstanceHandleSet = HandleSet; using LightHandle = Handle; using LightHandleSet = HandleSet; using MaterialHandle = Handle; using MaterialHandleSet = HandleSet; internal enum RenderedGameObjectsFilter { All = 0, OnlyStatic = 1, AllInCameraRaysThenOnlyStatic = 2 } internal enum LightPickingMethod { Uniform = 0, Regir, LightGrid } internal enum InstanceFlags { DIRECT_RAY_VIS_MASK = 1, INDIRECT_RAY_VIS_MASK = 2, SHADOW_RAY_VIS_MASK = 4, CURRENT_LOD_FOR_LIGHTMAP_INSTANCE = 8, LOD_ZERO_FOR_LIGHTMAP_INSTANCE = 16, CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW = 32, LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW = 64 } internal class World : IDisposable { internal const int EMISSIVE_MESH = 8; // Must match the EMISSIVE_MESH define in Common.hlsl internal const int ENVIRONMENT_LIGHT = 9; // Must match the ENVIRONMENT_LIGHT define in Common.hlsl // This trivial type only exists so that handles can be type-safe. // If we make an InstanceDescriptor type, we can use that instead. internal readonly struct InstanceKey { } internal struct LightDescriptor { public LightType Type; public Vector3 LinearLightColor; public LightShadows Shadows; public Matrix4x4 Transform; public float ColorTemperature; public LightmapBakeType LightmapBakeType; public Experimental.GlobalIllumination.FalloffType FalloffType; public Vector2 AreaSize; public float SpotAngle; public float InnerSpotAngle; public uint CullingMask; public float BounceIntensity; public float Range; public int ShadowMaskChannel; public bool UseColorTemperature; public float ShadowRadius; public Texture CookieTexture; public float CookieSize; } [StructLayout(LayoutKind.Sequential)] internal struct PTLight // Must match the PTLight definition in Common.hlsl { public Vector3 position; public int type; public Vector3 intensity; public int castShadows; public Vector3 forward; public int contributesToDirectLighting; public Vector4 attenuation; public Vector3 up; public float width; public Vector3 right; public float height; public uint layerMask; public float indirectScale; public float spotAngle; public float innerSpotAngle; public float range; public int shadowMaskChannel; public int falloffIndex; public float shadowRadius; public int cookieIndex; public override readonly int GetHashCode() { return HashCode.Combine(position, type, intensity, castShadows, forward, contributesToDirectLighting, range) ^ HashCode.Combine(attenuation, up, width, right, height, layerMask, indirectScale, falloffIndex); } } [StructLayout(LayoutKind.Sequential)] internal struct ThinReservoir { public int LightIndex; public float Weight; } private readonly InstanceHandleSet _instanceHandleSet = new(); private readonly MaterialHandleSet _materialHandleSet = new(); #region Lighting State internal class LightState { public LightPickingMethod lightPickingMethod; // Holds a list of light sources in the scene public readonly List LightList = new(64); public ComputeBuffer LightListBuffer; // Holds a list of light falloff LUTs public const uint LightFalloffLUTLength = 1024; public List LightFalloffDescs = new(); public float[] LightFalloff = null; // The light falloff LUT tables public float[] LightFalloffLUTRanges = null; // The range that each LUT applies to public ComputeBuffer LightFalloffBuffer; public ComputeBuffer LightFalloffLUTRangeBuffer; // Light dictionary (Handle, Light entry) public Dictionary LightHandleToLightListEntry = new(); // Lookup table from LightHandle to the corresponding index in LightList. Only used for baking. public Dictionary LightHandleToLightListIndex = new(); // Mesh light dictionary (SubMeshHash, emissive mesh entry) public Dictionary MeshLights = new(); // Light handle set public LightHandleSet LightHandleSet = new(); public bool HasEnvironmentLight = false; public int EnvLightCount => HasEnvironmentLight ? 1 : 0; public int MeshLightCount => MeshLights.Count; public int LightCount => LightHandleToLightListEntry.Count + MeshLightCount + EnvLightCount; private static PTLight CreateEnvironmentLight() { PTLight envLight; envLight.type = ENVIRONMENT_LIGHT; envLight.position = Vector3.zero; envLight.intensity = Vector3.zero; envLight.castShadows = 1; envLight.contributesToDirectLighting = 1; envLight.forward = Vector3.zero; envLight.attenuation = Vector4.one; envLight.up = Vector3.zero; envLight.right = Vector3.zero; envLight.width = 0; envLight.height = 0; envLight.spotAngle = 0; envLight.innerSpotAngle = 0; envLight.layerMask = uint.MaxValue; envLight.indirectScale = 1.0f; envLight.attenuation = Vector4.zero; envLight.range = float.MaxValue; envLight.shadowMaskChannel = -1; envLight.falloffIndex = -1; envLight.shadowRadius = 0.0f; envLight.cookieIndex = -1; return envLight; } public void Build(Bounds sceneBounds, CommandBuffer cmdBuf, bool addEnvironmentLight) { // Make sure the light list is empty before we build it. LightList.Clear(); LightHandleToLightListIndex.Clear(); // Combine the mesh lights and the rest of the lights. foreach (var light in LightHandleToLightListEntry) { LightHandleToLightListIndex.Add(light.Key, LightList.Count); LightList.Add(light.Value); } foreach (var meshLight in MeshLights) LightList.Add(meshLight.Value); // If we explicitly sample emitters, include the environment light in the light list if (addEnvironmentLight) { LightList.Add(CreateEnvironmentLight()); HasEnvironmentLight = true; } BuildLightFalloffLUTs(this); SetLightDataOnCommandBuffer(this, cmdBuf); } private static void BuildLightFalloffLUTs(LightState lightState) { // Build the LUT data lightState.LightFalloff = LightFalloffLUT.BuildLightFalloffLUTs(lightState.LightFalloffDescs.ToArray(), LightFalloffLUTLength); Debug.Assert(lightState.LightFalloff.Length == LightFalloffLUTLength * lightState.LightFalloffDescs.Count); // Store the range of each LUT lightState.LightFalloffLUTRanges = new float[lightState.LightFalloffDescs.Count]; int i = 0; foreach (var desc in lightState.LightFalloffDescs) { Debug.Assert(desc.LUTRange >= 0.0f); lightState.LightFalloffLUTRanges[i] = desc.LUTRange; i++; } // We have built the LUTs, now clear the descriptors so we don't rebuild them every frame lightState.LightFalloffDescs.Clear(); } private static void SetLightDataOnCommandBuffer(LightState lightState, CommandBuffer cmdBuf) { if (lightState.LightListBuffer == null || lightState.LightListBuffer.count < lightState.LightList.Count) { lightState.LightListBuffer?.Release(); lightState.LightListBuffer = new ComputeBuffer(math.max(64, lightState.LightList.Count), Marshal.SizeOf()); } if (lightState.LightList.Count > 0) { cmdBuf.SetBufferData(lightState.LightListBuffer, lightState.LightList, 0, 0, lightState.LightList.Count); } if (lightState.LightFalloffBuffer == null || lightState.LightFalloffBuffer.count < lightState.LightFalloff.Length) { lightState.LightFalloffBuffer?.Release(); int count = math.max(1, lightState.LightFalloff.Length); int stride = sizeof(float); lightState.LightFalloffBuffer = new ComputeBuffer(count, stride); } if (lightState.LightFalloff.Length > 0) { cmdBuf.SetBufferData(lightState.LightFalloffBuffer, lightState.LightFalloff, 0, 0, lightState.LightFalloff.Length); } if (lightState.LightFalloffLUTRangeBuffer == null || lightState.LightFalloffLUTRangeBuffer.count < lightState.LightFalloffLUTRanges.Length) { lightState.LightFalloffLUTRangeBuffer?.Release(); int count = math.max(1, lightState.LightFalloffLUTRanges.Length); int stride = sizeof(float); lightState.LightFalloffLUTRangeBuffer = new ComputeBuffer(count, stride); } if (lightState.LightFalloffLUTRanges.Length > 0) { cmdBuf.SetBufferData(lightState.LightFalloffLUTRangeBuffer, lightState.LightFalloffLUTRanges, 0, 0, lightState.LightFalloffLUTRanges.Length); } } } private LightState _lightState; public LightPickingMethod lightPickingMethod { set => _lightState.lightPickingMethod = value; get => _lightState.lightPickingMethod; } public int MaterialCount => _materialPool.MaterialCount; public int LightCount => NonMeshLightCount + MeshLightCount + EnvLightCount; public int NonMeshLightCount => _lightState.LightHandleToLightListEntry.Count; public int MeshLightCount => _lightState.MeshLights.Count; public int EnvLightCount => _lightState.HasEnvironmentLight ? 1 : 0; public List LightList => _lightState.LightList; public Dictionary LightHandleToLightListIndex => _lightState.LightHandleToLightListIndex; public ComputeBuffer LightListBuffer => _lightState.LightListBuffer; public ComputeBuffer LightFalloffBuffer => _lightState.LightFalloffBuffer; public ComputeBuffer LightFalloffLUTRangeBuffer => _lightState.LightFalloffLUTRangeBuffer; public uint LightFalloffLUTLength => LightState.LightFalloffLUTLength; public int LightListHashCode { get { int lightHashCode = 0; foreach (var light in _lightState.LightList) lightHashCode = HashCode.Combine(lightHashCode, light.GetHashCode()); return lightHashCode; } } // SubMesh dictionary (instance handle, emissive submesh list) private readonly Dictionary> _subMeshIndices = new(); #endregion private MaterialPool _materialPool; private AccelStructAdapter _rayTracingAccelerationStructure; // Skybox sampling private CubemapRender _cubemapRender; private EnvironmentImportanceSampling _environmentSampling; private int _currentSkyboxHash; // Many light sampling private RegirLightGrid _reservoirGrid; private ConservativeLightGrid _conservativeLightGrid; public void Init(RayTracingContext ctx, WorldResourceSet worldResources) { _materialPool = new MaterialPool(worldResources.SetAlphaChannelShader, worldResources.BlitCubemap, worldResources.BlitGrayScaleCookie); var options = new AccelerationStructureOptions() { buildFlags = BuildFlags.None, // TODO: Consider whether to use BuildFlags.MinimizeMemory once https://jira.unity3d.com/browse/UUM-54575 is fixed. }; _rayTracingAccelerationStructure = new AccelStructAdapter(ctx.CreateAccelerationStructure(options), new GeometryPool(GeometryPoolDesc.NewDefault(), ctx.Resources.geometryPoolKernels, ctx.Resources.copyBuffer)); _lightState = new LightState(); _cubemapRender = new CubemapRender(worldResources.SkyBoxMesh, worldResources.SixFaceSkyBoxMesh); _environmentSampling = new EnvironmentImportanceSampling(worldResources.EnvironmentImportanceSamplingBuild); _reservoirGrid = new RegirLightGrid(worldResources.BuildLightGridShader); _conservativeLightGrid = new ConservativeLightGrid(worldResources.BuildLightGridShader); } private static float Luminance(Color color) { return color.r * 0.2126f + color.g * 0.7152f + color.b * 0.0722f; } public void SetEnvironmentMaterial(Material mat) { _cubemapRender.SetMaterial(mat); } public ComputeBuffer GetMaterialListBuffer() { return _materialPool.MaterialBuffer; } public RenderTexture GetMaterialAlbedoTextures() { return _materialPool.AlbedoTextures; } public RenderTexture GetMaterialEmissionTextures() { return _materialPool.EmissionTextures; } public RenderTexture GetMaterialTransmissionTextures() { return _materialPool.TransmissionTextures; } public RenderTexture GetLightCookieTextures() { return _materialPool.LightCookieTextures; } public RenderTexture GetLightCubemapTextures() { return _materialPool.LightCubemapTextures; } public Texture GetEnvironmentTexture(CommandBuffer cmd, int resolution, out EnvironmentCDF environmentCDF) { Debug.Assert(_environmentSampling != null, "You should call World::Init() first"); var envTex = _cubemapRender.GetCubemap(resolution, out int skyHash); if (_currentSkyboxHash != skyHash) { _environmentSampling.ComputeCDFBuffers(cmd, envTex); _currentSkyboxHash = skyHash; } environmentCDF = _environmentSampling.GetSkyboxCDF(); return envTex; } public void BindLightAccelerationStructure(CommandBuffer cmd, IRayTracingShader shader) { // As we don't use shader variants, we need to bind buffers for all types of supported light sampling // Some of them will be dummy (see the underlying implementation) if (_lightState.lightPickingMethod == LightPickingMethod.Regir) _reservoirGrid.Bind(cmd, shader); else _conservativeLightGrid.Bind(cmd, shader); } public void Dispose() { _rayTracingAccelerationStructure?.Dispose(); _materialPool?.Dispose(); _subMeshIndices.Clear(); _lightState.LightListBuffer?.Dispose(); _lightState.LightFalloffBuffer?.Dispose(); _lightState.LightFalloffLUTRangeBuffer?.Dispose(); _lightState.LightFalloffDescs.Clear(); _lightState.LightFalloff = null; _lightState.LightList.Clear(); _lightState.LightHandleToLightListEntry.Clear(); _lightState.MeshLights.Clear(); _cubemapRender?.Dispose(); _environmentSampling?.Dispose(); _reservoirGrid?.Dispose(); _conservativeLightGrid?.Dispose(); } public AccelStructAdapter GetAccelerationStructure() { return _rayTracingAccelerationStructure; } public void NextFrame() { _rayTracingAccelerationStructure.NextFrame(); } // Provide a unique hash for the renderer sub meshes private static int GetSubMeshHash(InstanceHandle instance, int subMeshIndex) { return HashCode.Combine(instance.Value, subMeshIndex); } public void RemoveInstance(InstanceHandle instance) { try { _rayTracingAccelerationStructure.RemoveInstance(instance.Value); _instanceHandleSet.Remove(instance); RemoveEmissiveMeshes(instance); } catch (Exception e) { LogException("Failed to remove instance", e, instance.Value); } } private void RemoveEmissiveMeshes(InstanceHandle instance) { if (_subMeshIndices.ContainsKey(instance)) { var emissiveSubMeshes = _subMeshIndices[instance]; foreach (var subMeshIndex in emissiveSubMeshes) { var subMeshHash = GetSubMeshHash(instance, subMeshIndex); if (_lightState.MeshLights.ContainsKey(subMeshHash)) _lightState.MeshLights.Remove(subMeshHash); } _subMeshIndices.Remove(instance); } } public void RemoveMaterial(MaterialHandle materialHandle) { try { _materialHandleSet.Remove(materialHandle); _materialPool.RemoveMaterial(materialHandle.Value); } catch (Exception e) { LogException("failed to remove material", e, materialHandle.Value); } } public MaterialHandle AddMaterial(in MaterialPool.MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel) { MaterialHandle handle = _materialHandleSet.Add(); try { _materialPool.AddMaterial(handle.Value, in material, albedoAndEmissionUVChannel); } catch (Exception e) { LogException("failed to add material", e, handle.Value); } return handle; } public void UpdateMaterial(MaterialHandle materialHandle, in MaterialPool.MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel) { try { _materialPool.UpdateMaterial(materialHandle.Value, in material, albedoAndEmissionUVChannel); } catch (Exception e) { LogException("failed to modify material", e, materialHandle.Value); } } private void LogException(string message, Exception e, Object obj) { var objName = obj != null ? obj.name : "null"; Debug.LogError($"PathTracing: {message} <{objName}> \n{e.Message}", obj); } private void LogException(string message, Exception e, UInt64 instanceHandle) { Debug.LogError($"PathTracing: {message} <{instanceHandle}> \n{e.Message}"); } private void LogError(string message) { Debug.LogError($"PathTracing: {message} \n"); } public InstanceHandle AddInstance( Mesh mesh, Span materials, Span masks, uint renderingLayerMask, in Matrix4x4 localToWorldMatrix, Bounds bounds, bool isStatic, RenderedGameObjectsFilter filter, bool enableEmissiveSampling) { Debug.Assert(mesh.subMeshCount == materials.Length); Debug.Assert(mesh.subMeshCount == masks.Length); Span materialIndices = stackalloc uint[mesh.subMeshCount]; Span isOpaque = stackalloc bool[mesh.subMeshCount]; for (int i = 0; i < materials.Length; ++i) { if (materials[i] == MaterialHandle.Invalid) continue; bool isTransmissive = false; _materialPool.GetMaterialInfo(materials[i].Value, out materialIndices[i], out isTransmissive); isOpaque[i] = !isTransmissive; } InstanceHandle instance = _instanceHandleSet.Add(); _rayTracingAccelerationStructure.AddInstance(instance.Value, mesh, localToWorldMatrix, masks, materialIndices, isOpaque, renderingLayerMask); if (enableEmissiveSampling && !ProcessEmissiveMeshes(instance, mesh, bounds, materials, isStatic, _rayTracingAccelerationStructure, _materialPool, filter, _lightState.MeshLights, _subMeshIndices)) LogError($"Failed to process emissive triangles in mesh {mesh.name}."); return instance; } public void UpdateInstanceTransform(InstanceHandle instance, Matrix4x4 localToWorldMatrix) { _rayTracingAccelerationStructure.UpdateInstanceTransform(instance.Value, localToWorldMatrix); } public void UpdateInstanceMask(InstanceHandle instance, Span perSubMeshMask) { _rayTracingAccelerationStructure.UpdateInstanceMask(instance.Value, perSubMeshMask); } public void UpdateInstanceMask(InstanceHandle instance, uint mask) { _rayTracingAccelerationStructure.UpdateInstanceMask(instance.Value, mask); } public void UpdateInstanceMaterials(InstanceHandle instance, Span materials) { Span materialIndices = stackalloc uint[materials.Length]; for (int i = 0; i < materials.Length; ++i) { _materialPool.GetMaterialInfo(materials[i].Value, out materialIndices[i], out bool isTransmissive); } _rayTracingAccelerationStructure.UpdateInstanceMaterialIDs(instance.Value, materialIndices); } public void UpdateInstanceEmission( InstanceHandle instance, Mesh mesh, Bounds bounds, Span materials, bool isStatic, RenderedGameObjectsFilter filter) { if (!ProcessEmissiveMeshes(instance, mesh, bounds, materials, isStatic, _rayTracingAccelerationStructure, _materialPool, filter, _lightState.MeshLights, _subMeshIndices)) { LogError($"failed to process emissive triangles in mesh with handle {instance}"); } } // InstanceMask bit encoding format: internal static uint GetInstanceMask(ShadowCastingMode shadowMode, bool isStatic, RenderedGameObjectsFilter filter, bool hasLightmaps = true) { uint instanceMask = 0u; if (shadowMode != ShadowCastingMode.Off) { if (filter == RenderedGameObjectsFilter.All) { instanceMask |= (uint)InstanceFlags.SHADOW_RAY_VIS_MASK; } else { if (isStatic) { instanceMask |= (uint)InstanceFlags.SHADOW_RAY_VIS_MASK; } } } if (shadowMode != ShadowCastingMode.ShadowsOnly) { if (filter == RenderedGameObjectsFilter.All) { instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; } else if (filter == RenderedGameObjectsFilter.OnlyStatic) { if (isStatic) { if (hasLightmaps) { instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; } instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; } } else if (filter == RenderedGameObjectsFilter.AllInCameraRaysThenOnlyStatic) { if (isStatic) { instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; } else { instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; } } } return instanceMask; } private static bool ProcessEmissiveMeshes( InstanceHandle instance, Mesh mesh, Bounds bounds, Span materials, bool isStatic, AccelStructAdapter rtAccelStruct, MaterialPool sceneMaterials, RenderedGameObjectsFilter filter, Dictionary meshLights, Dictionary> subMeshIndexMap) { if (filter != RenderedGameObjectsFilter.All && !isStatic) return true; int subMeshCount = mesh.subMeshCount; if (!rtAccelStruct.GetInstanceIDs(instance.Value, out var instanceHandles)) { // This should never happen as long as the renderer was already added to the acceleration structure return false; } // Approximate area of the emissive mesh using the bounding box float boundingBoxArea = 2 * (bounds.size.x * bounds.size.y) + 2 * (bounds.size.y * bounds.size.z) + 2 * (bounds.size.x * bounds.size.z); // List to keep track of the emissive subMeshes List subMeshIndices = new List(subMeshCount); for (int i = 0; i < subMeshCount; ++i) { MaterialHandle mat = materials[i]; // If it's an emissive material, create emissive triangles for this submesh if (sceneMaterials.IsEmissive(mat.Value, out float3 emission)) { var triangleIndices = mesh.GetTriangles(i); var triangleCount = triangleIndices.Length / 3; // create a new MeshLight entry PTLight newLight; { newLight.type = EMISSIVE_MESH; newLight.height = instanceHandles[i]; newLight.attenuation = Vector4.one; newLight.attenuation.x = triangleCount; // number of emissive triangles newLight.intensity = emission; newLight.position = Vector3.zero; newLight.up = Vector3.zero; newLight.right = Vector3.zero; newLight.forward = Vector3.zero; newLight.width = boundingBoxArea; newLight.castShadows = 1; newLight.contributesToDirectLighting = 1; newLight.indirectScale = 1.0f; newLight.spotAngle = 0; newLight.innerSpotAngle = 0; newLight.layerMask = uint.MaxValue; newLight.range = float.MaxValue; newLight.shadowMaskChannel = -1; newLight.falloffIndex = -1; newLight.shadowRadius = 0.0f; newLight.cookieIndex = -1; // mesh lights sample directly the emission texture, they don't have a light cookie } var subMeshHash = GetSubMeshHash(instance, i); meshLights[subMeshHash] = newLight; // keep track of which subMeshes are emissive (need this for when removing meshes) subMeshIndices.Add(i); } } if (subMeshIndices.Count > 0) { subMeshIndexMap[instance] = subMeshIndices; } return true; } public LightHandle[] AddLights(Span lights, bool respectLightLayers, bool autoEstimateLUTRange, MixedLightingMode mixedLightingMode) { // Generate handles LightHandle[] handles = new LightHandle[lights.Length]; for (int i = 0; i < lights.Length; i++) { LightHandle handle = _lightState.LightHandleSet.Add(); handles[i] = handle; } // Use the handles to update the lights for the first time UpdateLights(handles, lights, respectLightLayers, autoEstimateLUTRange, mixedLightingMode); return handles; } private static float EstimateLUTRange(float range, float luminance, Experimental.GlobalIllumination.FalloffType falloffType, float threshold = 0.01f) { Debug.Assert(threshold > 0.0f); Debug.Assert(range > 0.0f); if (luminance <= 0.0f) return 1.0f; switch (falloffType) { case Experimental.GlobalIllumination.FalloffType.InverseSquaredNoRangeAttenuation: case Experimental.GlobalIllumination.FalloffType.InverseSquared: { // compute the range at which the attenuated luminance is below the threshold float estimatedRange = math.max(1.0f, math.ceil(math.sqrt(luminance / threshold))); Debug.Assert(luminance * LightFalloffLUT.InverseSquaredFalloff(estimatedRange * estimatedRange) <= threshold); return math.min(estimatedRange, range); } case Experimental.GlobalIllumination.FalloffType.Linear: case Experimental.GlobalIllumination.FalloffType.Legacy: return range; } return range; } public void UpdateLights(LightHandle[] lightHandles, Span lightDescriptors, bool respectLightLayers, bool autoEstimateLUTRange, MixedLightingMode mixedLightingMode) { Debug.Assert(lightHandles.Length == lightDescriptors.Length); Dictionary falloffHashToFalloffIndex = new(); int falloffIndex = 0; // Convert the lights. for (int i = 0; i < lightHandles.Length; i++) { ref readonly LightDescriptor light = ref lightDescriptors[i]; PTLight newLight; newLight.position = light.Transform.GetPosition(); newLight.intensity = light.LinearLightColor; newLight.type = (int)light.Type; newLight.castShadows = light.Shadows != LightShadows.None ? 1 : 0; newLight.forward = (light.Transform.rotation * Vector3.forward).normalized; newLight.attenuation = Vector4.one; newLight.up = (light.Transform.rotation * Vector3.up).normalized; newLight.right = (light.Transform.rotation * Vector3.right).normalized; newLight.width = light.AreaSize.x; newLight.height = light.AreaSize.y; newLight.spotAngle = light.SpotAngle; newLight.innerSpotAngle = light.InnerSpotAngle; newLight.layerMask = respectLightLayers ? light.CullingMask : uint.MaxValue; newLight.indirectScale = light.BounceIntensity; newLight.range = light.Range; newLight.shadowRadius = light.ShadowRadius; newLight.shadowMaskChannel = light.ShadowMaskChannel; switch (light.LightmapBakeType) { case LightmapBakeType.Baked: case LightmapBakeType.Mixed when mixedLightingMode == MixedLightingMode.Subtractive: newLight.contributesToDirectLighting = 1; break; case LightmapBakeType.Mixed when mixedLightingMode == MixedLightingMode.Shadowmask: // Fallback to baked behavior if we don't have a valid shadowmask channel newLight.contributesToDirectLighting = newLight.shadowMaskChannel != -1 ? 0 : 1; break; case LightmapBakeType.Mixed when mixedLightingMode == MixedLightingMode.IndirectOnly: case LightmapBakeType.Realtime: default: newLight.contributesToDirectLighting = 0; break; } if (light.Type == LightType.Spot) { // aspect ratio is serialized in areaSize.x float aspect = light.AreaSize.x; float frustumHeight = 2.0f * Mathf.Tan(light.SpotAngle * 0.5f * Mathf.Deg2Rad); float frustumWidth = frustumHeight * aspect; newLight.width = frustumWidth; newLight.height = frustumHeight; } if (light.Type == LightType.Spot || light.Type == LightType.Point) { Debug.Assert(light.FalloffType != Experimental.GlobalIllumination.FalloffType.Undefined); float estimatedLUTRange = light.Range; if (autoEstimateLUTRange) { // Guesstimate a good LUT range, such that the LUT covers the falloff up to a distance where it is nearly 0. // The range can be any number and we don't want to stretch the LUT to some arbitrary range (most of which is practically 0). estimatedLUTRange = EstimateLUTRange(light.Range, Luminance(new Color(light.LinearLightColor.x, light.LinearLightColor.y, light.LinearLightColor.z, 1.0f)), light.FalloffType, 0.01f); } LightFalloffDesc falloffDesc = new LightFalloffDesc { LUTRange = estimatedLUTRange, FalloffType = light.FalloffType }; var falloffHash = falloffDesc.GetHashCode(); if (!falloffHashToFalloffIndex.TryGetValue(falloffHash, out newLight.falloffIndex)) { // Add new falloff entry newLight.falloffIndex = falloffIndex++; falloffHashToFalloffIndex.Add(falloffHash, newLight.falloffIndex); _lightState.LightFalloffDescs.Add(falloffDesc); } } else newLight.falloffIndex = -1; #pragma warning disable 162 // Disable unreachable code warning // Light attenuation parameters and math from HDRP const bool applyRangeAttenuation = true; if (applyRangeAttenuation) { newLight.attenuation.x = 1.0f / (light.Range * light.Range); newLight.attenuation.y = 1.0f; } else { const float hugeValue = 16777216.0f; const float sqrtHuge = 4096.0f; newLight.attenuation.x = sqrtHuge / (light.Range * light.Range); newLight.attenuation.y = hugeValue; } #pragma warning restore 162 newLight.attenuation.z = 0.0f; if (light.Type == LightType.Spot) { var spotAngle = light.SpotAngle; var cosSpotOuterHalfAngle = Mathf.Clamp(Mathf.Cos(spotAngle * 0.5f * Mathf.Deg2Rad), 0.0f, 1.0f); var cosSpotInnerHalfAngle = Mathf.Clamp(Mathf.Cos(0.5f * light.InnerSpotAngle * Mathf.Deg2Rad), 0.0f, 1.0f); // inner cone var val = Mathf.Max(0.0001f, (cosSpotInnerHalfAngle - cosSpotOuterHalfAngle)); newLight.attenuation.z = 1.0f / val; newLight.attenuation.w = -cosSpotOuterHalfAngle * newLight.attenuation.z; } newLight.cookieIndex = _materialPool.AddCookieTexture(light.CookieTexture); _lightState.LightHandleToLightListEntry[lightHandles[i]] = newLight; } } public void RemoveLights(Span lights) { foreach (var light in lights) { if (_lightState.LightHandleToLightListEntry.TryGetValue(light, out var ptLight)) { var cookieFaces = (ptLight.type == (int)LightType.Point) ? 6 : 1; _materialPool.RemoveCookieTexture(cookieFaces, ptLight.cookieIndex); } _lightState.LightHandleSet.Remove(light); _lightState.LightHandleToLightListEntry.Remove(light); } } public void Build(Bounds sceneBounds, CommandBuffer cmdBuf, ref GraphicsBuffer scratchBuffer, Rendering.Sampling.SamplingResources samplingResources, bool emissiveSampling) { Debug.Assert(_rayTracingAccelerationStructure != null); _lightState.Build(sceneBounds, cmdBuf, emissiveSampling && _cubemapRender.GetMaterial() != null); if (_lightState.lightPickingMethod == LightPickingMethod.Regir) { _reservoirGrid.Build(cmdBuf, _lightState, sceneBounds, samplingResources); } else if (_lightState.lightPickingMethod == LightPickingMethod.LightGrid) { _conservativeLightGrid.Build(cmdBuf, _lightState, sceneBounds, samplingResources); } _materialPool.Build(cmdBuf); _rayTracingAccelerationStructure.Build(cmdBuf, ref scratchBuffer); } public UInt64 GetInstanceHandles(InstanceHandle handle) { _rayTracingAccelerationStructure.GetInstanceIDs(handle.Value, out var ids); return (UInt64)ids[0]; } } }