using System.Collections.Generic; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace EdgeFusion { // ------------------------------------------------------------------------------------------- // PORT NOTES (Unity 2022.3 LTS / URP 14 - classic ScriptableRenderPass API) // ------------------------------------------------------------------------------------------- // The original (Unity 6 / URP 17) implementation is built entirely on the Render Graph API // (RecordRenderGraph / TextureHandle / RasterGraphContext). URP 14 does not have Render Graph, // so this version uses the classic Execute(ScriptableRenderContext, ref RenderingData) API with // manually managed RTHandles, matching the same pass structure and shader contract 1:1: // 1) ObjectID pass -> objectID RT (+ its own depth RT if the camera uses MSAA) // 2) Special-group pass -> customGroup RT (only if a special group is configured) // 3) Fill ObjectID pass -> objectIDFilled RT (only if needed, same condition as original) // 4) Blend pass -> compare RT (if comparing) or camera color // 5) Compare pass -> camera color (if comparing) // 6) Debug pass -> camera color (if a debug mode is active and not comparing) // 7) Copy-back -> not needed here, see note at the bottom of Execute() // // Differences from the Unity 6 version worth knowing about: // - RTHandles are allocated once and reused/reallocated on demand (RenderingUtils.ReAllocateIfNeeded) // instead of being requested fresh from the render graph every frame. // - `Object.FindObjectsByType` (used in the original for id-exclusion lookup) does not exist in // 2022.3; this file uses the classic `Object.FindObjectsOfType()` instead. // - The classic renderer exposes `ScriptableRenderer.cameraColorTargetHandle` / // `cameraDepthTargetHandle` directly; there is no `UniversalResourceData`/`UniversalCameraData` // frame-data container to reroute. // - Camera-stack detection uses the same UniversalAdditionalCameraData.cameraStack check as the // original, kept here for parity/documentation even though the classic renderer doesn't need an // explicit copy-back step (see the note at the end of Execute()). // // This file has been ported by careful, line-by-line reading of the Unity 6 implementation and the // URP 14 classic API; it has not been compiled/run inside the Editor. Please test in your 2022.3 // project (single camera, stacked cameras, MSAA on/off, and XR if you use it) before shipping. // ------------------------------------------------------------------------------------------- public class EdgeFusionRenderPass : ScriptableRenderPass { // Shader names const string OBJECT_ID_SHADER_PATH = "Kronnect/EdgeFusion/ObjectID"; const string FUSION_SHADER_PATH = "Hidden/Kronnect/EdgeFusion/EdgeFusion"; const string FUSION_FILL_SHADER_PATH = "Hidden/Kronnect/EdgeFusion/EdgeFusionFill"; // Texture names const string OBJECT_ID_TEXTURE_NAME = "EdgeFusion_ObjectID"; const string OBJECT_ID_FILLED_TEXTURE_NAME = "EdgeFusion_ObjectID_Filled"; const string OBJECT_ID_DEPTH_TEXTURE_NAME = "EdgeFusion_ObjectID_Depth"; const string CUSTOM_GROUP_TEXTURE_NAME = "EdgeFusion_CustomGroups"; const string CAMERA_COLOR_TEXTURE_NAME = "EdgeFusion_CameraColor"; const string COMPARE_TEXTURE_NAME = "EdgeFusion_CompareTex"; // Profiling / pass name const string PROFILING_NAME = "Edge Fusion"; static readonly ProfilingSampler profilingSampler = new ProfilingSampler(PROFILING_NAME); EdgeFusionRenderFeature feature; Material objectIDMaterial; Material fusionMaterial; Material fusionFillMaterial; readonly List shaderTags; GraphicsFormat objectIdTextureFormat; Texture3D noiseTex; static readonly float[] exclusionMask = new float[32]; static readonly uint[] exclusionMaskBits = new uint[32]; static Volume cachedExclusionVolume; static EdgeFusion cachedEdgeFusion; // RTHandles reused/reallocated across frames RTHandle objectIDHandle; RTHandle objectIDFilledHandle; RTHandle objectIDDepthHandle; RTHandle customGroupHandle; RTHandle compareHandle; RTHandle cameraColorCopyHandle; // holds the pre-effect camera color (blend source & compare source) static bool CanStoreMaskValue (uint value) { return (uint)(float)value == value; } public EdgeFusionRenderPass (EdgeFusionRenderFeature feature) { this.feature = feature; shaderTags = new List { new ShaderTagId("SRPDefaultUnlit"), new ShaderTagId("UniversalForward"), new ShaderTagId("UniversalForwardOnly"), new ShaderTagId("LightweightForward") }; var rg32Format = GraphicsFormat.R32G32_SFloat; bool canUseRG32Format = SystemInfo.IsFormatSupported(rg32Format, FormatUsage.Render); objectIdTextureFormat = canUseRG32Format ? rg32Format : GraphicsFormat.R32G32B32A32_SFloat; InitializeMaterials(); } public void UpdateInputRequirement (EdgeFusion settings) { ConfigureInput(ScriptableRenderPassInput.Depth); } void InitializeMaterials () { if (objectIDMaterial == null) { var shader = Shader.Find(OBJECT_ID_SHADER_PATH); if (shader != null) objectIDMaterial = CoreUtils.CreateEngineMaterial(shader); if (objectIDMaterial != null) objectIDMaterial.enableInstancing = true; } if (fusionMaterial == null) { var shader = Shader.Find(FUSION_SHADER_PATH); if (shader != null) fusionMaterial = CoreUtils.CreateEngineMaterial(shader); } if (fusionFillMaterial == null) { var shader = Shader.Find(FUSION_FILL_SHADER_PATH); if (shader != null) fusionFillMaterial = CoreUtils.CreateEngineMaterial(shader); } noiseTex = Resources.Load("EdgeFusion/Textures/NoiseTex3D"); } void UpdateMaterialProperties (EdgeFusion settings, bool taa, float effectiveMaxBlendDistance, bool cameraUsesMsaa, bool specialGroupActive) { float mappedNoiseContrast = Mathf.Lerp(0.5f, 0.01f, settings.noiseContrast.value); Vector4 blendData1 = new Vector4( settings.intensity.value, // x: GlobalIntensity effectiveMaxBlendDistance, // y: MaxBlendDistance settings.normalThreshold.value, // z: NormalThreshold mappedNoiseContrast // w: NoiseContrast ); Vector4 blendData2 = new Vector4( settings.maxScreenRadius.value, // x: MaxScreenRadius settings.shadowProtection.value, // y: ShadowProtection settings.noiseIntensity.value, // z: NoiseIntensity settings.noiseScale.value // w: NoiseScale ); float radiusScale = Mathf.Max(1f, settings.radius.value / 0.89f); fusionMaterial.SetVector(ShaderParams.BlendData1, blendData1); fusionMaterial.SetVector(ShaderParams.BlendData2, blendData2); fusionMaterial.SetFloat(ShaderParams.DefaultRadiusWorld, settings.radius.value); fusionMaterial.SetFloat(ShaderParams.RadiusScale, radiusScale); fusionMaterial.SetFloat(ShaderParams.DistanceCompensation, settings.distanceCompensation.value); fusionMaterial.SetInt(ShaderParams.SampleCount, settings.sampleCount.value); fusionMaterial.SetInt(ShaderParams.BinarySearchSteps, settings.binarySearchSteps.value); fusionMaterial.SetInt(ShaderParams.EarlyExitHits, settings.earlyExitHits.value); if (settings.antiFlicker.value) { fusionMaterial.EnableKeyword(ShaderParams.SKW_ANTI_FLICKER); } else { fusionMaterial.DisableKeyword(ShaderParams.SKW_ANTI_FLICKER); } fusionMaterial.SetTexture(ShaderParams.NoiseTex3D, noiseTex); fusionFillMaterial.SetVector(ShaderParams.BlendData1, blendData1); fusionFillMaterial.SetVector(ShaderParams.BlendData2, blendData2); fusionFillMaterial.SetFloat(ShaderParams.DefaultRadiusWorld, settings.radius.value); fusionFillMaterial.SetFloat(ShaderParams.RadiusScale, radiusScale); fusionFillMaterial.SetFloat(ShaderParams.DistanceCompensation, settings.distanceCompensation.value); float msaaFixPower = settings.msaaEdgeFixPower.value; if (cameraUsesMsaa && msaaFixPower > 0f) { fusionMaterial.EnableKeyword(ShaderParams.SKW_MSAA_EDGE_FIX); fusionMaterial.SetFloat(ShaderParams.MsaaFixPower, msaaFixPower); } else { fusionMaterial.DisableKeyword(ShaderParams.SKW_MSAA_EDGE_FIX); } // ObjectID material setup objectIDMaterial.SetFloat(ShaderParams.MaxBlendDistance, effectiveMaxBlendDistance); objectIDMaterial.SetFloat(ShaderParams.DefaultRadiusWorld, settings.radius.value); objectIDMaterial.SetFloat(ShaderParams.RadiusScale, radiusScale); objectIDMaterial.SetFloat(ShaderParams.DistanceCompensation, settings.distanceCompensation.value); objectIDMaterial.SetVector(ShaderParams.BlendData2, blendData2); objectIDMaterial.SetInt(ShaderParams.ZWrite, cameraUsesMsaa ? 1 : 0); objectIDMaterial.SetInt(ShaderParams.ZTest, cameraUsesMsaa ? (int)CompareFunction.LessEqual : (int)CompareFunction.Equal); objectIDMaterial.SetFloat(ShaderParams.DisallowIntraFusion, settings.intraObjectFusionPerObject.value ? 1f : 0f); if (cameraUsesMsaa) { objectIDMaterial.EnableKeyword(ShaderParams.SKW_MSAA_ON); } else { objectIDMaterial.DisableKeyword(ShaderParams.SKW_MSAA_ON); } DebugMode debugMode = settings.debugMode.value; if (debugMode == DebugMode.SpecialGroup && !specialGroupActive) debugMode = DebugMode.None; fusionMaterial.SetInt(ShaderParams.DebugMode, (int)debugMode); fusionMaterial.SetFloat(ShaderParams.DepthDebugMultiplier, settings.depthDebugMultiplier.value); bool normalsDebug = debugMode == DebugMode.Normals; if (normalsDebug) { objectIDMaterial.EnableKeyword(ShaderParams.SKW_DEBUG_BLEND_NORMALS); fusionFillMaterial.EnableKeyword(ShaderParams.SKW_DEBUG_BLEND_NORMALS); } else { objectIDMaterial.DisableKeyword(ShaderParams.SKW_DEBUG_BLEND_NORMALS); fusionFillMaterial.DisableKeyword(ShaderParams.SKW_DEBUG_BLEND_NORMALS); } // Jitter keyword toggle if (settings.jitter.value) { fusionMaterial.EnableKeyword(ShaderParams.SKW_ENABLE_JITTER); fusionMaterial.SetFloat(ShaderParams.JitterFrame, taa ? Time.frameCount : 0); } else { fusionMaterial.DisableKeyword(ShaderParams.SKW_ENABLE_JITTER); } fusionMaterial.DisableKeyword(ShaderParams.SKW_INTRA_OBJECT_FUSION); fusionMaterial.DisableKeyword(ShaderParams.SKW_CONCAVE_ONLY); objectIDMaterial.DisableKeyword(ShaderParams.SKW_INTRA_OBJECT_FUSION); objectIDMaterial.DisableKeyword(ShaderParams.SKW_CONCAVE_ONLY); fusionFillMaterial.DisableKeyword(ShaderParams.SKW_INTRA_OBJECT_FUSION); fusionFillMaterial.DisableKeyword(ShaderParams.SKW_CONCAVE_ONLY); bool intraObjectFusionPerObject = settings.intraObjectFusionPerObject.value; if (settings.enableIntraObjectFusion.value) { if (settings.concavityTest.value) { fusionMaterial.EnableKeyword(ShaderParams.SKW_CONCAVE_ONLY); objectIDMaterial.EnableKeyword(ShaderParams.SKW_CONCAVE_ONLY); if (!intraObjectFusionPerObject) { fusionFillMaterial.EnableKeyword(ShaderParams.SKW_CONCAVE_ONLY); } } else { fusionMaterial.EnableKeyword(ShaderParams.SKW_INTRA_OBJECT_FUSION); objectIDMaterial.EnableKeyword(ShaderParams.SKW_INTRA_OBJECT_FUSION); if (!intraObjectFusionPerObject) { fusionFillMaterial.EnableKeyword(ShaderParams.SKW_INTRA_OBJECT_FUSION); } } } if (settings.noiseIntensity.value > 0.0f) { fusionMaterial.EnableKeyword(ShaderParams.SKW_NOISE); } else { fusionMaterial.DisableKeyword(ShaderParams.SKW_NOISE); } if (specialGroupActive) { fusionFillMaterial.EnableKeyword(ShaderParams.SKW_SPECIAL_GROUP); } else { fusionFillMaterial.DisableKeyword(ShaderParams.SKW_SPECIAL_GROUP); } var exclusionPairs = GetActiveExclusionPairs(); if (exclusionPairs != null && exclusionPairs.Length > 0) { System.Array.Clear(exclusionMask, 0, 32); System.Array.Clear(exclusionMaskBits, 0, 32); bool hasEnabledPairs = false; int exclusionPairCount = exclusionPairs.Length; for (int i = 0; i < exclusionPairCount; i++) { var pair = exclusionPairs[i]; if (!pair.enabled) continue; hasEnabledPairs = true; int a = Mathf.Clamp(pair.idA, 1, 32) - 1; int b = Mathf.Clamp(pair.idB, 1, 32) - 1; uint bitForA = 1u << b; uint bitForB = 1u << a; if (a == b) { uint candidate = exclusionMaskBits[a] | bitForA; if (CanStoreMaskValue(candidate)) { exclusionMaskBits[a] = candidate; } continue; } uint candidateA = exclusionMaskBits[a] | bitForA; uint candidateB = exclusionMaskBits[b] | bitForB; bool canA = CanStoreMaskValue(candidateA); bool canB = CanStoreMaskValue(candidateB); if (canA && (!canB || candidateA <= candidateB)) { exclusionMaskBits[a] = candidateA; } else if (canB) { exclusionMaskBits[b] = candidateB; } } if (hasEnabledPairs) { for (int i = 0; i < 32; i++) { uint maskValue = exclusionMaskBits[i]; exclusionMask[i] = maskValue; } fusionMaterial.EnableKeyword(ShaderParams.SKW_ID_EXCLUSION); fusionMaterial.SetFloatArray(ShaderParams.ExclusionMask, exclusionMask); } else { fusionMaterial.DisableKeyword(ShaderParams.SKW_ID_EXCLUSION); } } else { fusionMaterial.DisableKeyword(ShaderParams.SKW_ID_EXCLUSION); } } // ----------------------------------------------------------------------------------- // Classic execution // ----------------------------------------------------------------------------------- public override void Execute (ScriptableRenderContext context, ref RenderingData renderingData) { if (feature == null) return; var settings = VolumeManager.instance.stack.GetComponent(); if (settings == null || !settings.IsActive()) return; if (objectIDMaterial == null || fusionMaterial == null || fusionFillMaterial == null) { InitializeMaterials(); if (objectIDMaterial == null || fusionMaterial == null || fusionFillMaterial == null) return; } ref var cameraData = ref renderingData.cameraData; if (EdgeFusionRenderFeature.IsDepthOnlyTarget(cameraData.cameraTargetDescriptor)) return; var renderer = cameraData.renderer; RTHandle cameraColorTargetHandle = renderer.cameraColorTargetHandle; RTHandle cameraDepthTargetHandle = renderer.cameraDepthTargetHandle; if (cameraColorTargetHandle == null) return; CommandBuffer cmd = CommandBufferPool.Get(PROFILING_NAME); using (new ProfilingScope(cmd, profilingSampler)) { bool cameraUsesMsaa = cameraData.cameraTargetDescriptor.msaaSamples > 1; bool taa = cameraData.antialiasing == AntialiasingMode.TemporalAntiAliasing && !cameraUsesMsaa; int cameraCullingMask = cameraData.camera.cullingMask; int effectiveBlendLayers = settings.blendLayers.value & cameraCullingMask; int effectiveDoubleSided = settings.doubleSidedLayers.value & cameraCullingMask; int effectiveSpecialGroup = settings.specialGroupLayers.value & cameraCullingMask; uint rlMaskSingle = settings.renderingLayerFilter.value; uint rlMaskDouble = settings.doubleSidedRenderingLayerFilter.value; uint rlMaskSpecial = settings.specialGroupRenderingLayerFilter.value; int ssMask = effectiveBlendLayers; int dsMask = effectiveDoubleSided; int specialMask = effectiveSpecialGroup; if ((dsMask & specialMask) != 0 && RenderingLayersOverlap(rlMaskSpecial, rlMaskDouble)) { dsMask &= ~specialMask; } if ((ssMask & specialMask) != 0 && RenderingLayersOverlap(rlMaskSpecial, rlMaskSingle)) { ssMask &= ~specialMask; } if ((ssMask & dsMask) != 0 && RenderingLayersOverlap(rlMaskDouble, rlMaskSingle)) { ssMask &= ~dsMask; } bool hasSingleSided = ssMask != 0 && rlMaskSingle != 0u; bool hasDoubleSided = dsMask != 0 && rlMaskDouble != 0u; bool hasSpecialGroup = specialMask != 0 && rlMaskSpecial != 0u; uint specialGroupCombined = hasSpecialGroup ? rlMaskSpecial : 0u; if (!hasSingleSided && !hasDoubleSided && !hasSpecialGroup) { CommandBufferPool.Release(cmd); return; } float effectiveMaxBlendDistance = settings.maxBlendDistance.value; if (cameraData.camera.orthographic) { effectiveMaxBlendDistance = 1e10f; } UpdateMaterialProperties(settings, taa, effectiveMaxBlendDistance, cameraUsesMsaa, hasSpecialGroup); var baseDesc = cameraData.cameraTargetDescriptor; // Kept for parity/documentation with the original camera-stack handling; the classic // renderer already shares cameraColorTargetHandle between base and overlay cameras, so no // extra copy-back is required (see the note at the bottom of this method). bool isStackedBase = cameraData.camera.TryGetComponent(out UniversalAdditionalCameraData additionalCameraData) && additionalCameraData.cameraStack != null && additionalCameraData.cameraStack.Count > 0; // ---- Working copy of the pre-effect camera color (blend/compare source) ---- var colorCopyDesc = baseDesc; colorCopyDesc.depthBufferBits = 0; colorCopyDesc.msaaSamples = 1; RenderingUtils.ReAllocateIfNeeded(ref cameraColorCopyHandle, colorCopyDesc, name: CAMERA_COLOR_TEXTURE_NAME); Blitter.BlitCameraTexture(cmd, cameraColorTargetHandle, cameraColorCopyHandle); RTHandle colorTarget = cameraColorTargetHandle; // ---- ObjectID texture ---- var objIdDesc = baseDesc; bool forcedRGBA32F = settings.enableIntraObjectFusion.value || settings.debugMode.value == DebugMode.Normals; objIdDesc.graphicsFormat = forcedRGBA32F ? GraphicsFormat.R32G32B32A32_SFloat : objectIdTextureFormat; objIdDesc.depthBufferBits = 0; objIdDesc.msaaSamples = 1; RenderingUtils.ReAllocateIfNeeded(ref objectIDHandle, objIdDesc, name: OBJECT_ID_TEXTURE_NAME); Color clearColor = SystemInfo.usesReversedZBuffer ? Color.clear : new Color(0, 1, 0, 0); // ---- Special-group marker texture ---- bool useCustomGroups = hasSpecialGroup; if (useCustomGroups) { var customDesc = objIdDesc; RenderingUtils.ReAllocateIfNeeded(ref customGroupHandle, customDesc, name: CUSTOM_GROUP_TEXTURE_NAME); } // ---- Depth used while filling the ObjectID buffer ---- RTHandle depthForObjectID; if (cameraUsesMsaa) { var objIdDepthDesc = baseDesc; objIdDepthDesc.msaaSamples = 1; objIdDepthDesc.graphicsFormat = GraphicsFormat.None; objIdDepthDesc.depthBufferBits = 24; RenderingUtils.ReAllocateIfNeeded(ref objectIDDepthHandle, objIdDepthDesc, name: OBJECT_ID_DEPTH_TEXTURE_NAME); depthForObjectID = objectIDDepthHandle; } else { // No MSAA: reuse camera depth directly (read/testable, matches original ZTest.Equal setup) depthForObjectID = cameraDepthTargetHandle != null ? cameraDepthTargetHandle : objectIDDepthHandle; } // =========================== 1) ObjectID pass =========================== // Only clear depth when we own a dedicated depth buffer (MSAA path). In the non-MSAA // path depthForObjectID is the camera's shared depth target, already populated by the // opaque pass and needed as-is for the ZTest.Equal comparison below (and by later passes // that sample _CameraDepthTexture) - clearing it here would corrupt it for the rest of the frame. ClearFlag objectIdClearFlag = cameraUsesMsaa ? ClearFlag.All : ClearFlag.Color; CoreUtils.SetRenderTarget(cmd, objectIDHandle, depthForObjectID, objectIdClearFlag, clearColor); context.ExecuteCommandBuffer(cmd); cmd.Clear(); var drawSettingsObjId = CreateDrawingSettings(shaderTags, ref renderingData, SortingCriteria.CommonOpaque); drawSettingsObjId.overrideMaterial = objectIDMaterial; drawSettingsObjId.overrideMaterialPassIndex = 0; drawSettingsObjId.perObjectData = PerObjectData.None; drawSettingsObjId.enableInstancing = true; if (hasSingleSided) { cmd.SetGlobalInt(ShaderParams.Cull, (int)CullMode.Back); context.ExecuteCommandBuffer(cmd); cmd.Clear(); var filterSingle = new FilteringSettings(RenderQueueRange.opaque, ssMask, rlMaskSingle); context.DrawRenderers(renderingData.cullResults, ref drawSettingsObjId, ref filterSingle); } if (hasDoubleSided) { cmd.SetGlobalInt(ShaderParams.Cull, (int)CullMode.Off); context.ExecuteCommandBuffer(cmd); cmd.Clear(); var filterDouble = new FilteringSettings(RenderQueueRange.opaque, dsMask, rlMaskDouble); context.DrawRenderers(renderingData.cullResults, ref drawSettingsObjId, ref filterDouble); cmd.SetGlobalInt(ShaderParams.Cull, (int)CullMode.Back); } // =========================== 2) Special-group pass =========================== if (useCustomGroups) { CoreUtils.SetRenderTarget(cmd, customGroupHandle, depthForObjectID, ClearFlag.Color, Color.black); context.ExecuteCommandBuffer(cmd); cmd.Clear(); var drawSettingsSpecial = CreateDrawingSettings(shaderTags, ref renderingData, SortingCriteria.CommonOpaque); drawSettingsSpecial.overrideMaterial = null; // use original materials to preserve displacement drawSettingsSpecial.enableInstancing = true; var filterCustom = new FilteringSettings(RenderQueueRange.opaque, specialMask, specialGroupCombined); context.DrawRenderers(renderingData.cullResults, ref drawSettingsSpecial, ref filterCustom); } // =========================== 3) Fill ObjectID pass =========================== int layerUnion = ssMask | dsMask; bool layersExcluded = layerUnion != cameraCullingMask; bool renderingLayersExcluded = (rlMaskSingle != uint.MaxValue) || (rlMaskDouble != uint.MaxValue) || (rlMaskSpecial != uint.MaxValue); bool needsOthersFill = (layersExcluded || renderingLayersExcluded) && settings.blendWithOthers.value; RTHandle objectIDForBlend = objectIDHandle; if (useCustomGroups || needsOthersFill) { fusionFillMaterial.SetFloat(ShaderParams.TerrainObjectId, settings.blendWithOthers.value ? 31f : 0f); RenderingUtils.ReAllocateIfNeeded(ref objectIDFilledHandle, objIdDesc, name: OBJECT_ID_FILLED_TEXTURE_NAME); cmd.SetGlobalTexture(ShaderParams.ObjectIDTexture, objectIDHandle); if (useCustomGroups) { cmd.SetGlobalTexture(ShaderParams.CustomGroupTexture, customGroupHandle); } CoreUtils.SetRenderTarget(cmd, objectIDFilledHandle, ClearFlag.None, Color.clear); CoreUtils.DrawFullScreen(cmd, fusionFillMaterial, null, ShaderParams.FillPassIndex); context.ExecuteCommandBuffer(cmd); cmd.Clear(); objectIDForBlend = objectIDFilledHandle; } // =========================== 4) Blend pass =========================== bool compareActive = settings.compareMode.value && settings.debugMode.value == DebugMode.None; if (compareActive) { RenderingUtils.ReAllocateIfNeeded(ref compareHandle, colorCopyDesc, name: COMPARE_TEXTURE_NAME); } cmd.SetGlobalTexture(ShaderParams.SrcColorTexture, cameraColorCopyHandle); cmd.SetGlobalTexture(ShaderParams.ObjectIDTexture, objectIDForBlend); RTHandle blendDestination = (compareActive && compareHandle != null) ? compareHandle : colorTarget; CoreUtils.SetRenderTarget(cmd, blendDestination, ClearFlag.None, Color.clear); CoreUtils.DrawFullScreen(cmd, fusionMaterial, null, (int)ShaderParams.Passes.Blend); context.ExecuteCommandBuffer(cmd); cmd.Clear(); // =========================== 5) Compare pass =========================== if (compareActive && compareHandle != null) { float angle = settings.compareSameSide.value ? Mathf.PI * 0.5f : settings.compareLineAngle.value; Vector4 compareParamsValue = new Vector4(Mathf.Cos(angle), Mathf.Sin(angle), settings.compareSameSide.value ? settings.comparePanning.value : -10f, settings.compareLineWidth.value); Color lineColor = settings.compareLineColor.value; Vector4 compareLineColor = new Vector4(lineColor.r, lineColor.g, lineColor.b, lineColor.a); cmd.SetGlobalTexture(ShaderParams.SrcColorTexture, cameraColorCopyHandle); cmd.SetGlobalTexture(ShaderParams.CompareTex, compareHandle); cmd.SetGlobalVector(ShaderParams.CompareParams, compareParamsValue); cmd.SetGlobalVector(ShaderParams.CompareLineColor, compareLineColor); CoreUtils.SetRenderTarget(cmd, colorTarget, ClearFlag.None, Color.clear); CoreUtils.DrawFullScreen(cmd, fusionMaterial, null, (int)ShaderParams.Passes.Compare); context.ExecuteCommandBuffer(cmd); cmd.Clear(); } // =========================== 6) Debug pass =========================== if (!compareActive && settings.debugMode.value != DebugMode.None) { cmd.SetGlobalTexture(ShaderParams.ObjectIDTexture, objectIDForBlend); CoreUtils.SetRenderTarget(cmd, colorTarget, ClearFlag.None, Color.clear); CoreUtils.DrawFullScreen(cmd, fusionMaterial, null, (int)ShaderParams.Passes.Debug); context.ExecuteCommandBuffer(cmd); cmd.Clear(); } // NOTE on camera stacking: the Unity 6 / Render Graph version has to reroute // resourceData.cameraColor to a fresh texture and then copy it back into the shared stack // color at the end, because render-graph resources are re-requested every frame. The // classic (2022.3) renderer already gives base and overlay cameras the SAME persistent // cameraColorTargetHandle, and every pass above wrote directly into `colorTarget` // (== cameraColorTargetHandle), so overlay cameras in the stack already see the result - // no extra copy-back blit is needed here. `isStackedBase` is kept above only for // documentation / in case you need to special-case stacked cameras later. _ = isStackedBase; } context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); } static bool RenderingLayersOverlap (uint maskA, uint maskB) { if (maskA == 0u || maskB == 0u) return false; if (maskA == uint.MaxValue || maskB == uint.MaxValue) return true; return (maskA & maskB) != 0u; } static IdExclusionPair[] GetActiveExclusionPairs () { if (cachedExclusionVolume != null && cachedExclusionVolume.isActiveAndEnabled && cachedEdgeFusion != null) { return cachedEdgeFusion.idExclusionPairs; } cachedExclusionVolume = null; cachedEdgeFusion = null; // Unity 2022.3: Object.FindObjectsByType does not exist yet - use the classic (obsolete but // functional) FindObjectsOfType instead of the Unity 6 FindObjectsByType overload. var volumes = Object.FindObjectsOfType(); int volumeCount = volumes.Length; for (int i = 0; i < volumeCount; i++) { var volume = volumes[i]; if (volume == null || !volume.isActiveAndEnabled || volume.sharedProfile == null) continue; if (!volume.sharedProfile.TryGet(out var ef)) continue; if (ef.idExclusionPairs != null) { cachedExclusionVolume = volume; cachedEdgeFusion = ef; return cachedEdgeFusion.idExclusionPairs; } } return null; } public void Cleanup () { CoreUtils.Destroy(objectIDMaterial); CoreUtils.Destroy(fusionMaterial); CoreUtils.Destroy(fusionFillMaterial); objectIDHandle?.Release(); objectIDFilledHandle?.Release(); objectIDDepthHandle?.Release(); customGroupHandle?.Release(); compareHandle?.Release(); cameraColorCopyHandle?.Release(); } } }