#if ENABLE_PATH_TRACING_SRP
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine.PathTracing.Core;
using UnityEngine.Rendering.Denoising;
using UnityEngine.Rendering.Sampling;
using UnityEngine.Rendering.UnifiedRayTracing;
namespace UnityEngine.Rendering.LiveGI
{
using InstanceHandle = Handle<World.InstanceKey>;
using LightHandle = Handle<World.LightDescriptor>;
using MaterialHandle = Handle<MaterialPool.MaterialDescriptor>;
[Serializable]
internal class PathTracingSettings
{
[Header("Light Transport")]
[Range(0, 10)]
public int bounceCount = 2;
[Range(1, 16)]
public int sampleCount = 1;
[Range(1, 8)]
public int lightEvaluations = 1;
[Min(0)]
public int maxIntensity;
[HideInInspector]
public float exposureScale = 1.0f;
// Keep emissive mesh sampling disabled for now, as we need a more reliable sceneMaterials.IsEmissive.
// If you change that, you need to modify the EMISSIVE_SAMPLING define in the shader
[HideInInspector]
public bool enableEmissiveSampling;
// For now, some SRPs pre-multiply the light intensity with PI (like URP), while other don't
// https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
[HideInInspector]
public bool multiplyPunctualLightIntensityByPI;
// This automatically adjust the light range based on the light intensity and range to better fit the falloff LUT (applies to punctual lights with inverse squared falloff only).
[HideInInspector]
public bool autoEstimateLUTRange = true;
public LightPickingMethod lightPickingMethod = LightPickingMethod.Uniform;
[HideInInspector]
public PathTermination pathTermination = PathTermination.RusianRoulette;
[Header("Post Processing")]
public DenoiserType denoising = DenoiserType.None;
[Header("Intersections")]
public RayTracingBackend raytracingBackend = RayTracingBackend.Hardware;
// Use "only static" when previewing lightmaps. Use "All" to path trace the full frame
public RenderedGameObjectsFilter renderedGameObjects = RenderedGameObjectsFilter.OnlyStatic;
[Header("Artistic Controls")]
public bool respectLightLayers = true;
[Range(1, 10)]
public float albedoBoost = 1.0f;
[Range(0, 10)]
public float indirectIntensity = 1.0f;
[Range(0, 10)]
public float environmentIntensityMultiplier = 1.0f;
[HideInInspector]
public Vector3Int reservoirGridSize = new Vector3Int(64, 16, 64);
[HideInInspector]
public int reservoirsPerVoxel = 16;
[Header("Debug")]
public bool showRayHeatmap = false;
}
internal enum PathTracingOutput { FullPathTracer, GIPreview };
internal enum RayTracingBackend { Hardware = 0, Compute = 1 };
internal enum PathTermination { RusianRoulette = 0, NewUnbiased = 1, NewBiased = 2 };
internal class PathTracingContext : IDisposable
{
// Public API
#region Public API
public PathTracingContext(PathTracingOutput pathTracingOutput)
{
#if UNITY_EDITOR
// Note: this code is not ready to run in the player, we just disable some parts to make sure the player builds successfully
_defaultMaterial = UnityEditor.AssetDatabase.GetBuiltinExtraResource<Material>("Default-Material.mat");
#endif
_sceneUpdatesTracker = new SceneUpdatesTracker();
_pathTracingOutput = pathTracingOutput;
_samplingResources = new SamplingResources();
#if UNITY_EDITOR
_samplingResources.Load((uint)SamplingResources.ResourceType.All);
#endif
_emptyExposureTexture = RTHandles.Alloc(1, 1,
enableRandomWrite: true, name: "Empty EV100 Exposure");
}
public void Dispose()
{
_world?.Dispose();
_rayTracingContext?.Dispose();
_rayTracingContext = null;
_sceneUpdatesTracker?.Dispose();
_emptyExposureTexture?.Release();
_buildScratchBuffer?.Dispose();
_samplingResources?.Dispose();
// Delete temporary textures for the default material
CoreUtils.Destroy(_defaultMaterialDescriptor.Albedo);
CoreUtils.Destroy(_defaultMaterialDescriptor.Emission);
CoreUtils.Destroy(_defaultMaterialDescriptor.Transmission);
}
internal RayTracingBackend SelectRayTracingBackend(RayTracingBackend requestedBackend, RayTracingResources resources)
{
if (!RayTracingContext.IsBackendSupported((UnifiedRayTracing.RayTracingBackend)requestedBackend))
{
Debug.LogWarning("Hardware RayTracing not available. Falling back to Compute Shader based implementation.");
requestedBackend = RayTracingBackend.Compute;
}
// Early exit if the backend has not changed and the RT resources are already loaded
if (_world != null && _world.GetAccelerationStructure() != null && _rayTracingShader != null && _currentRayTracingBackend == requestedBackend)
return requestedBackend;
_rayTracingContext?.Dispose();
_rayTracingContext = new RayTracingContext((UnifiedRayTracing.RayTracingBackend)requestedBackend, resources);
_currentRayTracingBackend = requestedBackend;
#if UNITY_EDITOR
_rayTracingShader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader");
#endif
_world?.Dispose();
_world = new World();
var worldResources = new WorldResourceSet();
#if UNITY_EDITOR
worldResources.LoadFromAssetDatabase();
#endif
_world.Init(_rayTracingContext, worldResources);
_defaultMaterialDescriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(_defaultMaterial);
var defaultHandle = _world.AddMaterial(in _defaultMaterialDescriptor, UVChannel.UV0);
_instanceIDToWorldMaterialHandles.Add(_defaultMaterial.GetEntityId(), defaultHandle);
_instanceIDToWorldMaterialDescriptors.Add(_defaultMaterial.GetEntityId(), _defaultMaterialDescriptor);
return requestedBackend;
}
// Handles scene updates. Should be called once per frame (and not per camera)
public void Update(CommandBuffer cmd, PathTracingSettings settings)
{
using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Update")))
{
// TODO: enable this only for debugging
Unity.Collections.NativeLeakDetection.Mode = Unity.Collections.NativeLeakDetectionMode.EnabledWithStackTrace;
var changes = _sceneUpdatesTracker.GetChanges(_pathTracingOutput == PathTracingOutput.GIPreview);
_sceneChanged = changes.HasChanges();
UpdateMaterials(_world, _instanceIDToWorldMaterialHandles, _instanceIDToWorldMaterialDescriptors, changes.addedMaterials, changes.removedMaterials, changes.changedMaterials);
UpdateInstances(_world, _instanceIDToWorldInstanceHandles, _instanceIDToWorldMaterialHandles, changes.addedInstances, changes.changedInstances, changes.removedInstances, settings.renderedGameObjects, settings.enableEmissiveSampling, _defaultMaterial);
UpdateLights(_world, _instanceIDToWorldLightHandles, changes.addedLights, changes.removedLights, changes.changedLights, settings);
// Calculate scene bounds.
Bounds sceneBounds = new Bounds();
if (settings.lightPickingMethod == LightPickingMethod.Regir ||
settings.lightPickingMethod == LightPickingMethod.LightGrid)
{
var sceneRenderers = Object.FindObjectsByType<Renderer>();
foreach (Renderer r in sceneRenderers)
sceneBounds.Encapsulate(r.bounds);
}
_world.SetEnvironmentMaterial(RenderSettings.skybox);
//_world.EnableEmissiveSampling = settings.enableEmissiveSampling;
_world.Build(sceneBounds, cmd, ref _buildScratchBuffer, _samplingResources, settings.enableEmissiveSampling);
int newLightListHashCode = _world.LightListHashCode;
_sceneChanged |= (newLightListHashCode != _previousLightsHashCode);
_previousLightsHashCode = newLightListHashCode;
}
}
public void Render(CommandBuffer cmd, Vector2Int scaledSize, Vector4 viewFustum, Matrix4x4 cameraToWorldMatrix, Matrix4x4 worldToCameraMatrix, Matrix4x4 projectionMatrix, Matrix4x4 previousViewProjection, PathTracingSettings settings, RTHandle output, RTHandle normals, RTHandle motionVectors, RTHandle debugOutput, int frameIndex, bool preExpose = false)
{
using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Render")))
{
Debug.Assert(_world.GetAccelerationStructure() != null, "Acceleration structure does not exist. Did you call Update()?");
// Path-tracer Input
Util.BindWorld(cmd, _rayTracingShader, _world, _pathTracingOutput == PathTracingOutput.GIPreview ? 32 : 1024);
_rayTracingShader.SetVectorParam(cmd, Shader.PropertyToID("g_CameraFrustum"), viewFustum);
_rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_CameraToWorldMatrix"), cameraToWorldMatrix);
_rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_PreviousViewProjection"), previousViewProjection);
var viewProjection = projectionMatrix * worldToCameraMatrix;
_rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_CameraViewProjection"), viewProjection);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_FrameIndex"), frameIndex);
// For now the history rejection in the denoising is not compatible with stochastic jitter
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_EnableSubPixelJittering"), 0);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_LightPickingMethod"), (int)settings.lightPickingMethod);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_BounceCount"), settings.bounceCount);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_SampleCount"), settings.sampleCount);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_LightEvaluations"), settings.lightEvaluations);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_PathtracerAsGiPreviewMode"), (_pathTracingOutput == PathTracingOutput.GIPreview) ? 1 : 0);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_CountNEERayAsPathSegment"), 1);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_RenderedInstances"), (int)settings.renderedGameObjects);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_PreExpose"), preExpose ? 1 : 0);
_rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_MaxIntensity"), settings.maxIntensity > 0 ? settings.maxIntensity : int.MaxValue);
_rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_ExposureScale"), settings.exposureScale);
_rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_AlbedoBoost"), settings.albedoBoost);
_rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_IndirectScale"), settings.indirectIntensity);
_rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_EnvIntensityMultiplier"), settings.environmentIntensityMultiplier);
_rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_EnableDebug"), settings.showRayHeatmap ? 1 : 0);
_rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_PathTermination"), (int)settings.pathTermination);
// To avoid shader permutations, we always need to set an exposure texture, even if we don't read it
if (!preExpose)
{
_rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("_ExposureTexture"), _emptyExposureTexture);
}
SamplingResources.Bind(cmd, _samplingResources);
// Path-tracer Output
_rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_Radiance"), output);
_rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_MotionVectors"), motionVectors);
_rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_NormalsDepth"), normals);
_rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_DebugOutput"), debugOutput);
// Path-tracing
RayTracingHelper.ResizeScratchBufferForTrace(_rayTracingShader, (uint)scaledSize.x, (uint)scaledSize.y, 1, ref _traceScratchBuffer);
_rayTracingShader.Dispatch(cmd, _traceScratchBuffer, (uint)scaledSize.x, (uint)scaledSize.y, 1);
_world.NextFrame();
}
}
public void Denoise(CommandBuffer cmd, CommandBufferDenoiser denoiser, float nearClipPlane, float farClipPlane, Matrix4x4 viewProjection, PathTracingSettings settings, RTHandle color, RTHandle normals, RTHandle motionVectors)
{
using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Denoise")))
{
// Denoising
if (settings.denoising != DenoiserType.None && denoiser != null)
{
denoiser.DenoiseRequest(cmd, "color", color);
denoiser.DenoiseRequest(cmd, "flow", motionVectors);
denoiser.DenoiseRequest(cmd, "normals", normals);
//if (denoiser is RealTimeDenoiser)
// ((RealTimeDenoiser)denoiser).SetCameraMatrix(cmd, viewProjection, nearClipPlane, farClipPlane, _sceneChanged);
denoiser.GetResults(cmd, color);
}
}
}
#endregion
// Private Implementation
#region Private Implementation
private readonly SamplingResources _samplingResources;
private RayTracingBackend _currentRayTracingBackend;
private RayTracingContext _rayTracingContext;
private IRayTracingShader _rayTracingShader;
private GraphicsBuffer _traceScratchBuffer;
private GraphicsBuffer _buildScratchBuffer;
// TODO(Yvain) We should use the same buffer for m_TraceScratchBuffer and m_BuildScratchBuffer but that's impractical when we need to
// resize the buffer for both the build and the trace.
// (we can't call Dispose() on a GraphicsBuffer before submitting the CommandBuffer using it)
private readonly SceneUpdatesTracker _sceneUpdatesTracker;
private bool _sceneChanged = true;
private int _previousLightsHashCode;
private readonly Material _defaultMaterial;
private MaterialPool.MaterialDescriptor _defaultMaterialDescriptor;
private readonly PathTracingOutput _pathTracingOutput;
private readonly RTHandle _emptyExposureTexture;
private World _world;
// This dictionary maps from Unity InstanceID for MeshRenderer or Terrain, to corresponding InstanceHandle for accessing World.
private readonly Dictionary<EntityId, InstanceHandle> _instanceIDToWorldInstanceHandles = new();
// Same as above but for Lights
private readonly Dictionary<EntityId, LightHandle> _instanceIDToWorldLightHandles = new();
// Same as above but for Materials
private Dictionary<EntityId, MaterialHandle> _instanceIDToWorldMaterialHandles = new();
// We also keep track of associated material descriptors, so we can free temporary temporary textures when a material is removed
private Dictionary<EntityId, MaterialPool.MaterialDescriptor> _instanceIDToWorldMaterialDescriptors = new();
public static Vector4 GetCameraFrustum(Camera camera)
{
Vector3[] frustumCorners = new Vector3[4];
camera.CalculateFrustumCorners(new Rect(0, 0, 1, 1), 1.0f, Camera.MonoOrStereoscopicEye.Mono, frustumCorners);
float left = frustumCorners[0].x;
float right = frustumCorners[2].x;
float bottom = frustumCorners[0].y;
float top = frustumCorners[2].y;
return new Vector4(left, right, bottom, top);
}
internal static void UpdateMaterials(World world, Dictionary<EntityId, MaterialHandle> instanceIDToHandle, Dictionary<EntityId, MaterialPool.MaterialDescriptor> instanceIDToDescriptor, List<Material> addedMaterials, List<EntityId> removedMaterials, List<Material> changedMaterials)
{
static void DeleteTemporaryTextures(ref MaterialPool.MaterialDescriptor desc)
{
CoreUtils.Destroy(desc.Albedo);
CoreUtils.Destroy(desc.Emission);
CoreUtils.Destroy(desc.Transmission);
}
foreach (var materialInstanceID in removedMaterials)
{
// Clean up temporary textures in the descriptor
Debug.Assert(instanceIDToDescriptor.ContainsKey(materialInstanceID));
var descriptor = instanceIDToDescriptor[materialInstanceID];
DeleteTemporaryTextures(ref descriptor);
instanceIDToDescriptor.Remove(materialInstanceID);
// Remove the material from the world
Debug.Assert(instanceIDToHandle.ContainsKey(materialInstanceID));
world.RemoveMaterial(instanceIDToHandle[materialInstanceID]);
instanceIDToHandle.Remove(materialInstanceID);
}
foreach (var material in addedMaterials)
{
// Add material to the world
var descriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material);
var handle = world.AddMaterial(in descriptor, UVChannel.UV0);
instanceIDToHandle.Add(material.GetEntityId(), handle);
// Keep track of the descriptor
instanceIDToDescriptor.Add(material.GetEntityId(), descriptor);
}
foreach (var material in changedMaterials)
{
// Clean up temporary textures in the old descriptor
Debug.Assert(instanceIDToDescriptor.ContainsKey(material.GetEntityId()));
var oldDescriptor = instanceIDToDescriptor[material.GetEntityId()];
DeleteTemporaryTextures(ref oldDescriptor);
// Update the material in the world using the new descriptor
Debug.Assert(instanceIDToHandle.ContainsKey(material.GetEntityId()));
var newDescriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material);
world.UpdateMaterial(instanceIDToHandle[material.GetEntityId()], in newDescriptor, UVChannel.UV0);
instanceIDToDescriptor[material.GetEntityId()] = newDescriptor;
}
}
private static void UpdateLights(World world, Dictionary<EntityId, LightHandle> instanceIDToHandle, List<Light> addedLights, List<EntityId> removedLights,
List<Light> changedLights, PathTracingSettings settings, MixedLightingMode mixedLightingMode = MixedLightingMode.IndirectOnly)
{
world.lightPickingMethod = settings.lightPickingMethod;
// Remove deleted lights
LightHandle[] handlesToRemove = new LightHandle[removedLights.Count];
for (int i = 0; i < removedLights.Count; i++)
{
var lightInstanceID = removedLights[i];
handlesToRemove[i] = instanceIDToHandle[lightInstanceID];
instanceIDToHandle.Remove(lightInstanceID);
}
world.RemoveLights(handlesToRemove);
// Add new lights
LightHandle[] addedHandles = world.AddLights(Util.ConvertUnityLightsToLightDescriptors(addedLights.ToArray(), settings.multiplyPunctualLightIntensityByPI), settings.respectLightLayers, settings.autoEstimateLUTRange, mixedLightingMode);
for (int i = 0; i < addedLights.Count; ++i)
instanceIDToHandle.Add(addedLights[i].GetEntityId(), addedHandles[i]);
// Update changed lights
LightHandle[] handlesToUpdate = new LightHandle[changedLights.Count];
for (int i = 0; i < changedLights.Count; i++)
handlesToUpdate[i] = instanceIDToHandle[changedLights[i].GetEntityId()];
world.UpdateLights(handlesToUpdate, Util.ConvertUnityLightsToLightDescriptors(changedLights.ToArray(), settings.multiplyPunctualLightIntensityByPI), settings.respectLightLayers, settings.autoEstimateLUTRange, mixedLightingMode);
}
internal static void UpdateInstances(
World world,
Dictionary<EntityId, InstanceHandle> instanceIDToInstanceHandle,
Dictionary<EntityId, MaterialHandle> instanceIDToMaterialHandle,
List<MeshRenderer> addedInstances,
List<InstanceChanges> changedInstances,
List<EntityId> removedInstances,
RenderedGameObjectsFilter renderedGameObjects,
bool enableEmissiveSampling,
Material fallbackMaterial)
{
foreach (var meshRendererInstanceID in removedInstances)
{
if (instanceIDToInstanceHandle.TryGetValue(meshRendererInstanceID, out InstanceHandle instance))
{
world.RemoveInstance(instance);
instanceIDToInstanceHandle.Remove(meshRendererInstanceID);
}
else
{
Debug.LogError($"Failed to remove an instance with InstanceID {meshRendererInstanceID}");
}
}
foreach (var meshRenderer in addedInstances)
{
if (meshRenderer.isPartOfStaticBatch)
{
Debug.LogError("Static batching should be disabled when using the real time path tracer in play mode. You can disable it from the project settings.");
continue;
}
var mesh = meshRenderer.GetComponent<MeshFilter>().sharedMesh;
var localToWorldMatrix = meshRenderer.transform.localToWorldMatrix;
var materials = Util.GetMaterials(meshRenderer);
var materialHandles = new MaterialHandle[materials.Length];
bool[] visibility = new bool[materials.Length];
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] == null)
{
materialHandles[i] = instanceIDToMaterialHandle[fallbackMaterial.GetEntityId()];
visibility[i] = false;
}
else
{
materialHandles[i] = instanceIDToMaterialHandle[materials[i].GetEntityId()];
visibility[i] = true;
}
}
uint[] masks = new uint[materials.Length];
for (int i = 0; i < masks.Length; i++)
{
#if UNITY_EDITOR
bool hasLightmaps = (meshRenderer.receiveGI == ReceiveGI.Lightmaps);
#else
bool hasLightmaps = true;
#endif
var mask = World.GetInstanceMask(meshRenderer.shadowCastingMode, Util.IsStatic(meshRenderer.gameObject), renderedGameObjects, hasLightmaps);
masks[i] = visibility[i] ? mask : 0u;
}
InstanceHandle instance = world.AddInstance(
mesh,
materialHandles,
masks,
1u << meshRenderer.gameObject.layer,
in localToWorldMatrix,
meshRenderer.bounds,
Util.IsStatic(meshRenderer.gameObject),
renderedGameObjects,
enableEmissiveSampling);
var instanceID = meshRenderer.GetEntityId();
Debug.Assert(!instanceIDToInstanceHandle.ContainsKey(instanceID));
instanceIDToInstanceHandle.Add(instanceID, instance);
}
foreach (var instanceUpdate in changedInstances)
{
try
{
var renderer = instanceUpdate.meshRenderer;
var gameObject = renderer.gameObject;
if (!instanceIDToInstanceHandle.TryGetValue(renderer.GetEntityId(), out InstanceHandle instance))
{
Debug.LogError($"Failed to update an instance with InstanceID {instanceUpdate.meshRenderer.GetEntityId()}");
continue;
}
if ((instanceUpdate.changes & ModifiedProperties.Transform) != 0)
{
world.UpdateInstanceTransform(instance, gameObject.transform.localToWorldMatrix);
}
bool materialChanged = (instanceUpdate.changes & ModifiedProperties.Material) != 0;
bool maskPropertiesChanged = (instanceUpdate.changes & ModifiedProperties.IsStatic) != 0 || (instanceUpdate.changes & ModifiedProperties.ShadowCasting) != 0 || (instanceUpdate.changes & ModifiedProperties.Layer) != 0;
if (materialChanged || enableEmissiveSampling || maskPropertiesChanged)
{
var materials = Util.GetMaterials(renderer);
var materialHandles = new MaterialHandle[materials.Length];
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] == null)
{
materialHandles[i] = instanceIDToMaterialHandle[fallbackMaterial.GetEntityId()];
}
else
{
materialHandles[i] = instanceIDToMaterialHandle[materials[i].GetEntityId()];
}
}
if (materialChanged)
world.UpdateInstanceMaterials(instance, materialHandles);
if (enableEmissiveSampling)
world.UpdateInstanceEmission(instance, instanceUpdate.meshRenderer.GetComponent<MeshFilter>().sharedMesh, instanceUpdate.meshRenderer.bounds, materialHandles, Util.IsStatic(gameObject), renderedGameObjects);
if (maskPropertiesChanged || materialChanged)
{
bool[] visibility = new bool[materials.Length];
for (int i = 0; i < materials.Length; i++)
visibility[i] = materials[i] != null;
uint[] masks = new uint[materials.Length];
for (int i = 0; i < masks.Length; i++)
{
#if UNITY_EDITOR
bool hasLightmaps = (renderer.receiveGI == ReceiveGI.Lightmaps);
#else
bool hasLightmaps = true;
#endif
var mask = World.GetInstanceMask(renderer.shadowCastingMode, Util.IsStatic(renderer.gameObject), renderedGameObjects, hasLightmaps);
masks[i] = visibility[i] ? mask : 0u;
}
world.UpdateInstanceMask(instance, masks);
}
}
}
catch (Exception e)
{
Debug.LogError($"Failed to modify instance {instanceUpdate.meshRenderer.name}: {e}");
}
}
}
#endregion
}
}
#endif