Beautify addon

This commit is contained in:
Williams
2026-08-13 16:49:22 +02:00
parent 4c5f11522d
commit d2d75a6656
140 changed files with 9943 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
namespace EdgeFusion {
public class ContactUsWindow : EditorWindow {
const string OnlineGuidesUrl = "https://kronnect.com/guides";
const string SupportUrl = "https://kronnect.com/support";
const string YoutubeUrl = "https://youtube.com/@kronnect";
const string TwitterUrl = "https://twitter.com/kronnect";
const string KronnectUrl = "https://assetstore.unity.com/publishers/15018?aid=1101lGsd";
const string ReferralInfo = "?aid=1101lGsd&pubref=edgefusion";
Texture2D kronnectLogo, welcomeBanner;
GUIStyle headerStyle, bannerStyle;
readonly List<AssetCategory> assetCategories = new List<AssetCategory>
{
new AssetCategory
{
name = "Image Effects",
assets = new List<Asset>
{
new Asset { name = "Beautify 3 - Advanced Post Processing", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/beautify-3-advanced-post-processing-233073" },
new Asset { name = "Frame Pack for Beautify", url = "https://assetstore.unity.com/packages/2d/gui/frame-pack-204058" },
new Asset { name = "LUT Pack for Beautify", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/lut-pack-for-beautify-202502" },
new Asset { name = "Beautify HDRP", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/beautify-hdrp-165411" },
new Asset { name = "Cloud Shadows FX", url = "https://assetstore.unity.com/packages/vfx/shaders/cloud-shadows-fx-267702" },
new Asset { name = "Dynamic Fog & Mist 2", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/dynamic-fog-mist-2-48200" },
new Asset { name = "Edge Fusion", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/edge-fusion-smooth-surface-contacts-334484" },
new Asset { name = "Global Snow 2", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/global-snow-2-248191" },
new Asset { name = "Luma Based Ambient Occlusion (SSAO 2D)", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/luma-based-ambient-occlusion-2-ssao-2d-249066" },
new Asset { name = "Radiant Global Illumination", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/radiant-global-illumination-225934" },
new Asset { name = "Shiny SSR 2", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/shiny-ssr-2-screen-space-reflections-188638" },
new Asset { name = "Sun Flares HDRP", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/sun-flares-hdrp-171177" },
new Asset { name = "Umbra Soft Shadows", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/umbra-soft-shadows-better-directional-contact-shadows-for-urp-282485" },
new Asset { name = "Volumetric Fog & Mist 2", url = "https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/volumetric-fog-mist-2-162694" },
new Asset { name = "Volumetric Lights 2", url = "https://assetstore.unity.com/packages/vfx/shaders/volumetric-lights-2-234539" },
new Asset { name = "Volumetric Lights Set", url = "https://assetstore.unity.com/packages/3d/props/volumetric-lights-set-237873" },
new Asset { name = "Volumetric Lights 2 HDRP", url = "https://assetstore.unity.com/packages/vfx/shaders/volumetric-lights-2-hdrp-243807" },
}
},
new AssetCategory
{
name = "Tools & Shaders",
assets = new List<Asset>
{
new Asset { name = "Compass Navigator Pro 2", url = "https://assetstore.unity.com/packages/tools/gui/compass-navigator-pro-2-273662" },
new Asset { name = "Highlight Plus", url = "https://assetstore.unity.com/packages/vfx/shaders/highlight-plus-all-in-one-outline-selection-effects-134149" },
new Asset { name = "Highlight Plus 2D", url = "https://assetstore.unity.com/packages/vfx/shaders/highlight-plus-2d-138383" },
new Asset { name = "Liquid Volume 2", url = "https://assetstore.unity.com/packages/vfx/shaders/liquid-volume-2-249127" },
new Asset { name = "Liquid Volume Pro 2", url = "https://assetstore.unity.com/packages/vfx/shaders/liquid-volume-pro-2-129967" },
new Asset { name = "Liquid Volume Pro 2 HDRP", url = "https://assetstore.unity.com/packages/vfx/shaders/liquid-volume-pro-2-hdrp-253786" },
new Asset { name = "Potions & Volumetric Liquid", url = "https://assetstore.unity.com/packages/slug/123474" },
new Asset { name = "Shader Control", url = "https://assetstore.unity.com/packages/vfx/shaders/shader-control-74817" },
new Asset { name = "Split Screen Pro", url = "https://assetstore.unity.com/packages/tools/camera/split-screen-pro-207149" },
new Asset { name = "Skybox Plus", url = "https://assetstore.unity.com/packages/2d/environments/skybox-plus-182966" },
new Asset { name = "Trails FX", url = "https://assetstore.unity.com/packages/vfx/shaders/trails-fx-146898" },
new Asset { name = "Transitions Plus", url = "https://assetstore.unity.com/packages/tools/camera/transitions-plus-266067" },
new Asset { name = "Tunnel FX 2", url = "https://assetstore.unity.com/packages/vfx/shaders/tunnel-fx-2-86544" },
new Asset { name = "Voxel Play 3", url = "https://assetstore.unity.com/packages/tools/game-toolkits/voxel-play-3-310775" },
new Asset { name = "Pirates of Voxel Play", url = "https://assetstore.unity.com/packages/tools/game-toolkits/pirates-of-voxel-play-189096" },
new Asset { name = "X-Frame FPS Accelerator", url = "https://assetstore.unity.com/packages/tools/camera/x-frame-fps-accelerator-63965" }
}
},
new AssetCategory
{
name = "Grids & Maps",
assets = new List<Asset>
{
new Asset { name = "Grids 2D", url = "https://assetstore.unity.com/packages/tools/game-toolkits/grids-2d-59981" },
new Asset { name = "Hexasphere Grid System", url = "https://assetstore.unity.com/packages/tools/modeling/hexasphere-grid-system-89112" },
new Asset { name = "Terrain Grid System 2", url = "https://assetstore.unity.com/packages/tools/terrain/terrain-grid-system-2-244921" },
new Asset { name = "World Map 2D Edition 2", url = "https://assetstore.unity.com/packages/tools/gui/world-map-2d-edition-2-151238" },
new Asset { name = "World Map Globe Edition 2", url = "https://assetstore.unity.com/packages/tools/gui/world-map-globe-edition-2-150643" },
new Asset { name = "World Map Strategy Kit 2", url = "https://assetstore.unity.com/packages/tools/game-toolkits/world-map-strategy-kit-2-150938" },
new Asset { name = "World Maps & Weather Symbols", url = "https://assetstore.unity.com/packages/2d/textures-materials/world-flags-and-weather-symbols-69010" },
new Asset { name = "Military Units 2D", url = "https://assetstore.unity.com/packages/2d/textures-materials/military-units-the-stylized-art-collection-187769" },
new Asset { name = "Military Units 3D", url = "https://assetstore.unity.com/packages/3d/vehicles/military-units-3d-246876" },
}
}
};
void OnEnable () {
kronnectLogo = Resources.Load<Texture2D>("kronnectLogo");
welcomeBanner = Resources.Load<Texture2D>("welcomeBanner");
}
void DrawHeader (string title) {
if (headerStyle == null) {
GUIStyle skurikenModuleTitleStyle = "ShurikenModuleTitle";
headerStyle = new GUIStyle(skurikenModuleTitleStyle) {
contentOffset = new Vector2(5f, -2f),
normal = { textColor = Color.white },
fixedHeight = 24,
fontSize = 13
};
}
GUILayout.Label(title, headerStyle);
}
void OnGUI () {
GUILayout.BeginHorizontal();
GUILayout.Space(10);
GUILayout.BeginVertical();
GUILayout.Space(10);
DrawHeader("Online Resources");
GUILayout.BeginHorizontal();
GUILayout.Space(10);
GUILayout.BeginVertical();
if (GUILayout.Button("Asset Documentation", EditorStyles.linkLabel)) {
Application.OpenURL(OnlineGuidesUrl);
}
if (GUILayout.Button("Support & Community", EditorStyles.linkLabel)) {
Application.OpenURL(SupportUrl);
}
if (GUILayout.Button("YouTube Channel", EditorStyles.linkLabel)) {
Application.OpenURL(YoutubeUrl);
}
if (GUILayout.Button("X / Twitter", EditorStyles.linkLabel)) {
Application.OpenURL(TwitterUrl);
}
if (GUILayout.Button("Kronnect Asset Store", EditorStyles.linkLabel)) {
Application.OpenURL(KronnectUrl);
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
if (bannerStyle == null) {
bannerStyle = new GUIStyle(GUI.skin.button);
bannerStyle.normal.background = welcomeBanner;
bannerStyle.normal.scaledBackgrounds = new[] { welcomeBanner };
}
const float width = 1100 / 2;
const float height = 200 / 2;
if (GUILayout.Button("", bannerStyle, GUILayout.Width(width), GUILayout.Height(height))) {
Application.OpenURL(KronnectUrl);
}
GUILayout.EndHorizontal();
GUILayout.EndHorizontal();
GUILayout.Space(20);
DrawHeader("Kronnect Assets");
GUIStyle textStyle = new GUIStyle(EditorStyles.wordWrappedLabel) {
fontSize = 11
};
GUILayout.Label("Thank you for using this asset!\nWe invite you to explore more assets and complete your collection by visiting the affiliated links below to the Asset Store:", textStyle);
EditorGUILayout.Space(10);
GUILayout.BeginHorizontal();
foreach (var category in assetCategories) {
GUILayout.BeginVertical();
GUILayout.Label(category.name, EditorStyles.boldLabel);
foreach (var asset in category.assets) {
GUILayout.BeginHorizontal();
GUILayout.Space(10);
if (GUILayout.Button(asset.name, EditorStyles.linkLabel)) {
Application.OpenURL(asset.url + ReferralInfo);
}
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Close", GUILayout.Width(60))) {
Close();
}
EditorGUILayout.Space();
GUILayout.Label(new GUIContent(kronnectLogo), GUILayout.Width(100), GUILayout.Height(30));
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
GUILayout.EndVertical();
GUILayout.Space(10);
GUILayout.EndHorizontal();
}
public static void ShowScreen () {
ContactUsWindow window = GetWindow<ContactUsWindow>(true, "Online Resources", true);
window.minSize = window.maxSize = new Vector2(750, 640);
}
[System.Serializable]
public class AssetCategory {
public string name;
public List<Asset> assets;
}
[System.Serializable]
public class Asset {
public string name;
public string url;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 806f9c43fa6ac884c967fedf7fd3bf8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
{
"name": "EdgeFusionEditor",
"rootNamespace": "",
"references": [
"GUID:823cc7d3f7f1d438d9ad8413a6147ef0",
"GUID:3eae0364be2026648bf74846acb8a731",
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
"GUID:f279941a09ad6a84abae8535f4c1e3fe"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3c541b389c1ff8c4eb32767616e60357
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,489 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
namespace EdgeFusion {
[CustomEditor(typeof(global::EdgeFusion.EdgeFusion))]
public class EdgeFusionEditor : VolumeComponentEditor {
SerializedDataParameter blendLayers, renderingLayerFilter, blendWithOthers, specialGroupLayers, doubleSidedLayers, intensity, radius, distanceCompensation, enableIntraObjectFusion, normalThreshold, concavityTest, intraObjectFusionPerObject;
SerializedDataParameter sampleCount, jitter, maxBlendDistance, binarySearchSteps, maxScreenRadius;
SerializedDataParameter doubleSidedRenderingLayerFilter, specialGroupRenderingLayerFilter;
SerializedDataParameter earlyExitHits, antiFlicker;
SerializedDataParameter debugMode, depthDebugMultiplier, shadowProtection, noiseIntensity, noiseScale, noiseContrast;
SerializedDataParameter compareMode, compareSameSide, comparePanning, compareLineAngle, compareLineWidth, compareLineColor;
SerializedDataParameter msaaEdgeFixPower;
SerializedProperty idExclusionPairs;
static readonly QualityPreset[] k_Presets = (QualityPreset[])Enum.GetValues(typeof(QualityPreset));
static readonly int[] k_PresetValues = k_Presets
.Select(p => (int)p)
.ToArray();
static PropertyInfo pipelineMsaaProperty;
static Type cachedPipelineType;
string[] presetNames;
bool renderFeaturePresent;
Terrain[] sceneTerrainsCache;
bool hasEdgeFusionObjectInScene;
const string k_AdditionalGroupsFoldoutKey = "EdgeFusion.AdditionalGroupsFoldout";
bool showAdditionalGroups;
bool msaaCameraActive;
public override void OnEnable () {
try {
presetNames = k_Presets
.Select(p => ObjectNames.NicifyVariableName(p.ToString()))
.ToArray();
var o = new PropertyFetcher<global::EdgeFusion.EdgeFusion>(serializedObject);
blendLayers = Unpack(o.Find(x => x.blendLayers));
renderingLayerFilter = Unpack(o.Find(x => x.renderingLayerFilter));
blendWithOthers = Unpack(o.Find(x => x.blendWithOthers));
specialGroupLayers = Unpack(o.Find(x => x.specialGroupLayers));
doubleSidedLayers = Unpack(o.Find(x => x.doubleSidedLayers));
doubleSidedRenderingLayerFilter = Unpack(o.Find(x => x.doubleSidedRenderingLayerFilter));
specialGroupRenderingLayerFilter = Unpack(o.Find(x => x.specialGroupRenderingLayerFilter));
intensity = Unpack(o.Find(x => x.intensity));
radius = Unpack(o.Find(x => x.radius));
distanceCompensation = Unpack(o.Find(x => x.distanceCompensation));
enableIntraObjectFusion = Unpack(o.Find(x => x.enableIntraObjectFusion));
intraObjectFusionPerObject = Unpack(o.Find(x => x.intraObjectFusionPerObject));
normalThreshold = Unpack(o.Find(x => x.normalThreshold));
concavityTest = Unpack(o.Find(x => x.concavityTest));
sampleCount = Unpack(o.Find(x => x.sampleCount));
jitter = Unpack(o.Find(x => x.jitter));
maxBlendDistance = Unpack(o.Find(x => x.maxBlendDistance));
binarySearchSteps = Unpack(o.Find(x => x.binarySearchSteps));
earlyExitHits = Unpack(o.Find(x => x.earlyExitHits));
antiFlicker = Unpack(o.Find(x => x.antiFlicker));
debugMode = Unpack(o.Find(x => x.debugMode));
depthDebugMultiplier = Unpack(o.Find(x => x.depthDebugMultiplier));
msaaEdgeFixPower = Unpack(o.Find(x => x.msaaEdgeFixPower));
shadowProtection = Unpack(o.Find(x => x.shadowProtection));
maxScreenRadius = Unpack(o.Find(x => x.maxScreenRadius));
noiseIntensity = Unpack(o.Find(x => x.noiseIntensity));
noiseScale = Unpack(o.Find(x => x.noiseScale));
noiseContrast = Unpack(o.Find(x => x.noiseContrast));
compareMode = Unpack(o.Find(x => x.compareMode));
compareSameSide = Unpack(o.Find(x => x.compareSameSide));
comparePanning = Unpack(o.Find(x => x.comparePanning));
compareLineAngle = Unpack(o.Find(x => x.compareLineAngle));
compareLineWidth = Unpack(o.Find(x => x.compareLineWidth));
compareLineColor = Unpack(o.Find(x => x.compareLineColor));
idExclusionPairs = serializedObject.FindProperty("idExclusionPairs");
renderFeaturePresent = IsEdgeFusionRenderFeaturePresent();
// PORT NOTE (Unity 2022.3): FindObjectsByType does not exist before 2023.1; use the classic API.
sceneTerrainsCache = UnityEngine.Object.FindObjectsOfType<Terrain>();
showAdditionalGroups = EditorPrefs.GetBool(k_AdditionalGroupsFoldoutKey, false);
msaaCameraActive = CameraUsesMsaa();
hasEdgeFusionObjectInScene = SceneHasEdgeFusionObject();
}
catch {
}
}
public override void OnInspectorGUI () {
int blendLayersValue = blendLayers.overrideState.boolValue ? blendLayers.value.intValue : -1;
int doubleSidedLayersValue = doubleSidedLayers.overrideState.boolValue ? doubleSidedLayers.value.intValue : 0;
int specialGroupLayersValue = specialGroupLayers.overrideState.boolValue ? specialGroupLayers.value.intValue : 0;
uint renderingLayerFilterValue = renderingLayerFilter.overrideState.boolValue ? (uint)renderingLayerFilter.value.intValue : uint.MaxValue;
serializedObject.Update();
if (!renderFeaturePresent) {
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Edge Fusion Render Feature is not added to the current URP Renderer. The effect will not be visible.", MessageType.Error);
if (GUILayout.Button("Select Current URP Renderer", GUILayout.Height(20))) {
SelectCurrentURPRenderer();
}
EditorGUILayout.Space();
}
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Online Resources & Support")) {
ContactUsWindow.ShowScreen();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Object Selection", EditorStyles.boldLabel);
PropertyField(blendLayers);
PropertyField(renderingLayerFilter, new GUIContent("Rendering Layer Filter"));
// Only show "Blend With Others" if something is excluded (otherwise it has no effect)
bool somethingExcluded = blendLayersValue != -1 || renderingLayerFilterValue != uint.MaxValue;
if (somethingExcluded) {
PropertyField(blendWithOthers, new GUIContent("Blend With Others", "When enabled, visible geometry not selected by any layer setting will also blend with marked objects."));
}
EditorGUILayout.Space(4);
using (var foldoutScope = new EditorGUI.ChangeCheckScope()) {
showAdditionalGroups = EditorGUILayout.Foldout(showAdditionalGroups, "Additional Object Groups", true);
if (foldoutScope.changed) {
EditorPrefs.SetBool(k_AdditionalGroupsFoldoutKey, showAdditionalGroups);
}
}
if (showAdditionalGroups) {
EditorGUI.indentLevel++;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Double-Sided Objects", EditorStyles.miniLabel);
PropertyField(doubleSidedLayers, new GUIContent("Layers"));
PropertyField(doubleSidedRenderingLayerFilter, new GUIContent("Rendering Layer Filter"));
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Special Group (vertex displacement, animated vegetation)", EditorStyles.miniLabel);
PropertyField(specialGroupLayers, new GUIContent("Layers"));
PropertyField(specialGroupRenderingLayerFilter, new GUIContent("Rendering Layer Filter"));
EditorGUILayout.EndVertical();
EditorGUI.indentLevel--;
if (specialGroupLayers.overrideState.boolValue && specialGroupLayersValue == -1) {
EditorGUILayout.HelpBox("Special Group selection should only include objects with custom vertex shaders.", MessageType.Warning);
}
}
if ((blendLayersValue & doubleSidedLayersValue) != 0) {
EditorGUILayout.HelpBox("Layers included in Double-Sided Layers are automatically excluded from Blend Layers.", MessageType.Info);
}
if (((blendLayersValue & specialGroupLayersValue) != 0) || ((doubleSidedLayersValue & specialGroupLayersValue) != 0)) {
EditorGUILayout.HelpBox("Layers included in Special Group Layers are automatically excluded from Blend Layers and Double-Sided Layers.", MessageType.Info);
}
int effectiveBlendForTerrain = (blendLayersValue & ~doubleSidedLayersValue) & ~specialGroupLayersValue;
CheckTerrainBlendLayers(effectiveBlendForTerrain, renderingLayerFilterValue);
EditorGUILayout.Space();
EditorGUILayout.LabelField("ID Exclusions", EditorStyles.boldLabel);
DrawIdExclusionPairs(blendLayersValue, renderingLayerFilterValue);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Blending", EditorStyles.boldLabel);
PropertyField(intensity);
PropertyField(radius);
PropertyField(distanceCompensation, new GUIContent("Distance Compensation", "Scales the effective radius proportionally with distance from the camera. Keeps a small radius up close while increasing it for distant objects."));
PropertyField(maxScreenRadius);
PropertyField(enableIntraObjectFusion);
if (enableIntraObjectFusion.value.boolValue) {
EditorGUI.indentLevel++;
PropertyField(normalThreshold);
PropertyField(concavityTest);
PropertyField(intraObjectFusionPerObject, new GUIContent("Per Object", "When enabled, intra-object fusion only runs on renderers that have an Edge Fusion Object component with the effect allowed; fill-pass normals are skipped."));
if (intraObjectFusionPerObject.value.boolValue && !hasEdgeFusionObjectInScene) {
EditorGUILayout.HelpBox("Add 'Edge Fusion Object' component to the gameobjects you want to control intra-object fusion.", MessageType.Warning);
}
EditorGUI.indentLevel--;
}
PropertyField(shadowProtection);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Noise", EditorStyles.boldLabel);
PropertyField(noiseIntensity);
if (noiseIntensity.value.floatValue > 0) {
EditorGUI.indentLevel++;
PropertyField(noiseScale, new GUIContent("Scale"));
PropertyField(noiseContrast, new GUIContent("Contrast"));
EditorGUI.indentLevel--;
}
PropertyField(jitter);
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel);
if (GUILayout.Button("Tips", GUILayout.Width(50), GUILayout.Height(18))) {
Application.OpenURL("https://kronnect.com/guides/edge-fusion-performance-tips/");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
PropertyField(sampleCount);
int currentQuality = sampleCount.value.intValue;
int selectedIndex = GetClosestPresetIndex(currentQuality);
using (var cc = new EditorGUI.ChangeCheckScope()) {
EditorGUI.BeginDisabledGroup(!sampleCount.overrideState.boolValue);
int newIndex = EditorGUILayout.Popup(selectedIndex, presetNames ?? Array.Empty<string>(), GUILayout.Width(80f));
EditorGUI.EndDisabledGroup();
if (cc.changed && newIndex >= 0 && newIndex < k_PresetValues.Length) {
var selectedPreset = (QualityPreset)k_PresetValues[newIndex];
sampleCount.value.intValue = k_PresetValues[newIndex];
binarySearchSteps.value.intValue = GetBinarySearchStepsForPreset(selectedPreset);
binarySearchSteps.overrideState.boolValue = true;
earlyExitHits.value.intValue = GetEarlyExitHitsForPreset(selectedPreset);
earlyExitHits.overrideState.boolValue = true;
}
}
EditorGUILayout.EndHorizontal();
PropertyField(binarySearchSteps);
PropertyField(earlyExitHits, new GUIContent("Early Exit Hits", "Stops searching edge position after n nearest edge hits"));
PropertyField(antiFlicker, new GUIContent("Anti-Flicker", "Stabilises the blending when two edge candidates are near-tie in distance. Averages them with a soft-argmin so edgeUV moves continuously instead of snapping between winners. Helps in any scene with sub-pixel motion between frames (VR, slow camera motion, animated objects, FPS mouse look, camera shake). Disable to match the classic pick-one behaviour."));
PropertyField(maxBlendDistance);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Debug", EditorStyles.boldLabel);
PropertyField(debugMode);
if (debugMode.overrideState.boolValue && debugMode.value.intValue == (int)DebugMode.Depth) {
EditorGUI.indentLevel++;
PropertyField(depthDebugMultiplier, new GUIContent("Depth Multiplier"));
EditorGUI.indentLevel--;
}
if (debugMode.overrideState.boolValue && debugMode.value.intValue == (int)DebugMode.SpecialGroup && (!specialGroupLayers.overrideState.boolValue || specialGroupLayersValue == 0)) {
EditorGUILayout.HelpBox("Special Group is not executing because Special Group Layers is set to Nothing.", MessageType.Warning);
}
PropertyField(compareMode, new GUIContent("Compare Mode"));
if (compareMode.value.boolValue) {
EditorGUI.indentLevel++;
PropertyField(compareSameSide, new GUIContent("Same Side"));
if (compareSameSide.value.boolValue) {
PropertyField(comparePanning, new GUIContent("Panning"));
}
else {
PropertyField(compareLineAngle, new GUIContent("Line Angle"));
}
PropertyField(compareLineWidth, new GUIContent("Line Width"));
PropertyField(compareLineColor, new GUIContent("Line Color"));
EditorGUI.indentLevel--;
}
if (msaaCameraActive) {
PropertyField(msaaEdgeFixPower, new GUIContent("MSAA Edge Fix Amount", "When using MSAA in forward pass and certain blending layers configuration, you can use this option to reduce MSAA edge artifacts due to multi-sampling."));
}
serializedObject.ApplyModifiedProperties();
}
void CheckTerrainBlendLayers (int blendLayerMask, uint renderingLayerMaskValue) {
if (sceneTerrainsCache == null || sceneTerrainsCache.Length == 0) return;
bool hasTerrainWithDrawInstanced = false;
bool hasTerrainWithoutDrawInstanced = false;
uint rlMask = renderingLayerMaskValue;
foreach (var terrain in sceneTerrainsCache) {
if (terrain == null) continue;
bool selectedByBlendLayers = IsLayerInMask(terrain.gameObject.layer, blendLayerMask);
bool selectedByRenderingLayers = (rlMask == uint.MaxValue) || ((terrain.renderingLayerMask & rlMask) != 0);
bool consideredForBlend = selectedByBlendLayers && selectedByRenderingLayers;
if (!consideredForBlend) continue;
if (terrain.drawInstanced) {
hasTerrainWithDrawInstanced = true;
}
else {
hasTerrainWithoutDrawInstanced = true;
}
}
if (hasTerrainWithDrawInstanced) {
EditorGUILayout.HelpBox(
"Terrain has Draw Instanced option enabled which is not compatible with Edge Fusion. However, you can exclude it from blending layers or rendering layers as a workaround which is also faster.",
MessageType.Error
);
}
else if (hasTerrainWithoutDrawInstanced) {
EditorGUILayout.HelpBox(
"For best performance, you can exclude terrain objects from blending (via Blend Layers or Rendering Layers).",
MessageType.Warning
);
}
}
static bool IsLayerInMask (int layer, int layerMask) {
return ((layerMask & (1 << layer)) != 0);
}
static readonly string[] exclusionIdNames = BuildExclusionIdNames();
bool SceneHasEdgeFusionObject () {
// PORT NOTE (Unity 2022.3): FindObjectsByType does not exist before 2023.1; use the classic API.
return UnityEngine.Object.FindObjectsOfType<EdgeFusionObject>().Length > 0;
}
static string[] BuildExclusionIdNames () {
var names = new string[32];
for (int i = 0; i < 30; i++) {
names[i] = $"ID {i + 1}";
}
names[30] = "ID 31 (Terrain)";
names[31] = "ID 32 (Special Group)";
return names;
}
void DrawIdExclusionPairs (int blendLayersValue, uint renderingLayerFilterValue) {
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15f);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Exclusion Pairs", EditorStyles.miniLabel);
bool usesTerrain = false;
for (int i = 0; i < idExclusionPairs.arraySize; i++) {
var element = idExclusionPairs.GetArrayElementAtIndex(i);
var enabledProp = element.FindPropertyRelative("enabled");
var idA = element.FindPropertyRelative("idA");
var idB = element.FindPropertyRelative("idB");
if (enabledProp.boolValue && (idA.intValue == 31 || idB.intValue == 31)) usesTerrain = true;
EditorGUILayout.BeginHorizontal();
enabledProp.boolValue = EditorGUILayout.Toggle(enabledProp.boolValue, GUILayout.Width(16));
EditorGUI.BeginDisabledGroup(!enabledProp.boolValue);
idA.intValue = EditorGUILayout.Popup(idA.intValue - 1, exclusionIdNames) + 1;
EditorGUILayout.LabelField("↔", GUILayout.Width(20));
idB.intValue = EditorGUILayout.Popup(idB.intValue - 1, exclusionIdNames) + 1;
EditorGUI.EndDisabledGroup();
if (GUILayout.Button("×", GUILayout.Width(20))) {
idExclusionPairs.DeleteArrayElementAtIndex(i);
i--;
}
EditorGUILayout.EndHorizontal();
}
if (GUILayout.Button("+ Add Exclusion Pair")) {
idExclusionPairs.InsertArrayElementAtIndex(idExclusionPairs.arraySize);
var newElement = idExclusionPairs.GetArrayElementAtIndex(idExclusionPairs.arraySize - 1);
newElement.FindPropertyRelative("enabled").boolValue = true;
newElement.FindPropertyRelative("idA").intValue = 1;
newElement.FindPropertyRelative("idB").intValue = 31;
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
bool terrainIncludedInBlend = blendLayersValue == -1 && renderingLayerFilterValue == uint.MaxValue;
if (usesTerrain && terrainIncludedInBlend) {
EditorGUILayout.HelpBox(
"You are excluding Terrain (ID 31) but Blend Layers and Rendering Layer Filter include everything. " +
"For terrain to receive ID 31, it must be excluded from Blend Layers or Rendering Layer Filter so it goes through the fill pass.",
MessageType.Warning
);
}
if (idExclusionPairs.arraySize > 0) {
EditorGUILayout.HelpBox(
"IDs 1-30: Set via EdgeFusionObject component\n" +
"ID 31: Terrain (objects not in Blend Layers)\n" +
"ID 32: Special Group objects",
MessageType.Info
);
}
}
static int GetClosestPresetIndex (int sampleCount) {
int closestIndex = 0;
int smallestDelta = int.MaxValue;
for (int i = 0; i < k_PresetValues.Length; i++) {
int delta = Mathf.Abs(k_PresetValues[i] - sampleCount);
if (delta < smallestDelta) {
smallestDelta = delta;
closestIndex = i;
}
}
return closestIndex;
}
static int GetBinarySearchStepsForPreset (QualityPreset preset) {
switch (preset) {
case QualityPreset.VeryLow: return 2;
case QualityPreset.Low: return 4;
case QualityPreset.Medium: return 5;
case QualityPreset.High: return 7;
case QualityPreset.VeryHigh: return 8;
default: return 7;
}
}
static int GetEarlyExitHitsForPreset (QualityPreset preset) {
switch (preset) {
case QualityPreset.VeryLow: return 1;
case QualityPreset.Low: return 2;
case QualityPreset.Medium: return 3;
case QualityPreset.High: return 4;
case QualityPreset.VeryHigh: return 5;
default: return 5;
}
}
bool IsEdgeFusionRenderFeaturePresent () {
return GetCurrentURPRendererData() != null && CheckRendererForEdgeFusion(GetCurrentURPRendererData());
}
UnityEngine.Object GetCurrentURPRendererData () {
var pipeline = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
if (pipeline == null) return null;
var serializedAsset = new SerializedObject(pipeline);
var rendererDataList = serializedAsset.FindProperty("m_RendererDataList");
if (rendererDataList == null || !rendererDataList.isArray || rendererDataList.arraySize == 0) return null;
var defaultRendererIndex = serializedAsset.FindProperty("m_DefaultRendererIndex");
int index = defaultRendererIndex != null ? defaultRendererIndex.intValue : 0;
if (index < 0 || index >= rendererDataList.arraySize) return null;
var rendererDataProperty = rendererDataList.GetArrayElementAtIndex(index);
return rendererDataProperty.objectReferenceValue;
}
bool CheckRendererForEdgeFusion (UnityEngine.Object rendererData) {
if (rendererData == null) return false;
var serializedRenderer = new SerializedObject(rendererData);
var featuresProperty = serializedRenderer.FindProperty("m_RendererFeatures");
if (featuresProperty == null || !featuresProperty.isArray) return false;
for (int i = 0; i < featuresProperty.arraySize; i++) {
var featureProperty = featuresProperty.GetArrayElementAtIndex(i);
var feature = featureProperty.objectReferenceValue;
if (feature != null && feature.GetType().Name == "EdgeFusionRenderFeature") {
return true;
}
}
return false;
}
void SelectCurrentURPRenderer () {
var rendererData = GetCurrentURPRendererData();
if (rendererData != null) {
Selection.activeObject = rendererData;
EditorGUIUtility.PingObject(rendererData);
}
}
static bool CameraUsesMsaa () {
if (TryGetPipelineMsaaSamples(GraphicsSettings.currentRenderPipeline, out int pipelineSamples) && pipelineSamples > 1) {
return true;
}
foreach (Camera cam in Camera.allCameras) {
if (cam == null || !cam.allowMSAA) continue;
if (cam.targetTexture != null) {
if (cam.targetTexture.antiAliasing > 1) return true;
}
else if (QualitySettings.antiAliasing > 1) {
return true;
}
}
return false;
}
static bool TryGetPipelineMsaaSamples (RenderPipelineAsset pipeline, out int samples) {
samples = 0;
if (pipeline == null) return false;
var type = pipeline.GetType();
if (type != cachedPipelineType) {
cachedPipelineType = type;
pipelineMsaaProperty = type.GetProperty("msaaSampleCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
if (pipelineMsaaProperty != null && pipelineMsaaProperty.PropertyType == typeof(int)) {
samples = (int)pipelineMsaaProperty.GetValue(pipeline);
return true;
}
return false;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c98b9d430534f2048b7fd84249cf6125
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,96 @@
using UnityEditor;
using UnityEngine;
namespace EdgeFusion {
[CustomEditor(typeof(EdgeFusionObject))]
public class EdgeFusionObjectEditor : Editor {
SerializedProperty overrideRadius;
SerializedProperty customRadius;
SerializedProperty overrideObjectId;
SerializedProperty customObjectId;
SerializedProperty useRandomId;
SerializedProperty randomIdPerChild;
SerializedProperty disallowIntraObjectFusion;
SerializedProperty includeMode;
SerializedProperty childLayerMask;
void OnEnable () {
overrideRadius = serializedObject.FindProperty("overrideRadius");
customRadius = serializedObject.FindProperty("customRadius");
overrideObjectId = serializedObject.FindProperty("overrideObjectId");
customObjectId = serializedObject.FindProperty("customObjectId");
useRandomId = serializedObject.FindProperty("useRandomId");
randomIdPerChild = serializedObject.FindProperty("randomIdPerChild");
disallowIntraObjectFusion = serializedObject.FindProperty("disallowIntraObjectFusion");
includeMode = serializedObject.FindProperty("includeMode");
childLayerMask = serializedObject.FindProperty("childLayerMask");
EdgeFusionObject edgeFusionObject = (EdgeFusionObject)target;
edgeFusionObject.Refresh();
}
public override void OnInspectorGUI () {
serializedObject.Update();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Optional Override", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(overrideObjectId, new GUIContent("Override Object ID"));
if (overrideObjectId.boolValue) {
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(useRandomId, new GUIContent("Use Random ID", "Use a random Object ID, instead of an Id derived from object position"));
if (useRandomId.boolValue && includeMode.intValue == (int)IncludeMode.IncludeChildren) {
EditorGUILayout.PropertyField(randomIdPerChild, new GUIContent("Random Id Per Child", "Assign a different random ID to each child renderer"));
}
if (!useRandomId.boolValue) {
EditorGUILayout.PropertyField(customObjectId, new GUIContent("Custom Object ID", "0 = auto. >0 overrides the auto-generated ID. Objects sharing the same ID are considered the same for inter-object detection."));
if (customObjectId.intValue <= 0) {
EditorGUILayout.HelpBox("Custom Object ID must be greater than 0.", MessageType.Warning);
}
}
EditorGUI.indentLevel--;
}
EditorGUILayout.PropertyField(overrideRadius, new GUIContent("Override Radius"));
if (overrideRadius.boolValue) {
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(customRadius, new GUIContent("Custom Radius", "Custom blend radius in meters (0 = disables blending for this object)"));
EditorGUI.indentLevel--;
}
EditorGUILayout.PropertyField(disallowIntraObjectFusion, new GUIContent("Disallow Intra-Object Fusion", "Disable intra-object fusion for this object/children while keeping inter-object fusion."));
EditorGUILayout.Space();
EditorGUILayout.LabelField("Inclusion", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(includeMode, new GUIContent("Include Mode"));
if (includeMode.intValue == (int)IncludeMode.IncludeChildren) {
EditorGUILayout.PropertyField(childLayerMask, new GUIContent("Child Layer Mask"));
}
if (!serializedObject.isEditingMultipleObjects) {
EdgeFusionObject edgeFusionObject = (EdgeFusionObject)target;
Material invalidMaterial = edgeFusionObject.nonInstancingMaterial;
if (invalidMaterial != null) {
EditorGUILayout.Space();
EditorGUILayout.HelpBox($"Material '{invalidMaterial.name}' does not support GPU instancing.", MessageType.Warning);
if (GUILayout.Button("Select Material", EditorStyles.linkLabel)) {
Selection.activeObject = invalidMaterial;
EditorGUIUtility.PingObject(invalidMaterial);
}
}
} else {
for (int i = 0; i < targets.Length; i++) {
EdgeFusionObject edgeFusionObject = (EdgeFusionObject)targets[i];
if (edgeFusionObject.nonInstancingMaterial != null) {
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Some selected objects use materials that do not support GPU instancing.", MessageType.Warning);
break;
}
}
}
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 83616b2fc8a9f764d9290e84c7e1b5e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a4042a265c63b614e80c25998e440a47
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: e1ace189099e25c4ba86243c9d9c58e0
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: cce82a93703f33d47918c4bc452af9ed
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant: