using System; using System.Collections.Generic; using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; #if UNITY_2023_3_OR_NEWER using UnityEngine.Rendering.RenderGraphModule; #endif using UnityEngine.Rendering.Universal; using DebugView = RadiantGI.Universal.RadiantGlobalIllumination.DebugView; using DeferredLights = UnityEngine.Rendering.Universal.Internal.DeferredLights; namespace RadiantGI.Universal { [HelpURL("https://kronnect.com/docs/radiant-gi-urp/")] public partial class RadiantRenderFeature : ScriptableRendererFeature { public static bool IsAmbientProbeValid(SphericalHarmonicsL2 probe) { if (RenderSettings.ambientIntensity < 0.001f) return true; if (RenderSettings.ambientMode == AmbientMode.Flat && RenderSettings.ambientLight.maxColorComponent < 0.001f) return true; if (RenderSettings.ambientMode == AmbientMode.Trilight && RenderSettings.ambientSkyColor.maxColorComponent < 0.001f && RenderSettings.ambientEquatorColor.maxColorComponent < 0.001f && RenderSettings.ambientGroundColor.maxColorComponent < 0.001f) return true; float sumSq = 0f; for (int rgb = 0; rgb < 3; rgb++) { for (int i = 0; i < 9; i++) { float v = probe[rgb, i]; sumSq += v * v; } } return sumSq > 1e-6f; } public enum RenderingPath { [InspectorName("Forward | Forward+")] Forward, [InspectorName("Deferred | Deferred+")] Deferred, Both } enum Pass { CopyExact, Raycast, TemporalAccum, Albedo, Normals, Compose, Compare, FinalGIDebug, Copy, WideFilter, Depth, CopyDepth, RSM, NFO, NFOBlur, RayAccum, MotionVectors } static readonly List emitters = new List(); static bool emittersForceRefresh; static readonly List transparentSupport = new List(); static class ShaderParams { // targets public static int MainTex = Shader.PropertyToID("_MainTex"); public static int ResolveRT = Shader.PropertyToID("_ResolveRT"); public static int SourceSize = Shader.PropertyToID("_SourceSize"); public static int NoiseTex = Shader.PropertyToID("_NoiseTex"); public static int Downscaled1RT = Shader.PropertyToID("_Downscaled1RT"); public static int Downscaled2RT = Shader.PropertyToID("_Downscaled2RT"); public static int InputRT = Shader.PropertyToID("_InputRTGI"); public static int CompareTex = Shader.PropertyToID("_CompareTexGI"); public static int PrevResolve = Shader.PropertyToID("_RadiantPrevResolve"); public static int RayBufferRT = Shader.PropertyToID("_RadiantRayBufferRT"); public static int RayConfidenceRT = Shader.PropertyToID("_RadiantRayConfidenceRT"); public static int DownscaledDepthRT = Shader.PropertyToID("_DownscaledDepthRT"); public static int TransparentDepthTexture = Shader.PropertyToID("_RadiantTransparentDepthTexture"); public static int RadiantGITexture = Shader.PropertyToID("_RadiantGITexture"); public static int Probe1Cube = Shader.PropertyToID("_Probe1Cube"); public static int Probe2Cube = Shader.PropertyToID("_Probe2Cube"); public static int NFO_RT = Shader.PropertyToID("_NFO_RT"); public static int NFOBlurRT = Shader.PropertyToID("_NFOBlurRT"); public static int RSMBuffer = Shader.PropertyToID("_RadiantRSMBuffer"); // RG supplemental ids public static int CameraDepthTexture = Shader.PropertyToID("_CameraDepthTexture"); public static int CameraNormalsTexture = Shader.PropertyToID("_CameraNormalsTexture"); public static int MotionVectorTexture = Shader.PropertyToID("_MotionVectorTexture"); public static int CameraGBuffer0 = Shader.PropertyToID("_GBuffer0"); public static int CameraGBuffer1 = Shader.PropertyToID("_GBuffer1"); public static int CameraGBuffer2 = Shader.PropertyToID("_GBuffer2"); // uniforms public static int IndirectData = Shader.PropertyToID("_IndirectData"); public static int RayData = Shader.PropertyToID("_RayData"); public static int TemporalData = Shader.PropertyToID("_TemporalData"); public static int TemporalData2 = Shader.PropertyToID("_TemporalData2"); public static int WorldToViewDir = Shader.PropertyToID("_WorldToViewDir"); public static int PrevInvViewProj = Shader.PropertyToID("_PrevInvViewProj"); public static int CompareParams = Shader.PropertyToID("_CompareParams"); public static int ExtraData = Shader.PropertyToID("_ExtraData"); public static int ExtraData2 = Shader.PropertyToID("_ExtraData2"); public static int ExtraData3 = Shader.PropertyToID("_ExtraData3"); public static int ExtraData4 = Shader.PropertyToID("_ExtraData4"); public static int ExtraData5 = Shader.PropertyToID("_ExtraData5"); public static int EmittersPositions = Shader.PropertyToID("_EmittersPositions"); public static int EmittersBoxMin = Shader.PropertyToID("_EmittersBoxMin"); public static int EmittersBoxMax = Shader.PropertyToID("_EmittersBoxMax"); public static int EmittersColors = Shader.PropertyToID("_EmittersColors"); public static int EmittersCount = Shader.PropertyToID("_EmittersCount"); public static int RSMIntensity = Shader.PropertyToID("_RadiantShadowMapIntensity"); public static int StencilValue = Shader.PropertyToID("_StencilValue"); public static int StencilCompareFunction = Shader.PropertyToID("_StencilCompareFunction"); public static int ProbeData = Shader.PropertyToID("_ProbeData"); public static int Probe1HDR = Shader.PropertyToID("_Probe1HDR"); public static int Probe2HDR = Shader.PropertyToID("_Probe2HDR"); public static int Probe1BoxMin = Shader.PropertyToID("_Probe1BoxMin"); public static int Probe1BoxMax = Shader.PropertyToID("_Probe1BoxMax"); public static int Probe1ProbePosition = Shader.PropertyToID("_Probe1ProbePosition"); public static int Probe2BoxMin = Shader.PropertyToID("_Probe2BoxMin"); public static int Probe2BoxMax = Shader.PropertyToID("_Probe2BoxMax"); public static int Probe2ProbePosition = Shader.PropertyToID("_Probe2ProbePosition"); public static int BoundsXZ = Shader.PropertyToID("_BoundsXZ"); public static int DebugDepthMultiplier = Shader.PropertyToID("_DebugDepthMultiplier"); public static int DebugMotionVectorMultiplier = Shader.PropertyToID("_DebugMotionVectorMultiplier"); public static int NFOTint = Shader.PropertyToID("_NFOTint"); public static int FallbackDefaultAmbient = Shader.PropertyToID("_FallbackDefaultAmbient"); public static int OrganicLightData = Shader.PropertyToID("_OrganicLightData"); public static int OrganicLightTint = Shader.PropertyToID("_OrganicLightTint"); public static int OrganicLightOffset = Shader.PropertyToID("_OrganicLightOffset"); // keywords public const string SKW_FORWARD = "_FORWARD"; public const string SKW_FORWARD_AND_DEFERRED = "_FORWARD_AND_DEFERRED"; public const string SKW_USES_MULTIPLE_RAYS = "_USES_MULTIPLE_RAYS"; public const string SKW_REUSE_RAYS = "_REUSE_RAYS"; public const string SKW_ONE_EXTRA_BOUNCE = "_ONE_EXTRA_BOUNCE"; public const string SKW_FALLBACK_1_PROBE = "_FALLBACK_1_PROBE"; public const string SKW_FALLBACK_2_PROBES = "_FALLBACK_2_PROBES"; public const string SKW_FALLBACK_PROBE_ATLAS = "_FALLBACK_PROBE_ATLAS"; public const string SKW_VIRTUAL_EMITTERS = "_VIRTUAL_EMITTERS"; public const string SKW_USES_NEAR_FIELD_OBSCURANCE = "_USES_NEAR_FIELD_OBSCURANCE"; public const string SKW_ORTHO_SUPPORT = "_ORTHO_SUPPORT"; public const string SKW_DISTANCE_BLENDING = "_DISTANCE_BLENDING"; public const string SKW_FALLBACK_RSM = "_FALLBACK_RSM"; public const string SKW_TRANSPARENT_DEPTH_PREPASS = "_TRANSPARENT_DEPTH_PREPASS"; } static Mesh _fullScreenMesh; static Mesh fullscreenMesh { get { if (_fullScreenMesh != null) { return _fullScreenMesh; } float num = 1f; float num2 = 0f; Mesh val = new Mesh(); _fullScreenMesh = val; _fullScreenMesh.SetVertices(new List { new Vector3 (-1f, -1f, 0f), new Vector3 (-1f, 1f, 0f), new Vector3 (1f, -1f, 0f), new Vector3 (1f, 1f, 0f) }); _fullScreenMesh.SetUVs(0, new List { new Vector2 (0f, num2), new Vector2 (0f, num), new Vector2 (1f, num2), new Vector2 (1f, num) }); _fullScreenMesh.SetIndices(new int[6] { 0, 1, 2, 2, 1, 3 }, (MeshTopology)0, 0, false); _fullScreenMesh.UploadMeshData(true); return _fullScreenMesh; } } partial class RadiantPass : ScriptableRenderPass { const string RGI_CBUF_NAME = "RadiantGI"; const float GOLDEN_RATIO = 0.618033989f; const int MAX_EMITTERS = 32; const float TEMPORAL_CAMERA_TRANSLATION_RESPONSE = 200f; class PerCameraData { public Vector3 prevCameraRefPos; public Matrix4x4 prevInvViewProj; public bool prevInvViewProjValid; // Guard against duplicate execution for identical camera state within the same frame. public int lastExecutedFrame = -1; public Matrix4x4 lastExecutionViewProj; public bool lastExecutionViewProjValid; // Double-buffered accumulation RTs public RenderTexture[] rtAcum = new RenderTexture[2]; public int rtAcumIndex; public RenderTexture rtBounce; // Double-buffered ray temporal RTs public RenderTexture[] rtRayAcum = new RenderTexture[2]; public RenderTexture[] rtRaySamples = new RenderTexture[2]; public int rtRayIndex; public int bufferCreationFrame; // emitters public float emittersSortTime = float.MinValue; public Vector3 emittersLastCameraPosition; public readonly List emittersSorted = new List(); } UniversalRenderer renderer; static RadiantRenderFeature settings; static RenderTextureDescriptor sourceDesc, cameraTargetDesc; static readonly Dictionary prevs = new Dictionary(); static RadiantGlobalIllumination radiant; static float goldenRatioAcum; static bool usesCompareMode; static readonly Func emittersSortFunction = EmittersDistanceComparer; static Vector3 camPos; static Volume[] volumes; static Material mat; static readonly Vector4 unlimitedBounds = new Vector4(-1e8f, -1e8f, 1e8f, 1e8f); static Vector4[] emittersBoxMin, emittersBoxMax, emittersColors, emittersPositions; static readonly Plane[] cameraPlanes = new Plane[6]; static public RenderTargetIdentifier computedGIRT; static public bool computedGIRTValid; // SH coefficients for ambient lighting static readonly List probes = new List(); public static MaterialPropertyBlock probesProps; // Cached gradient ambient probe static SphericalHarmonicsL2 cachedGradientProbe; static bool cachedGradientProbeValid; static bool cachedAmbientSettingsValid; static AmbientMode cachedAmbientMode; static Color cachedAmbientSkyColor; static Color cachedAmbientEquatorColor; static Color cachedAmbientGroundColor; static Color cachedAmbientLightColor; static float cachedAmbientIntensity; static readonly RenderTargetIdentifier[] rayTemporalMRT = new RenderTargetIdentifier[2]; public bool Setup (RadiantGlobalIllumination radiant, ScriptableRenderer renderer, RadiantRenderFeature settings, bool isSceneView) { if (!radiant.IsActive()) return false; RadiantPass.radiant = radiant; #if UNITY_EDITOR if (isSceneView && !radiant.showInSceneView.value) return false; if (!Application.isPlaying && !radiant.showInEditMode.value) return false; #endif usesCompareMode = radiant.compareMode.value && !isSceneView; renderPassEvent = RenderPassEvent.AfterRenderingSkybox + 2; // just after depth texture and motion vectors #if UNITY_2022_1_OR_NEWER && !UNITY_2022_3_OR_NEWER // when using motion vectors in RG, it needs to execute after motion vector pass which happens BeforeRenderingPostProcessing so we force Radiant to run just before post processing // in Unity 2022.3 it was fixed, so motion vectors just follow the depth texture mode pass so we can keep Radiant run before transparent int minRenderPassEvent = (int)RenderPassEvent.BeforeRenderingPostProcessing; if ((int)renderPassEvent < minRenderPassEvent) { renderPassEvent = (RenderPassEvent)minRenderPassEvent; } #endif this.renderer = renderer as UniversalRenderer; RadiantPass.settings = settings; if (mat == null) { mat = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Kronnect/RadiantGI_URP")); mat.SetTexture(ShaderParams.NoiseTex, Resources.Load("RadiantGI/blueNoiseGI128RGB")); } mat.SetInt(ShaderParams.StencilValue, radiant.stencilValue.value); mat.SetInt(ShaderParams.StencilCompareFunction, radiant.stencilCheck.value ? (int)radiant.stencilCompareFunction.value : (int)CompareFunction.Always); return true; } ScriptableRenderPassInput GetRequiredInputs () { ScriptableRenderPassInput input = ScriptableRenderPassInput.Depth | ScriptableRenderPassInput.Motion; // Only request Normal input in forward mode - in deferred, use G-buffer normals instead if (settings != null && settings.renderingPath != RenderingPath.Deferred) { input |= ScriptableRenderPassInput.Normal; } return input; } static bool AreMatricesSimilar(Matrix4x4 a, Matrix4x4 b) { const float epsilon = 1e-6f; for (int i = 0; i < 16; i++) { if (Mathf.Abs(a[i] - b[i]) > epsilon) { return false; } } return true; } readonly PassData passData = new PassData(); class PassData { public CommandBuffer cmd; public Camera cam; public NativeArray reflectionProbes; public bool usesProbeAtlas; #if UNITY_2022_2_OR_NEWER public RTHandle source; #else public RenderTargetIdentifier source; #endif #if UNITY_2023_3_OR_NEWER public TextureHandle colorTexture, depthTexture, cameraNormalsTexture, motionVectorTexture, gBuffer0, gBuffer1, gBuffer2; #endif } #if UNITY_2023_3_OR_NEWER public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { using (var builder = renderGraph.AddUnsafePass("Radiant GI RG Pass", out var passData)) { builder.AllowPassCulling(false); ConfigureInput(GetRequiredInputs()); UniversalCameraData cameraData = frameData.Get(); UniversalRenderingData renderingData = frameData.Get(); passData.cam = cameraData.camera; passData.reflectionProbes = renderingData.cullResults.visibleReflectionProbes; sourceDesc = cameraData.cameraTargetDescriptor; sourceDesc.colorFormat = RenderTextureFormat.ARGBHalf; sourceDesc.useMipMap = false; sourceDesc.msaaSamples = 1; sourceDesc.depthBufferBits = 0; cameraTargetDesc = sourceDesc; float downsampling = radiant.downsampling.value; sourceDesc.width = (int)(sourceDesc.width / downsampling); sourceDesc.height = (int)(sourceDesc.height / downsampling); UniversalResourceData resourceData = frameData.Get(); passData.colorTexture = resourceData.activeColorTexture; builder.UseTexture(resourceData.activeColorTexture, AccessFlags.ReadWrite); if (settings.renderingPath == RenderingPath.Forward) { builder.UseTexture(resourceData.cameraDepthTexture); passData.depthTexture = resourceData.cameraDepthTexture; builder.UseTexture(resourceData.cameraNormalsTexture); passData.cameraNormalsTexture = resourceData.cameraNormalsTexture; } else if (settings.renderingPath == RenderingPath.Deferred) { if (resourceData.gBuffer[0].IsValid()) { builder.UseTexture(resourceData.gBuffer[0]); passData.gBuffer0 = resourceData.gBuffer[0]; } if (resourceData.gBuffer[1].IsValid()) { builder.UseTexture(resourceData.gBuffer[1]); passData.gBuffer1 = resourceData.gBuffer[1]; } if (resourceData.gBuffer[2].IsValid()) { builder.UseTexture(resourceData.gBuffer[2]); passData.gBuffer2 = resourceData.gBuffer[2]; } } else { // Both mode - use normals texture for forward compatibility builder.UseTexture(resourceData.cameraNormalsTexture); passData.cameraNormalsTexture = resourceData.cameraNormalsTexture; } builder.UseTexture(resourceData.motionVectorColor); passData.motionVectorTexture = resourceData.motionVectorColor; #if UNITY_6000_1_OR_NEWER bool usesProbeAtlas = false; int renderingMode = (int)renderer.renderingModeActual; bool isClusterLighting = (renderingMode == 2 || renderingMode == 3); if (isClusterLighting) { UniversalLightData lightData = frameData.Get(); usesProbeAtlas = lightData.reflectionProbeAtlas; } passData.usesProbeAtlas = usesProbeAtlas; #else passData.usesProbeAtlas = false; #endif builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => { CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); if (passData.depthTexture.IsValid()) { cmd.SetGlobalTexture(ShaderParams.CameraDepthTexture, passData.depthTexture); } if (passData.motionVectorTexture.IsValid()) { cmd.SetGlobalTexture(ShaderParams.MotionVectorTexture, passData.motionVectorTexture); } if (settings.renderingPath == RenderingPath.Deferred) { if (passData.gBuffer0.IsValid()) { cmd.SetGlobalTexture(ShaderParams.CameraGBuffer0, passData.gBuffer0); } if (passData.gBuffer1.IsValid()) { cmd.SetGlobalTexture(ShaderParams.CameraGBuffer1, passData.gBuffer1); } if (passData.gBuffer2.IsValid()) { cmd.SetGlobalTexture(ShaderParams.CameraGBuffer2, passData.gBuffer2); // Bind GBuffer2 to _CameraNormalsTexture so LoadSceneNormals can use it cmd.SetGlobalTexture(ShaderParams.CameraNormalsTexture, passData.gBuffer2); } } else if (passData.cameraNormalsTexture.IsValid()) { cmd.SetGlobalTexture(ShaderParams.CameraNormalsTexture, passData.cameraNormalsTexture); } passData.source = passData.colorTexture; passData.cmd = cmd; RenderGI(passData); }); } } #endif static SphericalHarmonicsL2 CreateAmbientProbeFromGradient (Color skyColor, Color equatorColor, Color groundColor, float intensity) { SphericalHarmonicsL2 probe = new SphericalHarmonicsL2(); Color linearSky = CoreUtils.ConvertSRGBToActiveColorSpace(skyColor) * intensity; Color linearEquator = CoreUtils.ConvertSRGBToActiveColorSpace(equatorColor) * intensity; Color linearGround = CoreUtils.ConvertSRGBToActiveColorSpace(groundColor) * intensity; probe[0, 0] = (linearSky.r + linearEquator.r + linearGround.r) / 3.0f; probe[1, 0] = (linearSky.g + linearEquator.g + linearGround.g) / 3.0f; probe[2, 0] = (linearSky.b + linearEquator.b + linearGround.b) / 3.0f; probe[0, 2] = (linearSky.r - linearGround.r) * 0.5f; probe[1, 2] = (linearSky.g - linearGround.g) * 0.5f; probe[2, 2] = (linearSky.b - linearGround.b) * 0.5f; return probe; } static SphericalHarmonicsL2 CreateAmbientProbeFromColor(Color ambientColor, float intensity) { Color linearAmbient = CoreUtils.ConvertSRGBToActiveColorSpace(ambientColor) * intensity; SphericalHarmonicsL2 probe = new SphericalHarmonicsL2(); probe[0, 0] = linearAmbient.r; probe[1, 0] = linearAmbient.g; probe[2, 0] = linearAmbient.b; return probe; } static void RenderGI (PassData passData) { computedGIRTValid = false; #if UNITY_2022_2_OR_NEWER RTHandle source = passData.source; #else RenderTargetIdentifier source = passData.source; #endif CommandBuffer cmd = passData.cmd; Camera cam = passData.cam; camPos = cam.transform.position; // Setup SH coefficients for ambient lighting (needed by SampleSH in shader) SphericalHarmonicsL2 ambientProbe = RenderSettings.ambientProbe; // Detect if ambient probe is available/baked bool isAmbientProbeAvailable = IsAmbientProbeValid(ambientProbe); bool realtimeAmbientProbe = false; // Fallback: If ambientProbe is empty, use cached gradient probe or create it once if (!isAmbientProbeAvailable) { bool ambientSettingsChanged = !cachedAmbientSettingsValid || cachedAmbientMode != RenderSettings.ambientMode || cachedAmbientIntensity != RenderSettings.ambientIntensity || cachedAmbientSkyColor != RenderSettings.ambientSkyColor || cachedAmbientEquatorColor != RenderSettings.ambientEquatorColor || cachedAmbientGroundColor != RenderSettings.ambientGroundColor || cachedAmbientLightColor != RenderSettings.ambientLight; if (!cachedGradientProbeValid || ambientSettingsChanged) { if (RenderSettings.ambientMode == AmbientMode.Flat) { cachedGradientProbe = CreateAmbientProbeFromColor( RenderSettings.ambientLight, RenderSettings.ambientIntensity ); } else { cachedGradientProbe = CreateAmbientProbeFromGradient( RenderSettings.ambientSkyColor, RenderSettings.ambientEquatorColor, RenderSettings.ambientGroundColor, RenderSettings.ambientIntensity ); } cachedGradientProbeValid = true; cachedAmbientSettingsValid = true; cachedAmbientMode = RenderSettings.ambientMode; cachedAmbientIntensity = RenderSettings.ambientIntensity; cachedAmbientSkyColor = RenderSettings.ambientSkyColor; cachedAmbientEquatorColor = RenderSettings.ambientEquatorColor; cachedAmbientGroundColor = RenderSettings.ambientGroundColor; cachedAmbientLightColor = RenderSettings.ambientLight; } ambientProbe = cachedGradientProbe; realtimeAmbientProbe = true; } if (probesProps == null) probesProps = new MaterialPropertyBlock(); probes.Clear(); probes.Add(ambientProbe); probesProps.CopySHCoefficientArraysFrom(probes); DebugView debugView = radiant.debugView.value; bool usesBounce = radiant.rayBounce.value; bool usesForward = settings.renderingPath != RenderingPath.Deferred; float normalMapInfluence = radiant.normalMapInfluence.value; float downsampling = radiant.downsampling.value; int currentFrame = GetRenderFrameId(); bool usesRSM = RadiantShadowMap.installed && radiant.fallbackReflectiveShadowMap.value && radiant.reflectiveShadowMapIntensity.value > 0; bool usesEmitters = radiant.virtualEmitters.value; float fallbackAmbient = radiant.fallbackAmbient.value; float unityAmbientIntensity = radiant.unityAmbientLighting.value; bool transparencySupportEnabled = radiant.transparencySupport.value; float occlusionIntensity = radiant.occlusionIntensity.value; // pass radiant settings to shader mat.SetVector(ShaderParams.IndirectData, new Vector4(radiant.indirectIntensity.value, radiant.indirectMaxSourceBrightness.value, radiant.indirectDistanceAttenuation.value, radiant.rayReuse.value)); mat.SetVector(ShaderParams.RayData, new Vector4(radiant.rayCount.value, radiant.rayMaxLength.value, radiant.rayMaxSamples.value, radiant.thickness.value)); // some uniforms required by compare render feature so declared as global vectors instead of material properties cmd.SetGlobalVector(ShaderParams.ExtraData2, new Vector4(radiant.brightnessThreshold.value, radiant.brightnessMax.value, radiant.saturation.value, radiant.reflectiveShadowMapIntensity.value)); // global because these params are needed by the compare pass mat.DisableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED); mat.DisableKeyword(ShaderParams.SKW_FORWARD); if (usesForward) { if (settings.renderingPath == RenderingPath.Both) { mat.EnableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED); } else { mat.EnableKeyword(ShaderParams.SKW_FORWARD); } } if (radiant.rayCount.value > 1) { mat.EnableKeyword(ShaderParams.SKW_USES_MULTIPLE_RAYS); } else { mat.DisableKeyword(ShaderParams.SKW_USES_MULTIPLE_RAYS); } float nearFieldObscurance = radiant.nearFieldObscurance.value; bool useNFO = nearFieldObscurance > 0; cmd.SetGlobalVector(ShaderParams.ExtraData4, new Vector4( useNFO ? radiant.nearFieldObscuranceMaxCameraDistance.value : 0f, useNFO ? (1f - radiant.nearFieldObscuranceOccluderDistance.value) * 10f : 0f, 1f - unityAmbientIntensity, realtimeAmbientProbe ? 1f : 0f)); if (useNFO) { cmd.SetGlobalColor(ShaderParams.NFOTint, radiant.nearFieldObscuranceTintColor.value); mat.EnableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE); } else { mat.DisableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE); } if (cam.orthographic) { mat.EnableKeyword(ShaderParams.SKW_ORTHO_SUPPORT); } else { mat.DisableKeyword(ShaderParams.SKW_ORTHO_SUPPORT); } cmd.SetGlobalVector(ShaderParams.ExtraData3, new Vector4(radiant.aoInfluence.value, radiant.nearFieldObscuranceSpread.value * 0.5f, 1f / (radiant.nearCameraAttenuation.value + 0.0001f), nearFieldObscurance)); // global because these params are needed by the compare pass // restricts to volume bounds? SetupVolumeBounds(cmd); // pass reprojection & other raymarch data goldenRatioAcum += GOLDEN_RATIO * radiant.rayCount.value; goldenRatioAcum %= 5000; cmd.SetGlobalVector(ShaderParams.SourceSize, new Vector4(cameraTargetDesc.width, cameraTargetDesc.height, goldenRatioAcum, currentFrame)); float baseBlurSpread = Mathf.Max(radiant.blurSpread.value, sourceDesc.width / 1920f); cmd.SetGlobalVector(ShaderParams.ExtraData, new Vector4(radiant.rayJitter.value, baseBlurSpread, normalMapInfluence, occlusionIntensity)); Vector4 extraData5 = new Vector4(downsampling, radiant.sourceBrightness.value, radiant.giWeight.value, fallbackAmbient); cmd.SetGlobalVector(ShaderParams.ExtraData5, extraData5); // pass UNITY_MATRIX_V cmd.SetGlobalMatrix(ShaderParams.WorldToViewDir, cam.worldToCameraMatrix); // create downscaled depth RenderTextureDescriptor downDesc = cameraTargetDesc; downDesc.width = Mathf.Min(sourceDesc.width, downDesc.width / 2); downDesc.height = Mathf.Min(sourceDesc.height, downDesc.height / 2); int downHalfDescWidth = downDesc.width; int downHalfDescHeight = downDesc.height; // depth buffer handling if (transparencySupportEnabled) { bool hasLayerMask = radiant.transparentLayerMask.value != 0; bool hasScriptObjects = transparentSupport.Count > 0; if (hasLayerMask || hasScriptObjects) { mat.EnableKeyword(ShaderParams.SKW_TRANSPARENT_DEPTH_PREPASS); } else { mat.DisableKeyword(ShaderParams.SKW_TRANSPARENT_DEPTH_PREPASS); } } int downsamplingDepth = 9 - radiant.raytracerAccuracy.value; RenderTextureDescriptor rtDownDepth = sourceDesc; rtDownDepth.width = Mathf.CeilToInt((float)rtDownDepth.width / downsamplingDepth); rtDownDepth.height = Mathf.CeilToInt((float)rtDownDepth.height / downsamplingDepth); #if UNITY_WEBGL rtDownDepth.colorFormat = RenderTextureFormat.RFloat; #else rtDownDepth.colorFormat = cam.orthographic ? RenderTextureFormat.RFloat : RenderTextureFormat.RHalf; #endif rtDownDepth.sRGB = false; GetTemporaryRT(cmd, ShaderParams.DownscaledDepthRT, ref rtDownDepth, FilterMode.Point); FullScreenBlit(cmd, ShaderParams.DownscaledDepthRT, Pass.CopyDepth); // are we reusing rays? if (!prevs.TryGetValue(cam, out PerCameraData frameAcumData)) { prevs[cam] = frameAcumData = new PerCameraData(); frameAcumData.bufferCreationFrame = currentFrame; } Matrix4x4 proj = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true); Matrix4x4 view = cam.worldToCameraMatrix; Matrix4x4 currViewProj = proj * view; // Only treat as duplicate when frame index and camera matrices match. // This avoids false positives during editor repaints/camera pivoting where // frame counters may not advance consistently. bool isSameFrame = frameAcumData.lastExecutedFrame == currentFrame; bool isSameViewProj = frameAcumData.lastExecutionViewProjValid && AreMatricesSimilar(frameAcumData.lastExecutionViewProj, currViewProj); bool isDoubleExecution = isSameFrame && isSameViewProj; frameAcumData.lastExecutedFrame = currentFrame; frameAcumData.lastExecutionViewProj = currViewProj; frameAcumData.lastExecutionViewProjValid = true; Matrix4x4 currInvViewProj = currViewProj.inverse; if (!frameAcumData.prevInvViewProjValid) { frameAcumData.prevInvViewProj = currInvViewProj; frameAcumData.prevInvViewProjValid = true; } cmd.SetGlobalMatrix(ShaderParams.PrevInvViewProj, frameAcumData.prevInvViewProj); frameAcumData.prevInvViewProj = currInvViewProj; // early debug views switch (debugView) { case DebugView.Albedo: FullScreenBlit(cmd, source, Pass.Albedo); return; case DebugView.Normals: FullScreenBlit(cmd, source, Pass.Normals); return; case DebugView.Depth: mat.SetFloat(ShaderParams.DebugDepthMultiplier, radiant.debugDepthMultiplier.value); FullScreenBlit(cmd, source, Pass.Depth); return; case DebugView.MotionVectors: mat.SetFloat(ShaderParams.DebugMotionVectorMultiplier, radiant.debugMotionVectorMultiplier.value); FullScreenBlit(cmd, source, Pass.MotionVectors); return; } RenderTexture bounceRT = frameAcumData.rtBounce; RenderTargetIdentifier raycastInput = source; if (usesBounce) { if (bounceRT != null && (bounceRT.width != cameraTargetDesc.width || bounceRT.height != cameraTargetDesc.height)) { bounceRT.Release(); bounceRT = null; } if (bounceRT == null) { bounceRT = new RenderTexture(cameraTargetDesc); bounceRT.Create(); frameAcumData.rtBounce = bounceRT; frameAcumData.bufferCreationFrame = currentFrame; } else { if (currentFrame - frameAcumData.bufferCreationFrame > 2) { raycastInput = bounceRT; // only uses bounce rt a few frames after it's created } } } else if (bounceRT != null) { bounceRT.Release(); DestroyImmediate(bounceRT); } // virtual emitters if (usesEmitters) { float now = Time.time; if (emittersForceRefresh) { emittersForceRefresh = false; foreach (PerCameraData cameraData in prevs.Values) { cameraData.emittersSortTime = float.MinValue; } } if (now - frameAcumData.emittersSortTime > 5 || (frameAcumData.emittersLastCameraPosition - camPos).sqrMagnitude > 25) { frameAcumData.emittersSortTime = now; frameAcumData.emittersLastCameraPosition = camPos; SortEmitters(); frameAcumData.emittersSorted.Clear(); frameAcumData.emittersSorted.AddRange(emitters); } usesEmitters = SetupEmitters(cam, frameAcumData.emittersSorted); } if (usesEmitters) { mat.EnableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS); } else { mat.DisableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS); } // set the fallback mode mat.DisableKeyword(ShaderParams.SKW_REUSE_RAYS); mat.DisableKeyword(ShaderParams.SKW_ONE_EXTRA_BOUNCE); mat.DisableKeyword(ShaderParams.SKW_FALLBACK_1_PROBE); mat.DisableKeyword(ShaderParams.SKW_FALLBACK_2_PROBES); mat.DisableKeyword(ShaderParams.SKW_FALLBACK_RSM); #if UNITY_6000_1_OR_NEWER mat.DisableKeyword(ShaderParams.SKW_FALLBACK_PROBE_ATLAS); #endif // Enable extra bounce keyword when bounce is active (for motion vector compensation) if (usesBounce && currentFrame - frameAcumData.bufferCreationFrame > 2) { mat.EnableKeyword(ShaderParams.SKW_ONE_EXTRA_BOUNCE); } if (radiant.fallbackReflectionProbes.value) { #if UNITY_6000_1_OR_NEWER if (passData.usesProbeAtlas) { // Forward+/Deferred+ with probe atlas - use cluster-based sampling // Skip calling SetupProbes() - no manual probe selection needed mat.EnableKeyword(ShaderParams.SKW_FALLBACK_PROBE_ATLAS); cmd.SetGlobalVector(ShaderParams.ProbeData, new Vector4(0, 0, radiant.probesIntensity.value, 0)); } else #endif { // Traditional Forward/Deferred path or Unity < 6.1 if (SetupProbes(cmd, passData.reflectionProbes, out int numProbes)) { mat.EnableKeyword(numProbes == 1 ? ShaderParams.SKW_FALLBACK_1_PROBE : ShaderParams.SKW_FALLBACK_2_PROBES); } } } if (radiant.fallbackReuseRays.value && currentFrame - frameAcumData.bufferCreationFrame > 2 && radiant.rayReuse.value > 0 && frameAcumData.rtAcum[frameAcumData.rtAcumIndex] != null) { RenderTargetIdentifier reuseRaysPrevRT = new RenderTargetIdentifier(frameAcumData.rtAcum[frameAcumData.rtAcumIndex], 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.PrevResolve, reuseRaysPrevRT); mat.EnableKeyword(ShaderParams.SKW_REUSE_RAYS); } if (usesRSM) { GetTemporaryRT(cmd, ShaderParams.RSMBuffer, ref downDesc, FilterMode.Bilinear); FullScreenBlit(cmd, ShaderParams.RSMBuffer, Pass.RSM); mat.EnableKeyword(ShaderParams.SKW_FALLBACK_RSM); } float invIndirectIntensity = radiant.indirectIntensity.value > 0f ? 1f / radiant.indirectIntensity.value : 0f; cmd.SetGlobalColor(ShaderParams.FallbackDefaultAmbient, radiant.fallbackDefaultAmbient.value * invIndirectIntensity); // raycast & resolve GetTemporaryRT(cmd, ShaderParams.ResolveRT, ref sourceDesc, FilterMode.Bilinear); FullScreenBlit(cmd, raycastInput, ShaderParams.ResolveRT, Pass.Raycast, probesProps); // Double-buffer indices: read from current, write to next // On double execution, undo the swap from the first execution to read/write the same buffers int rayReadIndex = isDoubleExecution ? (1 - frameAcumData.rtRayIndex) : frameAcumData.rtRayIndex; int rayWriteIndex = 1 - rayReadIndex; RenderTexture rayRead = frameAcumData.rtRayAcum[rayReadIndex]; RenderTexture rayWrite = frameAcumData.rtRayAcum[rayWriteIndex]; RenderTexture raySamplesRead = frameAcumData.rtRaySamples[rayReadIndex]; RenderTexture raySamplesWrite = frameAcumData.rtRaySamples[rayWriteIndex]; // Check if buffers need resizing bool needsRayResize = (rayRead != null && (rayRead.width != sourceDesc.width || rayRead.height != sourceDesc.height)) || (rayWrite != null && (rayWrite.width != sourceDesc.width || rayWrite.height != sourceDesc.height)); RenderTextureDescriptor raySamplesDesc = sourceDesc; #if UNITY_WEBGL raySamplesDesc.colorFormat = RenderTextureFormat.RFloat; #else raySamplesDesc.colorFormat = RenderTextureFormat.RHalf; #endif raySamplesDesc.sRGB = false; bool needsSamplesResize = (raySamplesRead != null && (raySamplesRead.width != sourceDesc.width || raySamplesRead.height != sourceDesc.height)) || (raySamplesWrite != null && (raySamplesWrite.width != sourceDesc.width || raySamplesWrite.height != sourceDesc.height)); // Release and recreate if size changed if (needsRayResize) { for (int i = 0; i < 2; i++) { if (frameAcumData.rtRayAcum[i] != null) { frameAcumData.rtRayAcum[i].Release(); DestroyImmediate(frameAcumData.rtRayAcum[i]); frameAcumData.rtRayAcum[i] = null; } } frameAcumData.bufferCreationFrame = currentFrame; rayRead = rayWrite = null; } if (needsSamplesResize) { for (int i = 0; i < 2; i++) { if (frameAcumData.rtRaySamples[i] != null) { frameAcumData.rtRaySamples[i].Release(); DestroyImmediate(frameAcumData.rtRaySamples[i]); frameAcumData.rtRaySamples[i] = null; } } raySamplesRead = raySamplesWrite = null; } // Create double buffers if needed for (int i = 0; i < 2; i++) { if (frameAcumData.rtRayAcum[i] == null) { frameAcumData.rtRayAcum[i] = new RenderTexture(sourceDesc); frameAcumData.rtRayAcum[i].Create(); frameAcumData.bufferCreationFrame = currentFrame; // Clear new ray accumulation buffer RenderTargetIdentifier initRT = new RenderTargetIdentifier(frameAcumData.rtRayAcum[i], 0, CubemapFace.Unknown, -1); cmd.SetRenderTarget(initRT, 0, CubemapFace.Unknown, -1); cmd.ClearRenderTarget(false, true, Color.clear); } if (frameAcumData.rtRaySamples[i] == null) { frameAcumData.rtRaySamples[i] = new RenderTexture(raySamplesDesc) { filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp }; frameAcumData.rtRaySamples[i].Create(); // Clear new sample buffer RenderTargetIdentifier initRT = new RenderTargetIdentifier(frameAcumData.rtRaySamples[i], 0, CubemapFace.Unknown, -1); cmd.SetRenderTarget(initRT, 0, CubemapFace.Unknown, -1); cmd.ClearRenderTarget(false, true, Color.clear); } } // Get current read/write buffers after potential creation rayRead = frameAcumData.rtRayAcum[rayReadIndex]; rayWrite = frameAcumData.rtRayAcum[rayWriteIndex]; raySamplesRead = frameAcumData.rtRaySamples[rayReadIndex]; raySamplesWrite = frameAcumData.rtRaySamples[rayWriteIndex]; RenderTargetIdentifier rayReadRT = new RenderTargetIdentifier(rayRead, 0, CubemapFace.Unknown, -1); RenderTargetIdentifier rayWriteRT = new RenderTargetIdentifier(rayWrite, 0, CubemapFace.Unknown, -1); RenderTargetIdentifier raySamplesReadRT = new RenderTargetIdentifier(raySamplesRead, 0, CubemapFace.Unknown, -1); RenderTargetIdentifier raySamplesWriteRT = new RenderTargetIdentifier(raySamplesWrite, 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.RayBufferRT, rayReadRT); cmd.SetGlobalTexture(ShaderParams.RayConfidenceRT, raySamplesReadRT); // On double execution, skip temporal draws - the persistent RTs already have // the correct result from the first execution if (!isDoubleExecution) { float temporalBlend = 0.95f; float motionScale = radiant.temporalResponseSpeed.value / 1080f; float historyLength = radiant.temporalStabilization.value; Vector3 refPos = camPos + cam.transform.forward; float camTranslationDelta = 0f; if (frameAcumData.rtAcum[0] != null) { camTranslationDelta = Vector3.Distance(refPos, frameAcumData.prevCameraRefPos) * TEMPORAL_CAMERA_TRANSLATION_RESPONSE; camTranslationDelta = Mathf.Clamp01(camTranslationDelta); } frameAcumData.prevCameraRefPos = refPos; mat.SetVector(ShaderParams.TemporalData, new Vector4(temporalBlend, motionScale, historyLength, camTranslationDelta)); mat.SetVector(ShaderParams.TemporalData2, new Vector4(radiant.darkThreshold.value, radiant.darkThresholdMultiplier.value, 0, 0)); // Double buffering: render directly to write buffers (no copy needed) rayTemporalMRT[0] = rayWriteRT; rayTemporalMRT[1] = raySamplesWriteRT; cmd.SetRenderTarget(rayTemporalMRT, BuiltinRenderTextureType.None); cmd.SetGlobalTexture(ShaderParams.MainTex, ShaderParams.ResolveRT); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)Pass.RayAccum); frameAcumData.rtRayIndex = rayWriteIndex; } // Prepare NFO if (useNFO) { RenderTextureDescriptor nfoDesc = downDesc; nfoDesc.colorFormat = RenderTextureFormat.RHalf; GetTemporaryRT(cmd, ShaderParams.NFO_RT, ref nfoDesc, FilterMode.Bilinear); GetTemporaryRT(cmd, ShaderParams.NFOBlurRT, ref nfoDesc, FilterMode.Bilinear); FullScreenBlit(cmd, ShaderParams.NFOBlurRT, Pass.NFO); FullScreenBlit(cmd, ShaderParams.NFOBlurRT, ShaderParams.NFO_RT, Pass.NFOBlur); cmd.ReleaseTemporaryRT(ShaderParams.NFOBlurRT); } // downscale & blur GetTemporaryRT(cmd, ShaderParams.Downscaled2RT, ref downDesc, FilterMode.Bilinear); int lastBlurRT = ShaderParams.Downscaled2RT; RenderTargetIdentifier raySource = rayWriteRT; if (downsampling <= 1f) { GetTemporaryRT(cmd, ShaderParams.Downscaled1RT, ref downDesc, FilterMode.Bilinear); FullScreenBlit(cmd, raySource, ShaderParams.Downscaled1RT, Pass.Copy); FullScreenBlit(cmd, ShaderParams.Downscaled1RT, ShaderParams.Downscaled2RT, Pass.WideFilter); } else { cmd.SetGlobalVector(ShaderParams.ExtraData, new Vector4(radiant.rayJitter.value, baseBlurSpread * 1.5f, normalMapInfluence, occlusionIntensity)); FullScreenBlit(cmd, raySource, ShaderParams.Downscaled2RT, Pass.WideFilter); } computedGIRT = lastBlurRT; // Double-buffer indices for final accumulation // On double execution, undo the swap from the first execution int acumReadIndex = isDoubleExecution ? (1 - frameAcumData.rtAcumIndex) : frameAcumData.rtAcumIndex; int acumWriteIndex = 1 - acumReadIndex; RenderTextureDescriptor acumDesc = sourceDesc; acumDesc.width = downHalfDescWidth; acumDesc.height = downHalfDescHeight; // Check if buffers need resizing bool needsAcumResize = false; for (int i = 0; i < 2; i++) { if (frameAcumData.rtAcum[i] != null && (frameAcumData.rtAcum[i].width != downHalfDescWidth || frameAcumData.rtAcum[i].height != downHalfDescHeight)) { needsAcumResize = true; break; } } if (needsAcumResize) { for (int i = 0; i < 2; i++) { if (frameAcumData.rtAcum[i] != null) { frameAcumData.rtAcum[i].Release(); DestroyImmediate(frameAcumData.rtAcum[i]); frameAcumData.rtAcum[i] = null; } } frameAcumData.bufferCreationFrame = currentFrame; } Pass acumPass = Pass.TemporalAccum; bool isFirstFrame = false; // Create double buffers if needed for (int i = 0; i < 2; i++) { if (frameAcumData.rtAcum[i] == null) { frameAcumData.rtAcum[i] = new RenderTexture(acumDesc); frameAcumData.rtAcum[i].Create(); // Clear new accumulation buffer RenderTargetIdentifier initRT = new RenderTargetIdentifier(frameAcumData.rtAcum[i], 0, CubemapFace.Unknown, -1); cmd.SetRenderTarget(initRT, 0, CubemapFace.Unknown, -1); cmd.ClearRenderTarget(false, true, Color.clear); if (i == 0) { frameAcumData.prevCameraRefPos = camPos + cam.transform.forward; frameAcumData.bufferCreationFrame = currentFrame; isFirstFrame = true; } } } if (isFirstFrame) { acumPass = Pass.Copy; } RenderTexture acumRead = frameAcumData.rtAcum[acumReadIndex]; RenderTexture acumWrite = frameAcumData.rtAcum[acumWriteIndex]; RenderTargetIdentifier acumReadRT = new RenderTargetIdentifier(acumRead, 0, CubemapFace.Unknown, -1); RenderTargetIdentifier acumWriteRT = new RenderTargetIdentifier(acumWrite, 0, CubemapFace.Unknown, -1); // Double buffering: read from previous, write directly to next (no copy needed) // On double execution, skip temporal draw - persistent RT has correct result if (!isDoubleExecution) { cmd.SetGlobalTexture(ShaderParams.PrevResolve, acumReadRT); FullScreenBlit(cmd, computedGIRT, acumWriteRT, acumPass); // Swap buffer index for next frame frameAcumData.rtAcumIndex = acumWriteIndex; } computedGIRT = acumWriteRT; computedGIRTValid = true; // Expose GI texture globally for transparent shaders if transparency support is enabled if (transparencySupportEnabled) { cmd.SetGlobalTexture(ShaderParams.RadiantGITexture, computedGIRT); } // Prepare output blending GetTemporaryRT(cmd, ShaderParams.InputRT, ref cameraTargetDesc, FilterMode.Point); FullScreenBlit(cmd, source, ShaderParams.InputRT, Pass.CopyExact); if (usesCompareMode) { GetTemporaryRT(cmd, ShaderParams.CompareTex, ref cameraTargetDesc, FilterMode.Point); // needed by the compare pass if (usesBounce) { FullScreenBlit(cmd, computedGIRT, bounceRT, Pass.Compose, probesProps); FullScreenBlit(cmd, bounceRT, ShaderParams.CompareTex, Pass.CopyExact); } } else if (usesBounce) { FullScreenBlit(cmd, computedGIRT, bounceRT, Pass.Compose, probesProps); FullScreenBlit(cmd, bounceRT, source, Pass.CopyExact); } else { FullScreenBlit(cmd, computedGIRT, source, Pass.Compose, probesProps); } // Debug views that require all buffers to be generated switch (debugView) { case DebugView.Downscaled: FullScreenBlit(cmd, ShaderParams.Downscaled2RT, source, Pass.CopyExact); return; case DebugView.Raycast: FullScreenBlit(cmd, ShaderParams.ResolveRT, source, Pass.CopyExact); return; case DebugView.RaycastAccumulated: FullScreenBlit(cmd, rayWriteRT, source, Pass.CopyExact); return; case DebugView.ReflectiveShadowMap: if (usesRSM) { FullScreenBlit(cmd, source, Pass.RSM); } return; case DebugView.TemporalAccumulationBuffer: FullScreenBlit(cmd, computedGIRT, source, Pass.CopyExact); return; case DebugView.FinalGI: FullScreenBlit(cmd, computedGIRT, source, Pass.FinalGIDebug); return; } } static void GetTemporaryRT (CommandBuffer cmd, int nameID, ref RenderTextureDescriptor desc, FilterMode filterMode) { if (desc.width < 0) desc.width = 1; if (desc.height < 0) desc.height = 1; cmd.GetTemporaryRT(nameID, desc, filterMode); cmd.SetGlobalTexture(nameID, nameID); } static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier destination, Pass pass) { cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass); } static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass) { cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.MainTex, source); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass); } static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass, MaterialPropertyBlock props) { cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.MainTex, source); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass, props); } static float CalculateProbeWeight (Vector3 wpos, Vector3 probeBoxMin, Vector3 probeBoxMax, float blendDistance) { Vector3 weightDir = Vector3.Min(wpos - probeBoxMin, probeBoxMax - wpos) / blendDistance; return Mathf.Clamp01(Mathf.Min(weightDir.x, Mathf.Min(weightDir.y, weightDir.z))); } static bool SetupProbes (CommandBuffer cmd, NativeArray visibleProbes, out int numProbes) { numProbes = PickNearProbes(visibleProbes, out ReflectionProbe probe1, out ReflectionProbe probe2); if (numProbes == 0) return false; if (!probe1.bounds.Contains(camPos)) return false; if (numProbes >= 2 && !probe2.bounds.Contains(camPos)) numProbes = 1; float probe1Weight = 0, probe2Weight = 0; if (numProbes >= 1) { cmd.SetGlobalTexture(ShaderParams.Probe1Cube, probe1.texture); cmd.SetGlobalVector(ShaderParams.Probe1HDR, probe1.textureHDRDecodeValues); Bounds probe1Bounds = probe1.bounds; probe1Weight = CalculateProbeWeight(camPos, probe1Bounds.min, probe1Bounds.max, probe1.blendDistance); if (probe1.boxProjection) { Vector3 probe1Position = probe1.transform.position; cmd.SetGlobalVector(ShaderParams.Probe1BoxMin, probe1Bounds.min); cmd.SetGlobalVector(ShaderParams.Probe1BoxMax, probe1Bounds.max); cmd.SetGlobalVector(ShaderParams.Probe1ProbePosition, new Vector4(probe1Position.x, probe1Position.y, probe1Position.z, 1)); } else { cmd.SetGlobalVector(ShaderParams.Probe1ProbePosition, Vector4.zero); } } if (numProbes >= 2) { cmd.SetGlobalTexture(ShaderParams.Probe2Cube, probe2.texture); cmd.SetGlobalVector(ShaderParams.Probe2HDR, probe2.textureHDRDecodeValues); Bounds probe2Bounds = probe2.bounds; probe2Weight = CalculateProbeWeight(camPos, probe2Bounds.min, probe2Bounds.max, probe2.blendDistance); if (probe2.boxProjection) { Vector3 probe2Position = probe2.transform.position; cmd.SetGlobalVector(ShaderParams.Probe2BoxMin, probe2Bounds.min); cmd.SetGlobalVector(ShaderParams.Probe2BoxMax, probe2Bounds.max); cmd.SetGlobalVector(ShaderParams.Probe2ProbePosition, new Vector4(probe2Position.x, probe2Position.y, probe2Position.z, 1)); } else { cmd.SetGlobalVector(ShaderParams.Probe2ProbePosition, Vector4.zero); } } float probesIntensity = radiant.probesIntensity.value; cmd.SetGlobalVector(ShaderParams.ProbeData, new Vector4(probe1Weight * probesIntensity, probe2Weight * probesIntensity, probesIntensity, 0)); return true; } static int PickNearProbes (NativeArray visibleProbes, out ReflectionProbe probe1, out ReflectionProbe probe2) { probe1 = probe2 = null; int probesCount = visibleProbes.Length; if (probesCount == 0) return 0; float probe1Value = float.MaxValue; float probe2Value = float.MaxValue; for (int k = 0; k < probesCount; k++) { ReflectionProbe probe = visibleProbes[k].reflectionProbe; if (probe == null || probe.texture == null) continue; float probeValue = ComputeProbeValue(camPos, probe); if (probeValue < probe2Value) { probe2 = probe; probe2Value = probeValue; if (probe2Value < probe1Value) { // swap probe1 & probe2 float tempValue = probe1Value; ReflectionProbe tempProbe = probe1; probe1 = probe2; probe1Value = probe2Value; probe2 = tempProbe; probe2Value = tempValue; } } } if (probe1 == null) return 0; if (probe2 == null) return 1; return 2; } static float ComputeProbeValue (Vector3 camPos, ReflectionProbe probe) { Vector3 probePos = probe.transform.position; float d = (probePos - camPos).sqrMagnitude * (probe.importance + 1) * 1000; if (!probe.bounds.Contains(camPos)) d += 100000; return d; } static void SetupVolumeBounds (CommandBuffer cmd) { if (!radiant.limitToVolumeBounds.value) { cmd.SetGlobalVector(ShaderParams.BoundsXZ, unlimitedBounds); return; } if (volumes == null) { volumes = VolumeManager.instance.GetVolumes(-1); } int volumeCount = volumes.Length; for (int k = 0; k < volumeCount; k++) { Volume volume = volumes[k]; if (volume == null) continue; List colliders = volume.colliders; int colliderCount = colliders.Count; for (int j = 0; j < colliderCount; j++) { Collider collider = colliders[j]; if (collider == null) continue; Bounds bounds = collider.bounds; if (collider.bounds.Contains(camPos) && volume.sharedProfile.Has()) { Vector4 effectBounds = new Vector4(bounds.min.x, bounds.min.z, bounds.max.x, bounds.max.z); cmd.SetGlobalVector(ShaderParams.BoundsXZ, effectBounds); return; } } } } static bool SetupEmitters (Camera cam, List emitters) { // copy emitters data if (emittersBoxMax == null || emittersBoxMax.Length != MAX_EMITTERS) { emittersBoxMax = new Vector4[MAX_EMITTERS]; emittersBoxMin = new Vector4[MAX_EMITTERS]; emittersColors = new Vector4[MAX_EMITTERS]; emittersPositions = new Vector4[MAX_EMITTERS]; } int emittersCount = 0; const int EMITTERS_BUDGET = 150; // max number of emitters to be processed per frame int emittersMax = Mathf.Min(EMITTERS_BUDGET, emitters.Count); GeometryUtility.CalculateFrustumPlanes(cam, cameraPlanes); for (int k = 0; k < emittersMax; k++) { RadiantVirtualEmitter emitter = emitters[k]; // Cull emitters // disabled emitter? if (emitter == null || !emitter.isActiveAndEnabled) continue; // emitter with no intensity or range? if (emitter.intensity <= 0 || emitter.range <= 0) continue; // emitter bounds out of camera frustum? Bounds emitterBounds = emitter.GetBounds(); if (!GeometryUtility.TestPlanesAABB(cameraPlanes, emitterBounds)) continue; // emitter with black color (nothing to inject)? Vector4 colorAndRange = emitter.GetGIColorAndRange(); if (emitter.fadeDistance > 0) { float fade = ComputeVolumeFade(emitterBounds, emitter.fadeDistance); colorAndRange.x *= fade; colorAndRange.y *= fade; colorAndRange.z *= fade; } if (colorAndRange.x == 0 && colorAndRange.y == 0 && colorAndRange.z == 0) continue; // add emitter Vector3 emitterPosition = emitter.transform.position; emittersPositions[emittersCount] = emitterPosition; emittersColors[emittersCount] = colorAndRange; Vector3 boxMin = emitterBounds.min; Vector3 boxMax = emitterBounds.max; float lightRangeSqr = colorAndRange.w * colorAndRange.w; // Commented out for future versions if needed //float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr; //float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr); //float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr; //float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr; float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightRangeSqr); float pointAttenX = oneOverLightRangeSqr; //float pointAttenY = lightRangeSqrOverFadeRangeSqr; emittersBoxMin[emittersCount] = new Vector4(boxMin.x, boxMin.y, boxMin.z, pointAttenX); emittersBoxMax[emittersCount] = new Vector4(boxMax.x, boxMax.y, boxMax.z, 0); // pointAttenY emittersCount++; if (emittersCount >= MAX_EMITTERS) break; } if (emittersCount == 0) return false; Shader.SetGlobalVectorArray(ShaderParams.EmittersPositions, emittersPositions); Shader.SetGlobalVectorArray(ShaderParams.EmittersBoxMin, emittersBoxMin); Shader.SetGlobalVectorArray(ShaderParams.EmittersBoxMax, emittersBoxMax); Shader.SetGlobalVectorArray(ShaderParams.EmittersColors, emittersColors); Shader.SetGlobalInt(ShaderParams.EmittersCount, emittersCount); return true; } static void SortEmitters () { emitters.Sort((p1, p2) => emittersSortFunction(p1, p2)); } static int EmittersDistanceComparer (RadiantVirtualEmitter p1, RadiantVirtualEmitter p2) { Vector3 p1Pos = p1.transform.position; Vector3 p2Pos = p2.transform.position; float d1 = (p1Pos - camPos).sqrMagnitude; float d2 = (p2Pos - camPos).sqrMagnitude; Bounds p1bounds = p1.GetBounds(); Bounds p2bounds = p2.GetBounds(); if (!p1bounds.Contains(camPos)) d1 += 100000; if (!p2bounds.Contains(camPos)) d2 += 100000; if (d1 < d2) return -1; else if (d1 > d2) return 1; return 0; } static float ComputeVolumeFade (Bounds emitterBounds, float fadeDistance) { Vector3 diff = emitterBounds.center - camPos; diff.x = diff.x < 0 ? -diff.x : diff.x; diff.y = diff.y < 0 ? -diff.y : diff.y; diff.z = diff.z < 0 ? -diff.z : diff.z; Vector3 extents = emitterBounds.extents; Vector3 gap = diff - extents; float maxDiff = gap.x > gap.y ? gap.x : gap.y; maxDiff = maxDiff > gap.z ? maxDiff : gap.z; fadeDistance += 0.0001f; float t = 1f - Mathf.Clamp01(maxDiff / fadeDistance); return t; } public void Cleanup () { CoreUtils.Destroy(mat); if (prevs != null) { foreach (PerCameraData fad in prevs.Values) { // Dispose double-buffered accumulation RTs for (int i = 0; i < 2; i++) { if (fad.rtAcum[i] != null) { fad.rtAcum[i].Release(); DestroyImmediate(fad.rtAcum[i]); fad.rtAcum[i] = null; } if (fad.rtRayAcum[i] != null) { fad.rtRayAcum[i].Release(); DestroyImmediate(fad.rtRayAcum[i]); fad.rtRayAcum[i] = null; } if (fad.rtRaySamples[i] != null) { fad.rtRaySamples[i].Release(); DestroyImmediate(fad.rtRaySamples[i]); fad.rtRaySamples[i] = null; } } if (fad.rtBounce != null) { fad.rtBounce.Release(); DestroyImmediate(fad.rtBounce); } } prevs.Clear(); } goldenRatioAcum = 0f; volumes = null; radiant = null; settings = null; cachedGradientProbeValid = false; cachedAmbientSettingsValid = false; } } partial class RadiantComparePass : ScriptableRenderPass { const string RGI_CBUF_NAME = "RadiantGICompare"; static Material mat; static RadiantGlobalIllumination radiant; static RadiantRenderFeature settings; class PassData { public CommandBuffer cmd; #if UNITY_2022_2_OR_NEWER public RTHandle source, sourceDepth; #else public RenderTargetIdentifier source, sourceDepth; #endif #if UNITY_2023_3_OR_NEWER public TextureHandle colorTexture, depthTexture; #endif public RenderTextureDescriptor cameraTargetDesc; } readonly PassData passData = new PassData(); public bool Setup (ScriptableRenderer renderer, RadiantRenderFeature settings) { radiant = VolumeManager.instance.stack.GetComponent(); if (radiant == null || !radiant.IsActive() || radiant.debugView.value != DebugView.None) return false; #if UNITY_EDITOR if (!Application.isPlaying && !radiant.showInEditMode.value) return false; #endif if (!radiant.compareMode.value) return false; renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing + 1; RadiantComparePass.settings = settings; if (mat == null) { mat = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Kronnect/RadiantGI_URP")); } return true; } #if UNITY_2023_3_OR_NEWER public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { using (var builder = renderGraph.AddUnsafePass("Radiant GI Compare RG Pass", out var passData)) { builder.AllowPassCulling(false); UniversalCameraData cameraData = frameData.Get(); UniversalResourceData resourceData = frameData.Get(); passData.colorTexture = resourceData.activeColorTexture; passData.depthTexture = resourceData.activeDepthTexture; RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor; desc.useMipMap = false; desc.msaaSamples = 1; desc.depthBufferBits = 0; passData.cameraTargetDesc = desc; builder.UseTexture(resourceData.activeColorTexture, AccessFlags.ReadWrite); builder.UseTexture(resourceData.activeDepthTexture, AccessFlags.Read); builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => { CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); passData.cmd = cmd; passData.source = passData.colorTexture; passData.sourceDepth = passData.depthTexture; ExecutePass(passData); }); } } #endif static void ExecutePass (PassData passData) { if (!RadiantPass.computedGIRTValid) return; mat.DisableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED); mat.DisableKeyword(ShaderParams.SKW_FORWARD); if (settings.renderingPath == RenderingPath.Both) { mat.EnableKeyword(ShaderParams.SKW_FORWARD_AND_DEFERRED); } else if (settings.renderingPath == RenderingPath.Forward) { mat.EnableKeyword(ShaderParams.SKW_FORWARD); } if (radiant.virtualEmitters.value) { mat.EnableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS); } else { mat.DisableKeyword(ShaderParams.SKW_VIRTUAL_EMITTERS); } float nearFieldObscurance = radiant.nearFieldObscurance.value; if (nearFieldObscurance > 0) { mat.EnableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE); } else { mat.DisableKeyword(ShaderParams.SKW_USES_NEAR_FIELD_OBSCURANCE); } mat.SetVector(ShaderParams.IndirectData, new Vector4(radiant.indirectIntensity.value, radiant.indirectMaxSourceBrightness.value, radiant.indirectDistanceAttenuation.value, radiant.rayReuse.value)); float angle = radiant.compareSameSide.value ? Mathf.PI * 0.5f : radiant.compareLineAngle.value; mat.SetVector(ShaderParams.CompareParams, new Vector4(Mathf.Cos(angle), Mathf.Sin(angle), radiant.compareSameSide.value ? radiant.comparePanning.value : -10, radiant.compareLineWidth.value)); mat.SetInt(ShaderParams.StencilValue, radiant.stencilValue.value); mat.SetInt(ShaderParams.StencilCompareFunction, radiant.stencilCheck.value ? (int)radiant.stencilCompareFunction.value : (int)CompareFunction.Always); CommandBuffer cmd = passData.cmd; RenderTextureDescriptor desc = passData.cameraTargetDesc; if (desc.width < 1) desc.width = 1; if (desc.height < 1) desc.height = 1; cmd.GetTemporaryRT(ShaderParams.InputRT, desc, FilterMode.Point); cmd.SetGlobalTexture(ShaderParams.InputRT, ShaderParams.InputRT); cmd.GetTemporaryRT(ShaderParams.CompareTex, desc, FilterMode.Point); cmd.SetGlobalTexture(ShaderParams.CompareTex, ShaderParams.CompareTex); FullScreenBlit(cmd, passData.source, ShaderParams.InputRT, Pass.CopyExact); // include transparent objects in the original compare texture FullScreenBlit(cmd, RadiantPass.computedGIRT, ShaderParams.CompareTex, Pass.Compose, RadiantPass.probesProps); // add gi FullScreenBlit(cmd, ShaderParams.InputRT, passData.source, Pass.Compare); // render the split cmd.ReleaseTemporaryRT(ShaderParams.InputRT); cmd.ReleaseTemporaryRT(ShaderParams.CompareTex); } static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass) { cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.MainTex, source); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass); } static void FullScreenBlit (CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Pass pass, MaterialPropertyBlock props) { cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.MainTex, source); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)pass, props); } public void Cleanup () { CoreUtils.Destroy(mat); } } partial class RadiantOrganicLightPass : ScriptableRenderPass { enum Pass { OrganicLight = 0 } const string m_strProfilerTag = "Radiant GI Organic Light"; static Material mat; DeferredLights m_DeferredLights; Texture2D noiseTex; Vector3 offset; public bool Setup (RadiantGlobalIllumination radiant, ScriptableRenderer renderer, bool isSceneView) { if (radiant.organicLight.value <= 0) return false; #if UNITY_EDITOR if (isSceneView && !radiant.showInSceneView.value) return false; if (!Application.isPlaying && !radiant.showInEditMode.value) return false; #endif DeferredLights deferredLights = ((UniversalRenderer)renderer).deferredLights; if (deferredLights == null) return false; renderPassEvent = RenderPassEvent.AfterRenderingGbuffer; m_DeferredLights = deferredLights; if (mat == null) { mat = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Kronnect/RadiantGIOrganicLight")); } if (noiseTex == null) { noiseTex = Resources.Load("RadiantGI/NoiseTex"); } mat.SetTexture(ShaderParams.NoiseTex, noiseTex); mat.SetVector(ShaderParams.OrganicLightData, new Vector4(1.001f - radiant.organicLightSpread.value, radiant.organicLight.value, radiant.organicLightThreshold.value, radiant.organicLightNormalsInfluence.value)); mat.SetColor(ShaderParams.OrganicLightTint, radiant.organicLightTintColor.value); offset += radiant.organicLightAnimationSpeed.value * Time.deltaTime; offset.x %= 10000f; offset.y %= 10000f; offset.z %= 10000f; mat.SetVector(ShaderParams.OrganicLightOffset, offset); if (radiant.organicLightDistanceScaling.value) { mat.EnableKeyword(ShaderParams.SKW_DISTANCE_BLENDING); } else { mat.DisableKeyword(ShaderParams.SKW_DISTANCE_BLENDING); } return true; } #if UNITY_2022_1_OR_NEWER RTHandle GetAlbedoFromGbuffer () { return m_DeferredLights.GbufferAttachments[m_DeferredLights.GBufferAlbedoIndex]; } #else RenderTargetIdentifier GetAlbedoFromGbuffer () { return m_DeferredLights.GbufferAttachmentIdentifiers[m_DeferredLights.GBufferAlbedoIndex]; } #endif #if UNITY_2023_3_OR_NEWER class PassData { public TextureHandle depthTexture; public TextureHandle normalsTexture; public TextureHandle gBuffer0; } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalResourceData resourceData = frameData.Get(); if (!resourceData.gBuffer[0].IsValid()) return; using (var builder = renderGraph.AddUnsafePass(m_strProfilerTag, out var passData)) { passData.depthTexture = resourceData.activeDepthTexture; passData.normalsTexture = resourceData.cameraNormalsTexture; passData.gBuffer0 = resourceData.gBuffer[0]; builder.UseTexture(resourceData.gBuffer[0], AccessFlags.ReadWrite); builder.UseTexture(resourceData.activeDepthTexture, AccessFlags.Read); if (resourceData.cameraNormalsTexture.IsValid()) { builder.UseTexture(resourceData.cameraNormalsTexture, AccessFlags.Read); } builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => { CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); cmd.SetGlobalTexture(ShaderParams.CameraDepthTexture, passData.depthTexture); if (passData.normalsTexture.IsValid()) { cmd.SetGlobalTexture(ShaderParams.CameraNormalsTexture, passData.normalsTexture); } cmd.SetRenderTarget(passData.gBuffer0); cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, mat, 0, (int)Pass.OrganicLight); }); } } #endif public void Cleanup () { CoreUtils.Destroy(mat); } } partial class TransparentDepthRenderPass : ScriptableRenderPass { const string m_ProfilerTag = "Radiant GI Transparent Depth PrePass"; const string m_DepthOnlyShader = "Hidden/Kronnect/RadiantGI/DepthOnly"; public static int transparentLayerMask; static FilteringSettings filterSettings; static readonly List shaderTagIdList = new List(); #if UNITY_2022_2_OR_NEWER RTHandle m_Depth; #else RenderTargetIdentifier m_Depth; #endif static Material depthOnlyMaterial; const bool useOptimizedDepthOnlyShader = true; public TransparentDepthRenderPass () { #if UNITY_2022_2_OR_NEWER RenderTargetIdentifier rti = new RenderTargetIdentifier(ShaderParams.TransparentDepthTexture, 0, CubemapFace.Unknown, -1); m_Depth = RTHandles.Alloc(rti, name: "_RadiantTransparentDepthTexture"); #else m_Depth = new RenderTargetIdentifier(ShaderParams.TransparentDepthTexture, 0, CubemapFace.Unknown, -1); #endif shaderTagIdList.Clear(); shaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit")); shaderTagIdList.Add(new ShaderTagId("UniversalForward")); shaderTagIdList.Add(new ShaderTagId("LightweightForward")); filterSettings = new FilteringSettings(RenderQueueRange.transparent, 0); } public void Setup (int transparentLayerMask) { TransparentDepthRenderPass.transparentLayerMask = transparentLayerMask; } #if UNITY_2023_3_OR_NEWER class PassData { public RendererListHandle rendererListHandle; public UniversalCameraData cameraData; } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { using (var builder = renderGraph.AddUnsafePass(m_ProfilerTag, out var passData)) { builder.AllowPassCulling(false); UniversalResourceData resourceData = frameData.Get(); UniversalRenderingData renderingData = frameData.Get(); UniversalLightData lightData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); passData.cameraData = cameraData; SortingCriteria sortingCriteria = SortingCriteria.CommonTransparent; var drawingSettings = CreateDrawingSettings(shaderTagIdList, renderingData, cameraData, lightData, sortingCriteria); drawingSettings.perObjectData = PerObjectData.None; if (useOptimizedDepthOnlyShader) { if (depthOnlyMaterial == null) { Shader depthOnly = Shader.Find(m_DepthOnlyShader); if (depthOnly != null) { depthOnlyMaterial = new Material(depthOnly); } } if (depthOnlyMaterial != null) { drawingSettings.overrideMaterial = depthOnlyMaterial; } } if (transparentLayerMask != 0) { if (transparentLayerMask != filterSettings.layerMask) { filterSettings = new FilteringSettings(RenderQueueRange.transparent, transparentLayerMask); } RendererListParams listParams = new RendererListParams(renderingData.cullResults, drawingSettings, filterSettings); passData.rendererListHandle = renderGraph.CreateRendererList(listParams); builder.UseRendererList(passData.rendererListHandle); } builder.SetRenderFunc((PassData passData, UnsafeGraphContext context) => { CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); RenderTextureDescriptor depthDesc = passData.cameraData.cameraTargetDescriptor; depthDesc.colorFormat = RenderTextureFormat.Depth; depthDesc.depthBufferBits = 24; depthDesc.msaaSamples = 1; cmd.GetTemporaryRT(ShaderParams.TransparentDepthTexture, depthDesc, FilterMode.Point); RenderTargetIdentifier rti = new RenderTargetIdentifier(ShaderParams.TransparentDepthTexture, 0, CubemapFace.Unknown, -1); cmd.SetGlobalTexture(ShaderParams.TransparentDepthTexture, rti); cmd.SetRenderTarget(rti); cmd.ClearRenderTarget(true, true, Color.black); if (transparentLayerMask != 0 && passData.rendererListHandle.IsValid()) { context.cmd.DrawRendererList(passData.rendererListHandle); } if (depthOnlyMaterial != null) { int transparentSupportCount = RadiantRenderFeature.transparentSupport.Count; for (int i = 0; i < transparentSupportCount; i++) { Renderer renderer = RadiantRenderFeature.transparentSupport[i].theRenderer; if (renderer != null) { cmd.DrawRenderer(renderer, depthOnlyMaterial); } } } }); } } #endif public void Cleanup () { CoreUtils.Destroy(depthOnlyMaterial); #if UNITY_2022_2_OR_NEWER if (m_Depth != null) { m_Depth.Release(); } #endif } } [Tooltip("Select the rendering mode according to the URP asset")] public RenderingPath renderingPath = RenderingPath.Deferred; [Tooltip("Allows Radiant to be executed even if camera has Post Processing option disabled")] public bool ignorePostProcessingOption = true; [Tooltip("Enable this option to skip rendering GI on overlay cameras")] public bool ignoreOverlayCameras = true; [Tooltip("Which cameras can use Radiant Global Illumination")] public LayerMask camerasLayerMask = -1; RadiantPass radiantPass; RadiantComparePass comparePass; RadiantOrganicLightPass organicLightPass; TransparentDepthRenderPass transparentDepthPass; void OnDisable () { if (radiantPass != null) { radiantPass.Cleanup(); } if (comparePass != null) { comparePass.Cleanup(); } if (organicLightPass != null) { organicLightPass.Cleanup(); } if (transparentDepthPass != null) { transparentDepthPass.Cleanup(); } installed = false; } public override void Create () { radiantPass = new RadiantPass(); comparePass = new RadiantComparePass(); organicLightPass = new RadiantOrganicLightPass(); transparentDepthPass = new TransparentDepthRenderPass(); emittersForceRefresh = true; } public static bool needRTRefresh; public static bool isRenderingInDeferred; public static bool installed; static int GetRenderFrameId() { int frameId = Application.isPlaying ? Time.frameCount : Time.renderedFrameCount; if (frameId <= 0) { frameId = Time.frameCount; } return frameId; } public override void AddRenderPasses (ScriptableRenderer renderer, ref RenderingData renderingData) { installed = true; // Quality of life check #if UNITY_EDITOR UniversalRenderer universalRenderer = renderer as UniversalRenderer; if (universalRenderer == null) return; bool isForward = (int)universalRenderer.renderingModeActual == 0 || (int)universalRenderer.renderingModeActual == 2; if (isForward && renderingPath == RenderingPath.Deferred) { renderingPath = RenderingPath.Forward; } else if (!isForward && renderingPath == RenderingPath.Forward) { renderingPath = RenderingPath.Deferred; } isRenderingInDeferred = !isForward; #endif if (!renderingData.cameraData.postProcessEnabled && !ignorePostProcessingOption) return; Camera cam = renderingData.cameraData.camera; if (cam.cameraType == CameraType.Reflection) return; bool isSceneView = cam.cameraType == CameraType.SceneView; if (cam.cameraType != CameraType.Game && !isSceneView) return; #if UNITY_EDITOR if (isSceneView && !cam.TryGetComponent(out _)) { cam.gameObject.AddComponent(); } #endif CameraRenderType renderType = renderingData.cameraData.renderType; if (ignoreOverlayCameras && renderType == CameraRenderType.Overlay) return; if ((camerasLayerMask & (1 << cam.gameObject.layer)) == 0) return; #if UNITY_EDITOR if (UnityEditor.ShaderUtil.anythingCompiling) { needRTRefresh = true; } if (needRTRefresh) { needRTRefresh = false; radiantPass.Cleanup(); comparePass.Cleanup(); organicLightPass.Cleanup(); } #endif RadiantGlobalIllumination radiant = VolumeManager.instance.stack.GetComponent(); if (radiant == null) return; if (organicLightPass.Setup(radiant, renderer, isSceneView)) { renderer.EnqueuePass(organicLightPass); } if (radiantPass.Setup(radiant, renderer, this, isSceneView)) { if (radiant.transparencySupport.value) { bool hasLayerMask = radiant.transparentLayerMask.value != 0; bool hasScriptObjects = transparentSupport.Count > 0; if (hasLayerMask || hasScriptObjects) { transparentDepthPass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox + 1; transparentDepthPass.Setup(radiant.transparentLayerMask.value); renderer.EnqueuePass(transparentDepthPass); } } renderer.EnqueuePass(radiantPass); if (!isSceneView && comparePass.Setup(renderer, this)) { renderer.EnqueuePass(comparePass); } } } public static void RegisterVirtualEmitter (RadiantVirtualEmitter emitter) { if (emitter == null) return; if (!emitters.Contains(emitter)) { emitters.Add(emitter); emittersForceRefresh = true; } } public static void UnregisterVirtualEmitter (RadiantVirtualEmitter emitter) { if (emitter == null) return; if (emitters.Contains(emitter)) { emitters.Remove(emitter); emittersForceRefresh = true; } } public static void RegisterTransparentSupport (RadiantTransparentSupport o) { if (o == null) return; if (!transparentSupport.Contains(o)) { transparentSupport.Add(o); } } public static void UnregisterTransparentSupport (RadiantTransparentSupport o) { if (o == null) return; if (transparentSupport.Contains(o)) { transparentSupport.Remove(o); } } } }