1797 lines
72 KiB
C#
1797 lines
72 KiB
C#
// Crest Water System
|
|
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
|
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.HighDefinition;
|
|
using UnityEngine.Rendering.Universal;
|
|
using WaveHarmonic.Crest.Editor.Settings;
|
|
using WaveHarmonic.Crest.Internal;
|
|
using WaveHarmonic.Crest.Watercraft;
|
|
|
|
using static WaveHarmonic.Crest.Editor.ValidatedHelper;
|
|
using MessageType = WaveHarmonic.Crest.Editor.ValidatedHelper.MessageType;
|
|
|
|
namespace WaveHarmonic.Crest.Editor
|
|
{
|
|
static class Validators
|
|
{
|
|
// HDRP sub-shader always first.
|
|
const int k_SubShaderIndexHDRP = 0;
|
|
const string k_NoneQueryProviderCollisionFloatingObjects = "The floating objects in the scene will use a flat horizontal plane.";
|
|
const string k_NoneQueryProviderFlowFloatingObjects = "The floating objects in the scene will not receive motion from flow.";
|
|
|
|
internal static WaterRenderer Water => Utility.Water;
|
|
static readonly System.Collections.Generic.List<Terrain> s_Terrains = new();
|
|
static readonly ShaderTagId s_RenderPipelineShaderTagID = new("RenderPipeline");
|
|
|
|
[Validator(typeof(LodInput))]
|
|
static bool ValidateTextureInput(LodInput target, ShowMessage messenger)
|
|
{
|
|
if (target.Data is not TextureLodInputData data) return true;
|
|
|
|
var isValid = true;
|
|
|
|
if (data._Texture == null)
|
|
{
|
|
messenger
|
|
(
|
|
"Texture mode requires a texture.",
|
|
"Assign a texture.",
|
|
MessageType.Error,
|
|
target
|
|
);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(LodInput))]
|
|
static bool ValidateGeometryInput(LodInput target, ShowMessage messenger)
|
|
{
|
|
if (target.Data is not GeometryLodInputData data) return true;
|
|
|
|
var isValid = true;
|
|
|
|
if (data._Geometry == null)
|
|
{
|
|
messenger
|
|
(
|
|
"Geometry mode requires geometry (ie mesh).",
|
|
"Assign geometry.",
|
|
MessageType.Error,
|
|
target
|
|
);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(LodInput))]
|
|
static bool ValidateRendererInput(LodInput target, ShowMessage messenger)
|
|
{
|
|
if (target.Data is not RendererLodInputData data) return true;
|
|
|
|
// Check if Renderer component is attached.
|
|
var isValid = ValidateRenderer<Renderer>
|
|
(
|
|
target,
|
|
data._Renderer,
|
|
messenger,
|
|
data._CheckShaderPasses && (!data._OverrideShaderPass || data._ShaderPassIndex != -1),
|
|
data._CheckShaderName ? data.ShaderPrefix : string.Empty
|
|
);
|
|
|
|
if (data._Renderer == null)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
// Can cause problems if culling masks are used.
|
|
if (!data._DisableRenderer)
|
|
{
|
|
isValid = ValidateRendererLayer(target.gameObject, messenger, Water) && isValid;
|
|
}
|
|
|
|
var isPersistent = target is FoamLodInput or DynamicWavesLodInput or ShadowLodInput;
|
|
|
|
var materials = data._Renderer.sharedMaterials;
|
|
for (var i = 0; i < materials.Length; i++)
|
|
{
|
|
var material = materials[i];
|
|
if (material == null) continue;
|
|
if (material.shader == null) continue;
|
|
|
|
if (data._OverrideShaderPass && data._ShaderPassIndex > material.shader.passCount - 1)
|
|
{
|
|
messenger
|
|
(
|
|
$"The shader <i>{material.shader.name}</i> used by this input has opted for the shader pass " +
|
|
$"index {data._ShaderPassIndex}, but there is only {material.shader.passCount} passes on the shader.",
|
|
"Choose a valid shader pass.",
|
|
MessageType.Error, target
|
|
);
|
|
}
|
|
|
|
if (isPersistent)
|
|
{
|
|
if (material.shader.name is "Crest/Inputs/All/Utility" or "Crest/Inputs/All/Scale")
|
|
{
|
|
messenger
|
|
(
|
|
$"The shader <i>{material.shader.name}</i> currently is not supported by this simulation " +
|
|
"(Foam, Dynamic Waves or Shadow) as the shader does not support time steps.",
|
|
"Choose a valid shader (not <i>Crest/Inputs/All/Utility</i> or <i>Crest/Inputs/All/Scale</i>).",
|
|
MessageType.Error, target
|
|
);
|
|
}
|
|
}
|
|
|
|
#if d_UnityHDRP
|
|
if (RenderPipelineHelper.IsHighDefinition)
|
|
{
|
|
if (AssetDatabase.GetAssetPath(material.shader).EndsWith(".shadergraph") && material.shader.FindSubshaderTagValue(k_SubShaderIndexHDRP, s_RenderPipelineShaderTagID).name == "HDRenderPipeline")
|
|
{
|
|
messenger
|
|
(
|
|
"It appears you are using Shader Graph with the HDRP target. " +
|
|
"Make sure to use the Built-In target instead for your Shader Graph to work.",
|
|
"Remove the HDRP target and add the Built-In target.",
|
|
MessageType.Warning, material.shader
|
|
);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateRendererLayer(GameObject gameObject, ShowMessage messenger, WaterRenderer water)
|
|
{
|
|
if (water != null && gameObject.layer != water.Surface.Layer)
|
|
{
|
|
var layerName = LayerMask.LayerToName(water.Surface.Layer);
|
|
messenger
|
|
(
|
|
$"The layer is not the same as the <i>{nameof(WaterRenderer)}.{nameof(WaterRenderer.Surface)}.{nameof(SurfaceRenderer.Layer)} ({layerName})</i> which can cause problems if the <i>{layerName}</i> layer is excluded from any culling masks.",
|
|
$"Set layer to <i>{layerName}</i>.",
|
|
MessageType.Warning, gameObject,
|
|
(_, _) =>
|
|
{
|
|
Undo.RecordObject(gameObject, $"Change Layer to {layerName}");
|
|
gameObject.layer = water.Surface.Layer;
|
|
}
|
|
);
|
|
}
|
|
|
|
// Is valid as not outright invalid but could be.
|
|
return true;
|
|
}
|
|
|
|
static bool Validate(WaterReflections target, ShowMessage messenger, WaterRenderer water)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (!target._Enabled)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
var material = water.Surface.Material;
|
|
|
|
if (material != null)
|
|
{
|
|
if (material.HasProperty(WaterRenderer.ShaderIDs.s_PlanarReflectionsEnabled) && material.GetFloat(WaterRenderer.ShaderIDs.s_PlanarReflectionsEnabled) == 0)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Planar Reflections</i> are not enabled on the <i>{material.name}</i> material and will not be visible.",
|
|
$"Enable <i>Planar Reflections</i> on the material (<i>{material.name}</i>) currently assigned to the <i>{nameof(WaterRenderer)}</i> component.",
|
|
MessageType.Warning, material
|
|
);
|
|
}
|
|
|
|
if (material.HasProperty(WaterRenderer.ShaderIDs.s_Occlusion) && target._Mode != WaterReflectionSide.Below && material.GetFloat(WaterRenderer.ShaderIDs.s_Occlusion) == 0)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Occlusion</i> is set to zero on the <i>{material.name}</i> material. Planar reflections will not be visible.",
|
|
$"Increase <i>Occlusion</i> on the material (<i>{material.name}</i>) currently assigned to the <i>{nameof(WaterRenderer)}</i> component.",
|
|
MessageType.Warning, material
|
|
);
|
|
}
|
|
|
|
if (material.HasProperty(WaterRenderer.ShaderIDs.s_OcclusionUnderwater) && target._Mode != WaterReflectionSide.Above && material.GetFloat(WaterRenderer.ShaderIDs.s_OcclusionUnderwater) == 0)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Occlusion (U)</i> is set to zero on the <i>{material.name}</i> material. Planar reflections will not be visible.",
|
|
$"Increase <i>Occlusion (U)</i> on the material (<i>{material.name}</i>) currently assigned to the <i>{nameof(WaterRenderer)}</i> component.",
|
|
MessageType.Warning, material
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!target._Sky)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Sky</i> on <i>Reflections</i> is not enabled. " +
|
|
"Any custom shaders which do not write alpha (eg some tree leaves) will not appear in the final reflections.",
|
|
"Enable <i>Sky</i>.",
|
|
MessageType.Info, target._Water,
|
|
(_, y) => y.boolValue = true,
|
|
$"{nameof(WaterRenderer._Reflections)}.{nameof(WaterReflections._Sky)}"
|
|
|
|
);
|
|
}
|
|
|
|
#if !UNITY_6000_0_OR_NEWER
|
|
#if d_UnityHDRP
|
|
if (!target._RenderOnlySingleCamera && RenderPipelineHelper.IsHighDefinition)
|
|
{
|
|
messenger
|
|
(
|
|
$"Please note that <i>Reflections > Render Only Single Camera</i> has no effect for Unity 2022.3 HDRP. " +
|
|
"It is forced to enabled, as HDRP cannot render to multiple cameras, as it requires recursive rendering.",
|
|
"Upgrade to Unity 6 if you need this feature.",
|
|
MessageType.Info, target._Water
|
|
);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool Validate(UnderwaterRenderer target, ShowMessage messenger, WaterRenderer water)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (!target._Enabled)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
if (target.Material != null)
|
|
{
|
|
var material = target.Material;
|
|
|
|
if (material.shader.name.StartsWithNoAlloc("Crest/") && material.shader.name != "Crest/Underwater")
|
|
{
|
|
messenger
|
|
(
|
|
$"The material {material.name} assigned to Underwater has the wrong shader ({material.shader.name}).",
|
|
"Use a material with the correct shader (Crest/Underwater).",
|
|
MessageType.Error, water
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
if (water != null && water.Surface.Material != null)
|
|
{
|
|
var material = water.Surface.Material;
|
|
|
|
var cullModeName =
|
|
#if d_UnityURP
|
|
RenderPipelineHelper.IsUniversal ? "_Cull" :
|
|
#endif
|
|
#if d_UnityHDRP
|
|
RenderPipelineHelper.IsHighDefinition ? "_CullMode" :
|
|
#endif
|
|
"_BUILTIN_CullMode";
|
|
|
|
if (material.HasFloat(cullModeName) && material.GetFloat(cullModeName) == (int)CullMode.Back)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Cull Mode</i> is set to <i>Back</i> on material <i>{material.name}</i>. " +
|
|
"The underside of the water surface will not be rendered.",
|
|
$"Set <i>Cull Mode</i> to <i>Off</i> (or <i>Front</i>) on <i>{material.name}</i>.",
|
|
MessageType.Warning, material,
|
|
(material, _) =>
|
|
{
|
|
FixSetMaterialIntProperty(material, "Cull Mode", cullModeName, (int)CullMode.Off);
|
|
if (RenderPipelineHelper.IsHighDefinition)
|
|
{
|
|
// HDRP material will not update without viewing it...
|
|
Selection.activeObject = material.targetObject;
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#if d_UnityHDRP
|
|
if (RenderPipelineHelper.IsHighDefinition)
|
|
{
|
|
if (material.GetFloat(cullModeName) == (int)CullMode.Off && !material.IsKeywordEnabled("_DOUBLESIDED_ON"))
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Double-Sided</i> is not enabled on material <i>{material.name}</i>. " +
|
|
"The underside of the water surface will not be rendered correctly.",
|
|
$"Enable <i>Double-Sided</i> on <i>{material.name}</i>.",
|
|
MessageType.Warning, material,
|
|
(material, _) =>
|
|
{
|
|
FixSetMaterialOptionEnabled(material, "_DOUBLESIDED_ON", "_DoubleSidedEnable", enabled: true);
|
|
// HDRP material will not update without viewing it...
|
|
Selection.activeObject = material.targetObject;
|
|
}
|
|
);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool Validate(Meniscus target, ShowMessage messenger, WaterRenderer water)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (!target._Enabled)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
if (target._Material == null)
|
|
{
|
|
messenger
|
|
(
|
|
"The meniscus material is missing. The meniscus will not render.",
|
|
"Add the default material or your own.",
|
|
MessageType.Warning,
|
|
water,
|
|
(so, sp) =>
|
|
{
|
|
sp.objectReferenceValue = AssetDatabase.LoadAssetAtPath<Material>(Meniscus.k_MaterialPath);
|
|
},
|
|
$"{nameof(WaterRenderer._Meniscus)}.{nameof(Meniscus._Material)}"
|
|
);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(WaterRenderer))]
|
|
static bool Validate(WaterRenderer target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
var water = target;
|
|
|
|
isValid = isValid && Validate(target._Underwater, messenger, target);
|
|
isValid = isValid && Validate(target._Reflections, messenger, target);
|
|
isValid = isValid && Validate(target._Meniscus, messenger, target);
|
|
isValid = isValid && ValidateNoRotation(target, target.transform, messenger);
|
|
isValid = isValid && ValidateNoScale(target, target.transform, messenger);
|
|
|
|
#if CREST_OCEAN
|
|
messenger
|
|
(
|
|
"The <i>CREST_OCEAN</i> scripting symbol is present from <i>Crest 4</i>. " +
|
|
"This enables migration mode. Please read the documentation for the migration guide.",
|
|
"Remove <i>CREST_OCEAN</i> from <i>Project Settings > Player > Other Settings > Scripting Define Symbols</i> once finished migrating.",
|
|
MessageType.Info, target
|
|
);
|
|
#endif
|
|
|
|
if (target._Resources == null)
|
|
{
|
|
messenger
|
|
(
|
|
"The Water Renderer is missing required internal data.",
|
|
"Populate required internal data.",
|
|
MessageType.Error, target,
|
|
(_, y) => y.objectReferenceValue = WaterResources.Instance,
|
|
nameof(target._Resources)
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
if (target.Surface.Material == null)
|
|
{
|
|
messenger
|
|
(
|
|
"No water material specified.",
|
|
$"Assign a valid water material to the Material property of the <i>{nameof(WaterRenderer)}</i> component.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
else
|
|
{
|
|
isValid = ValidateWaterMaterial(target, messenger, water, target.Surface.Material) && isValid;
|
|
|
|
if (RenderPipelineHelper.IsHighDefinition && target.Surface.Material.GetFloat("_RefractionModel") > 0)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Refraction Model</i> is not <i>None</i> for <i>{target.Surface.Material}</i>. " +
|
|
"This is set by default so it is available in the inspector, " +
|
|
"but it incurs an overhead and will produce a dark edge at the edge of the viewport (see <i>Screen Space Refraction > Screen Weight Distance</i>). " +
|
|
"Enabling the refraction model is only useful to allow volumetric clouds to render over the water surface when view from above. " +
|
|
"The refraction model has no effect on refractions.",
|
|
$"Set <i>Refraction Model</i> to <i>None</i>.",
|
|
MessageType.Info, target.Surface.Material
|
|
);
|
|
}
|
|
|
|
if (RenderPipelineHelper.IsHighDefinition && target.Surface.Material.HasFloat("_TransparentWritingMotionVec") && target.WriteMotionVectors != (target.Surface.Material.GetFloat("_TransparentWritingMotionVec") == 1f))
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Water Renderer > Surface Renderer > Motion Vectors</i> and <i>Transparent Writes Motion Vectors</i> on <i>{target.Surface.Material}</i> do not match. ",
|
|
$"Either disable or enable both <i>Water Renderer > Surface Renderer > Motion Vectors</i> and <i>Transparent Writes Motion Vectors</i>",
|
|
MessageType.Info, target.Surface.Material
|
|
);
|
|
}
|
|
|
|
ValidateMaterialParent(target.Surface.VolumeMaterial, target.Surface.Material, messenger);
|
|
}
|
|
|
|
if (Object.FindObjectsByType<WaterRenderer>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).Length > 1)
|
|
{
|
|
messenger
|
|
(
|
|
$"Multiple <i>{nameof(WaterRenderer)}</i> components detected in open scenes, this is not typical - usually only one <i>{nameof(WaterRenderer)}</i> is expected to be present.",
|
|
$"Remove extra <i>{nameof(WaterRenderer)}</i> components.",
|
|
MessageType.Warning, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
// Water Detail Parameters
|
|
var baseMeshDensity = target.LodResolution * 0.25f / target._GeometryDownSampleFactor;
|
|
|
|
if (baseMeshDensity < 8)
|
|
{
|
|
messenger
|
|
(
|
|
"Base mesh density is lower than 8. There will be visible gaps in the water surface.",
|
|
"Increase the <i>LOD Data Resolution</i> or decrease the <i>Geometry Down Sample Factor</i>.",
|
|
MessageType.Error, target
|
|
);
|
|
}
|
|
else if (baseMeshDensity < 16)
|
|
{
|
|
messenger
|
|
(
|
|
"Base mesh density is lower than 16. There will be visible transitions when traversing the water surface. ",
|
|
"Increase the <i>LOD Data Resolution</i> or decrease the <i>Geometry Down Sample Factor</i>.",
|
|
MessageType.Warning, target
|
|
);
|
|
}
|
|
|
|
// We need to find hidden probes too, but do not include assets.
|
|
if (Resources.FindObjectsOfTypeAll<ReflectionProbe>().Count(x => !EditorUtility.IsPersistent(x)) > 0)
|
|
{
|
|
messenger
|
|
(
|
|
"There are reflection probes in the scene. These can cause tiling to appear on the water surface if not set up correctly.",
|
|
"For reflections probes that affect the water, they will either need to cover the visible water tiles or water tiles need to ignore reflection probes (can done done with <i>Water Tile Prefab</i> field). " +
|
|
$"For all reflection probles that include the <i>{LayerMask.LayerToName(target.Surface.Layer)}</i> layer, make sure they are above the water surface as underwater reflections are not supported.",
|
|
MessageType.Info, target
|
|
);
|
|
}
|
|
|
|
// Validate scene view effects options.
|
|
if (SceneView.lastActiveSceneView != null && !Application.isPlaying)
|
|
{
|
|
var sceneView = SceneView.lastActiveSceneView;
|
|
|
|
// Validate "Animated Materials".
|
|
if (target != null && !target._ShowWaterProxyPlane && !sceneView.sceneViewState.alwaysRefresh)
|
|
{
|
|
messenger
|
|
(
|
|
"<i>Animated Materials</i> is not enabled on the scene view. The water's framerate will appear low as updates are not real-time.",
|
|
"Enable <i>Animated Materials</i> on the scene view.",
|
|
MessageType.Info, target,
|
|
(_, _) =>
|
|
{
|
|
SceneView.lastActiveSceneView.sceneViewState.alwaysRefresh = true;
|
|
// Required after changing sceneViewState according to:
|
|
// https://docs.unity3d.com/ScriptReference/SceneView.SceneViewState.html
|
|
SceneView.RepaintAll();
|
|
}
|
|
);
|
|
}
|
|
|
|
#if d_UnityPostProcessingBroken
|
|
// Validate "Post-Processing".
|
|
// Only check built-in renderer and Camera.main with enabled PostProcessLayer component.
|
|
if (GraphicsSettings.currentRenderPipeline == null && Camera.main != null &&
|
|
Camera.main.TryGetComponent<UnityEngine.Rendering.PostProcessing.PostProcessLayer>(out var ppLayer)
|
|
&& ppLayer.enabled && sceneView.sceneViewState.showImageEffects)
|
|
{
|
|
messenger
|
|
(
|
|
"<i>Post Processing</i> is enabled on the scene view. " +
|
|
"There is a Unity bug where gizmos and grid lines will render over opaque objects. " +
|
|
"This has been resolved in <i>Post Processing</i> version 3.4.0.",
|
|
"Disable <i>Post Processing</i> on the scene view or upgrade to version 3.4.0.",
|
|
MessageType.Warning, target,
|
|
_ =>
|
|
{
|
|
sceneView.sceneViewState.showImageEffects = false;
|
|
// Required after changing sceneViewState according to:
|
|
// https://docs.unity3d.com/ScriptReference/SceneView.SceneViewState.html
|
|
SceneView.RepaintAll();
|
|
}
|
|
);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Validate simulation settings.
|
|
foreach (var simulation in target.Simulations)
|
|
{
|
|
ExecuteValidators(simulation, messenger);
|
|
}
|
|
|
|
// For safety.
|
|
if (target != null && target.Surface.Material != null)
|
|
{
|
|
foreach (var simulation in target.Simulations)
|
|
{
|
|
ValidateSimulationAndMaterial(OptionalLod.Get(simulation.GetType()), messenger, water, target);
|
|
}
|
|
}
|
|
|
|
if (target.PrimaryLight == null)
|
|
{
|
|
messenger
|
|
(
|
|
"Crest needs to know which light to use as the sun light.",
|
|
"Please add a Directional Light to the scene.",
|
|
MessageType.Warning, target
|
|
);
|
|
}
|
|
|
|
if (target.Viewer == null && !target.IsRunningWithoutGraphics && !target.MultipleViewpoints)
|
|
{
|
|
messenger
|
|
(
|
|
"Crest needs to know which camera to use as the main camera.",
|
|
$"Either tag a camera as <i>Main Camera</i> or assign the camera to the {nameof(WaterRenderer)}.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
#if d_UnityHDRP
|
|
if (RenderPipelineHelper.IsHighDefinition)
|
|
{
|
|
var material = target.Surface.Material;
|
|
var camera = target._Camera != null ? target._Camera : Camera.main;
|
|
var hdCamera = camera != null ? HDCamera.GetOrCreate(camera) : null;
|
|
var hdAsset = GraphicsSettings.currentRenderPipeline as HDRenderPipelineAsset;
|
|
var mvs = hdAsset.currentPlatformRenderPipelineSettings.supportMotionVectors;
|
|
|
|
// Only check the RP asset for now.
|
|
if (mvs != water.WriteMotionVectors)
|
|
{
|
|
messenger
|
|
(
|
|
$"Motion Vectors are{(mvs ? "" : " not")} enabled in the HD render pipeline asset, but <i>Water Renderer > Surface Renderer > Motion Vectors</i> is{(mvs ? " not" : "")}. " +
|
|
"Both need to be enabled for motion vectors to work, or both should be disabled to save resources. " +
|
|
"This can safely be ignored if the setup is intentional.",
|
|
"Enable or disable both.",
|
|
MessageType.Info, target
|
|
);
|
|
}
|
|
|
|
if (!hdAsset.currentPlatformRenderPipelineSettings.supportCustomPass)
|
|
{
|
|
messenger
|
|
(
|
|
"Custom passes are disabled. Underwater and other features require them to work.",
|
|
"Enabled them on the global asset.",
|
|
MessageType.Error, hdCamera.camera
|
|
);
|
|
}
|
|
|
|
if (target.RenderBeforeTransparency && WaterRenderer.s_CameraMSAA)
|
|
{
|
|
messenger
|
|
(
|
|
$"The water injection point is before transparency and MSAA is enabled for a camera. This combination is not currently supported for HDRP.",
|
|
"Disable MSAA or change the water injection point.",
|
|
MessageType.Error, target
|
|
);
|
|
}
|
|
|
|
// Seems that logging is too early for these. And edit mode has false positives.
|
|
if (Application.isPlaying && messenger == ValidatedHelper.HelpBox)
|
|
{
|
|
if (hdCamera?.frameSettings.IsEnabled(FrameSettingsField.CustomPass) == false)
|
|
{
|
|
messenger
|
|
(
|
|
$"Custom passes are disabled for the primary camera ({camera}). Underwater and other features require them to work.",
|
|
"Enable them in the camera frame settings on the camera or the default frame settings in the global settings.",
|
|
MessageType.Error, hdCamera.camera
|
|
);
|
|
}
|
|
|
|
if (hdCamera?.frameSettings.IsEnabled(FrameSettingsField.Refraction) == false && material != null && SurfaceRenderer.IsTransparent(material))
|
|
{
|
|
messenger
|
|
(
|
|
"Refraction is disabled. Transparency requires it to work.",
|
|
"Enable it in the camera frame settings on the camera, or the default frame settings in the global settings.",
|
|
MessageType.Error, hdCamera.camera
|
|
);
|
|
}
|
|
}
|
|
}
|
|
#endif // d_UnityHDRP
|
|
|
|
#if d_UnityURP
|
|
if (RenderPipelineHelper.IsUniversal && target.Viewer != null)
|
|
{
|
|
var data = target.Viewer.GetUniversalAdditionalCameraData();
|
|
|
|
// Type is internal.
|
|
if (data != null && data.scriptableRenderer.GetType().Name == "Renderer2D")
|
|
{
|
|
messenger
|
|
(
|
|
"Crest does not support 2D rendering.",
|
|
"Please choose a 3D template.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
}
|
|
#endif // d_UnityURP
|
|
|
|
if (!RenderPipelineHelper.IsHighDefinition && target.Surface.Material != null)
|
|
{
|
|
if (!target.Surface.AllowRenderQueueSorting && !System.Enum.IsDefined(typeof(RenderQueue), target.Surface.Material.renderQueue))
|
|
{
|
|
var field = nameof(SurfaceRenderer.AllowRenderQueueSorting).Pretty().Italic();
|
|
messenger
|
|
(
|
|
$"The render queue has a sub-sort applied, but {field} is not enabled. Sub-sorting will not work.",
|
|
$"Enable {field}.",
|
|
MessageType.Warning, target
|
|
);
|
|
}
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(WaterBody))]
|
|
static bool Validate(WaterBody target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
var water = Water;
|
|
|
|
if (Object.FindObjectsByType<WaterRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None).Length == 0)
|
|
{
|
|
messenger
|
|
(
|
|
$"Water body <i>{target.gameObject.name}</i> requires a <i>{nameof(WaterRenderer)}</i> component to be present.",
|
|
$"Create a separate GameObject and add an <i>{nameof(WaterRenderer)}</i> component to it.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
if (Mathf.Abs(target.transform.lossyScale.x) < 2f && Mathf.Abs(target.transform.lossyScale.z) < 2f)
|
|
{
|
|
messenger
|
|
(
|
|
$"Water body {target.gameObject.name} has a very small size (the size is set by the X & Z scale of its transform), and will be a very small body of water.",
|
|
"Increase X & Z scale on water body transform (or parents).",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
if (target._Material != null)
|
|
{
|
|
isValid = ValidateWaterMaterial(target, messenger, Water, target._Material) && isValid;
|
|
ValidateMaterialParent(target._BelowSurfaceMaterial, target._Material, messenger);
|
|
}
|
|
|
|
isValid = isValid && ValidateNoRotation(target, target.transform, messenger);
|
|
|
|
if (target.Clipped && water != null)
|
|
{
|
|
// Validate main material, then overriden material.
|
|
ValidateLod(OptionalLod.Get(typeof(ClipLod)), messenger, water, material: target._Material, context: target);
|
|
|
|
if (water.ClipLod.DefaultClippingState == DefaultClippingState.NothingClipped)
|
|
{
|
|
messenger
|
|
(
|
|
$"The {nameof(ClipLod.DefaultClippingState)} on the {nameof(WaterRenderer)} is set to {DefaultClippingState.NothingClipped}. " +
|
|
$"The {nameof(WaterBody.Clipped)} option will have no effect.",
|
|
$"Disable {nameof(WaterBody.Clipped)} or set {nameof(ClipLod.DefaultClippingState)} to {DefaultClippingState.NothingClipped}.",
|
|
MessageType.Warning,
|
|
water,
|
|
caller: target
|
|
);
|
|
}
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Does validation for a feature on the water component and on the material
|
|
/// </summary>
|
|
internal static bool ValidateLod(OptionalLod target, ShowMessage messenger, WaterRenderer water, string dependent = null, Material material = null, Object context = null)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (target == null || water == null)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
isValid &= ValidateProjectSettings(target, water, messenger, context);
|
|
|
|
var simulation = target.GetLod(water);
|
|
|
|
var dependentClause = ".";
|
|
|
|
if (dependent != null)
|
|
{
|
|
dependentClause = $", as {dependent} needs it.";
|
|
}
|
|
|
|
if (!simulation._Enabled && material == null)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>{target.PropertyLabel}</i> must be enabled on the <i>{nameof(WaterRenderer)}</i> component{dependentClause}",
|
|
$"Enable <i>Simulations > {target.PropertyLabel} > Enabled</i> on the <i>{nameof(WaterRenderer)}</i> component.",
|
|
MessageType.Error, water,
|
|
(_, y) =>
|
|
{
|
|
y.boolValue = true;
|
|
if (Water.Active)
|
|
{
|
|
// ApplyModifiedProperties is called outside of this method but need it for next
|
|
// call. Then restore so ApplyModifiedProperties check works to add undo entry.
|
|
simulation._Enabled = true;
|
|
simulation.Initialize();
|
|
simulation._Enabled = false;
|
|
}
|
|
},
|
|
$"{target.PropertyName}.{nameof(Lod._Enabled)}",
|
|
context
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
if (material == null)
|
|
{
|
|
material = water.Surface.Material;
|
|
}
|
|
|
|
if (target.HasMaterialToggle && material != null)
|
|
{
|
|
if (material.HasProperty(target.MaterialProperty) && material.GetFloat(target.MaterialProperty) != 1f)
|
|
{
|
|
ShowMaterialValidationMessage(target, material, messenger, context);
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
if (target.Dependency != null)
|
|
{
|
|
ValidateLod(OptionalLod.Get(target.Dependency), messenger, water, dependent, context: context);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateSignedDistanceFieldsLod(ShowMessage messenger, WaterRenderer water, string feature, Object context)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (water != null && !water.DepthLod.EnableSignedDistanceFields)
|
|
{
|
|
messenger
|
|
(
|
|
$"{feature} requires <i>Signed Distance Fields</i> to be enabled on the Water Depth Simulation.",
|
|
"Enable <i>Signed Distance Fields</i>",
|
|
MessageType.Error, water,
|
|
(_, y) => y.boolValue = true,
|
|
$"{nameof(WaterRenderer._DepthLod)}.{nameof(DepthLod._EnableSignedDistanceFields)}",
|
|
caller: context
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static void ShowMaterialValidationMessage(OptionalLod target, Material material, ShowMessage messenger, Object caller)
|
|
{
|
|
messenger
|
|
(
|
|
$"{target.PropertyLabel} is not enabled (<i>{target.MaterialPropertyPath}</i>) on the water material and will not be visible.",
|
|
$"Enable <i>{target.PropertyLabel}</i> on the material currently assigned to the <i>{nameof(WaterRenderer)}</i> component.",
|
|
MessageType.Error, material,
|
|
(material, _) => FixSetMaterialOptionEnabled(material, target.MaterialKeyword, target.MaterialProperty, true)
|
|
);
|
|
}
|
|
|
|
static bool ValidateProjectSettings(OptionalLod target, WaterRenderer water, ShowMessage messenger, Object caller)
|
|
{
|
|
var isValid = true;
|
|
|
|
var lod = target.GetLod(water);
|
|
|
|
if (lod._Enabled && !target.GetProjectSettingToggle())
|
|
{
|
|
var platform = ScriptingSymbols.CurrentNamedBuildTarget;
|
|
|
|
messenger
|
|
(
|
|
$"<i>{target.PropertyLabel}</i> must be enabled for this platform in the project settings.",
|
|
$"Enable <i>Project Settings > Crest > Features > Default/{platform.TargetName} > {target.PropertyLabel}</i>. " +
|
|
$"It will be in either Default or {platform.TargetName}",
|
|
MessageType.Error, ProjectSettings.Instance,
|
|
caller: caller
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateSimulationAndMaterial(OptionalLod target, ShowMessage messenger, WaterRenderer water, Object caller)
|
|
{
|
|
if (target == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!ValidateProjectSettings(target, water, messenger, caller))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!target.HasMaterialToggle)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// These checks are not necessary for our material but there may be custom materials.
|
|
if (!water.Surface.Material.HasProperty(target.MaterialProperty))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var feature = target.GetLod(water);
|
|
|
|
// There is only a problem if there is a mismatch.
|
|
if (feature._Enabled == (water.Surface.Material.GetFloat(target.MaterialProperty) == 1f))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (feature._Enabled)
|
|
{
|
|
ShowMaterialValidationMessage(target, water.Surface.Material, messenger, caller);
|
|
}
|
|
else if (messenger != DebugLog)
|
|
{
|
|
messenger
|
|
(
|
|
$"The <i>{target.PropertyLabel}</i> feature is disabled on the <i>{nameof(WaterRenderer)}</i> but is enabled on the water material.",
|
|
$"If this is not intentional, either enable <i>{target.PropertyLabel}</i> on the <i>{nameof(WaterRenderer)}</i> to turn it on, or disable <i>{target.MaterialPropertyPath}</i> on the water material to save performance.",
|
|
MessageType.Warning, water
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[Validator(typeof(ShapeWaves))]
|
|
static bool Validate(ShapeWaves target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
var water = Object.FindAnyObjectByType<WaterRenderer>(FindObjectsInactive.Include);
|
|
|
|
if (!target.OverrideGlobalWindSpeed && water != null && water.WindSpeedKPH < WaterRenderer.k_MaximumWindSpeedKPH)
|
|
{
|
|
messenger
|
|
(
|
|
$"The wave spectrum is limited by the <i>Global Wind Speed</i> on the <i>Water Renderer</i> to {water.WindSpeedKPH} KPH.",
|
|
$"If you want fully developed waves, either override the wind speed on this component or increase the <i>Global Wind Speed</i>.",
|
|
MessageType.Info,
|
|
caller: target
|
|
);
|
|
}
|
|
|
|
if (target.Blend == LodInputBlend.AlphaClip && target.Mode is not (LodInputMode.Texture or LodInputMode.Paint))
|
|
{
|
|
messenger
|
|
(
|
|
$"Only {LodInputMode.Texture} mode supports {nameof(LodInputBlend.AlphaClip)}.",
|
|
$"Change Blend to {nameof(LodInputBlend.Alpha)}.",
|
|
MessageType.Error, target,
|
|
(_, y) => y.enumValueIndex = (int)LodInputBlend.Alpha,
|
|
nameof(ShapeWaves._Blend)
|
|
);
|
|
}
|
|
|
|
if (Water != null)
|
|
{
|
|
isValid &= ValidateLod(OptionalLod.Get(typeof(AnimatedWavesLod)), messenger, Water, context: target);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(SphereWaterInteraction))]
|
|
static bool Validate(SphereWaterInteraction target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
// Validate require water feature.
|
|
if (Water != null)
|
|
{
|
|
isValid &= ValidateLod(OptionalLod.Get(typeof(DynamicWavesLod)), messenger, Water, context: target);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(WatertightHull))]
|
|
static bool Validate(WatertightHull target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
// Validate require water feature.
|
|
if (Water != null)
|
|
{
|
|
isValid &= !target.UsesClip || ValidateLod(OptionalLod.Get(typeof(ClipLod)), messenger, Water, context: target);
|
|
isValid &= !target.UsesDisplacement || ValidateLod(OptionalLod.Get(typeof(AnimatedWavesLod)), messenger, Water, context: target);
|
|
isValid &= !target.UsesDisplacement || ValidateCollisionLayer(CollisionLayer.AfterDynamicWaves, target, messenger, "mode", target.Mode, required: true);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
internal static void FixSetQuerySourceToCompute(SerializedObject _, SerializedProperty property)
|
|
{
|
|
if (Water != null)
|
|
{
|
|
property.enumValueIndex = (int)LodQuerySource.GPU;
|
|
}
|
|
}
|
|
|
|
[Validator(typeof(FloatingObject))]
|
|
static bool Validate(FloatingObject target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
isValid &= ValidateComponent(target, messenger, target.RigidBody);
|
|
|
|
if (Water == null)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
isValid &= ValidateCollisionLayer(target.Layer, target, messenger, "layer", target.Layer, required: false);
|
|
isValid &= ValidateQuerySource(target, messenger, Water.AnimatedWavesLod, k_NoneQueryProviderCollisionFloatingObjects);
|
|
isValid &= ValidateQuerySource(target, messenger, Water.FlowLod, k_NoneQueryProviderFlowFloatingObjects);
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(CollisionAreaVisualizer))]
|
|
static bool Validate(CollisionAreaVisualizer target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (Water == null)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
isValid &= ValidateCollisionLayer(target._Layer, target, messenger, "layer", target._Layer, required: false);
|
|
isValid &= ValidateQuerySource(target, messenger, Water.AnimatedWavesLod, k_NoneQueryProviderCollisionFloatingObjects);
|
|
isValid &= ValidateQuerySource(target, messenger, Water.FlowLod, k_NoneQueryProviderFlowFloatingObjects);
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(Controller))]
|
|
static bool Validate(Controller target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
isValid &= ValidateComponent(target, messenger, target.Control);
|
|
isValid &= ValidateComponent(target, messenger, target.FloatingObject);
|
|
|
|
isValid &= isValid && target.TryGetComponent(out FloatingObject fo) && fo.RigidBody != null;
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(LodInput))]
|
|
static bool Validate(LodInput target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
var isDataInput = target.Mode is LodInputMode.Spline or LodInputMode.Texture or LodInputMode.Renderer or LodInputMode.Paint;
|
|
|
|
if (isDataInput)
|
|
{
|
|
// Find the type associated with the input type and mode.
|
|
var self = target.GetType();
|
|
var types = TypeCache.GetTypesWithAttribute<ForLodInput>();
|
|
System.Type type = null;
|
|
foreach (var t in types)
|
|
{
|
|
var attributes = t.GetCustomAttributes<ForLodInput>();
|
|
foreach (var attribute in attributes)
|
|
{
|
|
if (!attribute._Type.IsAssignableFrom(self)) continue;
|
|
if (attribute._Mode != target.Mode) continue;
|
|
type = t;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
isValid = type != null;
|
|
|
|
#if !d_CrestPaint
|
|
if (!isValid && target.Mode == LodInputMode.Paint)
|
|
{
|
|
messenger
|
|
(
|
|
"Missing the <i>Crest: Paint</i> package.",
|
|
$"Install the missing package or select a valid <i>Input Mode</i> such as {target.DefaultMode} to use this input.",
|
|
MessageType.Error,
|
|
target,
|
|
(_, y) => y.enumValueIndex = (int)target.DefaultMode,
|
|
nameof(target.Mode)
|
|
);
|
|
|
|
return isValid;
|
|
}
|
|
#endif
|
|
|
|
#if !d_CrestSpline
|
|
if (!isValid && target.Mode == LodInputMode.Spline)
|
|
{
|
|
messenger
|
|
(
|
|
"Missing the <i>Crest: Spline</i> package.",
|
|
$"Install the missing package or select a valid <i>Input Mode</i> such as {target.DefaultMode} to use this input.",
|
|
MessageType.Error,
|
|
target,
|
|
(_, y) => y.enumValueIndex = (int)target.DefaultMode,
|
|
nameof(target.Mode)
|
|
);
|
|
|
|
return isValid;
|
|
}
|
|
#endif
|
|
|
|
if (!isValid)
|
|
{
|
|
messenger
|
|
(
|
|
"Invalid or unset <i>Input Mode</i> setting.",
|
|
$"Select a valid <i>Input Mode</i> such as {target.DefaultMode} to use this input.",
|
|
MessageType.Error,
|
|
target,
|
|
(_, y) => y.enumValueIndex = (int)target.DefaultMode,
|
|
nameof(target._Mode)
|
|
);
|
|
|
|
return isValid;
|
|
}
|
|
|
|
isValid = target.Data != null;
|
|
|
|
if (!isValid)
|
|
{
|
|
var isPrefabInstance = PrefabUtility.IsPartOfPrefabInstance(target);
|
|
messenger
|
|
(
|
|
"Missing internal data or data type was renamed.",
|
|
isPrefabInstance ? "Repair the component in the prefab." : "Repair component.",
|
|
MessageType.Error,
|
|
target,
|
|
isPrefabInstance ? null : (_, _) =>
|
|
{
|
|
Undo.RecordObject(target, "Repair");
|
|
target.ChangeMode(target.Mode);
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
);
|
|
|
|
return isValid;
|
|
}
|
|
|
|
isValid = target.Data.GetType() == type;
|
|
|
|
// This might happen if scripting is used.
|
|
if (!isValid)
|
|
{
|
|
messenger
|
|
(
|
|
$"Instance set to <i>{nameof(LodInput.Data)}</i> as incorrect type.",
|
|
"Set the correct instance type.",
|
|
MessageType.Error,
|
|
target,
|
|
(_, _) =>
|
|
{
|
|
Undo.RecordObject(target, "Repair");
|
|
target.ChangeMode(target.Mode);
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
);
|
|
|
|
return isValid;
|
|
}
|
|
}
|
|
|
|
isValid &= ValidateFilteredChoice((int)target.Blend, "_Blend", target, messenger);
|
|
|
|
// Validate that any water feature required for this input is enabled, if any
|
|
if (Water != null)
|
|
{
|
|
isValid &= ValidateLod(OptionalLod.Get(target.GetType()), messenger, Water, context: target);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(LevelLodInput))]
|
|
static bool ValidateLevelLodInput(LevelLodInput target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (target.Mode is LodInputMode.Geometry or LodInputMode.Spline)
|
|
{
|
|
if (target.Blend is LodInputBlend.Minimum or LodInputBlend.Maximum && target.Weight < 1f)
|
|
{
|
|
messenger
|
|
(
|
|
"Weight with minimum or maximum blend modes do not always behave correctly. " +
|
|
"Any weight less than one will move the value in the simulation towards zero, rather than towards the existing value in the simulation. " +
|
|
"For example, this input with a zero weight, and with a blend mode set to maximum, will replace any negative water level instead of not doing anything.",
|
|
"", // Nothing to suggest yet, as in cases this is still valid.
|
|
MessageType.Warning,
|
|
target
|
|
);
|
|
}
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(DepthProbe))]
|
|
static bool Validate(DepthProbe target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
messenger
|
|
(
|
|
"If you see an error <i>RenderTexture color format cannot be set to a depth/stencil format</i> or <i>RenderTexture.Create failed</i>, this is likely a bug with Unity (grab pass) or third-party, as they may be registered to execute a custom pass to the DepthProbe camera.", "", MessageType.Info, target
|
|
);
|
|
|
|
if (target.Outdated && (messenger != DebugLog || WaterRendererEditor.ManualValidation))
|
|
{
|
|
messenger
|
|
(
|
|
"<i>Depth Probe</i> is outdated.",
|
|
"Click <i>Populate</i> or re-bake the probe to bring the probe up-to-date with component changes.",
|
|
MessageType.Warning, target,
|
|
(_, _) => target.Populate()
|
|
);
|
|
}
|
|
|
|
if (target.Type == DepthProbeMode.Baked)
|
|
{
|
|
messenger
|
|
(
|
|
"To change any read-only settings, switch back to real-time, adjust settings, and re-bake.",
|
|
"",
|
|
MessageType.Info, target
|
|
);
|
|
|
|
if (target.SavedTexture == null)
|
|
{
|
|
messenger
|
|
(
|
|
"Depth probe type is <i>Baked</i> but no saved probe data is provided.",
|
|
"Assign a saved probe asset.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (target._Layers == 0)
|
|
{
|
|
messenger
|
|
(
|
|
"No layers specified for rendering into depth probe.",
|
|
"Specify one or many layers using the Layers field.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
#if d_Unity_Terrain
|
|
else
|
|
{
|
|
Terrain.GetActiveTerrains(s_Terrains);
|
|
foreach (var terrain in s_Terrains)
|
|
{
|
|
if (Helpers.MaskIncludesLayer(target.Layers, terrain.gameObject.layer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
messenger
|
|
(
|
|
$"There are terrains on a layer that is not in {nameof(DepthProbe)}.{nameof(DepthProbe.Layers)}.",
|
|
"This is typically mistake leading to no data (ie no shorelines). Please ignore if intentional.",
|
|
MessageType.Info, target
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
#endif // d_Unity_Terrain
|
|
|
|
if (target._Debug._ForceAlwaysUpdateDebug)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>Force Always Update Debug</i> option is enabled on depth probe <i>{target.gameObject.name}</i>, which means it will render every frame instead of running from the probe.",
|
|
"Disable the <i>Force Always Update Debug</i> option.",
|
|
MessageType.Warning, target,
|
|
(_, y) => y.boolValue = false,
|
|
$"{nameof(DepthProbe._Debug)}.{nameof(DepthProbe._Debug._ForceAlwaysUpdateDebug)}"
|
|
);
|
|
}
|
|
|
|
if (target._Resolution < 4)
|
|
{
|
|
messenger
|
|
(
|
|
$"Probe resolution {target._Resolution} is very low, which may not be intentional.",
|
|
"Increase the probe resolution.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
if (!Mathf.Approximately(target.Scale.x, target.Scale.y))
|
|
{
|
|
messenger
|
|
(
|
|
$"The <i>{nameof(DepthProbe)}</i> in real-time only supports a uniform scale for X and Z. " +
|
|
"These values currently do not match. " +
|
|
$"Its current scale in the hierarchy is: X = {target.Scale.x} Z = {target.Scale.y}.",
|
|
"Ensure the X & Z scale values are equal on this object and all parents in the hierarchy.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
// We used to test if nothing is present that would render into the probe, but these could probably come from other scenes.
|
|
}
|
|
|
|
if (!target.Managed && target.transform.lossyScale.XZ().magnitude < 5f)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>{nameof(DepthProbe)}</i> transform scale is small and will capture a small area of the world. The scale sets the size of the area that will be probed, and this probe is set to render a very small area.",
|
|
"Increase the X & Z scale to increase the size of the probe.",
|
|
MessageType.Warning, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
if (!target.Managed && target.transform.lossyScale.y <= 0f)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>{nameof(DepthProbe)}</i> scale is zero or negative. Y should be set to 1.0, but can be other values providing it is greater than zero. Its current scale in the hierarchy is {target.transform.lossyScale.y}.",
|
|
"Set the Y scale to 1.0.",
|
|
MessageType.Error, target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
#if d_UnityURP
|
|
#if !UNITY_6000_0_OR_NEWER
|
|
#if UNITY_2022_3_OR_NEWER
|
|
if (int.Parse(Application.unityVersion.Substring(7, 2)) < 23)
|
|
{
|
|
// Asset based validation.
|
|
foreach (var asset in GraphicsSettings.allConfiguredRenderPipelines)
|
|
{
|
|
if (asset is UniversalRenderPipelineAsset urpAsset)
|
|
{
|
|
var urpRenderers = Helpers.UniversalRendererData(urpAsset);
|
|
|
|
foreach (var renderer in urpRenderers)
|
|
{
|
|
var urpRenderer = (UniversalRendererData)renderer;
|
|
|
|
if (urpRenderer.depthPrimingMode != DepthPrimingMode.Disabled)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>{nameof(DepthPrimingMode)}</i> is not set to <i>{nameof(DepthPrimingMode.Disabled)}</i>. " +
|
|
$"This can cause the <i>{nameof(DepthProbe)}</i> not to work. " +
|
|
$"Unity fixed this in 2022.3.23f1.",
|
|
$"If you are experiencing problems, disable depth priming or upgrade Unity.",
|
|
MessageType.Info, urpRenderer,
|
|
caller: target
|
|
);
|
|
}
|
|
|
|
foreach (var feature in renderer.rendererFeatures)
|
|
{
|
|
if (feature.GetType().Name == "ScreenSpaceAmbientOcclusion" && feature.isActive)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>ScreenSpaceAmbientOcclusion</i> is is active. " +
|
|
$"This can cause the <i>{nameof(DepthProbe)}</i> not to work. " +
|
|
$"Unity fixed this in 2022.3.23f1.",
|
|
$"If you are experiencing problems, disable SSAO or upgrade Unity.",
|
|
MessageType.Info, urpRenderer,
|
|
caller: target
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
// Check that there are no renderers in descendants.
|
|
var renderers = target.GetComponentsInChildren<Renderer>();
|
|
if (renderers.Length > 0)
|
|
{
|
|
foreach (var renderer in renderers)
|
|
{
|
|
messenger
|
|
(
|
|
"It is not expected that a depth probe object has a Renderer component in its hierarchy." +
|
|
"The probe is typically attached to an empty GameObject. Please refer to the example content.",
|
|
"Remove the Renderer component from this object or its children.",
|
|
MessageType.Warning, renderer,
|
|
caller: target
|
|
);
|
|
|
|
// Reporting only one renderer at a time will be enough to avoid overwhelming user and UI.
|
|
break;
|
|
}
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
var water = Water;
|
|
|
|
// Validate require water feature.
|
|
if (water != null)
|
|
{
|
|
isValid = isValid && ValidateLod(OptionalLod.Get(typeof(DepthLod)), messenger, water, context: target);
|
|
|
|
if (!water._DepthLod._EnableSignedDistanceFields && target._GenerateSignedDistanceField)
|
|
{
|
|
isValid = isValid && ValidateSignedDistanceFieldsLod(messenger, water, "Generate Signed Distance Field", target);
|
|
}
|
|
|
|
if (water.DepthLod.IncludeTerrainHeight && Object.FindAnyObjectByType<Terrain>(FindObjectsInactive.Include) != null)
|
|
{
|
|
messenger
|
|
(
|
|
"The Water Depth data is configured to automatically include terrain height via <i>Include Terrain Height</i>. " +
|
|
"Using a DepthProbe is still valid to capture non-terrain details like rocks. " +
|
|
"But typically, if you are using a DepthProbe, it is best to capture the terrain too, as it is more accurate. " +
|
|
"One reason to use a DepthProbe together with the auto capture is for better real-time/on-demand depth capture performance.",
|
|
string.Empty,
|
|
MessageType.Info, water,
|
|
caller: target
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(QueryEvents))]
|
|
static bool Validate(QueryEvents target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
var water = Water;
|
|
|
|
if (!target._DistanceFromEdge.IsEmpty())
|
|
{
|
|
isValid = isValid && ValidateLod(OptionalLod.Get(typeof(DepthLod)), messenger, water, context: target);
|
|
isValid = isValid && ValidateSignedDistanceFieldsLod(messenger, water, "Distance From Edge", target);
|
|
}
|
|
|
|
if (!target._DistanceFromSurface.IsEmpty())
|
|
{
|
|
isValid &= ValidateCollisionLayer(target._Layer, target, messenger, "layer", target._Layer, required: false);
|
|
isValid &= ValidateQuerySource(target, messenger, Water.AnimatedWavesLod, k_NoneQueryProviderCollisionFloatingObjects);
|
|
isValid &= ValidateQuerySource(target, messenger, Water.FlowLod, k_NoneQueryProviderFlowFloatingObjects);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(FoamLodSettings))]
|
|
static bool Validate(FoamLodSettings target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (Water == null)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
if (target.FilterWaves > Water.LodLevels - 2)
|
|
{
|
|
messenger
|
|
(
|
|
"<i>Filter Waves</i> is higher than the recommended maximum (LOD count - 2). There will be no whitecaps.",
|
|
"Reduce <i>Filter Waves</i>.",
|
|
MessageType.Warning, target
|
|
);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(Lod))]
|
|
static bool Validate(Lod target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (!target._Enabled)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
var optional = OptionalLod.Get(target.GetType());
|
|
|
|
if (Water != null && optional.Dependency != null)
|
|
{
|
|
isValid &= ValidateLod(OptionalLod.Get(optional.Dependency), messenger, Water, target.Name);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(AnimatedWavesLod))]
|
|
static bool Validate(AnimatedWavesLod target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
#if !d_CrestCPUQueries
|
|
if (target.QuerySource == LodQuerySource.CPU)
|
|
{
|
|
messenger
|
|
(
|
|
"Collision Source is set to CPU but the <i>CPU Queries</i> package is not installed.",
|
|
"Install the <i>CPU Queries</i> package or switch to GPU queries.",
|
|
MessageType.Warning, target.Water,
|
|
FixSetQuerySourceToCompute
|
|
);
|
|
}
|
|
#endif
|
|
|
|
isValid &= ValidateQuerySource(target.Water, messenger, target, k_NoneQueryProviderCollisionFloatingObjects);
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(FlowLod))]
|
|
static bool Validate(FlowLod target, ShowMessage messenger)
|
|
{
|
|
return ValidateQuerySource(target.Water, messenger, target, k_NoneQueryProviderFlowFloatingObjects);
|
|
}
|
|
|
|
[Validator(typeof(ScatteringLod))]
|
|
static bool Validate(ScatteringLod target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
var water = Water;
|
|
|
|
if (!target.Enabled)
|
|
{
|
|
return isValid;
|
|
}
|
|
|
|
if (target._ShorelineColorSource != ShorelineVolumeColorSource.None)
|
|
{
|
|
if (!water._DepthLod._Enabled)
|
|
{
|
|
ShowDependentPropertyMessage
|
|
(
|
|
"Shoreline Scattering",
|
|
"Water Depth",
|
|
$"{nameof(WaterRenderer._DepthLod)}.{nameof(Lod._Enabled)}",
|
|
messenger,
|
|
water
|
|
);
|
|
}
|
|
else if (target._ShorelineColorSource == ShorelineVolumeColorSource.Distance && !water._DepthLod._EnableSignedDistanceFields)
|
|
{
|
|
ShowDependentPropertyMessage
|
|
(
|
|
"Shoreline Distance Scattering",
|
|
"Signed Distance Fields",
|
|
$"{nameof(WaterRenderer._DepthLod)}.{nameof(WaterRenderer._DepthLod._EnableSignedDistanceFields)}",
|
|
messenger,
|
|
water
|
|
);
|
|
}
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
[Validator(typeof(CutsceneTimeProvider))]
|
|
static bool Validate(CutsceneTimeProvider target, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
var water = Water;
|
|
if (water == null)
|
|
{
|
|
messenger
|
|
(
|
|
$"No water present. {nameof(CutsceneTimeProvider)} will have no effect.",
|
|
"", MessageType.Warning,
|
|
caller: target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
#if d_ModuleUnityDirector
|
|
if (target._PlayableDirector == null)
|
|
{
|
|
messenger
|
|
(
|
|
$"No {nameof(UnityEngine.Playables.PlayableDirector)} component assigned. {nameof(CutsceneTimeProvider)} will have no effect.",
|
|
$"Add a {nameof(UnityEngine.Playables.PlayableDirector)}",
|
|
MessageType.Error,
|
|
caller: target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
#else
|
|
messenger
|
|
(
|
|
$"This component requires the com.unity.modules.director built-in module to function.",
|
|
$"Enable the com.unity.modules.director built-in module.",
|
|
MessageType.Error,
|
|
caller: target
|
|
);
|
|
|
|
isValid = false;
|
|
#endif
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateWaterMaterial(Object target, ShowMessage messenger, WaterRenderer water, Material material)
|
|
{
|
|
var isValid = true;
|
|
|
|
// TODO: We could be even more granular with what needs this property.
|
|
if (water._Underwater._Enabled && !material.HasVector(WaterRenderer.ShaderIDs.s_Absorption))
|
|
{
|
|
messenger
|
|
(
|
|
$"Material <i>{material.name}</i> does not have <i>Crest Absorption</i> property. " +
|
|
"Several features require absorption like underwater culling and lighting.",
|
|
$"Assign a valid water material.",
|
|
MessageType.Warning, target
|
|
);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateMaterialParent(Material child, Material parent, ShowMessage messenger)
|
|
{
|
|
var isValid = true;
|
|
|
|
if (child != null && child.parent != parent)
|
|
{
|
|
messenger
|
|
(
|
|
$"The <i>{child}</i> does not have <i>{parent}</i> as a parent. " +
|
|
"Linking these materials is typically how these are used to avoid trying to keep properties in sync.",
|
|
$"Parent <i>{parent}</i> to <i>{child}</i>.",
|
|
MessageType.Info, parent,
|
|
(_, _) =>
|
|
{
|
|
Undo.RecordObject(child, "Assign parent");
|
|
child.parent = parent;
|
|
}
|
|
);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateComponent<T, C>(T target, ShowMessage messenger, C @object)
|
|
where T : Component
|
|
where C : Component
|
|
{
|
|
var isValid = true;
|
|
|
|
if (@object == null && !target.gameObject.TryGetComponent<C>(out _))
|
|
{
|
|
messenger
|
|
(
|
|
$"{typeof(T).Name} requires a {typeof(C).Name} to be set or present on same object.",
|
|
$"Set the {typeof(C).Name} property or add a {typeof(C).Name}.",
|
|
MessageType.Error,
|
|
caller: target
|
|
);
|
|
|
|
isValid = false;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
static bool ValidateCollisionLayer(CollisionLayer layer, Object target, ShowMessage messenger, string label, object value, bool required)
|
|
{
|
|
if (Water == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var layers = Water.AnimatedWavesLod._CollisionLayers;
|
|
|
|
if (layer == CollisionLayer.Everything)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var flag = (CollisionLayers)((int)layer << 1);
|
|
|
|
if (!layers.HasFlag(flag))
|
|
{
|
|
var fix = $"Enable the {flag} layer on the {nameof(WaterRenderer)}.";
|
|
if (!required) fix += " You can safely ignore this warning.";
|
|
|
|
messenger
|
|
(
|
|
$"The {value} {label} requires the {flag} layer which is not enabled.",
|
|
fix,
|
|
required ? MessageType.Error : MessageType.Warning, Water,
|
|
(_, y) => y.intValue = (int)(layers | flag),
|
|
$"{nameof(WaterRenderer._AnimatedWavesLod)}.{nameof(WaterRenderer._AnimatedWavesLod._CollisionLayers)}",
|
|
caller: target
|
|
);
|
|
|
|
return !required;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ValidateQuerySource(Object target, ShowMessage messenger, IQueryableLod<IQueryProvider> lod, string extra)
|
|
{
|
|
if (Water == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!lod.Enabled)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (lod.QuerySource == LodQuerySource.None)
|
|
{
|
|
messenger
|
|
(
|
|
$"<i>{nameof(WaterRenderer)} > {lod.Name} > {nameof(lod.QuerySource)}</i> is set to <i>None</i>. {extra}",
|
|
$"Set the <i>{nameof(lod.QuerySource)}</i> to <i>{nameof(LodQuerySource.GPU)}</i> to use data.",
|
|
MessageType.Info, Water,
|
|
FixSetQuerySourceToCompute,
|
|
$"_{lod.GetType().Name}._{nameof(lod.QuerySource)}",
|
|
caller: target
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ValidateFilteredChoice(int choice, string property, Object target, ShowMessage messenger)
|
|
{
|
|
var filter = target
|
|
.GetType()
|
|
.GetCustomAttributes<FilterEnum>(inherit: true)
|
|
.FirstOrDefault(x => x._Property == property);
|
|
|
|
if (filter?._Values.Contains(choice) == false)
|
|
{
|
|
var label = property[1..];
|
|
|
|
messenger
|
|
(
|
|
$"The {label} property is invalid.",
|
|
$"Choose a correct {label} property.",
|
|
MessageType.Error,
|
|
target
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ShowDependentPropertyMessage(string dependentLabel, string dependencyLabel, string dependencyPropertyPath, ShowMessage messenger, Object dependencyContext)
|
|
{
|
|
messenger
|
|
(
|
|
$"{dependencyLabel} is not enabled, but {dependentLabel} requires it.",
|
|
$"Enable {dependencyLabel}.",
|
|
MessageType.Warning, dependencyContext,
|
|
(_, y) => y.boolValue = true,
|
|
dependencyPropertyPath
|
|
);
|
|
}
|
|
}
|
|
}
|