601 lines
32 KiB
C#
601 lines
32 KiB
C#
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<Volume>()` 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<ShaderTagId> 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<ShaderTagId> {
|
||
|
|
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<Texture3D>("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<EdgeFusion>();
|
||
|
|
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<Volume>();
|
||
|
|
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<EdgeFusion>(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();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|