升级6.4.升级水,升级天气
This commit is contained in:
@@ -20,7 +20,7 @@ MonoBehaviour:
|
||||
clusterMode: 0
|
||||
packingMode: 0
|
||||
pbrWorkflow: 0
|
||||
hash: -164212736
|
||||
hash: -941817856
|
||||
splatArray: {fileID: 0}
|
||||
diffuseArray: {fileID: 18700000, guid: 6966c6a2271704eeaa4697428c97b571, type: 2}
|
||||
normalSAOArray: {fileID: 18700000, guid: ca49526f427194c41b515c21c5dbe465, type: 2}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -117,6 +117,11 @@ namespace WaveHarmonic.Crest.Editor.Build
|
||||
ProjectSettings _Settings;
|
||||
WaterResources _Resources;
|
||||
|
||||
static readonly string[] s_LightMapKeywords =
|
||||
{
|
||||
"LIGHTMAP_ON", "DIRLIGHTMAP_COMBINED", "DYNAMICLIGHTMAP_ON", "LIGHTMAP_SHADOW_MIXING", "SHADOWS_SHADOWMASK"
|
||||
};
|
||||
|
||||
void Logger(string message)
|
||||
{
|
||||
Debug.Log(message);
|
||||
@@ -210,6 +215,24 @@ namespace WaveHarmonic.Crest.Editor.Build
|
||||
}
|
||||
}
|
||||
|
||||
// Strip lightmap variants.
|
||||
// Unity strips these unless they are used by any shader once in the scene, then
|
||||
// they are always included. Seems like a bug. To test, make sure lightmaps are
|
||||
// set up, and there is a static object in the scene. Or set the following:
|
||||
// Project Settings > Graphics > Shader Stripping > Lightmap Modes
|
||||
foreach (var keyword in keywords)
|
||||
{
|
||||
if (s_LightMapKeywords.Contains(keyword.name))
|
||||
{
|
||||
if (_Settings.LogStrippedVariants)
|
||||
{
|
||||
Logger($"Stripping Keyword: {keyword.name}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +66,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
message += $"Scale: {target.CalcLodScale(i)}\n";
|
||||
message += $"Texel: {2f * 2f * target.CalcLodScale(i) / target.LodResolution}\n";
|
||||
message += $"Minimum Slice: {Mathf.Floor(Mathf.Log(Mathf.Max(i / baseTexelSize, 1f), 2f))}";
|
||||
if (i < target.LodLevels - 1) message += "\n\n";
|
||||
message += "\n\n";
|
||||
}
|
||||
|
||||
message += $"Scale: {target.Scale}\n";
|
||||
|
||||
message += "\n";
|
||||
|
||||
if (target.Surface.Material.HasVector(WaterRenderer.ShaderIDs.s_Absorption))
|
||||
{
|
||||
message += $"\n\nDepth Fog Density: {target.Surface.Material.GetVector(WaterRenderer.ShaderIDs.s_Absorption)}";
|
||||
message += $"Depth Fog Density: {target.Surface.Material.GetVector(WaterRenderer.ShaderIDs.s_Absorption)}";
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(message, MessageType.None);
|
||||
@@ -106,7 +110,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
ValidatedHelper.ExecuteValidators(target, ValidatedHelper.DebugLog);
|
||||
|
||||
foreach (var component in FindObjectsByType<EditorBehaviour>(FindObjectsSortMode.None))
|
||||
foreach (var component in Helpers.FindObjectsByType<EditorBehaviour>())
|
||||
{
|
||||
if (component is WaterRenderer) continue;
|
||||
ValidatedHelper.ExecuteValidators(component, ValidatedHelper.DebugLog);
|
||||
@@ -116,6 +120,17 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
ManualValidation = false;
|
||||
}
|
||||
|
||||
#if CREST_DEBUG
|
||||
if (GUILayout.Button("Pause Time"))
|
||||
{
|
||||
var time = Undo.AddComponent<CustomTimeProvider>(target.gameObject);
|
||||
time.Paused = true;
|
||||
time.OverrideTime = true;
|
||||
target._TimeProvider = time;
|
||||
target.TimeProviders.Push(time);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,12 +143,27 @@ namespace WaveHarmonic.Crest.Editor
|
||||
"Fully developed sea with infinite fetch.",
|
||||
};
|
||||
|
||||
static readonly GUIContent s_TimeScaleLabel = new("Time Scale");
|
||||
|
||||
System.Type _HostComponentType = null;
|
||||
public void SetTypeOfHostComponent(System.Type hostComponentType)
|
||||
enum WaveModel
|
||||
{
|
||||
_HostComponentType = hostComponentType;
|
||||
None,
|
||||
FFT,
|
||||
Gerstner,
|
||||
}
|
||||
WaveModel _WaveModel;
|
||||
object _Host;
|
||||
|
||||
public void SetHostComponent(object host)
|
||||
{
|
||||
_Host = host;
|
||||
}
|
||||
|
||||
public void SetTypeOfHostComponent(System.Type host)
|
||||
{
|
||||
_WaveModel = host == typeof(ShapeGerstner)
|
||||
? WaveModel.Gerstner
|
||||
: host == typeof(ShapeFFT)
|
||||
? WaveModel.FFT
|
||||
: WaveModel.None;
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
@@ -164,7 +194,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
// Display a notice if its being edited as a standalone asset (not embedded in a component) because
|
||||
// it displays the FFT-interface.
|
||||
if (_HostComponentType == null)
|
||||
if (_WaveModel is WaveModel.None)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This editor is displaying the FFT spectrum settings. " +
|
||||
"To edit settings specific to the ShapeGerstner component, assign this asset to a ShapeGerstner component " +
|
||||
@@ -176,22 +206,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var beingEditedOnGerstnerComponent = _HostComponentType == typeof(ShapeGerstner);
|
||||
|
||||
var showAdvancedControls = false;
|
||||
if (beingEditedOnGerstnerComponent)
|
||||
if (_WaveModel is WaveModel.Gerstner)
|
||||
{
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(WaveSpectrum._GravityScale)));
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(WaveSpectrum._WaveDirectionVariance)));
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(WaveSpectrum._ShowAdvancedControls)));
|
||||
showAdvancedControls = serializedObject.FindProperty(nameof(WaveSpectrum._ShowAdvancedControls)).boolValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(WaveSpectrum._GravityScale)), s_TimeScaleLabel);
|
||||
}
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
var advancedControls = serializedObject.FindProperty(nameof(WaveSpectrum._ShowAdvancedControls));
|
||||
EditorGUILayout.PropertyField(advancedControls);
|
||||
var showAdvancedControls = advancedControls.boolValue;
|
||||
|
||||
var spSpectrumModel = serializedObject.FindProperty(nameof(WaveSpectrum._Model));
|
||||
var spectraIndex = serializedObject.FindProperty(nameof(WaveSpectrum._Model)).enumValueIndex;
|
||||
@@ -222,6 +246,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var spPower = serializedObject.FindProperty(nameof(WaveSpectrum._PowerLogarithmicScales));
|
||||
var spChopScales = serializedObject.FindProperty(nameof(WaveSpectrum._ChopScales));
|
||||
var spGravScales = serializedObject.FindProperty(nameof(WaveSpectrum._GravityScales));
|
||||
var spAttenuation = serializedObject.FindProperty(nameof(WaveSpectrum._Attenuation));
|
||||
|
||||
// Disable sliders if authoring with model.
|
||||
var canEditSpectrum = spectrumModel == WaveSpectrum.SpectrumModel.None;
|
||||
@@ -247,7 +272,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Disable slider if authoring with model.
|
||||
using (new EditorGUI.DisabledGroupScope(!canEditSpectrum || spDisabled_i.boolValue))
|
||||
{
|
||||
powerValue = EditorGUILayout.Slider(" Power", powerValue, WaveSpectrum.s_MinimumPowerLog, WaveSpectrum.s_MaximumPowerLog);
|
||||
powerValue = EditorGUILayout.Slider(" Power", powerValue, WaveSpectrum.s_MinimumPowerLog, WaveSpectrum.s_MaximumPowerLog);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -273,8 +298,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
if (showAdvancedControls)
|
||||
{
|
||||
EditorGUILayout.Slider(spChopScales.GetArrayElementAtIndex(i), 0f, 4f, " Chop Scale");
|
||||
EditorGUILayout.Slider(spGravScales.GetArrayElementAtIndex(i), 0f, 4f, " Grav Scale");
|
||||
var timeText = _WaveModel is WaveModel.Gerstner ? " Gravity Scale" : " Time Scale";
|
||||
EditorGUILayout.Slider(spChopScales.GetArrayElementAtIndex(i), 0f, 4f, " Chop Scale");
|
||||
EditorGUILayout.Slider(spGravScales.GetArrayElementAtIndex(i), 0f, 4f, timeText);
|
||||
EditorGUILayout.Slider(spAttenuation.GetArrayElementAtIndex(i), 0f, 1f, " Attenuation Scale");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +316,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox(s_ModelDescriptions[(int)spectrumModel], MessageType.Info);
|
||||
if (_Host is not null and ShapeWaves waves)
|
||||
{
|
||||
var windSpeedSource = waves.GetWindSpeedSource();
|
||||
|
||||
if (windSpeedSource != ShapeWaves.WindSpeedSource.None)
|
||||
{
|
||||
EditorHelpers.RichTextHelpBox
|
||||
(
|
||||
GetWindSpeedText(waves, windSpeedSource) + GetWindSpeedFixText(windSpeedSource),
|
||||
MessageType.Info
|
||||
);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (spectrumModel == WaveSpectrum.SpectrumModel.None)
|
||||
@@ -326,6 +366,35 @@ namespace WaveHarmonic.Crest.Editor
|
||||
EditorUtility.SetDirty(spec);
|
||||
}
|
||||
}
|
||||
|
||||
internal override GUIContent OnCustomLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
label = base.OnCustomLabel(property, label, drawer);
|
||||
|
||||
var isFFT = _WaveModel is WaveModel.FFT;
|
||||
|
||||
if (isFFT && property.name == "_GravityScale")
|
||||
{
|
||||
label.text = "Time Scale";
|
||||
label.tooltip = "More time means faster waves.";
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
internal static string GetWindSpeedText(ShapeWaves target, ShapeWaves.WindSpeedSource source)
|
||||
{
|
||||
var text = source == ShapeWaves.WindSpeedSource.WaterRenderer ? $"the {nameof(WaterRenderer).Pretty().Italic()}" : "this component";
|
||||
return $"The wave spectrum is currently limited by {nameof(ShapeWaves.WindSpeed).Pretty().Italic()} on {text} to {target.WindSpeedKPH} KPH, and will not be fully developed.";
|
||||
}
|
||||
|
||||
internal static string GetWindSpeedFixText(ShapeWaves.WindSpeedSource source)
|
||||
{
|
||||
var text = source == ShapeWaves.WindSpeedSource.WaterRenderer
|
||||
? $"either override the {nameof(ShapeWaves.WindSpeed).Pretty().Italic()} on this component or increase the {nameof(WaterRenderer.WindSpeed).Pretty().Italic()} on the {nameof(WaterRenderer).Pretty().Italic()}"
|
||||
: $"then increase the {nameof(ShapeWaves.WindSpeed).Pretty().Italic()} on this component";
|
||||
return $"If you want fully developed waves (ie large waves), {text}.";
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(LodSettings), true)]
|
||||
@@ -366,6 +435,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var boundsXZ = new Bounds(target._UnexpandedBoundsXZ.center.XNZ(), target._UnexpandedBoundsXZ.size.XNZ());
|
||||
EditorGUILayout.BoundsField("Bounds XZ", boundsXZ);
|
||||
EditorGUILayout.BoundsField("Expanded Bounds", _Renderer.bounds);
|
||||
EditorGUILayout.IntField("Sibling Index", target._SiblingIndex);
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7b81340420aa49ed9bd65a5a9c558ad
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
// <auto-generated/>
|
||||
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor.Settings
|
||||
{
|
||||
partial class PlatformSettings
|
||||
{
|
||||
public bool AlbedoSimulation => _Default || _OverrideAlbedoSimulation ? _AlbedoSimulation : Default.AlbedoSimulation;
|
||||
public bool AbsorptionSimulation => _Default || _OverrideAbsorptionSimulation ? _AbsorptionSimulation : Default.AbsorptionSimulation;
|
||||
public bool ScatteringSimulation => _Default || _OverrideScatteringSimulation ? _ScatteringSimulation : Default.ScatteringSimulation;
|
||||
public bool ShadowSimulation => _Default || _OverrideShadowSimulation ? _ShadowSimulation : Default.ShadowSimulation;
|
||||
public bool OutScattering => _Default || _OverrideOutScattering ? _OutScattering : Default.OutScattering;
|
||||
public bool NormalMaps => _Default || _OverrideNormalMaps ? _NormalMaps : Default.NormalMaps;
|
||||
public bool PlanarReflections => _Default || _OverridePlanarReflections ? _PlanarReflections : Default.PlanarReflections;
|
||||
public bool PlanarReflectionsApplySmoothness => _Default || _OverridePlanarReflectionsApplySmoothness ? _PlanarReflectionsApplySmoothness : Default.PlanarReflectionsApplySmoothness;
|
||||
public WaveHarmonic.Crest.Editor.Settings.SamplingMethod FoamSampling => _Default || _OverrideFoamSampling ? _FoamSampling : Default.FoamSampling;
|
||||
public bool FoamBioluminescence => _Default || _OverrideFoamBioluminescence ? _FoamBioluminescence : Default.FoamBioluminescence;
|
||||
public bool CausticsForceDistortion => _Default || _OverrideCausticsForceDistortion ? _CausticsForceDistortion : Default.CausticsForceDistortion;
|
||||
public bool AdditionalLights => _Default || _OverrideAdditionalLights ? _AdditionalLights : Default.AdditionalLights;
|
||||
public bool SimpleTransparency => _Default || _OverrideSimpleTransparency ? _SimpleTransparency : Default.SimpleTransparency;
|
||||
}
|
||||
}
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
partial class ShaderSettingsAndroid
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsDefault
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsIOS
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsServer
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsStandalone
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsTVOS
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsVisionOS
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
|
||||
partial class ShaderSettingsWeb
|
||||
{
|
||||
public static int s_CrestAlbedoSimulation = Settings.AlbedoSimulation ? 1 : 0;
|
||||
public static int s_CrestAbsorptionSimulation = Settings.AbsorptionSimulation ? 1 : 0;
|
||||
public static int s_CrestScatteringSimulation = Settings.ScatteringSimulation ? 1 : 0;
|
||||
public static int s_CrestShadowSimulation = Settings.ShadowSimulation ? 1 : 0;
|
||||
public static int s_CrestOutScattering = Settings.OutScattering ? 1 : 0;
|
||||
public static int s_CrestNormalMaps = Settings.NormalMaps ? 1 : 0;
|
||||
public static int s_CrestPlanarReflections = Settings.PlanarReflections ? 1 : 0;
|
||||
public static int s_CrestPlanarReflectionsApplySmoothness = Settings.PlanarReflectionsApplySmoothness ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingMultiScale = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.MultiScale) ? 1 : 0;
|
||||
public static int s_CrestFoamSamplingStochastic = Settings.FoamSampling.HasFlag(WaveHarmonic.Crest.Editor.Settings.SamplingMethod.Stochastic) ? 1 : 0;
|
||||
public static int s_CrestFoamBioluminescence = Settings.FoamBioluminescence ? 1 : 0;
|
||||
public static int s_CrestCausticsForceDistortion = Settings.CausticsForceDistortion ? 1 : 0;
|
||||
public static int s_CrestAdditionalLights = Settings.AdditionalLights ? 1 : 0;
|
||||
public static int s_CrestSimpleTransparency = Settings.SimpleTransparency ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a76b369d4084d4397a84d1586f34c0d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -132,6 +132,25 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.Selected)]
|
||||
static void DrawGizmos(LevelLodInput target, GizmoType type)
|
||||
{
|
||||
if (!target.Enabled) return;
|
||||
if (target.Data is not LodInputData data) return;
|
||||
|
||||
var bounds = data.Bounds;
|
||||
|
||||
if (target.OverrideHeight)
|
||||
{
|
||||
var minimum = target.HeightRange.x;
|
||||
var maximum = target.HeightRange.y;
|
||||
bounds.size = bounds.size.XNZ(Mathf.Abs(minimum - maximum));
|
||||
bounds.center = bounds.center.XNZ(Mathf.Lerp(minimum, maximum, 0.5f));
|
||||
}
|
||||
|
||||
bounds.GizmosDraw();
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.Selected)]
|
||||
static void DrawWatertightHullGizmos(WatertightHull target, GizmoType type)
|
||||
{
|
||||
@@ -317,9 +336,12 @@ namespace WaveHarmonic.Crest.Editor
|
||||
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected)]
|
||||
static void DrawGizmos(WaterChunkRenderer target, GizmoType type)
|
||||
{
|
||||
if (target._DrawRenderBounds)
|
||||
if ((target._DrawRenderBounds || type.HasFlag(GizmoType.Selected)) && target.Rend != null)
|
||||
{
|
||||
Gizmos.color = type.HasFlag(GizmoType.Selected) ? Color.green : Color.white;
|
||||
Handles.Label(target.transform.position, $"{target._LodIndex},{target._SiblingIndex}");
|
||||
target.Rend.bounds.GizmosDraw();
|
||||
Gizmos.color = Color.white;
|
||||
}
|
||||
|
||||
if (!type.HasFlag(GizmoType.Selected))
|
||||
@@ -327,11 +349,6 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.Rend != null)
|
||||
{
|
||||
target.Rend.bounds.GizmosDraw();
|
||||
}
|
||||
|
||||
if (WaterBody.WaterBodies.Count > 0)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
|
||||
@@ -6,20 +6,23 @@
|
||||
using Gaia;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.ShallowWater;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class GRC_Crest : GaiaRuntimeComponent
|
||||
{
|
||||
#pragma warning disable CS0414
|
||||
[SerializeField]
|
||||
bool _Wind = true;
|
||||
#pragma warning restore CS0414
|
||||
|
||||
[SerializeField]
|
||||
bool _Swell = true;
|
||||
|
||||
#pragma warning disable CS0414
|
||||
[SerializeField]
|
||||
bool _ShallowWater = true;
|
||||
#pragma warning restore CS0414
|
||||
|
||||
GUIContent _HelpLink;
|
||||
GUIContent _PanelLabel;
|
||||
@@ -67,8 +70,10 @@ namespace WaveHarmonic.Crest
|
||||
_Swell = EditorGUILayout.Toggle("Swell Waves", _Swell);
|
||||
DisplayHelp("Whether to add swell waves to the scene. Swell waves will come from conditions far away from the scene. Modify the component after creation to customize.");
|
||||
|
||||
#if d_UnityModuleWind
|
||||
_Wind = EditorGUILayout.Toggle("Wind Waves", _Wind);
|
||||
DisplayHelp("Whether to add wind waves to the scene. These waves are based on local wind conditions. Requires Gaia's Wind Zone (note that the defaul wind value will produce no waves). Modify the component after creation to customize.");
|
||||
#endif
|
||||
|
||||
#if d_WaveHarmonic_Crest_ShallowWater
|
||||
_ShallowWater = EditorGUILayout.Toggle("Shoreline Simulation", _ShallowWater);
|
||||
@@ -94,7 +99,7 @@ namespace WaveHarmonic.Crest
|
||||
public override void AddToScene()
|
||||
{
|
||||
// Re-initialize to keep user's changes.
|
||||
var water = FindFirstObjectByType<WaterRenderer>(FindObjectsInactive.Include);
|
||||
var water = FindAnyObjectByType<WaterRenderer>(FindObjectsInactive.Include);
|
||||
|
||||
if (water == null)
|
||||
{
|
||||
@@ -115,21 +120,23 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
managed.SetParent(water.transform, worldPositionStays: false);
|
||||
|
||||
#if d_UnityModuleWind
|
||||
// Wind
|
||||
if (_Wind)
|
||||
{
|
||||
var wind = FindFirstObjectByType<WindManager>();
|
||||
var wind = FindAnyObjectByType<WindManager>();
|
||||
|
||||
if (wind != null)
|
||||
{
|
||||
water.WindZone = wind.GetComponent<WindZone>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Depth
|
||||
water.DepthLod.IncludeTerrainHeight = false;
|
||||
|
||||
foreach (var terrain in FindObjectsByType<Terrain>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
||||
foreach (var terrain in Helpers.FindObjectsByType<Terrain>(FindObjectsInactive.Include))
|
||||
{
|
||||
var dp = terrain.GetComponentInChildren<DepthProbe>(includeInactive: true);
|
||||
|
||||
@@ -152,12 +159,14 @@ namespace WaveHarmonic.Crest
|
||||
dp.Populate();
|
||||
}
|
||||
|
||||
#if d_UnityModuleWind
|
||||
// Wind Waves
|
||||
if (_Wind && water.WindZone != null)
|
||||
{
|
||||
GetOrAddComponentToScene<ShapeFFT>(managed, "WaterWindWaves", out _);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
RemoveComponentFromScene<ShapeFFT>(managed);
|
||||
}
|
||||
@@ -193,7 +202,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
water.FlowLod.Enabled = true;
|
||||
|
||||
if (GetOrAddComponentToScene<ShallowWaterSimulation>(managed, "ShorelineSimulation", out var sws))
|
||||
if (GetOrAddComponentToScene<ShallowWater.ShallowWaterSimulation>(managed, "ShorelineSimulation", out var sws))
|
||||
{
|
||||
water.Surface.Material = AssetDatabase.LoadAssetAtPath<Material>("Packages/com.waveharmonic.crest/Runtime/Materials/Water (Flow).mat");
|
||||
sws.Width = 256;
|
||||
@@ -206,13 +215,13 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
dp.GenerateSignedDistanceField = false;
|
||||
|
||||
sws.Preset = ShallowWaterSimulationPreset.Shoreline;
|
||||
sws.Preset = ShallowWater.ShallowWaterSimulationPreset.Shoreline;
|
||||
sws.Placement = Placement.Viewpoint;
|
||||
sws.DynamicSeabed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveComponentFromScene<ShallowWaterSimulation>(managed);
|
||||
RemoveComponentFromScene<ShallowWater.ShallowWaterSimulation>(managed);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -246,10 +255,10 @@ namespace WaveHarmonic.Crest
|
||||
/// <inheritdoc/>
|
||||
public override void RemoveFromScene()
|
||||
{
|
||||
var water = FindFirstObjectByType<WaterRenderer>(FindObjectsInactive.Include);
|
||||
var water = FindAnyObjectByType<WaterRenderer>(FindObjectsInactive.Include);
|
||||
if (water != null) DestroyImmediate(water.gameObject);
|
||||
|
||||
foreach (var terrain in FindObjectsByType<Terrain>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
||||
foreach (var terrain in Helpers.FindObjectsByType<Terrain>(FindObjectsInactive.Include))
|
||||
{
|
||||
var depthCache = terrain.GetComponentInChildren<DepthProbe>(includeInactive: true);
|
||||
if (depthCache != null) DestroyImmediate(depthCache.gameObject);
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"GAIA_2023"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.modules.wind",
|
||||
"expression": "",
|
||||
"define": "d_UnityModuleWind"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.shallow-water",
|
||||
"expression": "",
|
||||
|
||||
@@ -7,6 +7,7 @@ using UnityEditor;
|
||||
using UnityEditor.Rendering;
|
||||
using UnityEditor.Rendering.HighDefinition;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Editor.Settings;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
@@ -27,6 +28,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{ "_Crest_HeightsOnly", "Treats the texture as a heightmap and reads from the R channel" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Crest/Inputs/Dynamic Waves/Add Bump", new()
|
||||
{
|
||||
{ "_Crest_Amplitude", "The vertical radius of the bump. How much the surface will rise or fall." },
|
||||
{ "_Crest_Radius", "The horizontal radius of the bump." },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Crest/Inputs/Flow/Add From Texture", new()
|
||||
{
|
||||
@@ -66,16 +74,24 @@ namespace WaveHarmonic.Crest.Editor
|
||||
WaterShaderUtility.k_ShaderName, new()
|
||||
{
|
||||
{ "_Crest_NormalsStrengthOverall", "Strength of the final surface normal (both wave normal and normal map)" },
|
||||
{ "_Crest_ApplyDisplacementNormals", "Whether to sample the displacement maps (Animated Waves and Dynamic Waves) for normal contributions." },
|
||||
{ "_Crest_NormalMapEnabled", "Whether to add normal detail from a texture. Can be used to add visual detail to the water surface" },
|
||||
{ "_Crest_NormalMapTexture", "Normal map texture" },
|
||||
{ "_Crest_NormalMapStrength", "Strength of normal map influence" },
|
||||
{ "_Crest_NormalMapScale", "Base scale of multi-scale normal map texture" },
|
||||
{ "_Crest_NormalMapScrollSpeed", "Speed of the normal maps scrolling" },
|
||||
{ "_Crest_NormalMapTurbulenceEnabled", "Increase chance of sparkles where water is turbulent" },
|
||||
{ "_Crest_NormalMapTurbulenceStrength", "Strength of the normal map influence where turbulent" },
|
||||
{ "_Crest_NormalMapTurbulenceCoverage", "The threshold where turbulence triggers this effect.\n\nValues above 0.9 may be too high for calmer wave conditions, as it will trigger the effect where water is not turbulent. Furthermore, a value of one or very close one can cause a pop when weighing out waves to zero" },
|
||||
{ "_Crest_AbsorptionColor", "Works as a color (ie red adds red rather than subtracts red). This value is converted to real absorption values (proportion of light getting absorbed by water in atoms per meter). Alpha channel is for density. High alpha and darker color reduces transparency" },
|
||||
{ "_Crest_Scattering", "Light scattered by the water towards the viewer (in-scattered) per meter. Brighter color reduces transparency" },
|
||||
{ "_Crest_Anisotropy", "The directionality of the scattering where zero means scattered in all directions. The further towards one, the less visible soft shadows will be" },
|
||||
{ "_Crest_DirectTerm", "Scale direct light contribution to volume lighting" },
|
||||
{ "_Crest_DirectTermAdditional", "Scale additional lights contribution to volume lighting" },
|
||||
{ "_Crest_AmbientTerm", "Scale ambient light contribution to volume lighting" },
|
||||
{ "_Crest_ShadowsAffectsAmbientFactor", "How much shadows affect ambient lighting. Typically this not required, but can help scenes with large shadowed areas" },
|
||||
{ "_Crest_AdditionalLightsBlend", "How much the additional light contributions blend with scattering." },
|
||||
{ "_Crest_ApplyFresnelToVolumeLighting", "Applies the fresnel to volume lighting to help the surface blend with the horizon in the form of being a mirror. Typically, only suitable with full smoothness (unless skybox is designed for water rather than earth)." },
|
||||
{ "_Crest_SSSEnabled", "Whether to to emulate light scattering through waves" },
|
||||
{ "_Crest_SSSIntensity", "Direct light contribution intensity. Applied to the scattering color. This effect is best if subtle" },
|
||||
{ "_Crest_SSSPinchMinimum", "Higher the value the more scattering is towards the peaks of the waves" },
|
||||
@@ -85,6 +101,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{ "_Crest_Specular", "Strength of specular lighting response" },
|
||||
{ "_Crest_Occlusion", "Strength of reflection" },
|
||||
{ "_Crest_OcclusionUnderwater", "Strength of reflection when underwater. Keep this at zero to avoid skybox reflections which look incorrect when underwater, unless you want reflections from Planar Reflections or probes" },
|
||||
{ "_Crest_Fresnel", "The fresnel power. Decrease to increase strength of reflections." },
|
||||
{ "_Crest_Smoothness", "Smoothness of surface. A value of one is ideal for flat water only" },
|
||||
{ "_Crest_SmoothnessFar", "Material smoothness at far distance from camera. Helps to spread out specular highlight in mid-to-background. From a theory point of view, models transfer of normal detail to microfacets in BRDF" },
|
||||
{ "_Crest_SmoothnessFarDistance", "Definition of far distance" },
|
||||
@@ -93,6 +110,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{ "_Crest_PlanarReflectionsEnabled", "Dynamically rendered 'reflection plane' style reflections. Requires Reflections to be enabled on the Water Renderer" },
|
||||
{ "_Crest_PlanarReflectionsIntensity", "Intensity of the planar reflections" },
|
||||
{ "_Crest_PlanarReflectionsDistortion", "How much the water normal affects the planar reflection" },
|
||||
{ "_Crest_PlanarReflectionsApplySmoothness", "Whether to apply smoothness to planar reflection sample via mip-map blending. Enabling will likely incur artifacts with most perturbed water" },
|
||||
{ "_Crest_PlanarReflectionsRoughness", "Controls the mipmap range" },
|
||||
{ "_Crest_RefractionStrength", "How strongly light is refracted when passing through water surface" },
|
||||
{ "_Crest_RefractiveIndexOfWater", "Index of refraction of water - typically left at 1.333. Changing this value can increase/decrease the size of the Snell's window" },
|
||||
@@ -107,6 +125,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{ "_Crest_FoamIntensityAlbedo", "Scale intensity of diffuse lighting" },
|
||||
{ "_Crest_FoamSmoothness", "Smoothness of foam material" },
|
||||
{ "_Crest_FoamNormalStrength", "Strength of the generated normals" },
|
||||
{ "_Crest_FoamBioluminescenceEnabled", "Enables a foam-based bioluminescence. This reuses the foam sample" },
|
||||
{ "_Crest_FoamBioluminescenceColor", "The color and intensity of the bioluminescence" },
|
||||
{ "_Crest_FoamBioluminescenceIntensity", "The intensity of the bioluminescent foam" },
|
||||
{ "_Crest_FoamBioluminescenceMaximumDepth", "The maximum water depth where bioluminescence can appear. This can be used to keep bioluminescence localized to the shoreline" },
|
||||
{ "_Crest_FoamBioluminescenceSeaLevelOnly", "Only render foam bioluminescence at sea level. This will fade bioluminescence to be fully gone at 1m from sea level." },
|
||||
{ "_Crest_FoamBioluminescenceGlowIntensity", "The intensity of the bioluminescent glow" },
|
||||
{ "_Crest_FoamBioluminescenceGlowCoverage", "The coverage of the bioluminescent glow. Glow is based on raw foam data which makes it more present than foam" },
|
||||
{ "_Crest_FoamBioluminescenceSparklesEnabled", "Whether to apply bioluminescent sparkles. This uses the green channel of the foam texture as an emissive map" },
|
||||
{ "_Crest_FoamBioluminescenceSparklesIntensity", "The intensity of the bioluminescent sparkles" },
|
||||
{ "_Crest_FoamBioluminescenceSparklesCoverage", "The coverage of the bioluminescent sparkles. Sparkles placement is based on raw foam data which makes it more present than foam" },
|
||||
{ "_Crest_CausticsEnabled", "Approximate rays being focused/defocused on underwater surfaces" },
|
||||
{ "_Crest_CausticsTexture", "Caustics texture" },
|
||||
{ "_Crest_CausticsStrength", "Intensity of caustics effect" },
|
||||
@@ -115,13 +143,15 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{ "_Crest_CausticsTextureAverage", "The 'mid' value of the caustics texture, around which the caustic texture values are scaled. Decreasing this value will reduce the caustics darkening underwater surfaces" },
|
||||
{ "_Crest_CausticsFocalDepth", "The depth at which the caustics are in focus" },
|
||||
{ "_Crest_CausticsDepthOfField", "The range of depths over which the caustics are in focus" },
|
||||
{ "_Crest_CausticsForceDistortion", "Forces the distortion texture to be applied for above water. This can be used to distort caustics even when there is no waves" },
|
||||
{ "_Crest_CausticsDistortionTexture", "Texture to distort caustics. Only applicable to underwater effect for now" },
|
||||
{ "_Crest_CausticsDistortionStrength", "How much the caustics texture is distorted" },
|
||||
{ "_Crest_CausticsDistortionScale", "The scale of the distortion pattern used to distort the caustics" },
|
||||
{ "_Crest_CausticsMotionBlur", "How much caustics are blurred when advected by flow" },
|
||||
{ "CREST_FLOW", "Flow is horizontal motion of water. Flow must be enabled on the Water Renderer to generate flow data" },
|
||||
{ "_CREST_FLOW_LOD", "Flow is horizontal motion of water. Flow must be enabled on the Water Renderer to generate flow data" },
|
||||
{ "_Crest_AlbedoEnabled", "Enable the Albedo simulation layer. Albedo must be enabled on the Water" },
|
||||
{ "_Crest_AlbedoIgnoreFoam", "Whether Albedo renders over the top of foam or not." },
|
||||
{ "_Crest_TransparencyMinimumAlpha", "The minimum alpha value for transparency. Makes water contribution to the final image stronger instead of the scene behind it" },
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -147,6 +177,25 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly List<string> s_HiddenCategories = new();
|
||||
static bool s_Initialized;
|
||||
internal static void FilterCategories()
|
||||
{
|
||||
// Only need to do it once after compile.
|
||||
if (s_Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = ProjectSettings.Instance.CurrentPlatformSettings;
|
||||
s_HiddenCategories.Clear();
|
||||
if (!settings.AlbedoSimulation) s_HiddenCategories.Add("Albedo");
|
||||
if (!settings.FoamBioluminescence) s_HiddenCategories.Add("Bioluminescence");
|
||||
if (!settings.PlanarReflections) s_HiddenCategories.Add("Reflections (Planar)");
|
||||
if (!settings.SimpleTransparency) s_HiddenCategories.Add("Simple Transparency");
|
||||
s_Initialized = true;
|
||||
}
|
||||
|
||||
internal static MaterialProperty[] FilterProperties(MaterialProperty[] properties)
|
||||
{
|
||||
// Show specular control.
|
||||
@@ -166,9 +215,25 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
#endif
|
||||
|
||||
return properties
|
||||
.Where(x => (specular || x.name != "_Crest_Specular") && x.name != "_Crest_Absorption")
|
||||
.ToArray();
|
||||
var settings = ProjectSettings.Instance.CurrentPlatformSettings;
|
||||
var filtered = properties.Where(x => (specular || x.name != "_Crest_Specular") && x.name != "_Crest_Absorption");
|
||||
|
||||
if (!settings.CausticsForceDistortion)
|
||||
{
|
||||
filtered = filtered.Where(x => x.name != "_Crest_CausticsForceDistortion");
|
||||
}
|
||||
|
||||
if (!settings.NormalMaps)
|
||||
{
|
||||
filtered = filtered.Where(x => !x.name.StartsWithNoAlloc("_Crest_NormalMap"));
|
||||
}
|
||||
|
||||
if (!settings.ShadowSimulation)
|
||||
{
|
||||
filtered = filtered.Where(x => x.name != "_Crest_ShadowsAffectsAmbientFactor");
|
||||
}
|
||||
|
||||
return filtered.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +297,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
protected override void DrawSurfaceInputs(Material material)
|
||||
{
|
||||
ShaderGraphPropertyDrawers.DrawShaderGraphGUI(_Editor, _Properties, MaterialTooltips.s_Grouped[_ShaderName]);
|
||||
WaterShaderUtility.FilterCategories();
|
||||
ShaderGraphPropertyDrawers.DrawShaderGraphGUI(_Editor, _Properties, MaterialTooltips.s_Grouped[_ShaderName], WaterShaderUtility.s_HiddenCategories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +340,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
public override void DrawSurfaceInputs(Material material)
|
||||
{
|
||||
ShaderGraphPropertyDrawers.DrawShaderGraphGUI(_Editor, _Properties, MaterialTooltips.s_Grouped[_ShaderName]);
|
||||
WaterShaderUtility.FilterCategories();
|
||||
ShaderGraphPropertyDrawers.DrawShaderGraphGUI(_Editor, _Properties, MaterialTooltips.s_Grouped[_ShaderName], WaterShaderUtility.s_HiddenCategories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,8 +374,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return;
|
||||
}
|
||||
|
||||
WaterShaderUtility.FilterCategories();
|
||||
|
||||
var name = (materialEditor.customShaderGUI as HighDefinitionCustomShaderGUI)._ShaderName;
|
||||
ShaderGraphPropertyDrawers.DrawShaderGraphGUI(materialEditor, properties, MaterialTooltips.s_Grouped[name]);
|
||||
ShaderGraphPropertyDrawers.DrawShaderGraphGUI(materialEditor, properties, MaterialTooltips.s_Grouped[name], WaterShaderUtility.s_HiddenCategories);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Editor.Settings;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
static partial class MaterialUpgrader
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void OnLoad()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
}
|
||||
|
||||
static void OnEditorUpdate()
|
||||
{
|
||||
if (Time.renderedFrameCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EditorApplication.isUpdating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProjectSettings.Instance._MaterialVersion > 0)
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
return;
|
||||
}
|
||||
|
||||
UpgradeMaterials(force: false);
|
||||
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
}
|
||||
|
||||
[MenuItem("Edit/Crest/Materials/Upgrade Materials")]
|
||||
static void OnMenuSelect()
|
||||
{
|
||||
UpgradeMaterials(force: true);
|
||||
}
|
||||
|
||||
static void UpgradeMaterials(bool force)
|
||||
{
|
||||
var dirty = false;
|
||||
var version = force ? 0 : ProjectSettings.Instance._MaterialVersion;
|
||||
|
||||
AssetDatabase.StartAssetEditing();
|
||||
try
|
||||
{
|
||||
foreach (var guid in AssetDatabase.FindAssets("t:Material"))
|
||||
{
|
||||
var material = AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(guid));
|
||||
dirty |= UpgradeMaterial(material, version);
|
||||
}
|
||||
|
||||
ProjectSettings.Instance._MaterialVersion = k_MaterialVersion;
|
||||
ProjectSettings.Save();
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
AssetDatabase.StopAssetEditing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static partial class MaterialUpgrader
|
||||
{
|
||||
enum SerializedType
|
||||
{
|
||||
Boolean,
|
||||
Integer,
|
||||
Float,
|
||||
Vector,
|
||||
Color,
|
||||
Texture,
|
||||
Keyword,
|
||||
}
|
||||
|
||||
static bool TryFindBase(SerializedObject material, SerializedType type, out SerializedProperty propertyBase)
|
||||
{
|
||||
propertyBase = material.FindProperty("m_SavedProperties");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SerializedType.Integer:
|
||||
propertyBase = propertyBase.FindPropertyRelative("m_Ints");
|
||||
return true;
|
||||
case SerializedType.Boolean:
|
||||
case SerializedType.Float:
|
||||
propertyBase = propertyBase.FindPropertyRelative("m_Floats");
|
||||
return true;
|
||||
case SerializedType.Color:
|
||||
case SerializedType.Vector:
|
||||
propertyBase = propertyBase.FindPropertyRelative("m_Colors");
|
||||
return true;
|
||||
case SerializedType.Texture:
|
||||
propertyBase = propertyBase.FindPropertyRelative("m_TexEnvs");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static SerializedProperty FindBase(SerializedObject material, SerializedType type)
|
||||
{
|
||||
if (!TryFindBase(material, type, out var root))
|
||||
{
|
||||
throw new System.ArgumentException($"Unknown SerializedType {type}");
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
static bool TryFindProperty(SerializedObject material, string name, SerializedType type, out SerializedProperty property, out int index, SerializedProperty root)
|
||||
{
|
||||
var isKeyword = type == SerializedType.Keyword;
|
||||
|
||||
property = null;
|
||||
var size = root.arraySize;
|
||||
for (index = 0; index < size; ++index)
|
||||
{
|
||||
property = root.GetArrayElementAtIndex(index);
|
||||
if (isKeyword ? property.stringValue == name : property.FindPropertyRelative("first").stringValue == name)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isKeyword)
|
||||
{
|
||||
property = property.FindPropertyRelative("second");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryFindProperty(SerializedObject material, string name, SerializedType type, out SerializedProperty property, out int index, out SerializedProperty root)
|
||||
{
|
||||
if (type == SerializedType.Keyword)
|
||||
{
|
||||
root = material.FindProperty("m_ValidKeywords");
|
||||
if (TryFindProperty(material, name, type, out property, out index, root))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
root = material.FindProperty("m_InvalidKeywords");
|
||||
if (TryFindProperty(material, name, type, out property, out index, root))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
root = FindBase(material, type);
|
||||
return TryFindProperty(material, name, type, out property, out index, root);
|
||||
}
|
||||
|
||||
static bool RenameFloat(SerializedObject so, Material material, string old, string @new)
|
||||
{
|
||||
if (!TryFindProperty(so, old, SerializedType.Float, out var oldProperty, out var oldIndex, out var parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldValue = oldProperty.floatValue;
|
||||
parent.DeleteArrayElementAtIndex(oldIndex);
|
||||
parent.InsertArrayElementAtIndex(0);
|
||||
var newProperty = parent.GetArrayElementAtIndex(0);
|
||||
newProperty.FindPropertyRelative("first").stringValue = @new;
|
||||
newProperty.FindPropertyRelative("second").floatValue = oldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool RenameKeyword(SerializedObject so, Material material, string old, string @new)
|
||||
{
|
||||
if (!TryFindProperty(so, old, SerializedType.Keyword, out var oldProperty, out var oldIndex, out var parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
parent.DeleteArrayElementAtIndex(oldIndex);
|
||||
parent.InsertArrayElementAtIndex(0);
|
||||
var keyword = parent.GetArrayElementAtIndex(0);
|
||||
keyword.stringValue = @new;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrades
|
||||
static partial class MaterialUpgrader
|
||||
{
|
||||
public const int k_MaterialVersion = 1;
|
||||
|
||||
static bool UpgradeMaterial(Material material, int version)
|
||||
{
|
||||
var so = new SerializedObject(material);
|
||||
var dirty = false;
|
||||
|
||||
// Upgrade materials.
|
||||
// Version is for all materials.
|
||||
if (version < 1)
|
||||
{
|
||||
switch (material.shader.name)
|
||||
{
|
||||
case "Crest/Water":
|
||||
dirty |= RenameKeyword(so, material, "CREST_FLOW_ON", "_CREST_FLOW_LOD");
|
||||
dirty |= RenameFloat(so, material, "CREST_FLOW", "_CREST_FLOW_LOD");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(material);
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c96ae7831f600451a8b0c5326b10ac54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,6 +2,7 @@
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using WaveHarmonic.Crest.Editor.Settings;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
@@ -29,6 +30,21 @@ namespace WaveHarmonic.Crest.Editor
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
internal bool GetProjectSettingToggle() => PropertyName switch
|
||||
{
|
||||
nameof(WaterRenderer._AbsorptionLod) => ProjectSettings.Instance.CurrentPlatformSettings.AbsorptionSimulation,
|
||||
nameof(WaterRenderer._AlbedoLod) => ProjectSettings.Instance.CurrentPlatformSettings.AlbedoSimulation,
|
||||
nameof(WaterRenderer._AnimatedWavesLod) => true,
|
||||
nameof(WaterRenderer._ClipLod) => true,
|
||||
nameof(WaterRenderer._DepthLod) => true,
|
||||
nameof(WaterRenderer._DynamicWavesLod) => true,
|
||||
nameof(WaterRenderer._FlowLod) => true,
|
||||
nameof(WaterRenderer._FoamLod) => true,
|
||||
nameof(WaterRenderer._LevelLod) => true,
|
||||
nameof(WaterRenderer._ScatteringLod) => ProjectSettings.Instance.CurrentPlatformSettings.ScatteringSimulation,
|
||||
nameof(WaterRenderer._ShadowLod) => ProjectSettings.Instance.CurrentPlatformSettings.ShadowSimulation,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
// Optional. Not all simulations will have a corresponding keyword.
|
||||
internal bool HasMaterialToggle => !string.IsNullOrEmpty(MaterialProperty);
|
||||
@@ -36,7 +52,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Needed as clip surface material toggle is Alpha Clipping.
|
||||
internal virtual string MaterialProperty => _MaterialProperty;
|
||||
internal virtual string MaterialPropertyPath => $"{PropertyLabel} > Enabled";
|
||||
internal virtual string MaterialKeyword => $"{MaterialProperty}_ON";
|
||||
internal virtual string MaterialKeyword => MaterialProperty;
|
||||
|
||||
internal static OptionalLod Get(System.Type type)
|
||||
{
|
||||
@@ -94,7 +110,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
PropertyLabel = "Flow",
|
||||
PropertyName = nameof(WaterRenderer._FlowLod),
|
||||
_MaterialProperty = "CREST_FLOW",
|
||||
_MaterialProperty = "_CREST_FLOW_LOD",
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -110,9 +126,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
PropertyLabel = "Water Level",
|
||||
PropertyName = nameof(WaterRenderer._LevelLod),
|
||||
_MaterialProperty = "_Crest_LevelEnabled",
|
||||
Dependency = typeof(AnimatedWavesLod),
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -153,7 +167,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// BIRP SG has prefixes for Unity properties but other RPs do not. These prefixes
|
||||
// are for serialisation only and are not used in the shader.
|
||||
internal override string MaterialPropertyPath => "Alpha Clipping";
|
||||
internal override string MaterialProperty => (RenderPipelineHelper.IsLegacy ? "_BUILTIN" : "") + "_AlphaClip";
|
||||
internal override string MaterialProperty => RenderPipelineHelper.RenderPipeline switch
|
||||
{
|
||||
RenderPipeline.Legacy => "_BUILTIN_AlphaClip",
|
||||
RenderPipeline.HighDefinition => "_AlphaCutoffEnable",
|
||||
RenderPipeline.Universal => "_AlphaClip",
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
internal override string MaterialKeyword => (RenderPipelineHelper.IsLegacy ? "_BUILTIN" : "") + "_ALPHATEST_ON";
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
public override void OnPreviewGUI(Rect rect, GUIStyle background)
|
||||
{
|
||||
var texture = Lod.DataTexture;
|
||||
var texture = (RenderTexture)OriginalTexture;
|
||||
var descriptor = texture.descriptor;
|
||||
_TemporaryTexture = RenderTexture.GetTemporary(descriptor);
|
||||
_TemporaryTexture.name = "Crest Preview (Temporary)";
|
||||
_TemporaryTexture.name = texture.name;
|
||||
Graphics.CopyTexture(texture, _TemporaryTexture);
|
||||
|
||||
if (VisualizeNegatives)
|
||||
@@ -80,15 +80,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
if (ForceAlpha)
|
||||
{
|
||||
// Set alpha to one otherwise it shows nothing when set to RGB.
|
||||
var clear = WaterResources.Instance.Compute._Clear;
|
||||
if (clear != null)
|
||||
if (WaterResources.Instance.Compute._Clear != null)
|
||||
{
|
||||
clear.SetTexture(0, ShaderIDs.s_Target, _TemporaryTexture);
|
||||
clear.SetVector(ShaderIDs.s_ClearMask, Color.black);
|
||||
clear.SetVector(ShaderIDs.s_ClearColor, Color.black);
|
||||
clear.Dispatch
|
||||
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
|
||||
var wrapper = new PropertyWrapperComputeStandalone(compute._Shader, compute._KernelClearTarget);
|
||||
compute.SetVariantForFormat(wrapper, _TemporaryTexture.graphicsFormat);
|
||||
wrapper.SetTexture(ShaderIDs.s_Target, _TemporaryTexture);
|
||||
wrapper.SetVector(ShaderIDs.s_ClearMask, Color.black);
|
||||
wrapper.SetVector(ShaderIDs.s_ClearColor, Color.black);
|
||||
wrapper.Dispatch
|
||||
(
|
||||
0,
|
||||
Lod.Resolution / Lod.k_ThreadGroupSizeX,
|
||||
Lod.Resolution / Lod.k_ThreadGroupSizeY,
|
||||
Lod.Slices
|
||||
@@ -290,7 +291,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
public override GUIContent GetPreviewTitle() => new("Water Reflections");
|
||||
protected override Texture OriginalTexture => (target as WaterRenderer)._Reflections._Enabled
|
||||
? (target as WaterRenderer)._Reflections.ReflectionTexture
|
||||
? (target as WaterRenderer)._Reflections.ColorTexture
|
||||
: s_DefaultReflection?.GetValue(null) as Texture;
|
||||
}
|
||||
|
||||
#if CREST_DEBUG
|
||||
[CustomPreview(typeof(WaterRenderer))]
|
||||
sealed class ReflectionDepthPreview : TexturePreview
|
||||
{
|
||||
public override GUIContent GetPreviewTitle() => new("Water Reflections (Depth)");
|
||||
protected override Texture OriginalTexture => (target as WaterRenderer)._Reflections.DepthTexture;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,23 +1,196 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor.Settings
|
||||
{
|
||||
[FilePath(k_Path, FilePathAttribute.Location.ProjectFolder)]
|
||||
sealed class ProjectSettings : ScriptableSingleton<ProjectSettings>
|
||||
[System.Flags]
|
||||
enum SamplingMethod
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
Nothing = 0,
|
||||
|
||||
[InspectorName("Multi-Scale")]
|
||||
MultiScale = 1 << 0,
|
||||
|
||||
Stochastic = 1 << 1,
|
||||
|
||||
Everything = ~0,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
sealed partial class PlatformSettings
|
||||
{
|
||||
const string k_OverrideTooltip = "Override the feature for this platform";
|
||||
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
internal bool _Default;
|
||||
|
||||
|
||||
[@Heading("Simulations", alwaysVisible: true)]
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideAlbedoSimulation;
|
||||
|
||||
[@Label("Albedo")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _AlbedoSimulation = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideAbsorptionSimulation;
|
||||
|
||||
[@Label("Absorption")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _AbsorptionSimulation = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideScatteringSimulation;
|
||||
|
||||
[@Label("Scattering")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _ScatteringSimulation = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideShadowSimulation;
|
||||
|
||||
[@Label("Shadow")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _ShadowSimulation = true;
|
||||
|
||||
|
||||
[@Heading("Water Material", alwaysVisible: true)]
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideOutScattering;
|
||||
|
||||
[@Label("Out-Scattering")]
|
||||
[Tooltip("Disables out-scattering for the surface and volume.\n\nOut-scattering darkens the water with depth.")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _OutScattering = true;
|
||||
|
||||
|
||||
[@Heading("Surface Material", alwaysVisible: true)]
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideNormalMaps;
|
||||
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _NormalMaps = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverridePlanarReflections;
|
||||
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _PlanarReflections = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverridePlanarReflectionsApplySmoothness;
|
||||
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _PlanarReflectionsApplySmoothness = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[@SerializeField]
|
||||
internal bool _OverrideFoamSampling;
|
||||
|
||||
[Tooltip("Which sampling method to use for foam.\n\nThese additional sampling techniques can be used together. Be wary that this will increase texture samples significantly.\n\nNothing: Uses the sampler set on the foam texture.\n\nMulti-Scale: Scales the foam texture by LOD to make foam pattern more visible at distances and reduces repetitive patterns. Doubles the foam texture samples.\n\nStochastic: reduces repetitive patterns. Triples the foam texture samples.")]
|
||||
[@DecoratedField]
|
||||
[@SerializeField]
|
||||
internal SamplingMethod _FoamSampling = SamplingMethod.MultiScale;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideFoamBioluminescence;
|
||||
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _FoamBioluminescence = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideCausticsForceDistortion;
|
||||
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _CausticsForceDistortion = true;
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideAdditionalLights;
|
||||
|
||||
[Tooltip("Whether to calculate scattering from additional lights.")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _AdditionalLights = true;
|
||||
|
||||
|
||||
[@Heading("Rendering", alwaysVisible: true)]
|
||||
|
||||
[Tooltip(k_OverrideTooltip)]
|
||||
[@Hide(nameof(_Default))]
|
||||
[@InlineToggle]
|
||||
[SerializeField]
|
||||
internal bool _OverrideSimpleTransparency;
|
||||
|
||||
[Tooltip("Refraction like transparency without requiring the Opaque or Depth Texture.\n\nRequires a populated Water Depth Simulation to render correctly. See the Main sample for a working scene.")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal bool _SimpleTransparency;
|
||||
|
||||
PlatformSettings Default => ProjectSettings.Instance._PlatformSettings;
|
||||
}
|
||||
|
||||
[FilePath(k_Path, FilePathAttribute.Location.ProjectFolder)]
|
||||
sealed partial class ProjectSettings : ScriptableSingleton<ProjectSettings>
|
||||
{
|
||||
#pragma warning disable IDE0032 // Use auto property
|
||||
|
||||
[@Heading("Variant Stripping", Heading.Style.Settings)]
|
||||
@@ -27,7 +200,7 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DebugEnableStrippingLogging;
|
||||
|
||||
[@Predicated(nameof(_DebugEnableStrippingLogging))]
|
||||
[@Enable(nameof(_DebugEnableStrippingLogging))]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DebugOnlyLogRemainingVariants;
|
||||
|
||||
@@ -47,10 +220,90 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _RenderAtmosphericScatteringWhenUnderWater;
|
||||
|
||||
[Tooltip("Renders the underwater effect after transparency and uses the more expensive mask.\n\nYou may need this if rendering the underwater to multiple cameras. The other benefit is that transparent objects will be fogged (albeit incorrectly).\n\nThe downsides are that there can be artifacts if waves are very choppy, has a less impressive meniscus, and generally more expensive to execute.")]
|
||||
[Tooltip("Renders the underwater effect after transparency and uses the more expensive mask.\n\nOne benefit is that transparent objects will be fogged (albeit incorrectly).\n\nThe downsides are that there can be artifacts if waves are very choppy, has a less impressive meniscus, and generally more expensive to execute.")]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _LegacyUnderwater;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[@PlatformTabs]
|
||||
[SerializeField]
|
||||
internal int _Platforms;
|
||||
|
||||
[@Label("Overriden Settings for Windows, Mac and Linux")]
|
||||
[@Show(nameof(_Platforms), (int)BuildTargetGroup.Standalone)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsDesktop = new();
|
||||
|
||||
[@Label("Overriden Settings for Dedicated Server")]
|
||||
[@Show(nameof(_Platforms), -2)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsServer = new();
|
||||
|
||||
[@Label("Overriden Settings for Android")]
|
||||
[@Show(nameof(_Platforms), (int)BuildTargetGroup.Android)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsAndroid = new();
|
||||
|
||||
[@Label("Overriden Settings for iOS")]
|
||||
[@Show(nameof(_Platforms), (int)BuildTargetGroup.iOS)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsIOS = new();
|
||||
|
||||
[@Label("Overriden Settings for tvOS")]
|
||||
[@Show(nameof(_Platforms), (int)BuildTargetGroup.tvOS)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsTVOS = new();
|
||||
|
||||
[@Label("Overriden Settings for visionOS")]
|
||||
[@Show(nameof(_Platforms), (int)BuildTargetGroup.VisionOS)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsVisionOS = new();
|
||||
|
||||
// Web has hard limitations on number of sampled textures. Set defaults with that
|
||||
// in mind so the surface renders.
|
||||
[@Label("Overriden Settings for Web")]
|
||||
[@Show(nameof(_Platforms), (int)BuildTargetGroup.WebGL)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettingsWeb = new()
|
||||
{
|
||||
_OverrideAbsorptionSimulation = true,
|
||||
_AbsorptionSimulation = false,
|
||||
_OverrideAlbedoSimulation = true,
|
||||
_AlbedoSimulation = false,
|
||||
_OverrideScatteringSimulation = true,
|
||||
_ScatteringSimulation = false,
|
||||
_OverrideShadowSimulation = true,
|
||||
_ShadowSimulation = false,
|
||||
|
||||
_OverrideCausticsForceDistortion = true,
|
||||
_CausticsForceDistortion = false,
|
||||
_OverridePlanarReflections = true,
|
||||
_PlanarReflections = false,
|
||||
_OverrideFoamBioluminescence = true,
|
||||
_FoamBioluminescence = false,
|
||||
};
|
||||
|
||||
// This will show if nothing else shows.
|
||||
[@Label("Default Settings")]
|
||||
[@Hide(nameof(_Platforms), (int)BuildTargetGroup.Standalone)]
|
||||
[@Hide(nameof(_Platforms), Reflected.BuildTargetGroup.k_Server)]
|
||||
[@Hide(nameof(_Platforms), (int)BuildTargetGroup.Android)]
|
||||
[@Hide(nameof(_Platforms), (int)BuildTargetGroup.iOS)]
|
||||
[@Hide(nameof(_Platforms), (int)BuildTargetGroup.WebGL)]
|
||||
[@Hide(nameof(_Platforms), (int)BuildTargetGroup.tvOS)]
|
||||
[@Hide(nameof(_Platforms), (int)BuildTargetGroup.VisionOS)]
|
||||
[@Stripped(Stripped.Style.PlatformTab, indent: true)]
|
||||
[SerializeField]
|
||||
internal PlatformSettings _PlatformSettings = new() { _Default = true };
|
||||
|
||||
#pragma warning restore IDE0032 // Use auto property
|
||||
|
||||
internal const string k_Path = "ProjectSettings/Packages/com.waveharmonic.crest/Settings.asset";
|
||||
@@ -72,11 +325,38 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
internal bool RenderAtmosphericScatteringWhenUnderWater => _RenderAtmosphericScatteringWhenUnderWater;
|
||||
internal bool LegacyUnderwater => _LegacyUnderwater;
|
||||
|
||||
internal PlatformSettings CurrentPlatformSettings =>
|
||||
#if PLATFORM_STANDALONE
|
||||
_PlatformSettingsDesktop;
|
||||
#elif PLATFORM_SERVER
|
||||
_PlatformSettingsServer;
|
||||
#elif PLATFORM_ANDROID
|
||||
_PlatformSettingsAndroid;
|
||||
#elif PLATFORM_IOS
|
||||
_PlatformSettingsIOS;
|
||||
#elif PLATFORM_TVOS
|
||||
_PlatformSettingsTVOS;
|
||||
#elif PLATFORM_VISIONOS
|
||||
_PlatformSettingsVisionOS;
|
||||
#else
|
||||
_PlatformSettings;
|
||||
#endif
|
||||
|
||||
internal bool _IsPlatformTabChange;
|
||||
readonly Dictionary<NamedBuildTarget, PlatformSettings> _PlatformSettingsMap = new();
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// Fixes not being editable.
|
||||
hideFlags = HideFlags.HideAndDontSave & ~HideFlags.NotEditable;
|
||||
|
||||
_PlatformSettingsMap.Clear();
|
||||
_PlatformSettingsMap.Add(NamedBuildTarget.Standalone, _PlatformSettingsDesktop);
|
||||
_PlatformSettingsMap.Add(NamedBuildTarget.Server, _PlatformSettingsServer);
|
||||
_PlatformSettingsMap.Add(NamedBuildTarget.Android, _PlatformSettingsAndroid);
|
||||
_PlatformSettingsMap.Add(NamedBuildTarget.iOS, _PlatformSettingsIOS);
|
||||
_PlatformSettingsMap.Add(NamedBuildTarget.tvOS, _PlatformSettingsTVOS);
|
||||
_PlatformSettingsMap.Add(NamedBuildTarget.VisionOS, _PlatformSettingsVisionOS);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +368,14 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
[@OnChange(skipIfInactive: false)]
|
||||
void OnChange(string path, object previous)
|
||||
{
|
||||
_IsPlatformTabChange = path == nameof(_Platforms);
|
||||
|
||||
if (path.StartsWithNoAlloc("_PlatformSettings"))
|
||||
{
|
||||
UpdateSymbols();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (path)
|
||||
{
|
||||
case nameof(_FullPrecisionDisplacementOnHalfPrecisionPlatforms):
|
||||
@@ -100,7 +388,15 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
|
||||
void UpdateScriptingSymbols()
|
||||
{
|
||||
ScriptingSymbols.Set(ProjectSymbols.k_LegacyUnderwaterScriptingSymbol, _LegacyUnderwater);
|
||||
foreach (var build in _PlatformSettingsMap.Keys)
|
||||
{
|
||||
ScriptingSymbols.s_OverrideCurrentNamedBuildTarget = true;
|
||||
ScriptingSymbols.s_CurrentNamedBuildTargetOverride = build;
|
||||
ScriptingSymbols.Set(ProjectSymbols.k_LegacyUnderwaterScriptingSymbol, _LegacyUnderwater);
|
||||
ScriptingSymbols.Set(ProjectSymbols.k_SimpleTransparencyScriptingSymbol, _PlatformSettingsMap[build].SimpleTransparency);
|
||||
ScriptingSymbols.Set(ProjectSymbols.k_PlanarReflectionApplySmoothnessScriptingSymbol, !_PlatformSettingsMap[build].PlanarReflectionsApplySmoothness);
|
||||
ScriptingSymbols.s_OverrideCurrentNamedBuildTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSymbols()
|
||||
@@ -111,7 +407,10 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
|
||||
sealed class ProjectSymbols : AssetModificationProcessor
|
||||
{
|
||||
// The default value should not produce a symbol.
|
||||
public const string k_LegacyUnderwaterScriptingSymbol = "d_Crest_LegacyUnderwater";
|
||||
public const string k_SimpleTransparencyScriptingSymbol = "d_Crest_SimpleTransparency";
|
||||
public const string k_PlanarReflectionApplySmoothnessScriptingSymbol = "d_Crest_DisablePlanarReflectionApplySmoothness";
|
||||
|
||||
static FileSystemWatcher s_Watcher;
|
||||
|
||||
@@ -142,6 +441,11 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (Instance != null && Instance._IsPlatformTabChange)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy instance to reflect changes.
|
||||
Helpers.Destroy(Instance);
|
||||
typeof(ScriptableSingleton<ProjectSettings>)
|
||||
@@ -220,31 +524,17 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset foldout values.
|
||||
DecoratedDrawer.s_IsFoldout = false;
|
||||
DecoratedDrawer.s_IsFoldoutOpen = false;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// Pad similar to settings header.
|
||||
var style = new GUIStyle();
|
||||
style.padding.left = 8;
|
||||
EditorGUILayout.BeginVertical(style);
|
||||
|
||||
// Same label with as other settings.
|
||||
EditorGUIUtility.labelWidth = 251;
|
||||
|
||||
EditorGUILayout.BeginVertical(style);
|
||||
_Editor.OnInspectorGUI();
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// Commit all changes. Normally settings are written when user hits save or exits
|
||||
// without any undo/redo entry and dirty state. No idea how to do the same.
|
||||
// SaveChanges and hasUnsavedChanges on custom editor did not work.
|
||||
// Not sure if hooking into EditorSceneManager.sceneSaving is correct.
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ProjectSettings.Save();
|
||||
}
|
||||
_Editor.OnInspectorGUI();
|
||||
|
||||
GUILayout.Space(10 * 2);
|
||||
|
||||
@@ -256,6 +546,8 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
AssetDatabase.ImportAsset(path);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
@@ -272,4 +564,43 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(ProjectSettings))]
|
||||
sealed class ProjectSettingsEditor : Inspector
|
||||
{
|
||||
protected override void OnChange()
|
||||
{
|
||||
base.OnChange();
|
||||
|
||||
// Commit all changes. Normally settings are written when user hits save or exits
|
||||
// without any undo/redo entry and dirty state. No idea how to do the same.
|
||||
// SaveChanges and hasUnsavedChanges on custom editor did not work.
|
||||
// Not sure if hooking into EditorSceneManager.sceneSaving is correct.
|
||||
ProjectSettings.Save();
|
||||
}
|
||||
}
|
||||
|
||||
partial class ProjectSettings : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
int _Version = 0;
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
internal int _MaterialVersion = MaterialUpgrader.k_MaterialVersion;
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
if (_Version == 0)
|
||||
{
|
||||
_MaterialVersion = 0;
|
||||
}
|
||||
|
||||
_Version = 1;
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,37 +14,37 @@
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.waveharmonic.crest.cpu-queries",
|
||||
"expression": "(,1.0.8)",
|
||||
"expression": "(,1.0.9)",
|
||||
"define": "d_UpdateCPUQueries"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.paint",
|
||||
"expression": "(,1.2.3)",
|
||||
"expression": "(,1.3.3)",
|
||||
"define": "d_UpdatePaint"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.portals",
|
||||
"expression": "(,1.2.8)",
|
||||
"expression": "(,1.3.1)",
|
||||
"define": "d_UpdatePortals"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.shallow-water",
|
||||
"expression": "(,1.3.3)",
|
||||
"expression": "(,1.4.2)",
|
||||
"define": "d_UpdateShallowWater"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.shifting-origin",
|
||||
"expression": "(,1.3.0)",
|
||||
"expression": "(,1.3.1)",
|
||||
"define": "d_UpdateShiftingOrigin"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.splines",
|
||||
"expression": "(,1.4.4)",
|
||||
"expression": "(,1.6.0)",
|
||||
"define": "d_UpdateSplines"
|
||||
},
|
||||
{
|
||||
"name": "com.waveharmonic.crest.whirlpool",
|
||||
"expression": "(,1.0.3)",
|
||||
"expression": "(,1.1.1)",
|
||||
"define": "d_UpdateWhirlpool"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,6 +10,9 @@ using WaveHarmonic.Crest.Editor.Settings;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
[System.AttributeUsage(System.AttributeTargets.Class)]
|
||||
sealed class GenerateShaderSettings : System.Attribute { }
|
||||
|
||||
static class ShaderSettingsGenerator
|
||||
{
|
||||
[DidReloadScripts]
|
||||
@@ -51,7 +54,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
sealed class AssetPostProcessor : AssetPostprocessor
|
||||
{
|
||||
const string k_SettingsPath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings.Crest.hlsl";
|
||||
const string k_SettingsPath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/";
|
||||
|
||||
static async void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] movedTo, string[] movedFrom, bool domainReload)
|
||||
{
|
||||
@@ -61,7 +64,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
if (EditorApplication.isCompiling)
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
if (imported.Contains(k_SettingsPath))
|
||||
if (imported.Count(x => x.StartsWithNoAlloc(k_SettingsPath)) > 0)
|
||||
{
|
||||
UnityEngine.Debug.Log($"Crest: Settings.Crest.hlsl changed during compilation!");
|
||||
}
|
||||
@@ -72,7 +75,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
if (EditorApplication.isUpdating)
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
if (imported.Contains(k_SettingsPath))
|
||||
if (imported.Count(x => x.StartsWithNoAlloc(k_SettingsPath)) > 0)
|
||||
{
|
||||
UnityEngine.Debug.Log($"Crest: Settings.Crest.hlsl changed during asset database update!");
|
||||
}
|
||||
@@ -81,7 +84,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
// Regenerate if file changed like re-importing.
|
||||
if (imported.Contains(k_SettingsPath))
|
||||
if (imported.Count(x => x.StartsWithNoAlloc(k_SettingsPath)) > 0)
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
UnityEngine.Debug.Log($"Crest: Settings.Crest.hlsl changed!");
|
||||
@@ -94,7 +97,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings.Crest")]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest")]
|
||||
sealed class ShaderSettings
|
||||
{
|
||||
// These two are here for compute shaders.
|
||||
@@ -126,10 +129,111 @@ namespace WaveHarmonic.Crest.Editor
|
||||
#endif
|
||||
;
|
||||
|
||||
// Active when build target is activated:
|
||||
// https://docs.unity3d.com/6000.3/Documentation/Manual/scripting-symbol-reference.html
|
||||
|
||||
public static int s_CrestPlatformStandalone =
|
||||
#if PLATFORM_STANDALONE
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestPlatformServer =
|
||||
#if PLATFORM_SERVER
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestPlatformAndroid =
|
||||
#if PLATFORM_ANDROID
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestPlatformIOS =
|
||||
#if PLATFORM_IOS
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestPlatformWeb =
|
||||
#if PLATFORM_WEBGL
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestPlatformTVOS =
|
||||
#if PLATFORM_TVOS
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestPlatformVISIONOS =
|
||||
#if PLATFORM_VISIONOS
|
||||
1 +
|
||||
#endif
|
||||
0;
|
||||
|
||||
public static int s_CrestFullPrecisionDisplacement = ProjectSettings.Instance.FullPrecisionDisplacementOnHalfPrecisionPlatforms ? 1 : 0;
|
||||
|
||||
public static int s_CrestDiscardAtmosphericScattering = ProjectSettings.Instance.RenderAtmosphericScatteringWhenUnderWater ? 0 : 1;
|
||||
|
||||
public static int s_CrestLegacyUnderwater = ProjectSettings.Instance.LegacyUnderwater ? 1 : 0;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.Default")]
|
||||
sealed partial class ShaderSettingsDefault
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettings;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.Standalone")]
|
||||
sealed partial class ShaderSettingsStandalone
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsDesktop;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.Server")]
|
||||
sealed partial class ShaderSettingsServer
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsServer;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.Android")]
|
||||
sealed partial class ShaderSettingsAndroid
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsAndroid;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.iOS")]
|
||||
sealed partial class ShaderSettingsIOS
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsIOS;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.Web")]
|
||||
sealed partial class ShaderSettingsWeb
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsWeb;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.tvOS")]
|
||||
sealed partial class ShaderSettingsTVOS
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsTVOS;
|
||||
}
|
||||
|
||||
[@GenerateShaderSettings]
|
||||
[GenerateHLSL(sourcePath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Settings.Crest.visionOS")]
|
||||
sealed partial class ShaderSettingsVisionOS
|
||||
{
|
||||
static PlatformSettings Settings => ProjectSettings.Instance._PlatformSettingsVisionOS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace WaveHarmonic.Crest.Attributes
|
||||
/// <summary>
|
||||
/// Override this method to customise the label.
|
||||
/// </summary>
|
||||
internal virtual GUIContent BuildLabel(GUIContent label) => label;
|
||||
internal virtual GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer) => label;
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to make your own IMGUI based GUI for the property.
|
||||
@@ -55,7 +55,7 @@ namespace WaveHarmonic.Crest.Attributes
|
||||
/// <summary>
|
||||
/// Override this method to customise the label.
|
||||
/// </summary>
|
||||
internal virtual GUIContent BuildLabel(GUIContent label) => label;
|
||||
internal virtual GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer) => label;
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to additively change the appearance of a decorated field.
|
||||
@@ -83,6 +83,37 @@ namespace WaveHarmonic.Crest.Attributes
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class CustomField : DecoratedProperty
|
||||
{
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
if (drawer._Inspector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
drawer._Inspector.OnCustomField(position, property, label, drawer);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CustomLabel : Decorator
|
||||
{
|
||||
public override bool AlwaysVisible => false;
|
||||
|
||||
internal override GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
label = base.BuildLabel(property, label, drawer);
|
||||
if (drawer._Inspector == null) return label;
|
||||
label = drawer._Inspector.OnCustomLabel(property, label, drawer);
|
||||
return label;
|
||||
}
|
||||
|
||||
internal override void Decorate(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the property using EditorGUI.PropertyField.
|
||||
/// </summary>
|
||||
@@ -142,11 +173,48 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class Order : Decorator
|
||||
{
|
||||
public override bool AlwaysVisible => true;
|
||||
|
||||
public readonly string _Target;
|
||||
public readonly Placement _Placement;
|
||||
|
||||
public enum Placement
|
||||
{
|
||||
Heading,
|
||||
Below,
|
||||
Above,
|
||||
}
|
||||
|
||||
public Order(string target, Placement placement = Placement.Heading)
|
||||
{
|
||||
_Target = target;
|
||||
_Placement = placement;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders foldout without the foldout.
|
||||
/// </summary>
|
||||
sealed class Stripped : DecoratedProperty
|
||||
{
|
||||
public enum Style
|
||||
{
|
||||
None,
|
||||
PlatformTab,
|
||||
}
|
||||
|
||||
readonly bool _KeepIndent;
|
||||
readonly Style _Style;
|
||||
|
||||
public Stripped(Style style = Style.None, bool indent = false)
|
||||
{
|
||||
_KeepIndent = indent;
|
||||
_Style = style;
|
||||
}
|
||||
|
||||
internal override bool NeedsControlRectangle(SerializedProperty property) => false;
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
@@ -155,11 +223,33 @@ namespace WaveHarmonic.Crest
|
||||
DecoratedDrawer.s_TemporaryColor = true;
|
||||
DecoratedDrawer.s_PreviousColor = GUI.color;
|
||||
|
||||
if (_Style == Style.PlatformTab)
|
||||
{
|
||||
EditorGUI.indentLevel += 1;
|
||||
EditorGUILayout.LabelField(label);
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
|
||||
GUI.color = new(0, 0, 0, 0);
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
if (!_KeepIndent) EditorGUI.indentLevel -= 1;
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
EditorGUI.indentLevel += 1;
|
||||
if (!_KeepIndent) EditorGUI.indentLevel += 1;
|
||||
|
||||
if (_Style == Style.PlatformTab)
|
||||
{
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.EndBuildTargetSelectionGrouping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PlatformTabs : DecoratedProperty
|
||||
{
|
||||
static readonly GUIContent s_DefaultTab = new(EditorGUIUtility.IconContent("d_Settings").image, "Default");
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
property.intValue = Editor.Reflected.EditorGUILayout.BeginBuildTargetSelectionGrouping(s_DefaultTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +460,7 @@ namespace WaveHarmonic.Crest
|
||||
property.floatValue = Mathf.Max(_Minimum, property.floatValue);
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
property.floatValue = Mathf.Max((int)_Minimum, property.intValue);
|
||||
property.intValue = Mathf.Max((int)_Minimum, property.intValue);
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
var vector2Value = property.vector2Value;
|
||||
@@ -621,29 +711,15 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
sealed class InlineToggle : DecoratedProperty
|
||||
{
|
||||
// Add extra y offset. Needed for foldouts in foldouts so far.
|
||||
readonly bool _Fix;
|
||||
|
||||
public InlineToggle(bool fix = false)
|
||||
{
|
||||
_Fix = fix;
|
||||
}
|
||||
|
||||
internal override bool NeedsControlRectangle(SerializedProperty property)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
var r = position;
|
||||
r.x -= 16f;
|
||||
// Align with Space offset.
|
||||
if (drawer.Space > 0) r.y += drawer.Space + 2f;
|
||||
if (_Fix) r.y += EditorGUIUtility.singleLineHeight + 2f;
|
||||
// Seems to be needed.
|
||||
|
||||
// Prevent click events blocking next property.
|
||||
r.width = 16f * (1f + EditorGUI.indentLevel);
|
||||
r.height = EditorGUIUtility.singleLineHeight;
|
||||
|
||||
// Hide text.
|
||||
label.text = "";
|
||||
|
||||
using (new EditorGUI.PropertyScope(r, label, property))
|
||||
@@ -654,6 +730,9 @@ namespace WaveHarmonic.Crest
|
||||
property.boolValue = EditorGUI.Toggle(r, property.boolValue);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
// Pull up next property. Extra space might be margin/padding.
|
||||
GUILayout.Space(-(EditorGUIUtility.singleLineHeight + 2f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,9 +938,9 @@ namespace WaveHarmonic.Crest
|
||||
_Label = label;
|
||||
}
|
||||
|
||||
internal override GUIContent BuildLabel(GUIContent label)
|
||||
internal override GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
label = base.BuildLabel(label);
|
||||
label = base.BuildLabel(property, label, drawer);
|
||||
label.text = _Label;
|
||||
return label;
|
||||
}
|
||||
@@ -877,7 +956,7 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
sealed class Heading : Decorator
|
||||
{
|
||||
readonly GUIContent _Text;
|
||||
public readonly GUIContent _Text;
|
||||
readonly string _HelpLink;
|
||||
readonly Style _Style;
|
||||
readonly bool _AlwaysVisible;
|
||||
|
||||
@@ -23,6 +23,12 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
if (drawer._Inspector != null && !drawer._Inspector._EmbeddedEditors.Contains(_Editor))
|
||||
{
|
||||
drawer._Inspector._EmbeddedEditors.Add(_Editor);
|
||||
Inspector.s_EmbeddedEditors.Add(_Editor);
|
||||
}
|
||||
|
||||
_Editor.DrawEditorCombo(this, label, drawer, property, "asset");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Predicated : Decorator
|
||||
abstract class Predicated : Decorator
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
@@ -24,6 +24,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
readonly bool _Inverted;
|
||||
readonly bool _Hide;
|
||||
readonly bool _Implicit;
|
||||
|
||||
readonly string _PropertyName;
|
||||
readonly object _DisableValue;
|
||||
@@ -38,16 +39,16 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
/// <param name="type">The type to call the method on. Must be either a static type or the type the field is defined on.</param>
|
||||
/// <param name="member">Member name. Method must match signature: bool MethodName(Component component). Can be any visibility and static or instance.</param>
|
||||
/// <param name="disableValue"></param>
|
||||
/// <param name="value">Disable/Hide if matches this value.</param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(Type type, string member, object disableValue, bool inverted = false, bool hide = false)
|
||||
public Predicated(Type type, string member, object value, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Member;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_Type = type;
|
||||
_DisableValue = disableValue;
|
||||
_DisableValue = value;
|
||||
_Member = _Type.GetMember(member, Helpers.s_AnyMethod)[0];
|
||||
}
|
||||
|
||||
@@ -71,21 +72,30 @@ namespace WaveHarmonic.Crest
|
||||
_Type = type;
|
||||
}
|
||||
|
||||
public Predicated(string property, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Property;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_PropertyName = property;
|
||||
_Implicit = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The field with this attribute will be drawn enabled/disabled based on another field. For example can be used
|
||||
/// to disable a field if a toggle is false.
|
||||
/// </summary>
|
||||
/// <param name="property">The name of the other property whose value dictates whether this field is enabled or not.</param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="disableValue">If the field has this value, disable the GUI (or enable if inverted is true).</param>
|
||||
/// <param name="value">If the field has this value, disable the GUI (or enable if inverted is true).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(string property, bool inverted = false, object disableValue = null, bool hide = false)
|
||||
public Predicated(string property, object value, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Property;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_PropertyName = property;
|
||||
_DisableValue = disableValue;
|
||||
_DisableValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,18 +116,24 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
public bool GUIEnabled(SerializedProperty property)
|
||||
{
|
||||
if (_Implicit && property.propertyType is not SerializedPropertyType.Boolean and not SerializedPropertyType.ObjectReference and not SerializedPropertyType.ManagedReference)
|
||||
{
|
||||
Debug.Log($"Crest.Predicated: Implicit predicated on {property.name} cannot be used for {property.propertyType}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _Inverted != property.propertyType switch
|
||||
{
|
||||
// Enable GUI if int value of field is not equal to 0, or whatever the disable-value is set to
|
||||
SerializedPropertyType.Integer => property.intValue != ((int?)_DisableValue ?? 0),
|
||||
// Enable GUI if disable-value is 0 and field is true, or disable-value is not 0 and field is false
|
||||
SerializedPropertyType.Boolean => property.boolValue ^ (((int?)_DisableValue ?? 0) != 0),
|
||||
SerializedPropertyType.Boolean => _Implicit ? !property.boolValue : (bool)_DisableValue == property.boolValue,
|
||||
SerializedPropertyType.Float => property.floatValue != ((float?)_DisableValue ?? 0),
|
||||
SerializedPropertyType.String => property.stringValue != ((string)_DisableValue ?? ""),
|
||||
// Must pass nameof enum entry as we are comparing names because index != value.
|
||||
SerializedPropertyType.Enum => property.enumNames[property.enumValueIndex] != ((string)_DisableValue ?? ""),
|
||||
SerializedPropertyType.ObjectReference => property.objectReferenceValue != null,
|
||||
SerializedPropertyType.ManagedReference => property.managedReferenceValue != null,
|
||||
SerializedPropertyType.ObjectReference => _Implicit ? property.objectReferenceValue == null : property.objectReferenceValue != null,
|
||||
SerializedPropertyType.ManagedReference => _Implicit ? property.managedReferenceValue == null : property.managedReferenceValue != null,
|
||||
_ => throw new ArgumentException($"Crest.Predicated: property type on <i>{property.serializedObject.targetObject}</i> not implemented yet: {property.propertyType}."),
|
||||
};
|
||||
}
|
||||
@@ -201,12 +217,12 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
|
||||
var enabledByTypeCheck = _Type.IsAssignableFrom(type);
|
||||
if (_Inverted) enabledByTypeCheck = !enabledByTypeCheck;
|
||||
if (!_Inverted) enabledByTypeCheck = !enabledByTypeCheck;
|
||||
enabled = enabledByTypeCheck && enabled;
|
||||
}
|
||||
else if (_Mode == Mode.RenderPipeline)
|
||||
{
|
||||
enabled = RenderPipelineHelper.RenderPipeline == _RenderPipeline != _Inverted;
|
||||
enabled = RenderPipelineHelper.RenderPipeline == _RenderPipeline == _Inverted;
|
||||
}
|
||||
|
||||
// Keep current disabled state.
|
||||
@@ -216,4 +232,228 @@ namespace WaveHarmonic.Crest
|
||||
DecoratedDrawer.s_HideInInspector = DecoratedDrawer.s_HideInInspector || (_Hide && !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Show : Predicated
|
||||
{
|
||||
const bool k_Hide = true;
|
||||
const bool k_Inverted = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Show(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Show(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show field if owning class is of type.
|
||||
/// </summary>
|
||||
public Show(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Show(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show if field matches value.
|
||||
/// </summary>
|
||||
public Show(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Show(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Hide : Predicated
|
||||
{
|
||||
const bool k_Hide = true;
|
||||
const bool k_Inverted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Hide this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Hide(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Hide(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide field if owning class is of type.
|
||||
/// </summary>
|
||||
public Hide(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Hide(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide if field matches value.
|
||||
/// </summary>
|
||||
public Hide(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Hide(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Enable : Predicated
|
||||
{
|
||||
const bool k_Hide = false;
|
||||
const bool k_Inverted = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Enable(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Enable(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable field if owning class is of type.
|
||||
/// </summary>
|
||||
public Enable(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Enable(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable if field matches value.
|
||||
/// </summary>
|
||||
public Enable(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Enable(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Disable : Predicated
|
||||
{
|
||||
const bool k_Hide = false;
|
||||
const bool k_Inverted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Disable this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Disable(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Disable(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable field if owning class is of type.
|
||||
/// </summary>
|
||||
public Disable(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Disable(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable if field matches value.
|
||||
/// </summary>
|
||||
public Disable(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Disable(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
@@ -23,19 +24,29 @@ namespace WaveHarmonic.Crest.Editor
|
||||
internal static bool s_HideInInspector = false;
|
||||
public static bool s_IsFoldout = false;
|
||||
public static bool s_IsFoldoutOpen = false;
|
||||
public static bool s_IsList = false;
|
||||
|
||||
public static bool s_TemporaryColor;
|
||||
public static Color s_PreviousColor;
|
||||
|
||||
// If instantiating ourselves. Avoids reflection.
|
||||
public PropertyAttribute _Attribute;
|
||||
public FieldInfo _Field;
|
||||
PropertyAttribute Attribute => _Attribute ?? attribute;
|
||||
FieldInfo Field => _Field ?? fieldInfo;
|
||||
|
||||
public float Space { get; private set; }
|
||||
|
||||
internal UnityEditor.Editor _Editor;
|
||||
internal Inspector _Inspector;
|
||||
|
||||
List<object> _Decorators = null;
|
||||
List<object> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
// Populate list with decorators.
|
||||
_Decorators ??= fieldInfo
|
||||
_Decorators ??= Field
|
||||
.GetCustomAttributes(typeof(Decorator), false)
|
||||
.ToList();
|
||||
|
||||
@@ -44,7 +55,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
List<Attributes.Validator> _Validators = null;
|
||||
List<Attributes.Validator> Validators => _Validators ??= fieldInfo
|
||||
List<Attributes.Validator> Validators => _Validators ??= Field
|
||||
.GetCustomAttributes(typeof(Attributes.Validator), false)
|
||||
.Cast<Attributes.Validator>()
|
||||
.ToList();
|
||||
@@ -63,14 +74,55 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var height = base.GetPropertyHeight(property, label);
|
||||
|
||||
if (property.isArray)
|
||||
{
|
||||
// Constructor caches value and this call retrieves it.
|
||||
var list = ReorderableList.GetReorderableListFromSerializedProperty(property);
|
||||
list ??= new ReorderableList(property.serializedObject, property);
|
||||
// GetHeight does not include bottom buttons height.
|
||||
height = property.isExpanded ? list.GetHeight() + list.footerHeight : height;
|
||||
}
|
||||
|
||||
// Make original control rectangle be invisible because we always create our own. Zero still adds a little
|
||||
// height which becomes noticeable once multiple properties are hidden. This could be some GUI style
|
||||
// property but could not find which one.
|
||||
return -2f;
|
||||
return s_IsList ? height : -2f;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Get the owning editor.
|
||||
if (_Editor == null)
|
||||
{
|
||||
foreach (var editor in ActiveEditorTracker.sharedTracker?.activeEditors)
|
||||
{
|
||||
if (editor.serializedObject == property.serializedObject)
|
||||
{
|
||||
_Editor = editor;
|
||||
_Inspector = editor as Inspector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check all embedded editors.
|
||||
foreach (var editor in Inspector.s_EmbeddedEditors)
|
||||
{
|
||||
if (editor._Editor == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (editor._Editor.serializedObject == property.serializedObject)
|
||||
{
|
||||
_Editor = editor._Editor;
|
||||
_Inspector = editor._Editor as Inspector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the original GUI state so it can be reset later.
|
||||
var originalColor = GUI.color;
|
||||
var originalEnabled = GUI.enabled;
|
||||
@@ -93,7 +145,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute is Space space) Space = space._Height;
|
||||
label = attribute.BuildLabel(label);
|
||||
label = attribute.BuildLabel(property, label, this);
|
||||
}
|
||||
|
||||
if (!s_HideInInspector && (!s_IsFoldout || s_IsFoldoutOpen))
|
||||
@@ -106,8 +158,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
attribute.Decorate(position, property, label, this);
|
||||
}
|
||||
|
||||
var a = (DecoratedProperty)attribute;
|
||||
position = a.NeedsControlRectangle(property)
|
||||
var a = (DecoratedProperty)Attribute;
|
||||
position = a.NeedsControlRectangle(property) && (!s_IsList || property.isArray)
|
||||
? EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(property, label, true))
|
||||
: position;
|
||||
|
||||
@@ -117,14 +169,18 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
label = attribute.BuildLabel(label);
|
||||
label = attribute.BuildLabel(property, label, this);
|
||||
}
|
||||
|
||||
var skipChange = fieldInfo.FieldType == typeof(UnityEvent);
|
||||
var skipChange = Field.FieldType == typeof(UnityEvent) || property.isArray;
|
||||
|
||||
var isExpanded = property.isExpanded;
|
||||
var isUndoRedo = false;
|
||||
|
||||
if (!skipChange)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
isUndoRedo = _Inspector != null && _Inspector._UndoRedo;
|
||||
}
|
||||
|
||||
var oldValue = skipChange ? null : property.boxedValue;
|
||||
@@ -135,13 +191,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
Validators[index].Validate(position, property, label, this, oldValue);
|
||||
}
|
||||
|
||||
// Guard against foldouts triggering change check due to changing isExpanded.
|
||||
if (!skipChange && EditorGUI.EndChangeCheck() && oldValue != property.boxedValue)
|
||||
if (!skipChange && (EditorGUI.EndChangeCheck() || isUndoRedo))
|
||||
{
|
||||
// Apply any changes.
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
if (!isUndoRedo)
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
OnChange(property, oldValue);
|
||||
// Guard against foldouts triggering change check due to changing isExpanded.
|
||||
if (property.isExpanded == isExpanded)
|
||||
{
|
||||
OnChange(property, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
@@ -206,6 +268,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var relativePath = string.Join(".", chunks[(i + 1)..]);
|
||||
|
||||
var nestedTarget = nestedProperty.managedReferenceValue;
|
||||
if (nestedTarget == null) continue;
|
||||
var nestedTargetType = nestedTarget.GetType();
|
||||
|
||||
foreach (var (method, attribute) in OnChangeHandlers)
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
/// </summary>
|
||||
interface IEmbeddableEditor
|
||||
{
|
||||
void SetHostComponent(object host);
|
||||
void SetTypeOfHostComponent(System.Type hostType);
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
public void OnDisable()
|
||||
{
|
||||
DestroyEditor();
|
||||
Helpers.Destroy(_DefaultTarget);
|
||||
Helpers.Destroy(_CreatedInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,11 +63,12 @@ namespace WaveHarmonic.Crest.Editor
|
||||
/// </summary>
|
||||
public GUIContent _CreateButtonGUIContent;
|
||||
|
||||
UnityEditor.Editor _Editor = null;
|
||||
internal UnityEditor.Editor _Editor = null;
|
||||
|
||||
System.Type _Type;
|
||||
|
||||
Object _DefaultTarget;
|
||||
Object _CreatedInstance;
|
||||
FieldInfo _DefaultTargetField;
|
||||
|
||||
const int k_IndentOffset = 3;
|
||||
@@ -214,7 +216,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
if (_DefaultTarget == null)
|
||||
{
|
||||
_DefaultTarget = ScriptableObject.CreateInstance(_Type);
|
||||
_CreatedInstance = ScriptableObject.CreateInstance(_Type);
|
||||
_DefaultTarget = _CreatedInstance;
|
||||
_DefaultTarget.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
}
|
||||
@@ -253,9 +256,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
_Editor = UnityEditor.Editor.CreateEditor(target);
|
||||
|
||||
// Pass through argument for editors that receive it
|
||||
if (property.serializedObject.targetObject != null)
|
||||
if (property.serializedObject.targetObject != null && _Editor is IEmbeddableEditor editor)
|
||||
{
|
||||
(_Editor as IEmbeddableEditor)?.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType());
|
||||
editor.SetHostComponent(property.serializedObject.targetObject);
|
||||
editor.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType());
|
||||
}
|
||||
|
||||
_OnCreateEditor?.Invoke(_Editor);
|
||||
|
||||
@@ -294,7 +294,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
width = Mathf.Max(width, minimumWidth);
|
||||
// TODO: Add option to disable this (consistent width).
|
||||
if (!hasDropDown && minimumWidth > 0) width += k_ButtonDropDownWidth;
|
||||
if (centerLabel && hasDropDown) style.padding.left += k_ButtonDropDownWidth;
|
||||
|
||||
// TODO: eyeballed based on Fix button but likely specific to it.
|
||||
if (centerLabel && hasDropDown)
|
||||
{
|
||||
style.padding.left += k_ButtonDropDownWidth / 2;
|
||||
width += k_ButtonDropDownWidth / 3;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(label, style, expandWidth ? GUILayout.ExpandWidth(true) : GUILayout.Width(width)))
|
||||
{
|
||||
|
||||
@@ -18,13 +18,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
[CustomEditor(typeof(EditorBehaviour), editorForChildClasses: true)]
|
||||
partial class Inspector : UnityEditor.Editor
|
||||
{
|
||||
public const int k_FieldGroupOrder = 1000;
|
||||
public const int k_FieldGroupOrder = k_FieldHeadingOrder * 100;
|
||||
public const int k_FieldHeadingOrder = k_FieldOrder * 100;
|
||||
public const int k_FieldOrder = 1000;
|
||||
const string k_NoPrefabModeSupportWarning = "Prefab mode is not supported. Changes made in prefab mode will not be reflected in the scene view. Save this prefab to see changes.";
|
||||
|
||||
internal static Inspector Current { get; private set; }
|
||||
|
||||
readonly Dictionary<FieldInfo, object> _MaterialOwners = new();
|
||||
readonly Dictionary<Material, MaterialEditor> _MaterialEditors = new();
|
||||
readonly Dictionary<string, DecoratedDrawer> _Lists = new();
|
||||
|
||||
internal readonly List<EmbeddedAssetEditor> _EmbeddedEditors = new();
|
||||
internal static readonly List<EmbeddedAssetEditor> s_EmbeddedEditors = new();
|
||||
|
||||
public override bool RequiresConstantRepaint() => TexturePreview.s_ActiveInstance?.Open == true;
|
||||
|
||||
@@ -32,12 +38,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
.GetFieldsWithAttribute<AttachMaterialEditor>()
|
||||
.OrderBy(x => x.GetCustomAttribute<AttachMaterialEditor>().Order);
|
||||
|
||||
readonly List<string> _Headings = new();
|
||||
readonly Utility.SortedList<int, SerializedProperty> _Properties = new(Helpers.DuplicateComparison);
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_MaterialOwners.Clear();
|
||||
|
||||
Undo.undoRedoPerformed -= OnUndoRedo;
|
||||
Undo.undoRedoPerformed += OnUndoRedo;
|
||||
|
||||
foreach (var field in s_AttachMaterialEditors)
|
||||
{
|
||||
var target = (object)this.target;
|
||||
@@ -60,7 +70,20 @@ namespace WaveHarmonic.Crest.Editor
|
||||
GUI.enabled = (target.hideFlags & HideFlags.NotEditable) == 0;
|
||||
|
||||
RenderBeforeInspectorGUI();
|
||||
RenderInspectorGUI();
|
||||
|
||||
try
|
||||
{
|
||||
RenderInspectorGUI();
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
// Happens when using New with embedded editor and not change the filename.
|
||||
if (exception.Message != "Stack empty.")
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
RenderValidationMessages();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(target is Behaviour component && !component.isActiveAndEnabled);
|
||||
@@ -74,10 +97,23 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= OnUndoRedo;
|
||||
|
||||
foreach (var (_, editor) in _MaterialEditors)
|
||||
{
|
||||
Helpers.Destroy(editor);
|
||||
}
|
||||
|
||||
foreach (var embedded in _EmbeddedEditors)
|
||||
{
|
||||
embedded?.OnDisable();
|
||||
s_EmbeddedEditors.Remove(embedded);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnChange()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void RenderBeforeInspectorGUI()
|
||||
@@ -95,10 +131,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var previous = Current;
|
||||
Current = this;
|
||||
|
||||
_Headings.Clear();
|
||||
_Properties.Clear();
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
using var iterator = serializedObject.GetIterator();
|
||||
if (iterator.NextVisible(true))
|
||||
{
|
||||
@@ -112,7 +151,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
if (iterator.name == "m_Script")
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
_Properties.Add(index++, property);
|
||||
_Properties.Add(index++ * k_FieldOrder, property);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
@@ -129,7 +168,44 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Null checking but there should always be one DecoratedProperty.
|
||||
var order = field.GetCustomAttribute<Attributes.DecoratedProperty>()?.order ?? 0;
|
||||
group = field.GetCustomAttribute<Group>()?.order * k_FieldGroupOrder ?? group;
|
||||
_Properties.Add(order + group + index++, property);
|
||||
|
||||
var headingAttribute = field.GetCustomAttribute<Heading>();
|
||||
if (headingAttribute != null)
|
||||
{
|
||||
_Headings.Add(headingAttribute._Text.text);
|
||||
}
|
||||
|
||||
var heading = Mathf.Max(0, _Headings.Count - 1);
|
||||
|
||||
var orderAttribute = field.GetCustomAttribute<Order>();
|
||||
|
||||
if (orderAttribute != null)
|
||||
{
|
||||
var target = orderAttribute._Target;
|
||||
|
||||
if (orderAttribute._Placement == Order.Placement.Heading)
|
||||
{
|
||||
heading = _Headings.FindIndex(x => x == target);
|
||||
}
|
||||
}
|
||||
|
||||
var key = order + group + heading * k_FieldHeadingOrder + index * k_FieldOrder;
|
||||
|
||||
// Override.
|
||||
if (orderAttribute != null)
|
||||
{
|
||||
var target = orderAttribute._Target;
|
||||
|
||||
switch (orderAttribute._Placement)
|
||||
{
|
||||
case Order.Placement.Below: key = _Properties.First(x => x.Value.name == target).Key + index; break;
|
||||
case Order.Placement.Above: key = _Properties.First(x => x.Value.name == target).Key - index; break;
|
||||
}
|
||||
}
|
||||
|
||||
_Properties.Add(key, property);
|
||||
|
||||
index += 1;
|
||||
}
|
||||
while (iterator.NextVisible(false));
|
||||
}
|
||||
@@ -140,6 +216,33 @@ namespace WaveHarmonic.Crest.Editor
|
||||
using (new EditorGUI.DisabledGroupScope(property.name == "m_Script"))
|
||||
#endif
|
||||
{
|
||||
// Handle lists as PropertyDrawer is not called on the list itself.
|
||||
if (property.isArray)
|
||||
{
|
||||
var field = property.GetFieldInfo(out var _);
|
||||
var attribute = field?.GetCustomAttribute<Attributes.DecoratedProperty>();
|
||||
|
||||
if (field != null && attribute != null)
|
||||
{
|
||||
var id = GetPropertyIdentifier(property);
|
||||
|
||||
if (!_Lists.ContainsKey(id))
|
||||
{
|
||||
_Lists[id] = new DecoratedDrawer()
|
||||
{
|
||||
_Attribute = attribute,
|
||||
_Field = field,
|
||||
};
|
||||
}
|
||||
|
||||
DecoratedDrawer.s_IsList = true;
|
||||
var label = new GUIContent(property.displayName);
|
||||
_Lists[id].OnGUI(Rect.zero, property, label);
|
||||
DecoratedDrawer.s_IsList = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only support top level ordering for now.
|
||||
EditorGUILayout.PropertyField(property, includeChildren: true);
|
||||
}
|
||||
@@ -148,11 +251,18 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Need to call just in case there is no decorated property.
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
|
||||
// Restore previous in case this is a nested editor.
|
||||
Current = previous;
|
||||
|
||||
// Fixes indented validation etc.
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
_UndoRedo = false;
|
||||
}
|
||||
|
||||
protected virtual void RenderBottomButtons()
|
||||
@@ -212,6 +322,55 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void OnCustomField(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal virtual GUIContent OnCustomLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
// Reflection
|
||||
partial class Inspector
|
||||
{
|
||||
static readonly PropertyInfo s_GUIViewCurrent = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("current", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
static readonly PropertyInfo s_GUIViewNativeHandle = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("nativeHandle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
// Adapted from:
|
||||
// https://github.com/Unity-Technologies/UnityCsReference/blob/59b03b8a0f179c0b7e038178c90b6c80b340aa9f/Editor/Mono/Inspector/ReorderableListWrapper.cs#L77-L88
|
||||
static string GetPropertyIdentifier(SerializedProperty serializedProperty)
|
||||
{
|
||||
// Property may be disposed.
|
||||
try
|
||||
{
|
||||
var handle = -1;
|
||||
var current = s_GUIViewCurrent.GetValue(null);
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
handle = ((IntPtr)s_GUIViewNativeHandle.GetValue(current)).ToInt32();
|
||||
}
|
||||
|
||||
return serializedProperty?.propertyPath + serializedProperty.serializedObject.targetObject.GetEntityId() + handle;
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class Inspector
|
||||
{
|
||||
internal bool _UndoRedo;
|
||||
void OnUndoRedo()
|
||||
{
|
||||
_UndoRedo = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from:
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor.Build;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor.Reflected
|
||||
{
|
||||
static class BuildTargetGroup
|
||||
{
|
||||
public const int k_Server = -2;
|
||||
}
|
||||
|
||||
static class BuildPlatform
|
||||
{
|
||||
internal static readonly Type s_BuildPlatformType = Type.GetType("UnityEditor.Build.BuildPlatform,UnityEditor.CoreModule");
|
||||
internal static readonly Type s_BuildPlatformArrayType = s_BuildPlatformType.MakeArrayType();
|
||||
|
||||
static readonly FieldInfo s_NamedBuildTargetField = s_BuildPlatformType.GetField
|
||||
(
|
||||
"namedBuildTarget",
|
||||
BindingFlags.Instance | BindingFlags.Public
|
||||
);
|
||||
|
||||
public static NamedBuildTarget GetNamedBuildTarget(object platform)
|
||||
{
|
||||
return (NamedBuildTarget)s_NamedBuildTargetField.GetValue(platform);
|
||||
}
|
||||
}
|
||||
|
||||
static class BuildPlatforms
|
||||
{
|
||||
static readonly Type s_BuildPlatformsType = Type.GetType("UnityEditor.Build.BuildPlatforms,UnityEditor.CoreModule");
|
||||
static readonly PropertyInfo s_BuildPlatformsInstanceProperty = s_BuildPlatformsType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public);
|
||||
static readonly MethodInfo s_GetValidPlatformsMethod = s_BuildPlatformsType.GetMethod("GetValidPlatforms", new Type[] { });
|
||||
static Array s_Platforms; // Should be safe to cache.
|
||||
|
||||
public static Array GetValidPlatforms()
|
||||
{
|
||||
if (s_Platforms == null)
|
||||
{
|
||||
var instance = s_BuildPlatformsInstanceProperty.GetValue(null);
|
||||
|
||||
// We cannot just cast to the type we want it seems.
|
||||
var enumerable = ((IEnumerable<object>)s_GetValidPlatformsMethod.Invoke(instance, null)).ToList();
|
||||
|
||||
s_Platforms = Array.CreateInstance(BuildPlatform.s_BuildPlatformType, enumerable.Count);
|
||||
|
||||
for (var i = 0; i < enumerable.Count; i++)
|
||||
{
|
||||
s_Platforms.SetValue(enumerable[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
return s_Platforms;
|
||||
}
|
||||
}
|
||||
|
||||
static class EditorGUILayout
|
||||
{
|
||||
static readonly MethodInfo s_BeginPlatformGroupingMethod = typeof(UnityEditor.EditorGUILayout).GetMethod
|
||||
(
|
||||
"BeginPlatformGrouping",
|
||||
BindingFlags.Static | BindingFlags.NonPublic,
|
||||
null,
|
||||
new Type[]
|
||||
{
|
||||
BuildPlatform.s_BuildPlatformArrayType,
|
||||
typeof(GUIContent),
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
static readonly object[] s_Parameters = new object[2];
|
||||
|
||||
public static int BeginBuildTargetSelectionGrouping(GUIContent defaultTab)
|
||||
{
|
||||
var platforms = BuildPlatforms.GetValidPlatforms();
|
||||
|
||||
s_Parameters[0] = platforms;
|
||||
s_Parameters[1] = defaultTab;
|
||||
|
||||
var index = (int)s_BeginPlatformGroupingMethod.Invoke(null, s_Parameters);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
// Default
|
||||
return (int)UnityEditor.BuildTargetGroup.Unknown;
|
||||
}
|
||||
|
||||
var target = BuildPlatform.GetNamedBuildTarget(platforms.GetValue(index));
|
||||
|
||||
if (target == NamedBuildTarget.Server)
|
||||
{
|
||||
// Server
|
||||
return BuildTargetGroup.k_Server;
|
||||
}
|
||||
|
||||
return (int)target.ToBuildTargetGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd362a01f52cc4002857e9f549c641be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -10,10 +10,19 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
{
|
||||
static class ScriptingSymbols
|
||||
{
|
||||
static NamedBuildTarget CurrentNamedBuildTarget
|
||||
// Too lazy to add separate calls just to provide a NamedBuildTarget.
|
||||
internal static bool s_OverrideCurrentNamedBuildTarget;
|
||||
internal static NamedBuildTarget s_CurrentNamedBuildTargetOverride;
|
||||
|
||||
internal static NamedBuildTarget CurrentNamedBuildTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_OverrideCurrentNamedBuildTarget)
|
||||
{
|
||||
return s_CurrentNamedBuildTargetOverride;
|
||||
}
|
||||
|
||||
#if UNITY_SERVER
|
||||
return NamedBuildTarget.Server;
|
||||
#else
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
static Dictionary<string, string> s_Tooltips;
|
||||
static readonly GUIContent s_Label = new();
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, Dictionary<string, string> tooltips)
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, Dictionary<string, string> tooltips, List<string> hiddenCategories)
|
||||
{
|
||||
s_Tooltips = tooltips;
|
||||
Material m = materialEditor.target as Material;
|
||||
@@ -37,7 +37,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
if (metadata != null)
|
||||
DrawShaderGraphGUI(materialEditor, properties, metadata.categoryDatas);
|
||||
DrawShaderGraphGUI(materialEditor, properties, metadata.categoryDatas, hiddenCategories);
|
||||
else
|
||||
PropertiesDefaultGUI(materialEditor, properties);
|
||||
}
|
||||
@@ -76,10 +76,15 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, IEnumerable<MinimalCategoryData> categoryDatas)
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, IEnumerable<MinimalCategoryData> categoryDatas, List<string> hiddenCategories)
|
||||
{
|
||||
foreach (MinimalCategoryData mcd in categoryDatas)
|
||||
{
|
||||
if (hiddenCategories.Count > 0 && hiddenCategories.Contains(mcd.categoryName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DrawCategory(materialEditor, properties, mcd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
abstract class TexturePreview : ObjectPreview
|
||||
{
|
||||
static readonly System.Reflection.MethodInfo s_DrawPreview = typeof(ObjectPreview).GetMethod("DrawPreview", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
||||
static readonly object[] s_DrawPreviewArguments = new object[3];
|
||||
static readonly Object[] s_DrawPreviewTargets = new Object[1];
|
||||
|
||||
public static TexturePreview s_ActiveInstance;
|
||||
public bool Open { get; private set; }
|
||||
|
||||
@@ -101,6 +105,9 @@ namespace WaveHarmonic.Crest.Editor
|
||||
Helpers.SafeCreateRenderTexture(ref _RenderTexture, descriptor);
|
||||
_RenderTexture.Create();
|
||||
Object.DestroyImmediate(_Editor);
|
||||
// Raises both, but no way to avoid it:
|
||||
// | The targets array should not be used inside OnSceneGUI or OnPreviewGUI. Use the single target property instead.
|
||||
// | The serializedObject should not be used inside OnSceneGUI or OnPreviewGUI. Use the target property directly instead.
|
||||
_Editor = UnityEditor.Editor.CreateEditor(_RenderTexture);
|
||||
// Reset for incompatible copy.
|
||||
descriptor = _OriginalDescriptor;
|
||||
@@ -121,7 +128,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
Graphics.CopyTexture(Texture, _RenderTexture);
|
||||
}
|
||||
|
||||
_Editor.DrawPreview(rect);
|
||||
s_DrawPreviewTargets[0] = _Editor.target;
|
||||
s_DrawPreviewArguments[0] = _Editor;
|
||||
s_DrawPreviewArguments[1] = rect;
|
||||
s_DrawPreviewArguments[2] = s_DrawPreviewTargets;
|
||||
|
||||
// Use to be _Editor.DrawPreview(rect) but spammed errors with multiple selected.
|
||||
s_DrawPreview?.Invoke(null, s_DrawPreviewArguments);
|
||||
}
|
||||
|
||||
#if CREST_DEBUG
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
@@ -54,9 +55,9 @@ namespace WaveHarmonic.Crest.Editor
|
||||
new(),
|
||||
};
|
||||
|
||||
public delegate void ShowMessage(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null);
|
||||
public delegate void ShowMessage(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null, Object caller = null);
|
||||
|
||||
public static void DebugLog(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
public static void DebugLog(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null, Object caller = null)
|
||||
{
|
||||
// Never log info validation to console.
|
||||
if (type == MessageType.Info)
|
||||
@@ -64,22 +65,27 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return;
|
||||
}
|
||||
|
||||
message = $"Crest Validation: {message} {fixDescription} Click this message to highlight the problem object.";
|
||||
// Always link back to the caller so developers know the origin. They can always
|
||||
// use the help box "Inspect" once there to get to the object to fix. Even better,
|
||||
// they can use any available fix buttons too.
|
||||
var context = caller != null ? caller : @object;
|
||||
|
||||
message = $"<b>Crest Validation:</b> {message} {fixDescription} Click this message to highlight the problem object.";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MessageType.Error: Debug.LogError(message, @object); break;
|
||||
case MessageType.Warning: Debug.LogWarning(message, @object); break;
|
||||
default: Debug.Log(message, @object); break;
|
||||
case MessageType.Error: Debug.LogError(message, context); break;
|
||||
case MessageType.Warning: Debug.LogWarning(message, context); break;
|
||||
default: Debug.Log(message, context); break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HelpBox(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
public static void HelpBox(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null, Object caller = null)
|
||||
{
|
||||
s_Messages[(int)type].Add(new() { _Message = message, _FixDescription = fixDescription, _Object = @object, _Action = action, _PropertyPath = property });
|
||||
}
|
||||
|
||||
public static void Suppressed(string _0, string _1, MessageType _2, Object _3 = null, FixValidation _4 = null, string _5 = null)
|
||||
public static void Suppressed(string _0, string _1, MessageType _2, Object _3 = null, FixValidation _4 = null, string _5 = null, Object _6 = null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -291,6 +297,24 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Nested components do not descend from Object, but they could and this
|
||||
// would work for them.
|
||||
if (target is Object @object)
|
||||
{
|
||||
foreach (var field in TypeCache.GetFieldsWithAttribute<Validated>())
|
||||
{
|
||||
if (field.DeclaringType != type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var attribute in field.GetCustomAttributes<Validated>())
|
||||
{
|
||||
isValid &= attribute.Validate(@object, field, messenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
@@ -299,4 +323,89 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return ExecuteValidators(target, DebugLog);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Validated : System.Attribute
|
||||
{
|
||||
public abstract bool Validate(Object target, FieldInfo property, ValidatedHelper.ShowMessage messenger);
|
||||
}
|
||||
}
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that field is not null.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
|
||||
sealed class Required : Validated
|
||||
{
|
||||
public override bool Validate(Object target, FieldInfo field, ValidatedHelper.ShowMessage messenger)
|
||||
{
|
||||
var isValid = true;
|
||||
|
||||
if ((Object)field.GetValue(target) == null)
|
||||
{
|
||||
var typeName = EditorHelpers.Pretty(target.GetType().Name);
|
||||
var fieldName = EditorHelpers.Pretty(field.Name);
|
||||
|
||||
messenger
|
||||
(
|
||||
$"<i>{fieldName}</i> is required for the <i>{typeName}</i> component to function.",
|
||||
$"Please set <i>{fieldName}</i>.",
|
||||
ValidatedHelper.MessageType.Error,
|
||||
target
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a info message if field is null.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
|
||||
sealed class Optional : Validated
|
||||
{
|
||||
readonly string _Message;
|
||||
|
||||
public Optional(string message)
|
||||
{
|
||||
_Message = message;
|
||||
}
|
||||
|
||||
public override bool Validate(Object target, FieldInfo field, ValidatedHelper.ShowMessage messenger)
|
||||
{
|
||||
var value = field.GetValue(target);
|
||||
|
||||
if (value is ICollection<Object> list)
|
||||
{
|
||||
if (list != null && list.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value is Object @object && @object != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var typeName = EditorHelpers.Pretty(target.GetType().Name);
|
||||
var fieldName = EditorHelpers.Pretty(field.Name);
|
||||
|
||||
messenger
|
||||
(
|
||||
$"<i>{fieldName}</i> is not set for the <i>{typeName}</i> component. " + _Message,
|
||||
string.Empty,
|
||||
ValidatedHelper.MessageType.Info,
|
||||
target
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
|
||||
@@ -20,6 +21,9 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
// 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");
|
||||
@@ -253,16 +257,15 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return isValid;
|
||||
}
|
||||
|
||||
#if !d_Crest_LegacyUnderwater
|
||||
if (target.AllCameras)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
"<i>All Cameras</i> requires <i>Legacy Underwater</i> to be enabled.",
|
||||
"Either disable <i>All Cameras</i> or enable <i>Project Settings > Crest > Legacy Underwater</i>.",
|
||||
MessageType.Warning, water
|
||||
);
|
||||
}
|
||||
#if d_Crest_SimpleTransparency
|
||||
messenger
|
||||
(
|
||||
"Currently the underwater effect will force enable both the opaque and depth texture which negates most of the benefit of simple transparency.",
|
||||
"Disable underwater.",
|
||||
MessageType.Info, water,
|
||||
(x, y) => y.boolValue = false,
|
||||
nameof(WaterRenderer._Underwater) + "." + nameof(UnderwaterRenderer._Enabled)
|
||||
);
|
||||
#endif
|
||||
|
||||
if (target.Material != null)
|
||||
@@ -448,7 +451,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
ValidateMaterialParent(target.Surface.VolumeMaterial, target.Surface.Material, messenger);
|
||||
}
|
||||
|
||||
if (Object.FindObjectsByType<WaterRenderer>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).Length > 1)
|
||||
if (Helpers.FindObjectsByType<WaterRenderer>().Length > 1)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
@@ -554,7 +557,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
foreach (var simulation in target.Simulations)
|
||||
{
|
||||
ValidateSimulationAndMaterial(OptionalLod.Get(simulation.GetType()), messenger, water);
|
||||
ValidateSimulationAndMaterial(OptionalLod.Get(simulation.GetType()), messenger, water, target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,7 +571,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
);
|
||||
}
|
||||
|
||||
if (target.Viewer == null && !target.IsRunningWithoutGraphics)
|
||||
if (target.Viewer == null && !target.IsRunningWithoutGraphics && !target.MultipleViewpoints)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
@@ -692,7 +695,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
var water = Water;
|
||||
|
||||
if (Object.FindObjectsByType<WaterRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None).Length == 0)
|
||||
if (Helpers.FindObjectsByType<WaterRenderer>(FindObjectsInactive.Include).Length == 0)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
@@ -727,8 +730,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
if (target.Clipped && water != null)
|
||||
{
|
||||
// Validate main material, then overriden material.
|
||||
ValidateLod(OptionalLod.Get(typeof(ClipLod)), messenger, water);
|
||||
ValidateLod(OptionalLod.Get(typeof(ClipLod)), messenger, water, material: target._Material);
|
||||
ValidateLod(OptionalLod.Get(typeof(ClipLod)), messenger, water, material: target._Material, context: target);
|
||||
|
||||
if (water.ClipLod.DefaultClippingState == DefaultClippingState.NothingClipped)
|
||||
{
|
||||
@@ -738,7 +740,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
$"The {nameof(WaterBody.Clipped)} option will have no effect.",
|
||||
$"Disable {nameof(WaterBody.Clipped)} or set {nameof(ClipLod.DefaultClippingState)} to {DefaultClippingState.NothingClipped}.",
|
||||
MessageType.Warning,
|
||||
water
|
||||
water,
|
||||
caller: target
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -759,6 +762,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return isValid;
|
||||
}
|
||||
|
||||
isValid &= ValidateProjectSettings(target, water, messenger, context);
|
||||
|
||||
var simulation = target.GetLod(water);
|
||||
|
||||
var dependentClause = ".";
|
||||
@@ -787,7 +792,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
simulation._Enabled = false;
|
||||
}
|
||||
},
|
||||
$"{target.PropertyName}.{nameof(Lod._Enabled)}"
|
||||
$"{target.PropertyName}.{nameof(Lod._Enabled)}",
|
||||
context
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
@@ -802,20 +808,20 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
if (material.HasProperty(target.MaterialProperty) && material.GetFloat(target.MaterialProperty) != 1f)
|
||||
{
|
||||
ShowMaterialValidationMessage(target, material, messenger);
|
||||
ShowMaterialValidationMessage(target, material, messenger, context);
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.Dependency != null)
|
||||
{
|
||||
ValidateLod(OptionalLod.Get(target.Dependency), messenger, water, dependent);
|
||||
ValidateLod(OptionalLod.Get(target.Dependency), messenger, water, dependent, context: context);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
static bool ValidateSignedDistanceFieldsLod(ShowMessage messenger, WaterRenderer water, string feature)
|
||||
static bool ValidateSignedDistanceFieldsLod(ShowMessage messenger, WaterRenderer water, string feature, Object context)
|
||||
{
|
||||
var isValid = true;
|
||||
|
||||
@@ -827,7 +833,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
"Enable <i>Signed Distance Fields</i>",
|
||||
MessageType.Error, water,
|
||||
(_, y) => y.boolValue = true,
|
||||
$"{nameof(WaterRenderer._DepthLod)}.{nameof(DepthLod._EnableSignedDistanceFields)}"
|
||||
$"{nameof(WaterRenderer._DepthLod)}.{nameof(DepthLod._EnableSignedDistanceFields)}",
|
||||
caller: context
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
@@ -836,7 +843,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return isValid;
|
||||
}
|
||||
|
||||
static void ShowMaterialValidationMessage(OptionalLod target, Material material, ShowMessage messenger)
|
||||
static void ShowMaterialValidationMessage(OptionalLod target, Material material, ShowMessage messenger, Object caller)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
@@ -847,13 +854,43 @@ namespace WaveHarmonic.Crest.Editor
|
||||
);
|
||||
}
|
||||
|
||||
static bool ValidateSimulationAndMaterial(OptionalLod target, ShowMessage messenger, WaterRenderer water)
|
||||
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;
|
||||
@@ -875,7 +912,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
if (feature._Enabled)
|
||||
{
|
||||
ShowMaterialValidationMessage(target, water.Surface.Material, messenger);
|
||||
ShowMaterialValidationMessage(target, water.Surface.Material, messenger, caller);
|
||||
}
|
||||
else if (messenger != DebugLog)
|
||||
{
|
||||
@@ -897,13 +934,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
var water = Object.FindAnyObjectByType<WaterRenderer>(FindObjectsInactive.Include);
|
||||
|
||||
if (!target.OverrideGlobalWindSpeed && water != null && water.WindSpeedKPH < WaterRenderer.k_MaximumWindSpeedKPH)
|
||||
var windSpeedSource = target.GetWindSpeedSource();
|
||||
|
||||
if (windSpeedSource != ShapeWaves.WindSpeedSource.None)
|
||||
{
|
||||
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
|
||||
WaveSpectrumEditor.GetWindSpeedText(target, windSpeedSource),
|
||||
WaveSpectrumEditor.GetWindSpeedFixText(windSpeedSource),
|
||||
MessageType.Info,
|
||||
caller: target
|
||||
);
|
||||
}
|
||||
|
||||
@@ -921,12 +961,29 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
if (Water != null)
|
||||
{
|
||||
isValid &= ValidateLod(OptionalLod.Get(typeof(AnimatedWavesLod)), messenger, Water);
|
||||
isValid &= ValidateLod(OptionalLod.Get(typeof(AnimatedWavesLod)), messenger, Water, context: target);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
[Validator(typeof(ShapeFFT))]
|
||||
static bool Validate(ShapeFFT target, ShowMessage messenger)
|
||||
{
|
||||
if (target.Spectrum != null && target.Spectrum._ShowAdvancedControls && !target.ApplyAdvancedSpectrumControls)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
$"{nameof(ShapeFFT.ApplyAdvancedSpectrumControls).Pretty().Italic()} needs to be enabled for anything exposed by {nameof(target.Spectrum._ShowAdvancedControls).Pretty().Italic()} to apply.",
|
||||
string.Empty,
|
||||
MessageType.Info,
|
||||
caller: target
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Validator(typeof(SphereWaterInteraction))]
|
||||
static bool Validate(SphereWaterInteraction target, ShowMessage messenger)
|
||||
{
|
||||
@@ -935,7 +992,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Validate require water feature.
|
||||
if (Water != null)
|
||||
{
|
||||
isValid &= ValidateLod(OptionalLod.Get(typeof(DynamicWavesLod)), messenger, Water);
|
||||
isValid &= ValidateLod(OptionalLod.Get(typeof(DynamicWavesLod)), messenger, Water, context: target);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
@@ -949,19 +1006,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Validate require water feature.
|
||||
if (Water != null)
|
||||
{
|
||||
isValid &= !target.UsesClip || ValidateLod(OptionalLod.Get(typeof(ClipLod)), messenger, Water);
|
||||
isValid &= !target.UsesDisplacement || ValidateLod(OptionalLod.Get(typeof(AnimatedWavesLod)), messenger, Water);
|
||||
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 FixSetCollisionSourceToCompute(SerializedObject _, SerializedProperty property)
|
||||
internal static void FixSetQuerySourceToCompute(SerializedObject _, SerializedProperty property)
|
||||
{
|
||||
if (Water != null)
|
||||
{
|
||||
property.enumValueIndex = (int)CollisionSource.GPU;
|
||||
property.enumValueIndex = (int)LodQuerySource.GPU;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,7 +1035,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
isValid &= ValidateCollisionLayer(target.Layer, target, messenger, "layer", target.Layer, required: false);
|
||||
isValid &= ValidateCollisionSource(target, messenger);
|
||||
isValid &= ValidateQuerySource(target, messenger, Water.AnimatedWavesLod, k_NoneQueryProviderCollisionFloatingObjects);
|
||||
isValid &= ValidateQuerySource(target, messenger, Water.FlowLod, k_NoneQueryProviderFlowFloatingObjects);
|
||||
|
||||
return isValid;
|
||||
}
|
||||
@@ -994,7 +1052,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
isValid &= ValidateCollisionLayer(target._Layer, target, messenger, "layer", target._Layer, required: false);
|
||||
isValid &= ValidateCollisionSource(target, messenger);
|
||||
isValid &= ValidateQuerySource(target, messenger, Water.AnimatedWavesLod, k_NoneQueryProviderCollisionFloatingObjects);
|
||||
isValid &= ValidateQuerySource(target, messenger, Water.FlowLod, k_NoneQueryProviderFlowFloatingObjects);
|
||||
|
||||
return isValid;
|
||||
}
|
||||
@@ -1139,12 +1198,46 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Validate that any water feature required for this input is enabled, if any
|
||||
if (Water != null)
|
||||
{
|
||||
isValid &= ValidateLod(OptionalLod.Get(target.GetType()), messenger, Water);
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var water = Water;
|
||||
|
||||
if (water == null)
|
||||
{
|
||||
return isValid;
|
||||
}
|
||||
|
||||
ValidateDisplacementPrecisionIssues(water, target, messenger);
|
||||
ValidateWaterLevelPrecisionIssues(water, target, messenger);
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
[Validator(typeof(DepthProbe))]
|
||||
static bool Validate(DepthProbe target, ShowMessage messenger)
|
||||
{
|
||||
@@ -1223,18 +1316,6 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
#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
|
||||
@@ -1312,7 +1393,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
$"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
|
||||
MessageType.Info, urpRenderer,
|
||||
caller: target
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1326,7 +1408,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
$"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
|
||||
MessageType.Info, urpRenderer,
|
||||
caller: target
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1349,7 +1432,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
"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
|
||||
MessageType.Warning, renderer,
|
||||
caller: target
|
||||
);
|
||||
|
||||
// Reporting only one renderer at a time will be enough to avoid overwhelming user and UI.
|
||||
@@ -1364,11 +1448,11 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Validate require water feature.
|
||||
if (water != null)
|
||||
{
|
||||
isValid = isValid && ValidateLod(OptionalLod.Get(typeof(DepthLod)), messenger, water);
|
||||
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");
|
||||
isValid = isValid && ValidateSignedDistanceFieldsLod(messenger, water, "Generate Signed Distance Field", target);
|
||||
}
|
||||
|
||||
if (water.DepthLod.IncludeTerrainHeight && Object.FindAnyObjectByType<Terrain>(FindObjectsInactive.Include) != null)
|
||||
@@ -1380,7 +1464,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
"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
|
||||
MessageType.Info, water,
|
||||
caller: target
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1397,14 +1482,15 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
if (!target._DistanceFromEdge.IsEmpty())
|
||||
{
|
||||
isValid = isValid && ValidateLod(OptionalLod.Get(typeof(DepthLod)), messenger, water);
|
||||
isValid = isValid && ValidateSignedDistanceFieldsLod(messenger, water, "Distance From Edge");
|
||||
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 &= ValidateCollisionSource(target, messenger);
|
||||
isValid &= ValidateQuerySource(target, messenger, Water.AnimatedWavesLod, k_NoneQueryProviderCollisionFloatingObjects);
|
||||
isValid &= ValidateQuerySource(target, messenger, Water.FlowLod, k_NoneQueryProviderFlowFloatingObjects);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
@@ -1459,34 +1545,45 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var isValid = true;
|
||||
|
||||
#if !d_CrestCPUQueries
|
||||
if (target.CollisionSource == CollisionSource.CPU)
|
||||
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,
|
||||
FixSetCollisionSourceToCompute
|
||||
FixSetQuerySourceToCompute
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (target.CollisionSource == CollisionSource.None)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
"Collision Source in Water Renderer is set to None. The floating objects in the scene will use a flat horizontal plane.",
|
||||
"Set collision source to GPU.",
|
||||
MessageType.Warning, target.Water,
|
||||
FixSetCollisionSourceToCompute,
|
||||
$"{nameof(WaterRenderer._AnimatedWavesLod)}.{nameof(AnimatedWavesLod._CollisionSource)}"
|
||||
|
||||
);
|
||||
}
|
||||
isValid &= ValidateQuerySource(target.Water, messenger, target, k_NoneQueryProviderCollisionFloatingObjects);
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
[Validator(typeof(LevelLod))]
|
||||
static bool Validate(LevelLod target, ShowMessage messenger)
|
||||
{
|
||||
var water = Water;
|
||||
|
||||
if (water == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ValidateDisplacementPrecisionIssues(water, water, messenger);
|
||||
ValidateWaterLevelPrecisionIssues(water, water, messenger);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[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)
|
||||
{
|
||||
@@ -1539,7 +1636,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
messenger
|
||||
(
|
||||
$"No water present. {nameof(CutsceneTimeProvider)} will have no effect.",
|
||||
"", MessageType.Warning
|
||||
"", MessageType.Warning,
|
||||
caller: target
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
@@ -1552,7 +1650,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
(
|
||||
$"No {nameof(UnityEngine.Playables.PlayableDirector)} component assigned. {nameof(CutsceneTimeProvider)} will have no effect.",
|
||||
$"Add a {nameof(UnityEngine.Playables.PlayableDirector)}",
|
||||
MessageType.Error
|
||||
MessageType.Error,
|
||||
caller: target
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
@@ -1562,7 +1661,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
(
|
||||
$"This component requires the com.unity.modules.director built-in module to function.",
|
||||
$"Enable the com.unity.modules.director built-in module.",
|
||||
MessageType.Error
|
||||
MessageType.Error,
|
||||
caller: target
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
@@ -1587,6 +1687,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
);
|
||||
}
|
||||
|
||||
if (water.RenderBeforeTransparency && !SurfaceRenderer.IsTransparent(material))
|
||||
{
|
||||
messenger
|
||||
(
|
||||
$"The {nameof(WaterRenderer.InjectionPoint).Pretty()} on the {nameof(WaterRenderer).Pretty()} is set to {nameof(WaterInjectionPoint.BeforeTransparent).Pretty()}, but it requires the water surface material to be set to transparent. " +
|
||||
"The water will not render.",
|
||||
$"Set to transparent or set {nameof(WaterRenderer.InjectionPoint).Pretty()} to {nameof(WaterInjectionPoint.Default).Pretty()}.",
|
||||
MessageType.Warning,
|
||||
material,
|
||||
caller: water
|
||||
);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
@@ -1625,7 +1738,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
(
|
||||
$"{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
|
||||
MessageType.Error,
|
||||
caller: target
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
@@ -1659,9 +1773,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
(
|
||||
$"The {value} {label} requires the {flag} layer which is not enabled.",
|
||||
fix,
|
||||
required ? MessageType.Error : MessageType.Warning, messenger == DebugLog ? target : Water,
|
||||
required ? MessageType.Error : MessageType.Warning, Water,
|
||||
(_, y) => y.intValue = (int)(layers | flag),
|
||||
$"{nameof(WaterRenderer._AnimatedWavesLod)}.{nameof(WaterRenderer._AnimatedWavesLod._CollisionLayers)}"
|
||||
$"{nameof(WaterRenderer._AnimatedWavesLod)}.{nameof(WaterRenderer._AnimatedWavesLod._CollisionLayers)}",
|
||||
caller: target
|
||||
);
|
||||
|
||||
return !required;
|
||||
@@ -1670,21 +1785,28 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ValidateCollisionSource(Object target, ShowMessage messenger)
|
||||
static bool ValidateQuerySource(Object target, ShowMessage messenger, IQueryableLod<IQueryProvider> lod, string extra)
|
||||
{
|
||||
if (Water == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Water._AnimatedWavesLod.CollisionSource == CollisionSource.None)
|
||||
if (!lod.Enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lod.QuerySource == LodQuerySource.None)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
"<i>Collision Source</i> on the <i>Water Renderer</i> is set to <i>None</i>. The floating objects in the scene will use a flat horizontal plane.",
|
||||
"Set the <i>Collision Source</i> to <i>GPU</i> to incorporate waves into physics.",
|
||||
MessageType.Warning, Water,
|
||||
FixSetCollisionSourceToCompute
|
||||
$"<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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1727,5 +1849,39 @@ namespace WaveHarmonic.Crest.Editor
|
||||
dependencyPropertyPath
|
||||
);
|
||||
}
|
||||
|
||||
static void ValidateWaterLevelPrecisionIssues(WaterRenderer water, Object caller, ShowMessage messenger)
|
||||
{
|
||||
if (water.LevelLod.Enabled && !water.LevelLod.Blur)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
$"If you are seeing bump artifacts on the water surface, try enabling {nameof(Lod.Blur)} on the {water.LevelLod.Name} simulation.",
|
||||
"Enable blur.",
|
||||
MessageType.Info,
|
||||
water,
|
||||
(x, y) => y.boolValue = true,
|
||||
nameof(water._LevelLod) + "." + Lod.k_BlurField,
|
||||
caller
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void ValidateDisplacementPrecisionIssues(WaterRenderer water, Object caller, ShowMessage messenger)
|
||||
{
|
||||
if (water.AnimatedWavesLod.Enabled && water.AnimatedWavesLod.TextureFormatMode != LodTextureFormatMode.Precision)
|
||||
{
|
||||
messenger
|
||||
(
|
||||
$"If you are seeing bump artifacts on the water surface, try setting {nameof(AnimatedWavesLod.TextureFormatMode)} to {LodTextureFormatMode.Precision} in {nameof(AnimatedWavesLod)}.",
|
||||
$"Set {nameof(AnimatedWavesLod.TextureFormatMode)} to {LodTextureFormatMode.Precision}.",
|
||||
MessageType.Info,
|
||||
water,
|
||||
(x, y) => y.intValue = (int)LodTextureFormatMode.Precision,
|
||||
nameof(water._AnimatedWavesLod) + "." + Lod.k_TextureFormatModeField,
|
||||
caller
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Shader Graph Complete
|
||||
// Copyright © 2025 Wave Harmonic. All rights reserved.
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
$include("Templates/SharedCode.template.hlsl")
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#ifndef d_WaveHarmonic_Crest_Editor_VisualizeData
|
||||
#define d_WaveHarmonic_Crest_Editor_VisualizeData
|
||||
|
||||
#define d_RequirePositionWS 1
|
||||
#define d_RequireUndisplacedXZ 1
|
||||
#define d_RequireLodAlpha 1
|
||||
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings/Visualize.Crest.hlsl"
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Macros.hlsl"
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Vertex/Surface.hlsl"
|
||||
|
||||
#define d_VisualizeAlbedo _Crest_DataType == VISUALIZEDATATYPES_ALBEDO
|
||||
#define d_VisualizeDepth _Crest_DataType == VISUALIZEDATATYPES_DEPTH
|
||||
#define d_VisualizeDisplacement _Crest_DataType == VISUALIZEDATATYPES_DISPLACEMENT
|
||||
#define d_VisualizeFlow _Crest_DataType == VISUALIZEDATATYPES_FLOW
|
||||
#define d_VisualizeFoam _Crest_DataType == VISUALIZEDATATYPES_FOAM
|
||||
#define d_VisualizeLevel _Crest_DataType == VISUALIZEDATATYPES_LEVEL
|
||||
#define d_VisualizeShadow _Crest_DataType == VISUALIZEDATATYPES_SHADOW
|
||||
#define d_VisualizeShorelineDistance _Crest_DataType == VISUALIZEDATATYPES_SHORELINE_DISTANCE
|
||||
#define d_VisualizeAbsorption _Crest_DataType == VISUALIZEDATATYPES_ABSORPTION
|
||||
#define d_VisualizeScattering _Crest_DataType == VISUALIZEDATATYPES_SCATTERING
|
||||
#define d_VisualizeDynamicWaves _Crest_DataType == VISUALIZEDATATYPES_DYNAMIC_WAVES
|
||||
#define d_VisualizeClip _Crest_DataType == VISUALIZEDATATYPES_CLIP
|
||||
#define d_VisualizeCascades _Crest_DataType == VISUALIZEDATATYPES_CASCADES
|
||||
|
||||
uint _Crest_DataType;
|
||||
bool _Crest_Saturate;
|
||||
float _Crest_Exposure;
|
||||
float _Crest_Range;
|
||||
|
||||
m_CrestNameSpace
|
||||
|
||||
half4 Fragment(const Varyings i_Input)
|
||||
{
|
||||
const uint slice0 = _Crest_LodIndex;
|
||||
const uint slice1 = slice0 + 1;
|
||||
|
||||
const Cascade cascade0 = Cascade::Make(slice0);
|
||||
const Cascade cascade1 = Cascade::Make(slice1);
|
||||
|
||||
const bool isLastLod = slice0 == (g_Crest_LodCount - 1);
|
||||
const float weight0 = (1.0 - i_Input._LodAlpha) * cascade0._Weight;
|
||||
const float weight1 = (1.0 - weight0) * cascade1._Weight;
|
||||
|
||||
const float3 position = i_Input._PositionWS;
|
||||
const float2 undisplaced = i_Input._UndispacedPositionXZ;
|
||||
|
||||
half3 displacement = 0.0;
|
||||
half2 ripples = 0.0;
|
||||
half level = 0.0;
|
||||
half depth = 0.0;
|
||||
half distance = 0.0;
|
||||
half4 albedo = 0.0;
|
||||
half clip = 0.0;
|
||||
half2 flow = 0.0;
|
||||
half foam = 0.0;
|
||||
half2 shadow = 0.0;
|
||||
half3 absorption = 0.0;
|
||||
half3 scattering = 0.0;
|
||||
|
||||
if (weight0 > m_CrestSampleLodThreshold)
|
||||
{
|
||||
Cascade::MakeAnimatedWaves(slice0).SampleDisplacement(undisplaced, weight0, displacement);
|
||||
Cascade::MakeLevel(slice0).SampleLevel(undisplaced, weight0, level);
|
||||
Cascade::MakeDynamicWaves(slice0).SampleDynamicWaves(undisplaced, weight0, ripples);
|
||||
|
||||
Cascade::MakeAlbedo(slice0).SampleAlbedo(undisplaced, weight0, albedo);
|
||||
Cascade::MakeDepth(slice0).SampleSignedDepthFromSeaLevelAndDistance(position.xz, weight0, depth, distance);
|
||||
Cascade::MakeClip(slice0).SampleClip(position.xz, weight0, clip);
|
||||
Cascade::MakeFlow(slice0).SampleFlow(undisplaced, weight0, flow);
|
||||
Cascade::MakeFoam(slice0).SampleFoam(undisplaced, weight0, foam);
|
||||
Cascade::MakeShadow(slice0).SampleShadow(position.xz, weight0, shadow);
|
||||
|
||||
Cascade::MakeAbsorption(slice0).SampleAbsorption(undisplaced, weight0, absorption);
|
||||
Cascade::MakeScattering(slice0).SampleScattering(undisplaced, weight0, scattering);
|
||||
}
|
||||
|
||||
if (weight1 > m_CrestSampleLodThreshold)
|
||||
{
|
||||
Cascade::MakeAnimatedWaves(slice1).SampleDisplacement(undisplaced, weight1, displacement);
|
||||
Cascade::MakeLevel(slice1).SampleLevel(undisplaced, weight1, level);
|
||||
Cascade::MakeDynamicWaves(slice1).SampleDynamicWaves(undisplaced, weight1, ripples);
|
||||
|
||||
Cascade::MakeAlbedo(slice1).SampleAlbedo(undisplaced, weight1, albedo);
|
||||
Cascade::MakeDepth(slice1).SampleSignedDepthFromSeaLevelAndDistance(position.xz, weight1, depth, distance);
|
||||
Cascade::MakeClip(slice1).SampleClip(position.xz, weight1, clip);
|
||||
Cascade::MakeFlow(slice1).SampleFlow(undisplaced, weight1, flow);
|
||||
Cascade::MakeFoam(slice1).SampleFoam(undisplaced, weight1, foam);
|
||||
Cascade::MakeShadow(slice1).SampleShadow(position.xz, weight1, shadow);
|
||||
|
||||
Cascade::MakeAbsorption(slice1).SampleAbsorption(undisplaced, weight1, absorption);
|
||||
Cascade::MakeScattering(slice1).SampleScattering(undisplaced, weight1, scattering);
|
||||
}
|
||||
|
||||
if (isLastLod)
|
||||
{
|
||||
depth = m_FloatMaximum;
|
||||
distance = m_FloatMaximum;
|
||||
}
|
||||
|
||||
|
||||
half3 result = 0.0;
|
||||
|
||||
if (d_VisualizeDisplacement)
|
||||
{
|
||||
result = (displacement + 1.0) * 0.5;
|
||||
}
|
||||
|
||||
if (d_VisualizeDynamicWaves)
|
||||
{
|
||||
result.xy = (ripples + 1.0) * 0.5;
|
||||
}
|
||||
|
||||
if (d_VisualizeLevel)
|
||||
{
|
||||
result = level;
|
||||
}
|
||||
|
||||
|
||||
if (d_VisualizeDepth)
|
||||
{
|
||||
result.x = depth / _Crest_Range;
|
||||
}
|
||||
|
||||
if (d_VisualizeShorelineDistance)
|
||||
{
|
||||
result.x = distance / _Crest_Range;
|
||||
}
|
||||
|
||||
|
||||
if (d_VisualizeAbsorption)
|
||||
{
|
||||
result = absorption;
|
||||
}
|
||||
|
||||
|
||||
if (d_VisualizeScattering)
|
||||
{
|
||||
result = scattering;
|
||||
}
|
||||
|
||||
if (d_VisualizeAlbedo)
|
||||
{
|
||||
result = albedo.rgb * albedo.a;
|
||||
}
|
||||
|
||||
if (d_VisualizeClip)
|
||||
{
|
||||
result.x = clip;
|
||||
}
|
||||
|
||||
if (d_VisualizeFlow)
|
||||
{
|
||||
result.xy = flow;
|
||||
}
|
||||
|
||||
if (d_VisualizeFoam)
|
||||
{
|
||||
result.x = foam;
|
||||
}
|
||||
|
||||
if (d_VisualizeShadow)
|
||||
{
|
||||
result.xy = shadow;
|
||||
}
|
||||
|
||||
|
||||
if (d_VisualizeCascades)
|
||||
{
|
||||
half3 tint[7];
|
||||
tint[0] = half3(1.0, 0.0, 0.0);
|
||||
tint[1] = half3(1.0, 1.0, 0.0);
|
||||
tint[2] = half3(1.0, 0.0, 1.0);
|
||||
tint[3] = half3(0.0, 1.0, 1.0);
|
||||
tint[4] = half3(0.0, 0.0, 1.0);
|
||||
tint[5] = half3(1.0, 0.0, 1.0);
|
||||
tint[6] = half3(0.5, 0.5, 1.0);
|
||||
result = weight0 * tint[slice0 % 7] + weight1 * tint[slice1 % 7];
|
||||
}
|
||||
else
|
||||
{
|
||||
result *= exp2(_Crest_Exposure);
|
||||
|
||||
if (_Crest_Saturate)
|
||||
{
|
||||
result = saturate(result);
|
||||
}
|
||||
}
|
||||
|
||||
return half4(result, 1.0);
|
||||
}
|
||||
|
||||
m_CrestNameSpaceEnd
|
||||
|
||||
m_CrestVertex
|
||||
m_CrestFragment(half4)
|
||||
|
||||
#endif // d_WaveHarmonic_Crest_Editor_VisualizeData
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa32936b11acb4c05af5f1c9e5603a41
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
Shader "Hidden/Crest/Debug/Visualize Data"
|
||||
{
|
||||
HLSLINCLUDE
|
||||
#pragma vertex Vertex
|
||||
#pragma fragment Fragment
|
||||
// #pragma enable_d3d11_debug_symbols
|
||||
ENDHLSL
|
||||
|
||||
SubShader
|
||||
{
|
||||
PackageRequirements
|
||||
{
|
||||
"com.unity.render-pipelines.high-definition"
|
||||
}
|
||||
|
||||
Tags
|
||||
{
|
||||
"RenderPipeline"="HDRenderPipeline"
|
||||
"LightMode"="Forward"
|
||||
"RenderType"="Transparent"
|
||||
"Queue"="Transparent"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Visualize"
|
||||
|
||||
HLSLPROGRAM
|
||||
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
|
||||
#include "VisualizeData.hlsl"
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
PackageRequirements
|
||||
{
|
||||
"com.unity.render-pipelines.universal"
|
||||
}
|
||||
|
||||
Tags
|
||||
{
|
||||
"RenderPipeline"="UniversalPipeline"
|
||||
"LightMode"="UniversalForward"
|
||||
// Required as I could not set ZWrite for some reason leading to overwritten.
|
||||
"RenderType"="Transparent"
|
||||
"Queue"="Transparent"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Visualize"
|
||||
|
||||
HLSLPROGRAM
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
#include "VisualizeData.hlsl"
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"LightMode"="ForwardBase"
|
||||
"RenderType"="Transparent"
|
||||
"Queue"="Transparent"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Visualize"
|
||||
|
||||
HLSLPROGRAM
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Legacy/Core.hlsl"
|
||||
#include "VisualizeData.hlsl"
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa9cd37a525c54185b9c3ca35b550ec5
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,8 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#pragma exclude_renderers glcore gles3
|
||||
|
||||
#pragma kernel CrestVisualizeNegativeValues_Scalar
|
||||
#pragma kernel CrestVisualizeNegativeValues_Array _ARRAY
|
||||
|
||||
|
||||
@@ -4,74 +4,13 @@
|
||||
#ifndef d_WaterLevelDepth
|
||||
#define d_WaterLevelDepth
|
||||
|
||||
#define d_WaterLevelDisplacementOnly 1
|
||||
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Macros.hlsl"
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/InputsDriven.hlsl"
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Cascade.hlsl"
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Geometry.hlsl"
|
||||
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Vertex/Surface.hlsl"
|
||||
|
||||
m_CrestNameSpace
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float3 positionOS : POSITION;
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
};
|
||||
|
||||
Varyings Vertex(Attributes attributes)
|
||||
{
|
||||
// This will work for all pipelines.
|
||||
Varyings varyings = (Varyings)0;
|
||||
|
||||
const Cascade cascade0 = Cascade::Make(_Crest_LodIndex);
|
||||
const Cascade cascade1 = Cascade::Make(_Crest_LodIndex + 1);
|
||||
|
||||
float3 positionWS = mul(UNITY_MATRIX_M, float4(attributes.positionOS.xyz, 1.0)).xyz;
|
||||
|
||||
#if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0)
|
||||
positionWS.xz += _WorldSpaceCameraPos.xz;
|
||||
#endif
|
||||
|
||||
float alpha;
|
||||
SnapAndTransitionVertLayout(_Crest_ChunkMeshScaleAlpha, cascade0, _Crest_ChunkGeometryGridWidth, positionWS, alpha);
|
||||
|
||||
{
|
||||
// :WaterGridPrecisionErrors
|
||||
float2 center = UNITY_MATRIX_M._m03_m23;
|
||||
#if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0)
|
||||
center += _WorldSpaceCameraPos.xz;
|
||||
#endif
|
||||
const float2 camera = abs(_WorldSpaceCameraPos.xz);
|
||||
positionWS.xz = lerp(center, positionWS.xz, lerp(1.0, 1.01, max(camera.x, camera.y) * 0.00001));
|
||||
}
|
||||
|
||||
const float weight0 = (1.0 - alpha) * cascade0._Weight;
|
||||
const float weight1 = (1.0 - weight0) * cascade1._Weight;
|
||||
|
||||
half offset = 0.0;
|
||||
if (weight0 > m_CrestSampleLodThreshold)
|
||||
{
|
||||
Cascade::MakeLevel(_Crest_LodIndex).SampleLevel(positionWS.xz, weight0, offset);
|
||||
}
|
||||
if (weight1 > m_CrestSampleLodThreshold)
|
||||
{
|
||||
Cascade::MakeLevel(_Crest_LodIndex + 1).SampleLevel(positionWS.xz, weight1, offset);
|
||||
}
|
||||
|
||||
positionWS.y += offset;
|
||||
|
||||
#if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0)
|
||||
positionWS.xz -= _WorldSpaceCameraPos.xz;
|
||||
#endif
|
||||
|
||||
varyings.positionCS = mul(UNITY_MATRIX_VP, float4(positionWS, 1.0));
|
||||
|
||||
return varyings;
|
||||
}
|
||||
|
||||
half4 Fragment(Varyings varyings)
|
||||
{
|
||||
return half4(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
@@ -13,9 +13,10 @@ MonoBehaviour:
|
||||
m_Name: WavesCalm
|
||||
m_EditorClassIdentifier:
|
||||
_Version: 0
|
||||
_WaveDirectionVariance: 56
|
||||
_GravityScale: 1
|
||||
_Multiplier: 1
|
||||
_Chop: 1
|
||||
_GravityScale: 1
|
||||
_WaveDirectionVariance: 56
|
||||
_PowerLogarithmicScales:
|
||||
- -7.39794
|
||||
- -7.39794
|
||||
@@ -62,6 +63,20 @@ MonoBehaviour:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_Chop: 1
|
||||
_Attenuation:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_ShowAdvancedControls: 0
|
||||
_Model: 0
|
||||
|
||||
@@ -13,9 +13,10 @@ MonoBehaviour:
|
||||
m_Name: WavesDead
|
||||
m_EditorClassIdentifier:
|
||||
_Version: 0
|
||||
_WaveDirectionVariance: 56
|
||||
_GravityScale: 1
|
||||
_Multiplier: 1
|
||||
_Chop: 1
|
||||
_GravityScale: 1
|
||||
_WaveDirectionVariance: 56
|
||||
_PowerLogarithmicScales:
|
||||
- -7.39794
|
||||
- -7.39794
|
||||
@@ -62,6 +63,20 @@ MonoBehaviour:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_Chop: 1
|
||||
_Attenuation:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_ShowAdvancedControls: 0
|
||||
_Model: 0
|
||||
|
||||
@@ -13,9 +13,10 @@ MonoBehaviour:
|
||||
m_Name: WavesModerate
|
||||
m_EditorClassIdentifier:
|
||||
_Version: 0
|
||||
_WaveDirectionVariance: 90
|
||||
_GravityScale: 1
|
||||
_Multiplier: 1
|
||||
_Chop: 1.54
|
||||
_GravityScale: 1
|
||||
_WaveDirectionVariance: 90
|
||||
_PowerLogarithmicScales:
|
||||
- -7.1418724
|
||||
- -6.539813
|
||||
@@ -62,6 +63,20 @@ MonoBehaviour:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_Chop: 1.54
|
||||
_Attenuation:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_ShowAdvancedControls: 0
|
||||
_Model: 0
|
||||
|
||||
@@ -13,9 +13,10 @@ MonoBehaviour:
|
||||
m_Name: WavesModerateSmooth
|
||||
m_EditorClassIdentifier:
|
||||
_Version: 0
|
||||
_WaveDirectionVariance: 90
|
||||
_GravityScale: 1
|
||||
_Multiplier: 1
|
||||
_Chop: 1.6
|
||||
_GravityScale: 1
|
||||
_WaveDirectionVariance: 90
|
||||
_PowerLogarithmicScales:
|
||||
- -7.39794
|
||||
- -7.39794
|
||||
@@ -62,6 +63,20 @@ MonoBehaviour:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_Chop: 1.6
|
||||
_Attenuation:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_ShowAdvancedControls: 0
|
||||
_Model: 0
|
||||
|
||||
@@ -13,9 +13,10 @@ MonoBehaviour:
|
||||
m_Name: WavesShoreline
|
||||
m_EditorClassIdentifier:
|
||||
_Version: 0
|
||||
_WaveDirectionVariance: 20
|
||||
_GravityScale: 1
|
||||
_Multiplier: 1
|
||||
_Chop: 1.54
|
||||
_GravityScale: 1
|
||||
_WaveDirectionVariance: 20
|
||||
_PowerLogarithmicScales:
|
||||
- -7.1418724
|
||||
- -6.539813
|
||||
@@ -62,6 +63,20 @@ MonoBehaviour:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_Chop: 1.54
|
||||
_Attenuation:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_ShowAdvancedControls: 0
|
||||
_Model: 0
|
||||
|
||||
@@ -13,9 +13,10 @@ MonoBehaviour:
|
||||
m_Name: WavesSwell
|
||||
m_EditorClassIdentifier:
|
||||
_Version: 0
|
||||
_WaveDirectionVariance: 90
|
||||
_GravityScale: 1
|
||||
_Multiplier: 1
|
||||
_Chop: 1.6
|
||||
_GravityScale: 1
|
||||
_WaveDirectionVariance: 90
|
||||
_PowerLogarithmicScales:
|
||||
- -7.10794
|
||||
- -6.42794
|
||||
@@ -62,6 +63,20 @@ MonoBehaviour:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_Chop: 1.6
|
||||
_Attenuation:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
_ShowAdvancedControls: 0
|
||||
_Model: 0
|
||||
|
||||
@@ -25,12 +25,12 @@ Material:
|
||||
m_Parent: {fileID: 2100000, guid: 8ab064b6606504a55b489af2787350c2, type: 2}
|
||||
m_ModifiedSerializedProperties: 26
|
||||
m_ValidKeywords:
|
||||
- CREST_FLOW_ON
|
||||
- _ALPHATEST_ON
|
||||
- _BUILTIN_ALPHATEST_ON
|
||||
- _BUILTIN_AlphaClip
|
||||
- _BUILTIN_SURFACE_TYPE_TRANSPARENT
|
||||
- _BUILTIN_TRANSPARENT_RECEIVES_SHADOWS
|
||||
- _CREST_FLOW_LOD
|
||||
- _DOUBLESIDED_ON
|
||||
- _ENABLE_FOG_ON_TRANSPARENT
|
||||
- _SURFACE_TYPE_TRANSPARENT
|
||||
@@ -56,7 +56,8 @@ Material:
|
||||
m_TexEnvs: []
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- CREST_FLOW: 1
|
||||
- _CREST_FLOW_LOD: 1
|
||||
- _DstBlendAlpha: 10
|
||||
- _SrcBlend: 5
|
||||
m_Colors: []
|
||||
m_BuildTextureStacks: []
|
||||
|
||||
@@ -15,14 +15,11 @@ Material:
|
||||
- d_Crest_NoMaskDepth
|
||||
- d_Dithering
|
||||
m_InvalidKeywords:
|
||||
- CREST_CAUSTICS_ON
|
||||
- CREST_FOAM_ON
|
||||
- _ALPHATEST_ON
|
||||
- _BUILTIN_ALPHATEST_ON
|
||||
- _BUILTIN_AlphaClip
|
||||
- _BUILTIN_SURFACE_TYPE_TRANSPARENT
|
||||
- _DOUBLESIDED_ON
|
||||
- _EMISSION
|
||||
- _ENABLE_FOG_ON_TRANSPARENT
|
||||
- _REFRACTION_PLANE
|
||||
- _SURFACE_TYPE_TRANSPARENT
|
||||
@@ -51,6 +48,7 @@ Material:
|
||||
- _Crest_Version: 0
|
||||
m_Floats:
|
||||
- CREST_FLOW: 0
|
||||
- _CREST_FLOW_LOD: 0
|
||||
- _Crest_AmbientTerm: 1
|
||||
- _Crest_Anisotropy: 0.5
|
||||
- _Crest_CausticsDepthOfField: 6
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Ripples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Waterfall")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint")]
|
||||
|
||||
@@ -24,12 +24,25 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public const int k_FieldGroupOrder = Editor.Inspector.k_FieldGroupOrder;
|
||||
public const int k_FieldHeadingOrder = Editor.Inspector.k_FieldHeadingOrder;
|
||||
#else
|
||||
public const int k_FieldGroupOrder = 0;
|
||||
public const int k_FieldHeadingOrder = 0;
|
||||
#endif
|
||||
|
||||
// Unity only supports textures up to a size of 16384, even if maxTextureSize returns a larger size.
|
||||
// https://docs.unity3d.com/ScriptReference/SystemInfo-maxTextureSize.html
|
||||
public const int k_MaximumTextureResolution = 16384;
|
||||
|
||||
public static class Symbols
|
||||
{
|
||||
public const string k_Refraction =
|
||||
#if d_Crest_SimpleTransparency
|
||||
"CREST_NOOP";
|
||||
#else
|
||||
"UNITY_2022_3_OR_NEWER";
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace WaveHarmonic.Crest
|
||||
internal override string ID => "Albedo";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.clear;
|
||||
private protected override bool NeedToReadWriteTextureData => false;
|
||||
private protected override bool NeedToReadWriteTextureData => Blur;
|
||||
internal override bool SkipEndOfFrame => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
abstract class BakedWaveData : ScriptableObject
|
||||
abstract class BakedWaveData : CustomScriptableObject
|
||||
{
|
||||
public abstract ICollisionProvider CreateCollisionProvider();
|
||||
public abstract float WindSpeed { get; }
|
||||
@@ -20,6 +22,7 @@ namespace WaveHarmonic.Crest
|
||||
/// <summary>
|
||||
/// The source of collisions (ie water shape).
|
||||
/// </summary>
|
||||
[System.Obsolete("Please use QuerySource and LodQuerySource.")]
|
||||
[@GenerateDoc]
|
||||
public enum CollisionSource
|
||||
{
|
||||
@@ -67,13 +70,9 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
// NOTE: numbers must be in order for defaults to work (everything first).
|
||||
|
||||
/// <inheritdoc cref="Generated.CollisionLayers.Everything"/>
|
||||
[Tooltip("All layers.")]
|
||||
Everything = -1,
|
||||
|
||||
/// <inheritdoc cref="Generated.CollisionLayers.Nothing"/>
|
||||
[Tooltip("No extra layers (ie single layer).")]
|
||||
Nothing,
|
||||
Nothing = 0,
|
||||
|
||||
/// <inheritdoc cref="Generated.CollisionLayers.DynamicWaves"/>
|
||||
[Tooltip("Separate layer for dynamic waves.\n\nDynamic waves are normally combined together for efficiency. By enabling this layer, dynamic waves are combined and added in a separate pass.")]
|
||||
@@ -82,6 +81,29 @@ namespace WaveHarmonic.Crest
|
||||
/// <inheritdoc cref="Generated.CollisionLayers.Displacement"/>
|
||||
[Tooltip("Extra displacement layer for visual displacement.")]
|
||||
Displacement = 1 << 2,
|
||||
|
||||
/// <inheritdoc cref="Generated.CollisionLayers.Everything"/>
|
||||
[Tooltip("All layers.")]
|
||||
Everything = ~0,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wave sampling method to determine quality and performance.
|
||||
/// </summary>
|
||||
[@GenerateDoc]
|
||||
public enum WaveSampling
|
||||
{
|
||||
/// <inheritdoc cref="Generated.WaveSampling.Automatic"/>
|
||||
[Tooltip("Automatically chooses the other options as needed (512+ resolution needs precision).")]
|
||||
Automatic,
|
||||
|
||||
/// <inheritdoc cref="Generated.WaveSampling.Performance"/>
|
||||
[Tooltip("Reduces samples by copying waves from higher LODs to lower LODs.\n\nBest for resolutions lower than 512.")]
|
||||
Performance,
|
||||
|
||||
/// <inheritdoc cref="Generated.WaveSampling.Precision"/>
|
||||
[Tooltip("Samples directly from the wave buffers to preserve wave quality.\n\nNeeded for higher resolutions (512+). Higher LOD counts can also benefit with this enabled.")]
|
||||
Precision,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,14 +130,33 @@ namespace WaveHarmonic.Crest
|
||||
[@FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class AnimatedWavesLod : Lod<ICollisionProvider>
|
||||
{
|
||||
[Tooltip("Collision layers to enable.\n\nSome layers will have overhead with CPU, GPU and memory.")]
|
||||
[@Enable(nameof(_QuerySource), nameof(LodQuerySource.GPU))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionLayers _CollisionLayers = CollisionLayers.Everything;
|
||||
|
||||
[@Enable(nameof(_QuerySource), nameof(LodQuerySource.CPU))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal BakedWaveData _BakedWaveData;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Shifts wavelengths to maintain quality for higher resolutions.\n\nSet this to 2 to improve wave quality. In some cases like flowing rivers, this can make a substantial difference to visual stability. We recommend doubling the Resolution on the WaterRenderer component to preserve detail after making this change.")]
|
||||
[@Range(1f, 4f)]
|
||||
[Tooltip("The wave sampling method to determine quality and performance.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
internal WaveSampling _WaveSampling;
|
||||
|
||||
[Tooltip("Shifts wavelengths to maintain quality for higher resolutions.\n\nSet this to 2 to improve wave quality. In some cases like flowing rivers, this can make a substantial difference to visual stability. We recommend doubling the Resolution on the WaterRenderer component to preserve detail after making this change.")]
|
||||
[@Enable(nameof(_WaveSampling), nameof(WaveSampling.Performance))]
|
||||
[@Range(1f, 4f)]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
[SerializeField]
|
||||
float _WaveResolutionMultiplier = 1f;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("How much waves are dampened in shallow water.")]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
@@ -129,30 +170,6 @@ namespace WaveHarmonic.Crest
|
||||
float _ShallowsMaximumDepth = 1000f;
|
||||
|
||||
|
||||
[@Heading("Collisions")]
|
||||
|
||||
[Tooltip("Where to obtain water shape on CPU for physics / gameplay.")]
|
||||
[@GenerateAPI(Setter.Internal)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionSource _CollisionSource = CollisionSource.GPU;
|
||||
|
||||
[Tooltip("Collision layers to enable.\n\nSome layers will have overhead with CPU, GPU and memory.")]
|
||||
[@Predicated(nameof(_CollisionSource), inverted: true, nameof(CollisionSource.GPU))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionLayers _CollisionLayers = CollisionLayers.Everything;
|
||||
|
||||
[Tooltip("Maximum number of wave queries that can be performed when using GPU queries.")]
|
||||
[@Predicated(nameof(_CollisionSource), true, nameof(CollisionSource.GPU))]
|
||||
[@GenerateAPI(Setter.None)]
|
||||
[@DecoratedField, SerializeField]
|
||||
int _MaximumQueryCount = QueryBase.k_DefaultMaximumQueryCount;
|
||||
|
||||
[@Predicated(nameof(_CollisionSource), true, nameof(CollisionSource.CPU))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal BakedWaveData _BakedWaveData;
|
||||
|
||||
|
||||
const string k_DrawCombine = "Combine";
|
||||
|
||||
|
||||
@@ -160,7 +177,6 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
public static readonly int s_WaveBuffer = Shader.PropertyToID("_Crest_WaveBuffer");
|
||||
public static readonly int s_DynamicWavesTarget = Shader.PropertyToID("_Crest_DynamicWavesTarget");
|
||||
public static readonly int s_AnimatedWavesTarget = Shader.PropertyToID("_Crest_AnimatedWavesTarget");
|
||||
public static readonly int s_AttenuationInShallows = Shader.PropertyToID("_Crest_AttenuationInShallows");
|
||||
}
|
||||
|
||||
@@ -171,6 +187,9 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
internal static bool s_Combine = true;
|
||||
|
||||
WaterResources.ShapeCombineCompute _CombineShader;
|
||||
RenderTexture _PersistentDataTexture;
|
||||
|
||||
internal override string ID => "AnimatedWaves";
|
||||
internal override string Name => "Animated Waves";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
@@ -187,17 +206,13 @@ namespace WaveHarmonic.Crest
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
ComputeShader _CombineShader;
|
||||
|
||||
int _KernalShapeCombine = -1;
|
||||
int _KernalShapeCombine_DISABLE_COMBINE = -1;
|
||||
int _KernalShapeCombine_FLOW_ON = -1;
|
||||
int _KernalShapeCombine_FLOW_ON_DISABLE_COMBINE = -1;
|
||||
int _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON = -1;
|
||||
int _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = -1;
|
||||
int _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON = -1;
|
||||
int _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = -1;
|
||||
|
||||
internal bool PreserveWaveQuality => WaveSampling switch
|
||||
{
|
||||
WaveSampling.Automatic => Resolution >= 512,
|
||||
WaveSampling.Performance => false,
|
||||
WaveSampling.Precision => true,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
internal AnimatedWavesLod()
|
||||
{
|
||||
@@ -208,35 +223,67 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
internal override void Initialize()
|
||||
{
|
||||
_CombineShader = WaterResources.Instance.Compute._ShapeCombine;
|
||||
if (_CombineShader == null)
|
||||
_CombineShader = WaterResources.Instance._ComputeLibrary._ShapeCombineCompute;
|
||||
if (_CombineShader._Shader == null)
|
||||
{
|
||||
_Valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
base.Initialize();
|
||||
|
||||
if (Persistent && !_Water.IsMultipleViewpointMode)
|
||||
{
|
||||
_PersistentDataTexture = CreateLodDataTextures();
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void Allocate()
|
||||
internal override void Destroy()
|
||||
{
|
||||
base.Allocate();
|
||||
base.Destroy();
|
||||
|
||||
_KernalShapeCombine = _CombineShader.FindKernel("ShapeCombine");
|
||||
_KernalShapeCombine_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_DISABLE_COMBINE");
|
||||
_KernalShapeCombine_FLOW_ON = _CombineShader.FindKernel("ShapeCombine_FLOW_ON");
|
||||
_KernalShapeCombine_FLOW_ON_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_FLOW_ON_DISABLE_COMBINE");
|
||||
_KernalShapeCombine_DYNAMIC_WAVE_SIM_ON = _CombineShader.FindKernel("ShapeCombine_DYNAMIC_WAVE_SIM_ON");
|
||||
_KernalShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE");
|
||||
_KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON = _CombineShader.FindKernel("ShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON");
|
||||
_KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE");
|
||||
if (_PersistentDataTexture != null) _PersistentDataTexture.Release();
|
||||
Helpers.Destroy(_PersistentDataTexture);
|
||||
|
||||
foreach (var data in _AdditionalCameraData.Values)
|
||||
{
|
||||
if (data != null) data.Release();
|
||||
Helpers.Destroy(data);
|
||||
}
|
||||
|
||||
_AdditionalCameraData.Clear();
|
||||
}
|
||||
|
||||
internal override void SetGlobals(bool enable)
|
||||
{
|
||||
base.SetGlobals(enable);
|
||||
|
||||
if (_Water.IsRunningWithoutGraphics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Persistent)
|
||||
{
|
||||
Shader.SetGlobalTexture(_TextureSourceShaderID, enable && Enabled ? _PersistentDataTexture : NullTexture);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
buffer.BeginSample(ID);
|
||||
|
||||
FlipBuffers();
|
||||
FlipBuffers(buffer);
|
||||
|
||||
// Flip textures
|
||||
if (Persistent)
|
||||
{
|
||||
(_PersistentDataTexture, DataTexture) = (DataTexture, _PersistentDataTexture);
|
||||
|
||||
// Update current and previous. Latter for MVs and/or VFX.
|
||||
buffer.SetGlobalTexture(_TextureSourceShaderID, _PersistentDataTexture);
|
||||
buffer.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
}
|
||||
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_AttenuationInShallows, _AttenuationInShallows);
|
||||
|
||||
@@ -244,6 +291,18 @@ namespace WaveHarmonic.Crest
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_WaveBuffer, DataTexture.descriptor);
|
||||
CoreUtils.SetRenderTarget(buffer, ShaderIDs.s_WaveBuffer, ClearFlag.Color, ClearColor);
|
||||
|
||||
// Custom clear because clear not working.
|
||||
if (Helpers.RequiresCustomClear && WaterResources.Instance.Compute._Clear != null)
|
||||
{
|
||||
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
|
||||
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTarget);
|
||||
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, ShaderIDs.s_WaveBuffer);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearMask, Color.white);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
|
||||
wrapper.Dispatch(Resolution / k_ThreadGroupSizeX, Resolution / k_ThreadGroupSizeY, Slices);
|
||||
}
|
||||
|
||||
// LOD dependent data.
|
||||
// Write to per-octave _WaveBuffers. Not the same as _AnimatedWaves.
|
||||
// Draw any data with lod preference.
|
||||
@@ -254,54 +313,53 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
// Combine the LODs - copy results from biggest LOD down to LOD 0
|
||||
{
|
||||
var combineShaderKernel = _KernalShapeCombine;
|
||||
var combineShaderKernel_lastLOD = _KernalShapeCombine_DISABLE_COMBINE;
|
||||
{
|
||||
var isFlowOn = _Water._FlowLod.Enabled;
|
||||
var isDynamicWavesOn = _Water._DynamicWavesLod.Enabled && !_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves);
|
||||
var wrapper = new PropertyWrapperCompute
|
||||
(
|
||||
buffer,
|
||||
_CombineShader._Shader,
|
||||
PreserveWaveQuality
|
||||
? _CombineShader._CopyAnimatedWavesKernel
|
||||
: _CombineShader._CombineAnimatedWavesKernel
|
||||
);
|
||||
|
||||
// Set the shader kernels that we will use.
|
||||
if (isFlowOn && isDynamicWavesOn)
|
||||
{
|
||||
combineShaderKernel = _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON;
|
||||
combineShaderKernel_lastLOD = _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE;
|
||||
}
|
||||
else if (isFlowOn)
|
||||
{
|
||||
combineShaderKernel = _KernalShapeCombine_FLOW_ON;
|
||||
combineShaderKernel_lastLOD = _KernalShapeCombine_FLOW_ON_DISABLE_COMBINE;
|
||||
}
|
||||
else if (isDynamicWavesOn)
|
||||
{
|
||||
combineShaderKernel = _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON;
|
||||
combineShaderKernel_lastLOD = _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE;
|
||||
}
|
||||
if (_Water._DynamicWavesLod.Enabled)
|
||||
{
|
||||
_Water._DynamicWavesLod.Bind(wrapper);
|
||||
}
|
||||
|
||||
buffer.BeginSample(k_DrawCombine);
|
||||
// Start with last LOD which does not combine.
|
||||
wrapper.SetKeyword(_CombineShader._CombineKeyword, false);
|
||||
wrapper.SetKeyword(_CombineShader._DynamicWavesKeyword, _Water._DynamicWavesLod.Enabled && !PreserveWaveQuality && !_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves));
|
||||
|
||||
// Combine waves.
|
||||
for (var slice = lastSlice; slice >= 0; slice--)
|
||||
// The per-octave wave buffers we read from.
|
||||
wrapper.SetTexture(ShaderIDs.s_WaveBuffer, ShaderIDs.s_WaveBuffer);
|
||||
|
||||
// Set the animated waves texture where we read/write to combine the results.
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
|
||||
if (PreserveWaveQuality)
|
||||
{
|
||||
var kernel = slice < lastSlice && s_Combine
|
||||
? combineShaderKernel : combineShaderKernel_lastLOD;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader, kernel);
|
||||
|
||||
// The per-octave wave buffers we read from.
|
||||
wrapper.SetTexture(ShaderIDs.s_WaveBuffer, ShaderIDs.s_WaveBuffer);
|
||||
|
||||
if (_Water._DynamicWavesLod.Enabled) _Water._DynamicWavesLod.Bind(wrapper);
|
||||
|
||||
// Set the animated waves texture where we read/write to combine the results. Use
|
||||
// compute suffix to avoid collision as a file already uses the normal name.
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
|
||||
|
||||
wrapper.Dispatch(threadSize, threadSize, 1);
|
||||
wrapper.Dispatch(threadSize, threadSize, Slices);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.BeginSample(k_DrawCombine);
|
||||
|
||||
buffer.EndSample(k_DrawCombine);
|
||||
// Combine waves.
|
||||
for (var slice = lastSlice; slice >= 0; slice--)
|
||||
{
|
||||
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
|
||||
wrapper.Dispatch(threadSize, threadSize, 1);
|
||||
|
||||
if (slice == lastSlice)
|
||||
{
|
||||
// From here on, use combine.
|
||||
wrapper.SetKeyword(_CombineShader._CombineKeyword, s_Combine);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.EndSample(k_DrawCombine);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_WaveBuffer);
|
||||
@@ -312,20 +370,15 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
// Alpha channel is cleared in combine step, but if any inputs draw in post-combine
|
||||
// step then alpha may have data.
|
||||
var clear = WaterResources.Instance.Compute._Clear;
|
||||
if (drawn && clear != null)
|
||||
if (drawn && WaterResources.Instance.Compute._Clear != null)
|
||||
{
|
||||
buffer.SetComputeTextureParam(clear, 0, Crest.ShaderIDs.s_Target, DataTexture);
|
||||
buffer.SetComputeVectorParam(clear, Crest.ShaderIDs.s_ClearMask, Color.black);
|
||||
buffer.SetComputeVectorParam(clear, Crest.ShaderIDs.s_ClearColor, Color.clear);
|
||||
buffer.DispatchCompute
|
||||
(
|
||||
clear,
|
||||
0,
|
||||
Resolution / k_ThreadGroupSizeX,
|
||||
Resolution / k_ThreadGroupSizeY,
|
||||
Slices
|
||||
);
|
||||
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
|
||||
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTarget);
|
||||
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearMask, Color.black);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, Color.clear);
|
||||
wrapper.Dispatch(Resolution / k_ThreadGroupSizeX, Resolution / k_ThreadGroupSizeY, Slices);
|
||||
}
|
||||
|
||||
// Pack height data into alpha channel.
|
||||
@@ -355,15 +408,21 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
|
||||
// Transfer Dynamic Waves to Animated Waves.
|
||||
if (_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves) && _Water._DynamicWavesLod.Enabled)
|
||||
if ((_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves) || PreserveWaveQuality) && _Water._DynamicWavesLod.Enabled)
|
||||
{
|
||||
buffer.BeginSample(k_DrawCombine);
|
||||
// Clearing not required as we overwrite enter texture.
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_DynamicWavesTarget, DataTexture.descriptor);
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader, 9);
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader._Shader, _CombineShader._CombineDynamicWavesKernel);
|
||||
|
||||
wrapper.SetTexture(ShaderIDs.s_DynamicWavesTarget, ShaderIDs.s_DynamicWavesTarget);
|
||||
// Flow keyword is already set, and Dynamic Waves already bound. If binding Dynamic
|
||||
// Waves becomes kernel specific (eg binding textures), then we need to rebind.
|
||||
|
||||
// Start with last LOD which does not combine.
|
||||
wrapper.SetKeyword(_CombineShader._CombineKeyword, false);
|
||||
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, ShaderIDs.s_DynamicWavesTarget);
|
||||
|
||||
_Water._DynamicWavesLod.Bind(wrapper);
|
||||
|
||||
@@ -373,18 +432,21 @@ namespace WaveHarmonic.Crest
|
||||
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
|
||||
wrapper.Dispatch(threadSize, threadSize, 1);
|
||||
|
||||
// Change to kernel with combine enabled.
|
||||
if (slice == lastSlice)
|
||||
{
|
||||
wrapper = new(buffer, _CombineShader, 8);
|
||||
// From here on, use combine.
|
||||
wrapper.SetKeyword(_CombineShader._CombineKeyword, s_Combine);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy Dynamic Waves displacement into Animated Waves.
|
||||
if (WaterResources.Instance.Compute._Blit != null)
|
||||
{
|
||||
wrapper = new(buffer, _CombineShader, 10);
|
||||
wrapper.SetTexture(ShaderIDs.s_AnimatedWavesTarget, DataTexture);
|
||||
wrapper.SetTexture(ShaderIDs.s_DynamicWavesTarget, ShaderIDs.s_DynamicWavesTarget);
|
||||
var compute = WaterResources.Instance._ComputeLibrary._BlitCompute;
|
||||
wrapper = new(buffer, compute._Shader, 0);
|
||||
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Source, ShaderIDs.s_DynamicWavesTarget);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.Dispatch(threadSize, threadSize, Slices);
|
||||
}
|
||||
|
||||
@@ -393,7 +455,10 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
// Query collisions including Dynamic Waves.
|
||||
// Does not require copying the water level as they are added with zero alpha.
|
||||
Provider.UpdateQueries(_Water, CollisionLayer.AfterDynamicWaves);
|
||||
if (_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves))
|
||||
{
|
||||
Provider.UpdateQueries(_Water, CollisionLayer.AfterDynamicWaves);
|
||||
}
|
||||
}
|
||||
|
||||
if (_CollisionLayers.HasFlag(CollisionLayers.Displacement))
|
||||
@@ -408,13 +473,6 @@ namespace WaveHarmonic.Crest
|
||||
Queryable?.UpdateQueries(_Water);
|
||||
}
|
||||
|
||||
if (BufferCount > 1)
|
||||
{
|
||||
// Update current and previous. Latter for MVs and/or VFX.
|
||||
Shader.SetGlobalTexture(_TextureSourceShaderID, _Targets.Previous(1));
|
||||
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
}
|
||||
|
||||
buffer.EndSample(ID);
|
||||
}
|
||||
|
||||
@@ -426,50 +484,59 @@ namespace WaveHarmonic.Crest
|
||||
/// <summary>
|
||||
/// Provides water shape to CPU.
|
||||
/// </summary>
|
||||
private protected override ICollisionProvider CreateProvider(bool enable)
|
||||
private protected override ICollisionProvider CreateProvider(bool onEnable)
|
||||
{
|
||||
ICollisionProvider result = null;
|
||||
ICollisionProvider provider = ICollisionProvider.None;
|
||||
|
||||
Queryable?.CleanUp();
|
||||
|
||||
if (!enable)
|
||||
// Respect user choice and early exit. If not in OnEnable, then none provider.
|
||||
// Do not check Enabled, as it includes _Valid which checks for GPU.
|
||||
if (!_Enabled || !onEnable)
|
||||
{
|
||||
return ICollisionProvider.None;
|
||||
return provider;
|
||||
}
|
||||
|
||||
switch (_CollisionSource)
|
||||
var source = QuerySource;
|
||||
|
||||
if (_Water.Surface.IsQuadMesh)
|
||||
{
|
||||
case CollisionSource.None:
|
||||
result = ICollisionProvider.None;
|
||||
break;
|
||||
case CollisionSource.GPU:
|
||||
if (_Valid && !_Water.IsRunningWithoutGraphics)
|
||||
source = LodQuerySource.None;
|
||||
}
|
||||
|
||||
switch (source)
|
||||
{
|
||||
case LodQuerySource.GPU:
|
||||
{
|
||||
if (_Valid)
|
||||
{
|
||||
result = new CollisionQueryWithPasses(_Water);
|
||||
provider = ICollisionProvider.Create(_Water);
|
||||
}
|
||||
|
||||
if (_Water.IsRunningWithoutGraphics)
|
||||
{
|
||||
Debug.LogError($"Crest: GPU queries not supported in headless/batch mode. To resolve, assign an Animated Wave Settings asset to the {nameof(WaterRenderer)} component and set the Collision Source to be a CPU option.");
|
||||
Debug.LogError("Crest: GPU queries requires a GPU. Please consider CPU queries if running from a server without a GPU.");
|
||||
}
|
||||
|
||||
break;
|
||||
case CollisionSource.CPU:
|
||||
}
|
||||
case LodQuerySource.CPU:
|
||||
{
|
||||
if (_BakedWaveData != null)
|
||||
{
|
||||
result = _BakedWaveData.CreateCollisionProvider();
|
||||
provider = _BakedWaveData.CreateCollisionProvider();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
// This should not be hit, but can be if compute shaders aren't loaded correctly.
|
||||
// They will print out appropriate errors. Don't just return null and have null reference
|
||||
// exceptions spamming the logs.
|
||||
return ICollisionProvider.None;
|
||||
}
|
||||
// This should not be hit, but can be if compute shaders aren't loaded correctly.
|
||||
// They will print out appropriate errors. Don't just return null and have null
|
||||
// reference exceptions spamming the logs.
|
||||
provider ??= ICollisionProvider.None;
|
||||
|
||||
return result;
|
||||
return provider;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -484,15 +551,17 @@ namespace WaveHarmonic.Crest
|
||||
public readonly float _ViewerAltitudeLevelAlpha;
|
||||
public readonly int _Slice;
|
||||
public readonly int _Slices;
|
||||
public readonly bool _HighQualityCombine;
|
||||
|
||||
public WavelengthFilter(WaterRenderer water, int slice)
|
||||
public WavelengthFilter(WaterRenderer water, int slice, int resolution)
|
||||
{
|
||||
_Slice = slice;
|
||||
_Slices = water.LodLevels;
|
||||
_Maximum = water.MaximumWavelength(slice);
|
||||
_Maximum = water.MaximumWavelength(slice, resolution);
|
||||
_Minimum = _Maximum * 0.5f;
|
||||
_TransitionThreshold = water.MaximumWavelength(_Slices - 1) * 0.5f;
|
||||
_TransitionThreshold = water.MaximumWavelength(_Slices - 1, resolution) * 0.5f;
|
||||
_ViewerAltitudeLevelAlpha = water.ViewerAltitudeLevelAlpha;
|
||||
_HighQualityCombine = water.AnimatedWavesLod.PreserveWaveQuality;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +582,7 @@ namespace WaveHarmonic.Crest
|
||||
// If approaching end of lod chain, start smoothly transitioning any large wavelengths across last two lods
|
||||
if (wavelength >= filter._TransitionThreshold)
|
||||
{
|
||||
if (filter._Slice == filter._Slices - 2)
|
||||
if (filter._Slice == filter._Slices - 2 && !filter._HighQualityCombine)
|
||||
{
|
||||
return 1f - filter._ViewerAltitudeLevelAlpha;
|
||||
}
|
||||
@@ -529,12 +598,12 @@ namespace WaveHarmonic.Crest
|
||||
return 1f;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
return filter._HighQualityCombine ? 1f : 0f;
|
||||
}
|
||||
|
||||
internal static float FilterByWavelength(WaterRenderer water, int slice, float wavelength)
|
||||
internal static float FilterByWavelength(WaterRenderer water, int slice, float wavelength, int resolution)
|
||||
{
|
||||
return FilterByWavelength(new(water, slice), wavelength);
|
||||
return FilterByWavelength(new(water, slice, resolution), wavelength);
|
||||
}
|
||||
|
||||
|
||||
@@ -550,8 +619,83 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple Viewpoints
|
||||
partial class AnimatedWavesLod
|
||||
{
|
||||
readonly Dictionary<Camera, RenderTexture> _AdditionalCameraData = new();
|
||||
|
||||
internal override void LoadCameraData(Camera camera)
|
||||
{
|
||||
base.LoadCameraData(camera);
|
||||
|
||||
if (!Persistent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
_PersistentDataTexture = CreateLodDataTextures();
|
||||
Clear(_PersistentDataTexture);
|
||||
_AdditionalCameraData.Add(camera, _PersistentDataTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
_PersistentDataTexture = _AdditionalCameraData[camera];
|
||||
}
|
||||
}
|
||||
|
||||
internal override void StoreCameraData(Camera camera)
|
||||
{
|
||||
base.StoreCameraData(camera);
|
||||
|
||||
if (!Persistent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_AdditionalCameraData[camera] = _PersistentDataTexture;
|
||||
}
|
||||
|
||||
internal override void RemoveCameraData(Camera camera)
|
||||
{
|
||||
base.RemoveCameraData(camera);
|
||||
|
||||
if (_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
var rt = _AdditionalCameraData[camera];
|
||||
if (rt != null) rt.Release();
|
||||
Helpers.Destroy(rt);
|
||||
_AdditionalCameraData.Remove(camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
partial class AnimatedWavesLod
|
||||
{
|
||||
float GetWaveResolutionMultiplier()
|
||||
{
|
||||
return PreserveWaveQuality ? 1f : _WaveResolutionMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
partial class AnimatedWavesLod
|
||||
{
|
||||
[@HideInInspector]
|
||||
[@System.Obsolete("Please use QuerySource instead.")]
|
||||
[Tooltip("Where to obtain water shape on CPU for physics / gameplay.")]
|
||||
[@GenerateAPI(Setter.Internal)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionSource _CollisionSource = CollisionSource.GPU;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Editor
|
||||
partial class AnimatedWavesLod
|
||||
{
|
||||
[@OnChange]
|
||||
private protected override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
@@ -560,13 +704,10 @@ namespace WaveHarmonic.Crest
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_CollisionLayers):
|
||||
case nameof(_CollisionSource):
|
||||
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
|
||||
Queryable?.CleanUp();
|
||||
InitializeProvider(true);
|
||||
ResetQueryChange();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
@@ -37,11 +36,6 @@ namespace WaveHarmonic.Crest
|
||||
[@DecoratedField, SerializeField]
|
||||
internal DefaultClippingState _DefaultClippingState = DefaultClippingState.NothingClipped;
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_ClipByDefault = Shader.PropertyToID("g_Crest_ClipByDefault");
|
||||
}
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(0f, 1f, 1f, 0.5f);
|
||||
|
||||
internal override string ID => "Clip";
|
||||
@@ -50,6 +44,7 @@ namespace WaveHarmonic.Crest
|
||||
private protected override Color ClearColor => _DefaultClippingState == DefaultClippingState.EverythingClipped ? Color.white : Color.black;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
private protected override bool RequiresClearBorder => true;
|
||||
internal override bool SkipEndOfFrame => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
@@ -65,24 +60,6 @@ namespace WaveHarmonic.Crest
|
||||
_TextureFormat = GraphicsFormat.R8_UNorm;
|
||||
}
|
||||
|
||||
internal override void SetGlobals(bool enable)
|
||||
{
|
||||
base.SetGlobals(enable);
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ClipByDefault, enable && Enabled ? (float)_DefaultClippingState : (float)DefaultClippingState.NothingClipped);
|
||||
}
|
||||
|
||||
internal override void Disable()
|
||||
{
|
||||
base.Disable();
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ClipByDefault, (float)DefaultClippingState.NothingClipped);
|
||||
}
|
||||
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
base.BuildCommandBuffer(water, buffer);
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ClipByDefault, (float)_DefaultClippingState);
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
@@ -98,7 +75,7 @@ namespace WaveHarmonic.Crest
|
||||
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
|
||||
|
||||
// Change default clipping state.
|
||||
_TargetsToClear = Mathf.Max(1, _TargetsToClear);
|
||||
_TargetsToClear = true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -41,19 +41,19 @@ namespace WaveHarmonic.Crest
|
||||
internal ShorelineVolumeColorSource _ShorelineColorSource;
|
||||
|
||||
[Tooltip("Color of the shoreline color.")]
|
||||
[@Predicated(nameof(_ShorelineColorSource), inverted: false, nameof(ShorelineVolumeColorSource.None), hide: true)]
|
||||
[@Hide(nameof(_ShorelineColorSource), nameof(ShorelineVolumeColorSource.None))]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
private protected Color _ShorelineColor;
|
||||
|
||||
[Tooltip("The maximum distance of the shoreline color.\n\nIf using Depth, then it is maximum depth.")]
|
||||
[@Predicated(nameof(_ShorelineColorSource), inverted: false, nameof(ShorelineVolumeColorSource.None), hide: true)]
|
||||
[@Hide(nameof(_ShorelineColorSource), nameof(ShorelineVolumeColorSource.None))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _ShorelineColorMaximumDistance = 10f;
|
||||
|
||||
[Tooltip("Shoreline color falloff value.")]
|
||||
[@Predicated(nameof(_ShorelineColorSource), inverted: false, nameof(ShorelineVolumeColorSource.None), hide: true)]
|
||||
[@Hide(nameof(_ShorelineColorSource), nameof(ShorelineVolumeColorSource.None))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _ShorelineColorFalloff = 2f;
|
||||
@@ -69,6 +69,7 @@ namespace WaveHarmonic.Crest
|
||||
private protected abstract void SetShorelineColor(Color previous, Color current);
|
||||
private protected Vector4 _ShorelineColorValue;
|
||||
ShorelineColorInput _ShorelineColorInput;
|
||||
internal override bool SkipEndOfFrame => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace WaveHarmonic.Crest
|
||||
/// <summary>
|
||||
/// Data that gives depth of the water (height of sea level above water floor).
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_QuerySource), Filtered.Mode.Exclude, (int)LodQuerySource.CPU)]
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class DepthLod : Lod<IDepthProvider>
|
||||
{
|
||||
@@ -33,11 +34,12 @@ namespace WaveHarmonic.Crest
|
||||
// We want the clear color to be the mininimum terrain height (-1000m).
|
||||
// Mathf.Infinity can cause problems for distance.
|
||||
static readonly Color s_NullColor = new(-k_DepthBaseline, k_DepthBaseline, 0, 0);
|
||||
static Color NullColor => Helpers.IsWebGPU ? new(float.MinValue, float.MaxValue, 0, 0) : s_NullColor;
|
||||
|
||||
internal override string ID => "Depth";
|
||||
internal override string Name => "Water Depth";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => s_NullColor;
|
||||
private protected override Color ClearColor => NullColor;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
@@ -56,7 +58,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
if (_NullTexture == null)
|
||||
{
|
||||
var texture = TextureArrayHelpers.CreateTexture2D(s_NullColor, UnityEngine.TextureFormat.RFloat);
|
||||
var texture = TextureArrayHelpers.CreateTexture2D(NullColor, UnityEngine.TextureFormat.RFloat);
|
||||
texture.name = $"_Crest_{ID}LodTemporaryDefaultTexture";
|
||||
_NullTexture = TextureArrayHelpers.CreateTexture2DArray(texture, k_MaximumSlices);
|
||||
_NullTexture.name = $"_Crest_{ID}LodDefaultTexture";
|
||||
@@ -71,13 +73,16 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
_Enabled = true;
|
||||
_TextureFormat = GraphicsFormat.R16G16_SFloat;
|
||||
_MaximumQueryCount = 512;
|
||||
}
|
||||
|
||||
private protected override IDepthProvider CreateProvider(bool enable)
|
||||
private protected override IDepthProvider CreateProvider(bool onEnable)
|
||||
{
|
||||
Queryable?.CleanUp();
|
||||
// Depth is GPU only, and can only be queried using the compute path.
|
||||
return enable && Enabled ? new DepthQuery(_Water) : IDepthProvider.None;
|
||||
return onEnable && Enabled && QuerySource == LodQuerySource.GPU
|
||||
? IDepthProvider.Create(_Water)
|
||||
: IDepthProvider.None;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
|
||||
@@ -97,12 +97,6 @@ namespace WaveHarmonic.Crest
|
||||
simMaterial.SetFloat(AnimatedWavesLod.ShaderIDs.s_AttenuationInShallows, _AttenuationInShallows);
|
||||
}
|
||||
|
||||
private protected override void GetSubstepData(float timeToSimulate, out int substeps, out float delta)
|
||||
{
|
||||
substeps = Mathf.FloorToInt(timeToSimulate * _SimulationFrequency);
|
||||
delta = substeps > 0 ? (1f / _SimulationFrequency) : 0f;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
@@ -10,11 +11,17 @@ namespace WaveHarmonic.Crest
|
||||
/// <summary>
|
||||
/// Simulates horizontal motion of water.
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_QuerySource), Filtered.Mode.Exclude, (int)LodQuerySource.CPU)]
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class FlowLod : Lod<IFlowProvider>
|
||||
{
|
||||
const string k_FlowKeyword = "CREST_FLOW_ON_INTERNAL";
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_Flow = Shader.PropertyToID("g_Crest_Flow");
|
||||
}
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(0f, 0f, 1f, 0.5f);
|
||||
|
||||
internal override string ID => "Flow";
|
||||
@@ -34,6 +41,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
_Resolution = 128;
|
||||
_TextureFormat = GraphicsFormat.R16G16_SFloat;
|
||||
_MaximumQueryCount = 1024;
|
||||
}
|
||||
|
||||
internal override void Enable()
|
||||
@@ -50,11 +58,37 @@ namespace WaveHarmonic.Crest
|
||||
Shader.DisableKeyword(k_FlowKeyword);
|
||||
}
|
||||
|
||||
private protected override IFlowProvider CreateProvider(bool enable)
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
var time = water.CurrentTime;
|
||||
var period = 1f;
|
||||
var half = period * 0.5f;
|
||||
var offset0 = Helpers.Fmod(time, period);
|
||||
var weight0 = offset0 / half;
|
||||
if (weight0 > 1f) weight0 = 2f - weight0;
|
||||
var offset1 = Helpers.Fmod(time + half, period);
|
||||
var weight1 = 1f - weight0;
|
||||
|
||||
Shader.SetGlobalVector(ShaderIDs.s_Flow, new(offset0, weight0, offset1, weight1));
|
||||
|
||||
base.BuildCommandBuffer(water, buffer);
|
||||
}
|
||||
|
||||
private protected override IFlowProvider CreateProvider(bool onEnable)
|
||||
{
|
||||
Queryable?.CleanUp();
|
||||
// Flow is GPU only, and can only be queried using the compute path.
|
||||
return enable && Enabled ? new FlowQuery(_Water) : IFlowProvider.None;
|
||||
return onEnable && Enabled && QuerySource == LodQuerySource.GPU
|
||||
? IFlowProvider.Create(_Water)
|
||||
: IFlowProvider.None;
|
||||
}
|
||||
|
||||
internal override void SetGlobals(bool onEnable)
|
||||
{
|
||||
base.SetGlobals(onEnable);
|
||||
|
||||
// Zero offset. Use the first sample.
|
||||
Shader.SetGlobalVector(ShaderIDs.s_Flow, new(0, 1, 0, 0));
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
|
||||
@@ -69,13 +69,6 @@ namespace WaveHarmonic.Crest
|
||||
properties.SetInteger(ShaderIDs.s_MinimumWavesSlice, Settings.FilterWaves);
|
||||
}
|
||||
|
||||
private protected override void GetSubstepData(float timeToSimulate, out int substeps, out float delta)
|
||||
{
|
||||
substeps = Mathf.FloorToInt(timeToSimulate * _SimulationFrequency);
|
||||
|
||||
delta = substeps > 0 ? (1f / _SimulationFrequency) : 0f;
|
||||
}
|
||||
|
||||
internal FoamLod()
|
||||
{
|
||||
_Enabled = true;
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
base.Destroy();
|
||||
Helpers.Destroy(_DefaultSettings);
|
||||
_DefaultSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +94,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
base.Destroy();
|
||||
Helpers.Destroy(_DefaultSettings);
|
||||
_DefaultSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/WaterAppearance.html#volume-color-inputs")]
|
||||
public sealed partial class AbsorptionLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
#if d_CrestPaint
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Paint;
|
||||
#else
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/WaterAppearance.html#albedo-inputs")]
|
||||
public sealed partial class AlbedoLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace WaveHarmonic.Crest
|
||||
DisplacementPass _DisplacementPass = DisplacementPass.LodIndependent;
|
||||
|
||||
[Tooltip("Whether to filter this input by wavelength.\n\nIf disabled, it will render to all LODs.")]
|
||||
[@Predicated(nameof(_DisplacementPass), inverted: true, nameof(DisplacementPass.LodDependent))]
|
||||
[@Enable(nameof(_DisplacementPass), nameof(DisplacementPass.LodDependent))]
|
||||
[@GenerateAPI]
|
||||
[DecoratedField, SerializeField]
|
||||
bool _FilterByWavelength;
|
||||
|
||||
[Tooltip("Which octave to render into.\n\nFor example, set this to 2 to render into the 2m-4m octave. These refer to the same octaves as the wave spectrum editor.")]
|
||||
[@Predicated(nameof(_DisplacementPass), inverted: true, nameof(DisplacementPass.LodDependent))]
|
||||
[@Predicated(nameof(_FilterByWavelength))]
|
||||
[@Enable(nameof(_DisplacementPass), nameof(DisplacementPass.LodDependent))]
|
||||
[@Enable(nameof(_FilterByWavelength))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _OctaveWavelength = 512f;
|
||||
@@ -49,7 +49,7 @@ namespace WaveHarmonic.Crest
|
||||
float _MaximumDisplacementHorizontal = 0f;
|
||||
|
||||
[Tooltip("Use the bounding box of an attached renderer component to determine the maximum vertical displacement.")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Renderer))]
|
||||
[@Enable(nameof(_Mode), nameof(LodInputMode.Renderer))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _ReportRendererBounds = false;
|
||||
@@ -63,23 +63,35 @@ namespace WaveHarmonic.Crest
|
||||
_FollowHorizontalWaveMotion = true;
|
||||
}
|
||||
|
||||
internal override float Filter(WaterRenderer water, int slice)
|
||||
private protected override void Initialize()
|
||||
{
|
||||
return AnimatedWavesLod.FilterByWavelength(water, slice, _FilterByWavelength ? _OctaveWavelength : 0f);
|
||||
base.Initialize();
|
||||
_Reporter ??= new(this);
|
||||
_DisplacementReporter = _Reporter;
|
||||
}
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnUpdate(water);
|
||||
base.OnDisable();
|
||||
_DisplacementReporter = null;
|
||||
}
|
||||
|
||||
internal override float Filter(WaterRenderer water, int slice)
|
||||
{
|
||||
return AnimatedWavesLod.FilterByWavelength(water, slice, _FilterByWavelength ? _OctaveWavelength : 0f, water.AnimatedWavesLod.Resolution);
|
||||
}
|
||||
|
||||
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical)
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var maxDispVert = _MaximumDisplacementVertical;
|
||||
|
||||
// let water system know how far from the sea level this shape may displace the surface
|
||||
// TODO: we need separate min/max vertical displacement to be optimal.
|
||||
if (_ReportRendererBounds)
|
||||
{
|
||||
var range = Data.HeightRange;
|
||||
@@ -89,19 +101,40 @@ namespace WaveHarmonic.Crest
|
||||
maxDispVert = Mathf.Max(maxDispVert, Mathf.Abs(seaLevel - minY), Mathf.Abs(seaLevel - maxY));
|
||||
}
|
||||
|
||||
if (_MaximumDisplacementHorizontal > 0f || maxDispVert > 0f)
|
||||
var rect = Data.Rect;
|
||||
|
||||
if (bounds.Overlaps(rect, false))
|
||||
{
|
||||
water.ReportMaximumDisplacement(_MaximumDisplacementHorizontal, maxDispVert, 0f);
|
||||
horizontal += _MaximumDisplacementHorizontal;
|
||||
vertical += maxDispVert;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float ReportWaveDisplacement(WaterRenderer water, float displacement)
|
||||
{
|
||||
return displacement;
|
||||
}
|
||||
}
|
||||
|
||||
partial class AnimatedWavesLodInput : ISerializationCallbackReceiver
|
||||
partial class AnimatedWavesLodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
Reporter _Reporter;
|
||||
|
||||
sealed class Reporter : IReportsDisplacement, IReportWaveDisplacement
|
||||
{
|
||||
readonly AnimatedWavesLodInput _Input;
|
||||
public Reporter(AnimatedWavesLodInput input) => _Input = input;
|
||||
public bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(water, ref bounds, ref horizontal, ref vertical);
|
||||
public float ReportWaveDisplacement(WaterRenderer water, float displacement) => _Input.ReportWaveDisplacement(water, displacement);
|
||||
}
|
||||
}
|
||||
|
||||
partial class AnimatedWavesLodInput
|
||||
{
|
||||
private protected override int Version => Mathf.Max(base.Version, 1);
|
||||
|
||||
[System.Obsolete("Please use DisplacementPass instead.")]
|
||||
[Tooltip("When to render the input into the displacement data.\n\nIf enabled, it renders into all LODs of the simulation after the combine step rather than before with filtering. Furthermore, it will also affect dynamic waves.")]
|
||||
@@ -110,30 +143,25 @@ namespace WaveHarmonic.Crest
|
||||
[HideInInspector]
|
||||
bool _RenderPostCombine = true;
|
||||
|
||||
[System.Obsolete]
|
||||
void SetRenderPostCombine(bool previous, bool current, bool force = false)
|
||||
{
|
||||
if (previous == current && !force) return;
|
||||
_DisplacementPass = current ? DisplacementPass.LodIndependent : DisplacementPass.LodDependent;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
base.OnMigrate();
|
||||
|
||||
if (_Version < 1)
|
||||
{
|
||||
SetRenderPostCombine(_RenderPostCombine, _RenderPostCombine, force: true);
|
||||
_Version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,22 +16,17 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/Clipping.html#clip-inputs")]
|
||||
public sealed partial class ClipLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[@Heading("Primitive")]
|
||||
|
||||
[Tooltip("The primitive to render (signed distance) into the simulation.")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Primitive), hide: true)]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Primitive))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal LodInputPrimitive _Primitive = LodInputPrimitive.Cube;
|
||||
|
||||
// Only Mode.Primitive SDF supports inverted.
|
||||
[Tooltip("Removes clip surface data instead of adding it.")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Primitive), hide: true)]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Primitive))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _Inverted;
|
||||
@@ -39,8 +34,7 @@ namespace WaveHarmonic.Crest
|
||||
[@Heading("Culling")]
|
||||
|
||||
[Tooltip("Prevents inputs from cancelling each other out when aligned vertically.\n\nIt is imperfect so custom logic might be needed for your use case.")]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Paint), hide: true)]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Renderer))]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Renderer))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _WaterHeightDistanceCulling = false;
|
||||
|
||||
@@ -21,11 +21,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/ShallowsAndShorelines.html#sea-floor-depth")]
|
||||
public sealed partial class DepthLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[@Label("Relative Height")]
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,11 +13,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/Waves.html#dynamic-waves-inputs")]
|
||||
public sealed partial class DynamicWavesLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -16,11 +14,6 @@ namespace WaveHarmonic.Crest
|
||||
[FilterEnum(nameof(_Blend), Filtered.Mode.Include, (int)LodInputBlend.Additive, (int)LodInputBlend.Alpha)]
|
||||
public sealed partial class FlowLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
// Countering will incur thrashing. Previously we allowed the option so the
|
||||
// serialized value could be "false".
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -16,11 +14,6 @@ namespace WaveHarmonic.Crest
|
||||
[@FilterEnum(nameof(_Blend), Filtered.Mode.Include, (int)LodInputBlend.Additive, (int)LodInputBlend.Maximum)]
|
||||
public sealed partial class FoamLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
#if d_CrestPaint
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Paint;
|
||||
#else
|
||||
|
||||
@@ -19,11 +19,12 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[Tooltip("Whether to use the manual \"Height Range\" for water chunk culling.\n\nMandatory for non mesh inputs like \"Texture\".")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
[@InlineToggle]
|
||||
[@SerializeField]
|
||||
bool _OverrideHeight;
|
||||
|
||||
[Tooltip("The minimum and maximum height value to report for water chunk culling.")]
|
||||
[@Predicated(nameof(_OverrideHeight))]
|
||||
[@Enable(nameof(_OverrideHeight))]
|
||||
[@Range(-100, 100, Range.Clamp.None)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
@@ -38,8 +39,6 @@ namespace WaveHarmonic.Crest
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Geometry;
|
||||
|
||||
internal Rect _Rect;
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
@@ -56,35 +55,52 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
base.Initialize();
|
||||
_Reporter ??= new(this);
|
||||
WaterChunkRenderer.HeightReporters.Add(_Reporter);
|
||||
_HeightReporter = _Reporter;
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
WaterChunkRenderer.HeightReporters.Remove(_Reporter);
|
||||
_HeightReporter = null;
|
||||
}
|
||||
|
||||
bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum)
|
||||
bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum)
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_Rect = Data.Rect;
|
||||
|
||||
// These modes do not provide a height yet.
|
||||
if (!Data.HasHeightRange && !_OverrideHeight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bounds.Overlaps(_Rect, false))
|
||||
var rect = Data.Rect;
|
||||
|
||||
if (bounds.Overlaps(rect, false))
|
||||
{
|
||||
var range = _OverrideHeight ? _HeightRange : Data.HeightRange;
|
||||
minimum = range.x;
|
||||
maximum = range.y;
|
||||
range *= Weight;
|
||||
|
||||
// Make relative to sea level.
|
||||
range.x -= water.SeaLevel;
|
||||
range.y -= water.SeaLevel;
|
||||
|
||||
var current = new Vector2(minimum, maximum);
|
||||
|
||||
range = _Blend switch
|
||||
{
|
||||
LodInputBlend.Additive => range + current,
|
||||
LodInputBlend.Minimum => Vector2.Min(range, current),
|
||||
LodInputBlend.Maximum => Vector2.Max(range, current),
|
||||
_ => range,
|
||||
};
|
||||
|
||||
minimum = Mathf.Min(minimum, range.x);
|
||||
maximum = Mathf.Max(maximum, range.y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,33 +116,25 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
readonly LevelLodInput _Input;
|
||||
public Reporter(LevelLodInput input) => _Input = input;
|
||||
public bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum) => _Input.ReportHeight(ref bounds, ref minimum, ref maximum);
|
||||
public bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum) =>
|
||||
_Input.ReportHeight(water, ref bounds, ref minimum, ref maximum);
|
||||
}
|
||||
}
|
||||
|
||||
partial class LevelLodInput : ISerializationCallbackReceiver
|
||||
partial class LevelLodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
private protected override int Version => Mathf.Max(base.Version, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
base.OnMigrate();
|
||||
|
||||
// Version 1
|
||||
// - Implemented blend mode but default value was serialized as Additive.
|
||||
if (_Version < 1)
|
||||
{
|
||||
if (_Mode is LodInputMode.Spline or LodInputMode.Renderer) _Blend = LodInputBlend.Off;
|
||||
_Version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
MonoBehaviour Component { get; }
|
||||
|
||||
IReportsHeight HeightReporter => null;
|
||||
IReportsDisplacement DisplacementReporter => null;
|
||||
IReportWaveDisplacement WaveDisplacementReporter => null;
|
||||
|
||||
// Allow sorting within a queue. Callers can pass in things like sibling index to
|
||||
// get deterministic sorting.
|
||||
int Order => Queue * k_QueueMaximumSubIndex + Mathf.Min(Component.transform.GetSiblingIndex(), k_QueueMaximumSubIndex - 1);
|
||||
@@ -70,9 +74,9 @@ namespace WaveHarmonic.Crest
|
||||
// For others it is a case of only supporting unsupported mode(s).
|
||||
|
||||
[Tooltip("Scales the input.")]
|
||||
[@Predicated(typeof(AlbedoLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(DepthLodInput), inverted: true, hide: true)]
|
||||
[@Hide(typeof(AlbedoLodInput))]
|
||||
[@Hide(typeof(ClipLodInput))]
|
||||
[@Hide(typeof(DepthLodInput))]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
@@ -84,17 +88,17 @@ namespace WaveHarmonic.Crest
|
||||
int _Queue;
|
||||
|
||||
[Tooltip("How this input blends into existing data.\n\nSimilar to blend operations in shaders. For inputs which have materials, use the blend functionality on the shader/material.")]
|
||||
[@Predicated(typeof(AbsorptionLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(AlbedoLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(AnimatedWavesLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(DepthLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(DynamicWavesLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(ScatteringLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(ShadowLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Primitive))]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Renderer))]
|
||||
[@Hide(typeof(AbsorptionLodInput))]
|
||||
[@Hide(typeof(AlbedoLodInput))]
|
||||
[@Hide(typeof(AnimatedWavesLodInput))]
|
||||
[@Hide(typeof(ClipLodInput))]
|
||||
[@Hide(typeof(DepthLodInput))]
|
||||
[@Hide(typeof(DynamicWavesLodInput))]
|
||||
[@Hide(typeof(ScatteringLodInput))]
|
||||
[@Hide(typeof(ShadowLodInput))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Primitive))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Renderer))]
|
||||
[@Filtered]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
@@ -102,35 +106,35 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Feather")]
|
||||
[Tooltip("The width of the feathering to soften the edges to blend inputs.\n\nInputs that do not support feathering will have this field disabled or hidden in UI.")]
|
||||
[@Predicated(typeof(AlbedoLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(AnimatedWavesLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(DepthLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(DynamicWavesLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(LevelLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Renderer))]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Primitive))]
|
||||
[@Hide(typeof(AlbedoLodInput))]
|
||||
[@Hide(typeof(AnimatedWavesLodInput))]
|
||||
[@Hide(typeof(ClipLodInput))]
|
||||
[@Hide(typeof(DepthLodInput))]
|
||||
[@Hide(typeof(DynamicWavesLodInput))]
|
||||
[@Hide(typeof(LevelLodInput))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Renderer))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Primitive))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _FeatherWidth = 0.1f;
|
||||
|
||||
[Tooltip("How this input responds to horizontal displacement.\n\nIf false, data will not move horizontally with the waves. Has a small performance overhead when disabled. Only suitable for inputs of small size.")]
|
||||
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(FlowLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(LevelLodInput), inverted: true, hide: true)]
|
||||
[@Predicated(typeof(ShapeWaves), inverted: true, hide: true)]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Spline))]
|
||||
[@Hide(typeof(ClipLodInput))]
|
||||
[@Hide(typeof(FlowLodInput))]
|
||||
[@Hide(typeof(LevelLodInput))]
|
||||
[@Hide(typeof(ShapeWaves))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Spline))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
private protected bool _FollowHorizontalWaveMotion = false;
|
||||
|
||||
[@Heading("Mode")]
|
||||
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Unset), hide: true)]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Primitive), hide: true)]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global), hide: true)]
|
||||
[@Hide(nameof(_Mode), nameof(LodInputMode.Unset))]
|
||||
[@Hide(nameof(_Mode), nameof(LodInputMode.Primitive))]
|
||||
[@Hide(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@Stripped]
|
||||
[SerializeReference]
|
||||
internal LodInputData _Data;
|
||||
@@ -140,7 +144,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Group("Debug", order = k_DebugGroupOrder)]
|
||||
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _DrawBounds;
|
||||
|
||||
@@ -376,6 +380,16 @@ namespace WaveHarmonic.Crest
|
||||
//
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void SetMode(LodInputMode previous, LodInputMode current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (!isActiveAndEnabled) { ChangeMode(Mode); return; }
|
||||
OnDisable();
|
||||
ChangeMode(Mode);
|
||||
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
[@OnChange(skipIfInactive: false)]
|
||||
void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
@@ -385,15 +399,11 @@ namespace WaveHarmonic.Crest
|
||||
SetQueue((int)previousValue, _Queue);
|
||||
break;
|
||||
case nameof(_Mode):
|
||||
if (!isActiveAndEnabled) { ChangeMode(Mode); break; }
|
||||
OnDisable();
|
||||
ChangeMode(Mode);
|
||||
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
|
||||
OnEnable();
|
||||
SetMode((LodInputMode)previousValue, Mode);
|
||||
break;
|
||||
case nameof(_Blend):
|
||||
// TODO: Make compatible with disabled.
|
||||
if (isActiveAndEnabled) Data.OnChange($"../{propertyPath}", previousValue);
|
||||
if (isActiveAndEnabled) Data?.OnChange($"../{propertyPath}", previousValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -467,6 +477,9 @@ namespace WaveHarmonic.Crest
|
||||
partial class LodInput
|
||||
{
|
||||
Input _Input;
|
||||
private protected IReportsHeight _HeightReporter;
|
||||
internal IReportsDisplacement _DisplacementReporter;
|
||||
private protected IReportWaveDisplacement _WaveDisplacementReporter;
|
||||
|
||||
sealed class Input : ILodInput
|
||||
{
|
||||
@@ -478,6 +491,9 @@ namespace WaveHarmonic.Crest
|
||||
public int Pass => _Input.Pass;
|
||||
public Rect Rect => _Input.Rect;
|
||||
public MonoBehaviour Component => _Input;
|
||||
public IReportsHeight HeightReporter => _Input._HeightReporter;
|
||||
public IReportsDisplacement DisplacementReporter => _Input._DisplacementReporter;
|
||||
public IReportWaveDisplacement WaveDisplacementReporter => _Input._WaveDisplacementReporter;
|
||||
public float Filter(WaterRenderer water, int slice) => _Input.Filter(water, slice);
|
||||
public void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1) => _Input.Draw(lod, buffer, target, pass, weight, slice);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
@@ -24,7 +25,7 @@ namespace WaveHarmonic.Crest
|
||||
/// Data storage for an input, pertinent to the associated input mode.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class LodInputData
|
||||
public abstract partial class LodInputData : Versioned
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
internal LodInput _Input;
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
|
||||
#endif
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,7 +38,9 @@ namespace WaveHarmonic.Crest
|
||||
[Tooltip("Populates the DepthProbe in Start.")]
|
||||
OnStart = 0,
|
||||
|
||||
// EveryFrame = 1,
|
||||
/// <inheritdoc cref="Generated.DepthProbeRefreshMode.EveryFrame"/>
|
||||
[Tooltip("Populates the DepthProbe every frame.")]
|
||||
EveryFrame = 1,
|
||||
|
||||
/// <inheritdoc cref="Generated.DepthProbeRefreshMode.ViaScripting"/>
|
||||
[Tooltip("Requires manual updating via DepthProbe.Populate.")]
|
||||
@@ -77,8 +84,13 @@ namespace WaveHarmonic.Crest
|
||||
[SerializeField]
|
||||
internal DepthProbeMode _Type = DepthProbeMode.RealTime;
|
||||
|
||||
[Tooltip("Controls how the probe is refreshed in the Player.\n\nCall Populate() if scripting.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime), hide: true)]
|
||||
[Tooltip("Where the Depth Probe is placed.\n\nThe default performs the best.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
Placement _Placement;
|
||||
|
||||
[Tooltip("Controls how the probe is refreshed in the Player.\n\nCall Populate() if scripting.\n\nWhen Placement is not set to Fixed, EveryFrame is still applicable, but the others are not (update only happens on position change).")]
|
||||
[@Show(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal DepthProbeRefreshMode _RefreshMode = DepthProbeRefreshMode.OnStart;
|
||||
@@ -87,26 +99,26 @@ namespace WaveHarmonic.Crest
|
||||
[@Heading("Capture")]
|
||||
|
||||
[Tooltip("The layers to render into the probe.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal LayerMask _Layers = 1; // Default
|
||||
|
||||
[Tooltip("The resolution of the probe.\n\nLower will be more efficient.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal int _Resolution = 512;
|
||||
|
||||
[Tooltip("The far and near plane of the depth probe camera respectively, relative to the transform.\n\nDepth is captured top-down and orthographically. The gizmo will visualize this range as the bottom box.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@Range(-100f, 100f, Range.Clamp.None)]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[SerializeField]
|
||||
internal Vector2 _CaptureRange = new(-1000f, 1000f);
|
||||
|
||||
[Tooltip("Fills holes left by the maximum of the capture range.\n\nSetting the maximum capture range lower than the highest point of geometry can be useful for eliminating depth artifacts from overhangs, but the side effect is there will be a hole in the depth data where geometry is clipped by the near plane. This will only capture where the holes are to fill them in. This height is relative to the maximum capture range. Set to zero to skip.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@Range(0f, 100f, Range.Clamp.Minimum)]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_MaximumHeight")]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
@@ -115,14 +127,14 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Enable Back-Face Inclusion")]
|
||||
[Tooltip("Increase coverage by testing mesh back faces within the Fill Holes area.\n\nUses the back-faces to include meshes where the front-face is within the Fill Holes area and the back-face is within the capture area. An example would be an upright cylinder not over a hole but was not captured due to the top being clipped by the near plane.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Predicated(nameof(_FillHolesCaptureHeight), inverted: false, 0f)]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@Disable(nameof(_FillHolesCaptureHeight), 0f)]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _EnableBackFaceInclusion = true;
|
||||
|
||||
[Tooltip("Overrides global quality settings.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[GenerateAPI(Setter.None)]
|
||||
[@DecoratedField, SerializeField]
|
||||
QualitySettingsOverride _QualitySettingsOverride = new()
|
||||
@@ -139,7 +151,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[Tooltip("Baked probe.\n\nCan only bake in edit mode.")]
|
||||
[@Disabled]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.Baked), hide: true)]
|
||||
[@Show(nameof(_Type), nameof(DepthProbeMode.Baked))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
#pragma warning disable 649
|
||||
@@ -151,7 +163,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Generate")]
|
||||
[Tooltip("Generate a signed distance field for the shoreline.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _GenerateSignedDistanceField = true;
|
||||
@@ -159,8 +171,8 @@ namespace WaveHarmonic.Crest
|
||||
// Additional rounds of JFA, over the standard log2(resolution), can help reduce
|
||||
// innacuracies from JFA, see paper for details.
|
||||
[Tooltip("How many additional Jump Flood rounds to use.\n\nThe standard number of rounds is log2(resolution). Additional rounds can reduce innaccuracies.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@Predicated(nameof(_GenerateSignedDistanceField))]
|
||||
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
|
||||
[@Enable(nameof(_GenerateSignedDistanceField))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
int _AdditionalJumpFloodRounds = 7;
|
||||
@@ -174,11 +186,6 @@ namespace WaveHarmonic.Crest
|
||||
[System.Serializable]
|
||||
internal sealed class DebugFields
|
||||
{
|
||||
[Tooltip("Will render into the probe every frame. Intended for debugging, will generate garbage.")]
|
||||
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
|
||||
[@DecoratedField, SerializeField]
|
||||
public bool _ForceAlwaysUpdateDebug;
|
||||
|
||||
[Tooltip("Shows hidden objects like the camera which renders into the probe.")]
|
||||
[@DecoratedField, SerializeField]
|
||||
public bool _ShowHiddenObjects;
|
||||
@@ -342,6 +349,11 @@ namespace WaveHarmonic.Crest
|
||||
if (transform.hasChanged)
|
||||
{
|
||||
_RecalculateBounds = true;
|
||||
|
||||
if (_Placement == Placement.Transform)
|
||||
{
|
||||
UpdatePosition(water, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +363,14 @@ namespace WaveHarmonic.Crest
|
||||
transform.hasChanged = false;
|
||||
}
|
||||
|
||||
void OnBeforeBuildCommandBuffer(WaterRenderer water, Camera camera)
|
||||
{
|
||||
if (_Placement == Placement.Viewpoint)
|
||||
{
|
||||
UpdatePosition(water, camera.transform);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool Outdated => _CurrentStateHash != _RenderedStateHash;
|
||||
|
||||
bool IsTextureOutdated(RenderTexture texture, bool target)
|
||||
@@ -358,16 +378,19 @@ namespace WaveHarmonic.Crest
|
||||
return texture != null &&
|
||||
texture.width != _Resolution ||
|
||||
texture.height != _Resolution ||
|
||||
texture.format != (target ? RenderTextureFormat.Depth : FinalFormat);
|
||||
texture.graphicsFormat != (target ? SystemInfo.GetGraphicsFormat(DefaultFormat.DepthStencil) : FinalFormat);
|
||||
}
|
||||
|
||||
RenderTextureFormat FinalFormat => _GenerateSignedDistanceField ? RenderTextureFormat.RGFloat : RenderTextureFormat.RFloat;
|
||||
GraphicsFormat FinalFormat => _GenerateSignedDistanceField
|
||||
? Helpers.GetCompatibleTextureFormat(GraphicsFormat.R32G32_SFloat, Helpers.s_DataGraphicsFormatUsage, "Depth Probe", randomWrite: true)
|
||||
: GraphicsFormat.R32_SFloat;
|
||||
|
||||
void MakeRT(RenderTexture texture, bool target)
|
||||
{
|
||||
var format = target ? RenderTextureFormat.Depth : FinalFormat;
|
||||
var format = target ? SystemInfo.GetGraphicsFormat(DefaultFormat.DepthStencil) : FinalFormat;
|
||||
var descriptor = texture.descriptor;
|
||||
descriptor.colorFormat = format;
|
||||
descriptor.graphicsFormat = target ? GraphicsFormat.None : format;
|
||||
descriptor.depthStencilFormat = target ? format : GraphicsFormat.None;
|
||||
descriptor.width = descriptor.height = _Resolution;
|
||||
descriptor.depthBufferBits = target ? 24 : 0;
|
||||
descriptor.useMipMap = false;
|
||||
@@ -375,7 +398,11 @@ namespace WaveHarmonic.Crest
|
||||
descriptor.enableRandomWrite = !target;
|
||||
texture.descriptor = descriptor;
|
||||
texture.Create();
|
||||
Debug.Assert(SystemInfo.SupportsRenderTextureFormat(format), "Crest: The graphics device does not support the render texture format " + format.ToString());
|
||||
|
||||
if (!target)
|
||||
{
|
||||
Debug.Assert(SystemInfo.IsFormatSupported(format, GraphicsFormatUsage.Sample), "Crest: The graphics device does not support the render texture format " + format.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
bool InitObjects()
|
||||
@@ -704,7 +731,7 @@ namespace WaveHarmonic.Crest
|
||||
var descriptor = new RenderTextureDescriptor(_Resolution, _Resolution)
|
||||
{
|
||||
autoGenerateMips = false,
|
||||
colorFormat = RenderTextureFormat.RGHalf,
|
||||
graphicsFormat = Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16_SFloat, Helpers.s_DataGraphicsFormatUsage, "Depth Probe SDF", randomWrite: true),
|
||||
useMipMap = false,
|
||||
enableRandomWrite = true,
|
||||
depthBufferBits = 0,
|
||||
@@ -838,6 +865,9 @@ namespace WaveHarmonic.Crest
|
||||
ILodInput.Attach(_Input, DepthLod.s_Inputs);
|
||||
HashState(ref _CurrentStateHash);
|
||||
|
||||
WaterRenderer.s_OnBeforeBuildCommandBuffer -= OnBeforeBuildCommandBuffer;
|
||||
WaterRenderer.s_OnBeforeBuildCommandBuffer += OnBeforeBuildCommandBuffer;
|
||||
|
||||
#if CREST_DEBUG
|
||||
if (_Debug._ShowSimulationDataInScene)
|
||||
{
|
||||
@@ -852,6 +882,8 @@ namespace WaveHarmonic.Crest
|
||||
base.OnDisable();
|
||||
ILodInput.Detach(_Input, DepthLod.s_Inputs);
|
||||
|
||||
WaterRenderer.s_OnBeforeBuildCommandBuffer -= OnBeforeBuildCommandBuffer;
|
||||
|
||||
#if CREST_DEBUG
|
||||
if (_Debug._ShowSimulationDataInScene)
|
||||
{
|
||||
@@ -926,6 +958,29 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
sealed partial class DepthProbe
|
||||
{
|
||||
Vector3 _PreviousPosition;
|
||||
|
||||
void UpdatePosition(WaterRenderer water, Transform target)
|
||||
{
|
||||
var position = target.position.XNZ(water.transform.position.y);
|
||||
var texelSize = Scale / _Resolution;
|
||||
position.x = Mathf.Round(position.x / texelSize.x) * texelSize.x;
|
||||
position.z = Mathf.Round(position.z / texelSize.y) * texelSize.y;
|
||||
|
||||
if ((_Placement == Placement.Viewpoint && !water.IsSingleViewpointMode) || _RefreshMode == DepthProbeRefreshMode.EveryFrame || _PreviousPosition != position)
|
||||
{
|
||||
Managed = true;
|
||||
OverridePosition = true;
|
||||
Position = position;
|
||||
Scale = new(transform.lossyScale.x, transform.lossyScale.z);
|
||||
Populate();
|
||||
_PreviousPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed partial class DepthProbe
|
||||
{
|
||||
// Hash is used to notify whether the probe is outdated in the UI.
|
||||
@@ -974,7 +1029,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_Debug._ForceAlwaysUpdateDebug && _Type != DepthProbeMode.Baked)
|
||||
if (_RefreshMode == DepthProbeRefreshMode.EveryFrame && _Placement == Placement.Fixed && _Type != DepthProbeMode.Baked)
|
||||
{
|
||||
Populate();
|
||||
}
|
||||
@@ -987,30 +1042,21 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
}
|
||||
|
||||
partial class DepthProbe : ISerializationCallbackReceiver
|
||||
partial class DepthProbe
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
private protected override int Version => Mathf.Max(base.Version, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
base.OnMigrate();
|
||||
|
||||
// Version 1 (2024.06.04)
|
||||
// - Added new capture options replacing Maximum Height's behaviour.
|
||||
if (_Version < 1)
|
||||
{
|
||||
_CaptureRange.y = _FillHolesCaptureHeight;
|
||||
_FillHolesCaptureHeight = 0f;
|
||||
_Version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,12 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[Tooltip("Whether to set the shader pass manually.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
[@InlineToggle]
|
||||
[@SerializeField]
|
||||
internal bool _OverrideShaderPass;
|
||||
|
||||
[Tooltip("The shader pass to execute.\n\nSet to -1 to execute all passes.")]
|
||||
[@Predicated(nameof(_OverrideShaderPass))]
|
||||
[@Enable(nameof(_OverrideShaderPass))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal int _ShaderPassIndex;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/WaterAppearance.html#volume-color-inputs")]
|
||||
public sealed partial class ScatteringLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
#if d_CrestPaint
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Paint;
|
||||
#else
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/WaterAppearance.html#shadows-lod")]
|
||||
public sealed partial class ShadowLodInput : LodInput
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,21 +14,32 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
// Waves
|
||||
|
||||
[Tooltip("Whether to use the wind turbulence on this component rather than the global wind turbulence.\n\nGlobal wind turbulence comes from the Water Renderer component.")]
|
||||
[Tooltip("Whether to apply the options shown when \"Show Advanced Controls\" is active.")]
|
||||
[@Order(nameof(_EvaluateSpectrumAtRunTimeEveryFrame), Order.Placement.Below)]
|
||||
[@DecoratedField]
|
||||
[@GenerateAPI]
|
||||
[@InlineToggle(order = -3), SerializeField]
|
||||
[@SerializeField]
|
||||
bool _ApplyAdvancedSpectrumControls;
|
||||
|
||||
[Tooltip("Whether to use the wind turbulence on this component rather than the global wind turbulence.\n\nGlobal wind turbulence comes from the Water Renderer component.")]
|
||||
[@Order("Waves")]
|
||||
[@InlineToggle]
|
||||
[@GenerateAPI]
|
||||
[@SerializeField]
|
||||
bool _OverrideGlobalWindTurbulence;
|
||||
|
||||
[Tooltip("How turbulent/chaotic the waves are.")]
|
||||
[@Predicated(nameof(_OverrideGlobalWindTurbulence), hide: true)]
|
||||
[@Order("Waves")]
|
||||
[@Show(nameof(_OverrideGlobalWindTurbulence))]
|
||||
[@ShowComputedProperty(nameof(WindTurbulence))]
|
||||
[@Range(0, 1, order = -4)]
|
||||
[@Range(0, 1)]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
[SerializeField]
|
||||
float _WindTurbulence = 0.145f;
|
||||
|
||||
[Tooltip("How aligned the waves are with wind.")]
|
||||
[@Range(0, 1, order = -5)]
|
||||
[@Range(0, 1)]
|
||||
[@Order("Waves")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WindAlignment;
|
||||
@@ -37,21 +48,30 @@ namespace WaveHarmonic.Crest
|
||||
// Generation
|
||||
|
||||
[Tooltip("FFT waves will loop with a period of this many seconds.")]
|
||||
[@Order("Generation Settings")]
|
||||
[@Range(4f, 128f, Range.Clamp.Minimum)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _TimeLoopLength = Mathf.Infinity;
|
||||
|
||||
|
||||
[Header("Culling")]
|
||||
[@Heading("Culling")]
|
||||
|
||||
[Tooltip("Whether to override automatic culling based on heuristics.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
bool _OverrideCulling;
|
||||
|
||||
[Tooltip("Maximum amount the surface will be displaced vertically from sea level.\n\nIncrease this if gaps appear at bottom of screen.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
float _MaximumVerticalDisplacement = 10f;
|
||||
|
||||
[Tooltip("Maximum amount a point on the surface will be displaced horizontally by waves from its rest position.\n\nIncrease this if gaps appear at sides of screen.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
float _MaximumHorizontalDisplacement = 15f;
|
||||
|
||||
@@ -63,7 +83,7 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
|
||||
[Tooltip("Enable running this FFT with baked data.\n\nThis makes the FFT periodic (repeating in time).")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _EnableBakedCollision = false;
|
||||
|
||||
@@ -72,8 +92,8 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
|
||||
[Tooltip("Frames per second of baked data.\n\nLarger values may help the collision track the surface closely at the cost of more frames and increase baked data size.")]
|
||||
[@Predicated(nameof(_EnableBakedCollision))]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
|
||||
[@Enable(nameof(_EnableBakedCollision))]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal int _TimeResolution = 4;
|
||||
|
||||
@@ -82,8 +102,8 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
|
||||
[Tooltip("Smallest wavelength required in collision.\n\nTo preview the effect of this, disable power sliders in spectrum for smaller values than this number. Smaller values require more resolution and increase baked data size.")]
|
||||
[@Predicated(nameof(_EnableBakedCollision))]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
|
||||
[@Enable(nameof(_EnableBakedCollision))]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal float _SmallestWavelengthRequired = 2f;
|
||||
|
||||
@@ -92,8 +112,8 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
|
||||
[Tooltip("FFT waves will loop with a period of this many seconds.\n\nSmaller values decrease data size but can make waves visibly repetitive.")]
|
||||
[@Predicated(nameof(_EnableBakedCollision))]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
|
||||
[@Enable(nameof(_EnableBakedCollision))]
|
||||
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@Range(4f, 128f)]
|
||||
[SerializeField]
|
||||
internal float _BakedTimeLoopLength = 32f;
|
||||
@@ -104,8 +124,9 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
_TimeLoopLength;
|
||||
|
||||
// WebGPU will crash above at 128.
|
||||
private protected override int MinimumResolution => 16;
|
||||
private protected override int MaximumResolution => int.MaxValue;
|
||||
private protected override int MaximumResolution => Helpers.IsWebGPU ? 64 : int.MaxValue;
|
||||
|
||||
FFTCompute _FFTCompute;
|
||||
|
||||
@@ -119,7 +140,8 @@ namespace WaveHarmonic.Crest
|
||||
WindDirRadForFFT,
|
||||
WindTurbulence,
|
||||
_WindAlignment,
|
||||
gravity
|
||||
gravity,
|
||||
_ApplyAdvancedSpectrumControls
|
||||
);
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
@@ -189,13 +211,28 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
// Apply weight or will cause popping due to scale change.
|
||||
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
|
||||
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
if (_OverrideCulling)
|
||||
{
|
||||
water.ReportMaximumDisplacement(MaximumReportedHorizontalDisplacement, MaximumReportedVerticalDisplacement, MaximumReportedVerticalDisplacement);
|
||||
// Apply weight or will cause popping due to scale change.
|
||||
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
|
||||
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
|
||||
}
|
||||
else
|
||||
{
|
||||
var powerLinear = 0f;
|
||||
|
||||
for (var i = 0; i < WaveSpectrum.k_NumberOfOctaves; i++)
|
||||
{
|
||||
powerLinear += _ActiveSpectrum._PowerLinearScales[i];
|
||||
}
|
||||
|
||||
// Empirical multiplier (3-5), went with 5 to be safe.
|
||||
// We may be missing some more multipliers from the compute shader.
|
||||
// Look there if this proves insufficient.
|
||||
var wind = Mathf.Clamp01(WindSpeedKPH / 150f);
|
||||
var rms = Mathf.Sqrt(powerLinear) * 5f;
|
||||
MaximumReportedHorizontalDisplacement = rms * _ActiveSpectrum._Chop * Weight * wind;
|
||||
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = rms * Weight * wind;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,28 +287,18 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
partial class ShapeFFT : ISerializationCallbackReceiver
|
||||
partial class ShapeFFT
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 2;
|
||||
#pragma warning restore 414
|
||||
private protected override int Version => Mathf.Max(base.Version, 2);
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
_Version = MigrateV1(_Version);
|
||||
base.OnMigrate();
|
||||
|
||||
if (_Version < 2)
|
||||
{
|
||||
_OverrideGlobalWindTurbulence = true;
|
||||
}
|
||||
|
||||
_Version = MigrateV2(_Version);
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,18 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
// If disabled, there will be a difference due to Random.value.
|
||||
[Tooltip("Use a swell spectrum as the default.\n\nUses a swell spectrum as default (when none is assigned), and disabled reverse waves.")]
|
||||
[@Order("Waves")]
|
||||
[@DecoratedField]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField(order = -3), SerializeField]
|
||||
[@SerializeField]
|
||||
bool _Swell = true;
|
||||
|
||||
[Tooltip("The weight of the opposing, second pair of Gerstner waves.\n\nEach Gerstner wave is actually a pair of waves travelling in opposite directions (similar to FFT). This weight is applied to the wave travelling in against-wind direction. Set to zero to obtain simple single waves which are useful for shorelines waves.")]
|
||||
[Predicated(nameof(_Swell), inverted: true)]
|
||||
[@Range(0f, 1f, order = -4)]
|
||||
[@Order("Waves")]
|
||||
[@Disable(nameof(_Swell))]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
[SerializeField]
|
||||
float _ReverseWaveWeight = 0.5f;
|
||||
@@ -35,23 +39,28 @@ namespace WaveHarmonic.Crest
|
||||
// Generation Settings
|
||||
|
||||
[Tooltip("How many wave components to generate in each octave.")]
|
||||
[@Order("Generation Settings")]
|
||||
[@Delayed]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
int _ComponentsPerOctave = 8;
|
||||
|
||||
[Tooltip("Change to get a different set of waves.")]
|
||||
[@Order("Generation Settings")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
int _RandomSeed = 0;
|
||||
|
||||
[Tooltip("Prevent data arrays from being written to so one can provide their own.")]
|
||||
[@Order("Generation Settings")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
bool _ManualGeneration;
|
||||
|
||||
private protected override int MinimumResolution => 8;
|
||||
private protected override int MaximumResolution => 64;
|
||||
private protected override int MaximumResolution => int.MaxValue;
|
||||
|
||||
float _WindSpeedWhenGenerated = -1f;
|
||||
|
||||
@@ -93,12 +102,7 @@ namespace WaveHarmonic.Crest
|
||||
float[] _Amplitudes2;
|
||||
float[] _Phases2;
|
||||
|
||||
struct GerstnerCascadeParams
|
||||
{
|
||||
public int _StartIndex;
|
||||
}
|
||||
ComputeBuffer _BufferCascadeParameters;
|
||||
readonly GerstnerCascadeParams[] _CascadeParameters = new GerstnerCascadeParams[k_CascadeCount + 1];
|
||||
readonly int[] _StartIndices = new int[k_CascadeCount];
|
||||
|
||||
// Caution - order here impact performance. Rearranging these to match order
|
||||
// they're read in the compute shader made it 50% slower..
|
||||
@@ -111,6 +115,7 @@ namespace WaveHarmonic.Crest
|
||||
public Vector4 _Omega;
|
||||
public Vector4 _Phase;
|
||||
public Vector4 _ChopAmplitude;
|
||||
|
||||
// Waves are generated in pairs, these values are for the second in the pair
|
||||
public Vector4 _Amplitude2;
|
||||
public Vector4 _ChopAmplitude2;
|
||||
@@ -119,8 +124,7 @@ namespace WaveHarmonic.Crest
|
||||
ComputeBuffer _BufferWaveData;
|
||||
readonly GerstnerWaveComponent4[] _WaveData = new GerstnerWaveComponent4[k_MaximumWaveComponents / 4];
|
||||
|
||||
ComputeShader _ShaderGerstner;
|
||||
int _KernelGerstner = -1;
|
||||
WaterResources.GerstnerCompute _Shader;
|
||||
|
||||
private protected override WaveSpectrum DefaultSpectrum => _Swell ? SwellSpectrum : WindSpectrum;
|
||||
static WaveSpectrum s_SwellSpectrum;
|
||||
@@ -154,7 +158,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
public static readonly int s_FirstCascadeIndex = Shader.PropertyToID("_Crest_FirstCascadeIndex");
|
||||
public static readonly int s_TextureRes = Shader.PropertyToID("_Crest_TextureRes");
|
||||
public static readonly int s_CascadeParams = Shader.PropertyToID("_Crest_GerstnerCascadeParams");
|
||||
public static readonly int s_StartIndices = Shader.PropertyToID("_Crest_StartIndices");
|
||||
public static readonly int s_GerstnerWaveData = Shader.PropertyToID("_Crest_GerstnerWaveData");
|
||||
}
|
||||
|
||||
@@ -179,7 +183,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
if (_WaveBuffers == null)
|
||||
{
|
||||
_WaveBuffers = new(_Resolution, _Resolution, 0, GraphicsFormat.R16G16B16A16_SFloat);
|
||||
_WaveBuffers = new(Resolution, Resolution, 0, Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16B16A16_SFloat, randomWrite: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,7 +191,7 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
|
||||
{
|
||||
_WaveBuffers.width = _WaveBuffers.height = _Resolution;
|
||||
_WaveBuffers.width = _WaveBuffers.height = Resolution;
|
||||
_WaveBuffers.wrapMode = TextureWrapMode.Clamp;
|
||||
_WaveBuffers.antiAliasing = 1;
|
||||
_WaveBuffers.filterMode = FilterMode.Bilinear;
|
||||
@@ -200,14 +204,11 @@ namespace WaveHarmonic.Crest
|
||||
_WaveBuffers.Create();
|
||||
}
|
||||
|
||||
_BufferCascadeParameters?.Release();
|
||||
_BufferWaveData?.Release();
|
||||
|
||||
_BufferCascadeParameters = new(k_CascadeCount + 1, UnsafeUtility.SizeOf<GerstnerCascadeParams>());
|
||||
_BufferWaveData = new(k_MaximumWaveComponents / 4, UnsafeUtility.SizeOf<GerstnerWaveComponent4>());
|
||||
|
||||
_ShaderGerstner = WaterResources.Instance.Compute._Gerstner;
|
||||
_KernelGerstner = _ShaderGerstner.FindKernel("Gerstner");
|
||||
_Shader = WaterResources.Instance._ComputeLibrary._GerstnerCompute;
|
||||
}
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
@@ -216,7 +217,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
base.OnUpdate(water);
|
||||
|
||||
if (_WaveBuffers == null || _Resolution != _WaveBuffers.width || _BufferCascadeParameters == null || _BufferWaveData == null)
|
||||
if (_WaveBuffers == null || Resolution != _WaveBuffers.width || _BufferWaveData == null)
|
||||
{
|
||||
InitData();
|
||||
}
|
||||
@@ -266,7 +267,7 @@ namespace WaveHarmonic.Crest
|
||||
var cascadeIdx = 0;
|
||||
var componentIdx = 0;
|
||||
var outputIdx = 0;
|
||||
_CascadeParameters[0]._StartIndex = 0;
|
||||
_StartIndices[0] = 0;
|
||||
|
||||
if (_ManualGeneration)
|
||||
{
|
||||
@@ -313,12 +314,14 @@ namespace WaveHarmonic.Crest
|
||||
_WaveData[vi]._Omega[ei] = 0f;
|
||||
_WaveData[vi]._Phase[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude[ei] = 0f;
|
||||
if (!_ManualGeneration)
|
||||
|
||||
if (!_ManualGeneration && !_Swell)
|
||||
{
|
||||
_WaveData[vi]._Phase2[ei] = 0f;
|
||||
_WaveData[vi]._Amplitude2[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude2[ei] = 0f;
|
||||
}
|
||||
|
||||
ei = (ei + 1) % 4;
|
||||
outputIdx++;
|
||||
}
|
||||
@@ -326,7 +329,7 @@ namespace WaveHarmonic.Crest
|
||||
if (outputIdx > 0 && _FirstCascade < 0) _FirstCascade = cascadeIdx;
|
||||
|
||||
cascadeIdx++;
|
||||
_CascadeParameters[cascadeIdx]._StartIndex = outputIdx / 4;
|
||||
_StartIndices[cascadeIdx] = outputIdx / 4;
|
||||
minWl *= 2f;
|
||||
|
||||
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
|
||||
@@ -343,7 +346,7 @@ namespace WaveHarmonic.Crest
|
||||
var chopScale = _ActiveSpectrum._ChopScales[componentIdx / _ComponentsPerOctave];
|
||||
_WaveData[vi]._ChopAmplitude[ei] = -chopScale * _ActiveSpectrum._Chop * _Amplitudes[componentIdx];
|
||||
|
||||
if (!_ManualGeneration)
|
||||
if (!_ManualGeneration && !_Swell)
|
||||
{
|
||||
_WaveData[vi]._Amplitude2[ei] = _Amplitudes2[componentIdx];
|
||||
_WaveData[vi]._ChopAmplitude2[ei] = -chopScale * _ActiveSpectrum._Chop * _Amplitudes2[componentIdx];
|
||||
@@ -385,7 +388,7 @@ namespace WaveHarmonic.Crest
|
||||
_WaveData[vi]._Omega[ei] = k * c;
|
||||
_WaveData[vi]._Phase[ei] = Mathf.Repeat(_Phases[componentIdx], Mathf.PI * 2f);
|
||||
|
||||
if (!_ManualGeneration)
|
||||
if (!_ManualGeneration && !_Swell)
|
||||
{
|
||||
_WaveData[vi]._Phase2[ei] = Mathf.Repeat(_Phases2[componentIdx], Mathf.PI * 2f);
|
||||
}
|
||||
@@ -410,40 +413,46 @@ namespace WaveHarmonic.Crest
|
||||
_WaveData[vi]._Omega[ei] = 0f;
|
||||
_WaveData[vi]._Phase[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude[ei] = 0f;
|
||||
if (!_ManualGeneration)
|
||||
|
||||
if (!_ManualGeneration && !_Swell)
|
||||
{
|
||||
_WaveData[vi]._Phase2[ei] = 0f;
|
||||
_WaveData[vi]._Amplitude2[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude2[ei] = 0f;
|
||||
}
|
||||
|
||||
ei = (ei + 1) % 4;
|
||||
outputIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
while (cascadeIdx < k_CascadeCount)
|
||||
// Fill the remaining.
|
||||
while (cascadeIdx < k_CascadeCount - 1)
|
||||
{
|
||||
cascadeIdx++;
|
||||
minWl *= 2f;
|
||||
_CascadeParameters[cascadeIdx]._StartIndex = outputIdx / 4;
|
||||
_StartIndices[cascadeIdx] = outputIdx / 4;
|
||||
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
|
||||
}
|
||||
|
||||
_BufferCascadeParameters.SetData(_CascadeParameters);
|
||||
_BufferWaveData.SetData(_WaveData);
|
||||
}
|
||||
|
||||
void UpdateGenerateWaves(CommandBuffer buf)
|
||||
void UpdateGenerateWaves(CommandBuffer buffer)
|
||||
{
|
||||
// Clear existing waves or they could get copied.
|
||||
CoreUtils.SetRenderTarget(buf, _WaveBuffers, ClearFlag.Color);
|
||||
buf.SetComputeFloatParam(_ShaderGerstner, ShaderIDs.s_TextureRes, _WaveBuffers.width);
|
||||
buf.SetComputeIntParam(_ShaderGerstner, ShaderIDs.s_FirstCascadeIndex, _FirstCascade);
|
||||
buf.SetComputeBufferParam(_ShaderGerstner, _KernelGerstner, ShaderIDs.s_CascadeParams, _BufferCascadeParameters);
|
||||
buf.SetComputeBufferParam(_ShaderGerstner, _KernelGerstner, ShaderIDs.s_GerstnerWaveData, _BufferWaveData);
|
||||
buf.SetComputeTextureParam(_ShaderGerstner, _KernelGerstner, ShapeWaves.ShaderIDs.s_WaveBuffer, _WaveBuffers);
|
||||
CoreUtils.SetRenderTarget(buffer, _WaveBuffers, ClearFlag.Color);
|
||||
|
||||
buf.DispatchCompute(_ShaderGerstner, _KernelGerstner, _WaveBuffers.width / Lod.k_ThreadGroupSizeX, _WaveBuffers.height / Lod.k_ThreadGroupSizeY, _LastCascade - _FirstCascade + 1);
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _Shader._Shader, _Shader._ExecuteKernel);
|
||||
|
||||
wrapper.SetFloat(ShaderIDs.s_TextureRes, _WaveBuffers.width);
|
||||
wrapper.SetInteger(ShaderIDs.s_FirstCascadeIndex, _FirstCascade);
|
||||
wrapper.SetIntegers(ShaderIDs.s_StartIndices, _StartIndices);
|
||||
wrapper.SetBuffer(ShaderIDs.s_GerstnerWaveData, _BufferWaveData);
|
||||
wrapper.SetTexture(ShapeWaves.ShaderIDs.s_WaveBuffer, _WaveBuffers);
|
||||
wrapper.SetKeyword(_Shader._WavePairsKeyword, !_Swell && _ReverseWaveWeight > 0f);
|
||||
|
||||
wrapper.Dispatch(_WaveBuffers.width / Lod.k_ThreadGroupSizeX, _WaveBuffers.height / Lod.k_ThreadGroupSizeY, _LastCascade - _FirstCascade + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -503,16 +512,16 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
var amp = _ActiveSpectrum.GetAmplitude(_Wavelengths[i], _ComponentsPerOctave, windSpeed, water.Gravity, out _Powers[i]);
|
||||
_Amplitudes[i] = Random.value * amp;
|
||||
_Amplitudes2[i] = Random.value * amp * ReverseWaveWeight;
|
||||
|
||||
if (!_Swell)
|
||||
{
|
||||
_Amplitudes2[i] = Random.value * amp * ReverseWaveWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitPhases()
|
||||
{
|
||||
// Set random seed to get repeatable results
|
||||
var randomStateBkp = Random.state;
|
||||
Random.InitState(_RandomSeed);
|
||||
|
||||
var totalComps = _ComponentsPerOctave * WaveSpectrum.k_NumberOfOctaves;
|
||||
_Phases = new float[totalComps];
|
||||
_Phases2 = new float[totalComps];
|
||||
@@ -524,12 +533,13 @@ namespace WaveHarmonic.Crest
|
||||
var rnd = (i + Random.value) / _ComponentsPerOctave;
|
||||
_Phases[index] = 2f * Mathf.PI * rnd;
|
||||
|
||||
var rnd2 = (i + Random.value) / _ComponentsPerOctave;
|
||||
_Phases2[index] = 2f * Mathf.PI * rnd2;
|
||||
if (!_Swell)
|
||||
{
|
||||
var rnd2 = (i + Random.value) / _ComponentsPerOctave;
|
||||
_Phases2[index] = 2f * Mathf.PI * rnd2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Random.state = randomStateBkp;
|
||||
}
|
||||
|
||||
private protected override void ReportMaxDisplacement(WaterRenderer water)
|
||||
@@ -546,23 +556,23 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
var ampSum = 0f;
|
||||
MaximumReportedVerticalDisplacement = 0;
|
||||
MaximumReportedHorizontalDisplacement = 0;
|
||||
|
||||
for (var i = 0; i < _Wavelengths.Length; i++)
|
||||
{
|
||||
ampSum += _Amplitudes[i] * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
|
||||
var amplitude = _Amplitudes[i];
|
||||
MaximumReportedVerticalDisplacement += amplitude;
|
||||
MaximumReportedHorizontalDisplacement += amplitude * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
|
||||
}
|
||||
|
||||
MaximumReportedHorizontalDisplacement *= _ActiveSpectrum._Chop;
|
||||
|
||||
// Apply weight or will cause popping due to scale change.
|
||||
ampSum *= Weight;
|
||||
MaximumReportedVerticalDisplacement *= Weight;
|
||||
MaximumReportedHorizontalDisplacement *= Weight;
|
||||
|
||||
MaximumReportedHorizontalDisplacement = ampSum * _ActiveSpectrum._Chop;
|
||||
MaximumReportedVerticalDisplacement = ampSum;
|
||||
MaximumReportedWavesDisplacement = ampSum;
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
water.ReportMaximumDisplacement(ampSum * _ActiveSpectrum._Chop, ampSum, ampSum);
|
||||
}
|
||||
MaximumReportedWavesDisplacement = MaximumReportedVerticalDisplacement;
|
||||
}
|
||||
|
||||
private protected override void Initialize()
|
||||
@@ -577,11 +587,6 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
s_Instances.Remove(this);
|
||||
|
||||
if (_BufferCascadeParameters != null && _BufferCascadeParameters.IsValid())
|
||||
{
|
||||
_BufferCascadeParameters.Dispose();
|
||||
_BufferCascadeParameters = null;
|
||||
}
|
||||
if (_BufferWaveData != null && _BufferWaveData.IsValid())
|
||||
{
|
||||
_BufferWaveData.Dispose();
|
||||
@@ -623,32 +628,23 @@ namespace WaveHarmonic.Crest
|
||||
if (s_SwellSpectrum != null)
|
||||
{
|
||||
Helpers.Destroy(s_SwellSpectrum);
|
||||
s_SwellSpectrum = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class ShapeGerstner : ISerializationCallbackReceiver
|
||||
partial class ShapeGerstner
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 2;
|
||||
#pragma warning restore 414
|
||||
private protected override int Version => Mathf.Max(base.Version, 2);
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
_Version = MigrateV1(_Version);
|
||||
base.OnMigrate();
|
||||
|
||||
if (_Version < 2)
|
||||
{
|
||||
_Swell = false;
|
||||
}
|
||||
|
||||
_Version = MigrateV2(_Version);
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Serialization;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
@@ -26,8 +27,11 @@ namespace WaveHarmonic.Crest
|
||||
[Tooltip("Whether to evaluate the spectrum every frame.\n\nWhen false, the wave spectrum is evaluated once on startup in editor play mode and standalone builds, rather than every frame. This is less flexible, but it reduces the performance cost significantly.")]
|
||||
[@GenerateAPI]
|
||||
[FormerlySerializedAs("_SpectrumFixedAtRuntime")]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
bool _EvaluateSpectrumAtRunTimeEveryFrame;
|
||||
private protected bool _EvaluateSpectrumAtRunTimeEveryFrame;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("How much these waves respect the shallow water attenuation.\n\nAttenuation is defined on the Animated Waves. Set to zero to ignore attenuation.")]
|
||||
[@Range(0f, 1f)]
|
||||
@@ -35,6 +39,15 @@ namespace WaveHarmonic.Crest
|
||||
[SerializeField]
|
||||
float _RespectShallowWaterAttenuation = 1f;
|
||||
|
||||
[Tooltip("Whether global waves is applied above or below sea level.\n\nWaves are faded out to avoid hard transitionds. They are fully faded by 1m from sea level.")]
|
||||
[@Enable(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
bool _SeaLevelOnly = true;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Whether to use the wind direction on this component rather than the global wind direction.\n\nGlobal wind direction comes from the Water Renderer component.")]
|
||||
[@GenerateAPI]
|
||||
[@InlineToggle, SerializeField]
|
||||
@@ -42,8 +55,8 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Wind Direction")]
|
||||
[Tooltip("Primary wave direction heading (degrees).\n\nThis is the angle from x axis in degrees that the waves are oriented towards. If a spline is being used to place the waves, this angle is relative to the spline.")]
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Paint))]
|
||||
[@Predicated(nameof(_OverrideGlobalWindDirection), hide: true)]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Paint))]
|
||||
[@Show(nameof(_OverrideGlobalWindDirection))]
|
||||
[@ShowComputedProperty(nameof(WaveDirectionHeadingAngle))]
|
||||
[@Range(-180, 180)]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
@@ -57,22 +70,32 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[Tooltip("Wind speed in km/h. Controls wave conditions.")]
|
||||
[@ShowComputedProperty(nameof(WindSpeedKPH))]
|
||||
[@Predicated(nameof(_OverrideGlobalWindSpeed), hide: true)]
|
||||
[@Show(nameof(_OverrideGlobalWindSpeed))]
|
||||
[@Range(0, 150f, scale: 2f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WindSpeed = 20f;
|
||||
|
||||
|
||||
[Header("Generation Settings")]
|
||||
[@Heading("Generation Settings")]
|
||||
|
||||
[Tooltip("Resolution to use for wave generation buffers.\n\nLow resolutions are more efficient but can result in noticeable patterns in the shape.")]
|
||||
[@Stepped(16, 512, step: 2, power: true)]
|
||||
[@GenerateAPI]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
[SerializeField]
|
||||
private protected int _Resolution = 128;
|
||||
|
||||
|
||||
[@Heading("Level of Detail")]
|
||||
|
||||
[Tooltip("Whether the maximum possible vertical displacement is used for the Drop Detail Height Based On Waves calculation.\n\nThis setting is ignored for global waves, as they always contribute. For local waves, only enable for large areas that are treated like global waves (eg a storm).")]
|
||||
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
bool _IncludeInDropDetailHeightBasedOnWaves;
|
||||
|
||||
|
||||
// Debug
|
||||
|
||||
[Tooltip("In Editor, shows the wave generation buffers on screen.")]
|
||||
@@ -92,6 +115,8 @@ namespace WaveHarmonic.Crest
|
||||
public static readonly int s_RespectShallowWaterAttenuation = Shader.PropertyToID("_Crest_RespectShallowWaterAttenuation");
|
||||
public static readonly int s_MaximumAttenuationDepth = Shader.PropertyToID("_Crest_MaximumAttenuationDepth");
|
||||
public static readonly int s_AxisX = Shader.PropertyToID("_Crest_AxisX");
|
||||
public static readonly int s_SeaLevelOnly = Shader.PropertyToID("_Crest_SeaLevelOnly");
|
||||
public static readonly int s_WaveBufferAttenuation = Shader.PropertyToID("_Crest_WaveBufferAttenuation");
|
||||
}
|
||||
|
||||
private protected virtual WaveSpectrum DefaultSpectrum => WindSpectrum;
|
||||
@@ -136,7 +161,7 @@ namespace WaveHarmonic.Crest
|
||||
/// <summary>
|
||||
/// The wind speed in meters per second (MPS).
|
||||
/// </summary>
|
||||
/// /// <remarks>
|
||||
/// <remarks>
|
||||
/// Wind speed can come from this component or the <see cref="WaterRenderer"/>.
|
||||
/// </remarks>
|
||||
public float WindSpeedMPS => WindSpeedKPH / 3.6f;
|
||||
@@ -150,13 +175,32 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
base.Attach();
|
||||
_Reporter ??= new(this);
|
||||
WaterChunkRenderer.DisplacementReporters.Add(_Reporter);
|
||||
_DisplacementReporter = _Reporter;
|
||||
_WaveDisplacementReporter = _Reporter;
|
||||
}
|
||||
|
||||
private protected override void Detach()
|
||||
{
|
||||
base.Detach();
|
||||
WaterChunkRenderer.DisplacementReporters.Remove(_Reporter);
|
||||
_DisplacementReporter = null;
|
||||
_WaveDisplacementReporter = null;
|
||||
}
|
||||
|
||||
internal enum WindSpeedSource
|
||||
{
|
||||
None, // Wind Speed is at maximum.
|
||||
ShapeWaves,
|
||||
WaterRenderer,
|
||||
}
|
||||
|
||||
internal WindSpeedSource GetWindSpeedSource()
|
||||
{
|
||||
if (WindSpeedKPH >= WaterRenderer.k_MaximumWindSpeedKPH)
|
||||
{
|
||||
return WindSpeedSource.None;
|
||||
}
|
||||
|
||||
return OverrideGlobalWindSpeed ? WindSpeedSource.ShapeWaves : WindSpeedSource.WaterRenderer;
|
||||
}
|
||||
|
||||
internal override void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1)
|
||||
@@ -173,30 +217,32 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
var lodCount = simulation.Slices;
|
||||
|
||||
var shape = (AnimatedWavesLod)simulation;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, s_TransferWavesComputeShader, 0);
|
||||
|
||||
if (_FirstCascade < 0 || _LastCascade < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lodCount = simulation.Slices;
|
||||
var lodResolution = simulation.Resolution;
|
||||
var shape = (AnimatedWavesLod)simulation;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, s_TransferWavesComputeShader, 0);
|
||||
|
||||
// Write to per-octave _WaveBuffers (ie pre-combined). Not the same as _AnimatedWaves.
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
|
||||
// Input weight. Weight for each octave calculated in compute.
|
||||
wrapper.SetFloat(LodInput.ShaderIDs.s_Weight, Weight);
|
||||
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
|
||||
|
||||
var water = shape._Water;
|
||||
|
||||
for (var lodIdx = lodCount - 1; lodIdx >= lodCount - slice; lodIdx--)
|
||||
{
|
||||
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 0);
|
||||
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 1);
|
||||
|
||||
var found = false;
|
||||
var filter = new AnimatedWavesLod.WavelengthFilter(water, lodIdx);
|
||||
var filter = new AnimatedWavesLod.WavelengthFilter(water, lodIdx, lodResolution);
|
||||
|
||||
for (var i = _FirstCascade; i <= _LastCascade; i++)
|
||||
{
|
||||
@@ -221,13 +267,17 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
|
||||
// Set transitional weights.
|
||||
_WaveBufferParameters[lodCount - 2].w = 1f - water.ViewerAltitudeLevelAlpha;
|
||||
if (!shape.PreserveWaveQuality)
|
||||
{
|
||||
_WaveBufferParameters[lodCount - 2].w = 1f - water.ViewerAltitudeLevelAlpha;
|
||||
}
|
||||
|
||||
_WaveBufferParameters[lodCount - 1].w = water.ViewerAltitudeLevelAlpha;
|
||||
|
||||
SetRenderParameters(water, wrapper);
|
||||
|
||||
wrapper.SetFloat(ShaderIDs.s_WaveResolutionMultiplier, shape.WaveResolutionMultiplier);
|
||||
wrapper.SetFloat(ShaderIDs.s_TransitionalWavelengthThreshold, water.MaximumWavelength(water.LodLevels - 1) * 0.5f);
|
||||
wrapper.SetFloat(ShaderIDs.s_TransitionalWavelengthThreshold, water.MaximumWavelength(water.LodLevels - 1, simulation.Resolution) * 0.5f);
|
||||
wrapper.SetVectorArray(ShaderIDs.s_WaveBufferParameters, _WaveBufferParameters);
|
||||
|
||||
var isTexture = Mode is LodInputMode.Paint or LodInputMode.Texture;
|
||||
@@ -236,6 +286,8 @@ namespace WaveHarmonic.Crest
|
||||
wrapper.SetKeyword(s_KeywordTexture, isTexture && !isAlphaBlend);
|
||||
wrapper.SetKeyword(s_KeywordTextureBlend, isTexture && isAlphaBlend);
|
||||
|
||||
wrapper.SetFloatArray(ShaderIDs.s_WaveBufferAttenuation, _ActiveSpectrum._Attenuation);
|
||||
|
||||
if (isTexture)
|
||||
{
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Blend, (int)_Blend);
|
||||
@@ -243,6 +295,8 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
wrapper.SetBoolean(ShaderIDs.s_SeaLevelOnly, _SeaLevelOnly);
|
||||
|
||||
var threads = shape.Resolution / Lod.k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, slice);
|
||||
}
|
||||
@@ -255,6 +309,7 @@ namespace WaveHarmonic.Crest
|
||||
void GraphicsDraw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass, float weight, int slice)
|
||||
{
|
||||
var lod = simulation as AnimatedWavesLod;
|
||||
var lodResolution = simulation.Resolution;
|
||||
|
||||
var wrapper = new PropertyWrapperBuffer(buffer);
|
||||
SetRenderParameters(simulation._Water, wrapper);
|
||||
@@ -266,7 +321,7 @@ namespace WaveHarmonic.Crest
|
||||
_Wavelength = MinWavelength(i) / lod.WaveResolutionMultiplier;
|
||||
|
||||
// Do the weight from scratch because this is the real filter.
|
||||
weight = AnimatedWavesLod.FilterByWavelength(simulation._Water, slice, _Wavelength) * Weight;
|
||||
weight = AnimatedWavesLod.FilterByWavelength(simulation._Water, slice, _Wavelength, lodResolution) * Weight;
|
||||
if (weight <= 0f) continue;
|
||||
|
||||
var average = _Wavelength * 1.5f * lod.WaveResolutionMultiplier;
|
||||
@@ -411,40 +466,101 @@ namespace WaveHarmonic.Crest
|
||||
s_KeywordTextureBlend = WaterResources.Instance.Keywords.AnimatedWavesTransferWavesTextureBlend;
|
||||
}
|
||||
|
||||
bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical)
|
||||
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical)
|
||||
{
|
||||
if (Mode == LodInputMode.Global || !Enabled)
|
||||
if (!Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
// Global is always additive.
|
||||
horizontal += MaximumReportedHorizontalDisplacement;
|
||||
vertical += MaximumReportedVerticalDisplacement;
|
||||
return true;
|
||||
}
|
||||
|
||||
_Rect = Data.Rect;
|
||||
|
||||
if (bounds.Overlaps(_Rect, false))
|
||||
{
|
||||
horizontal = MaximumReportedHorizontalDisplacement;
|
||||
vertical = MaximumReportedVerticalDisplacement;
|
||||
var nh = horizontal;
|
||||
var nv = vertical;
|
||||
switch (Blend)
|
||||
{
|
||||
case LodInputBlend.Off:
|
||||
nh = MaximumReportedHorizontalDisplacement;
|
||||
nv = MaximumReportedVerticalDisplacement;
|
||||
break;
|
||||
case LodInputBlend.Additive:
|
||||
nh += MaximumReportedHorizontalDisplacement;
|
||||
nv += MaximumReportedVerticalDisplacement;
|
||||
break;
|
||||
case LodInputBlend.Alpha:
|
||||
case LodInputBlend.AlphaClip:
|
||||
nh = Mathf.Max(nh, MaximumReportedHorizontalDisplacement);
|
||||
nv = Mathf.Max(nh, MaximumReportedVerticalDisplacement);
|
||||
break;
|
||||
}
|
||||
|
||||
horizontal = Mathf.Max(horizontal, nh);
|
||||
vertical = Mathf.Max(vertical, nv);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float ReportWaveDisplacement(WaterRenderer water, float displacement)
|
||||
{
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
return displacement + MaximumReportedWavesDisplacement;
|
||||
}
|
||||
|
||||
if (!_IncludeInDropDetailHeightBasedOnWaves)
|
||||
{
|
||||
return displacement;
|
||||
}
|
||||
|
||||
// TODO: use bounds to transition slowly to avoid pops.
|
||||
if (_Rect.Contains(water.Position.XZ()))
|
||||
{
|
||||
displacement = Blend switch
|
||||
{
|
||||
LodInputBlend.Off => MaximumReportedWavesDisplacement,
|
||||
LodInputBlend.Additive => displacement + MaximumReportedWavesDisplacement,
|
||||
LodInputBlend.Alpha or LodInputBlend.AlphaClip => Mathf.Max(displacement, MaximumReportedWavesDisplacement),
|
||||
_ => MaximumReportedWavesDisplacement,
|
||||
};
|
||||
}
|
||||
|
||||
return displacement;
|
||||
}
|
||||
|
||||
float GetWaveDirectionHeadingAngle()
|
||||
{
|
||||
return _OverrideGlobalWindDirection || WaterRenderer.Instance == null ? _WaveDirectionHeadingAngle : WaterRenderer.Instance.WindDirection;
|
||||
}
|
||||
|
||||
internal int GetResolution()
|
||||
{
|
||||
return Mathf.Clamp(_Resolution, MinimumResolution, MaximumResolution);
|
||||
}
|
||||
}
|
||||
|
||||
partial class ShapeWaves
|
||||
{
|
||||
Reporter _Reporter;
|
||||
|
||||
sealed class Reporter : IReportsDisplacement
|
||||
sealed class Reporter : IReportsDisplacement, IReportWaveDisplacement
|
||||
{
|
||||
readonly ShapeWaves _Input;
|
||||
public Reporter(ShapeWaves input) => _Input = input;
|
||||
public bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(ref bounds, ref horizontal, ref vertical);
|
||||
public bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(water, ref bounds, ref horizontal, ref vertical);
|
||||
public float ReportWaveDisplacement(WaterRenderer water, float displacement) => _Input.ReportWaveDisplacement(water, displacement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +581,7 @@ namespace WaveHarmonic.Crest
|
||||
if (s_WindSpectrum != null)
|
||||
{
|
||||
Helpers.Destroy(s_WindSpectrum);
|
||||
s_WindSpectrum = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,16 +589,20 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
partial class ShapeWaves
|
||||
{
|
||||
private protected override int Version => Mathf.Max(base.Version, 3);
|
||||
|
||||
[HideInInspector, SerializeField]
|
||||
AlphaSource _AlphaSource;
|
||||
enum AlphaSource { AlwaysOne, FromZero, FromZeroNormalized }
|
||||
|
||||
private protected int MigrateV1(int version)
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
base.OnMigrate();
|
||||
|
||||
// Version 1
|
||||
// - Merge Alpha Source into Blend.
|
||||
// - Rename and invert Spectrum Fixed at Run-Time
|
||||
if (version < 1)
|
||||
if (_Version < 1)
|
||||
{
|
||||
if (_Blend == LodInputBlend.Alpha)
|
||||
{
|
||||
@@ -495,24 +616,19 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
|
||||
_EvaluateSpectrumAtRunTimeEveryFrame = !_EvaluateSpectrumAtRunTimeEveryFrame;
|
||||
|
||||
version = 1;
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private protected int MigrateV2(int version)
|
||||
{
|
||||
// Version 2
|
||||
// - Global wind direction
|
||||
if (version < 2)
|
||||
if (_Version < 2)
|
||||
{
|
||||
_OverrideGlobalWindDirection = true;
|
||||
version = 2;
|
||||
}
|
||||
|
||||
return version;
|
||||
if (_Version < 3)
|
||||
{
|
||||
_SeaLevelOnly = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/Waves.html#adding-interaction-forces")]
|
||||
public sealed partial class SphereWaterInteraction : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[Tooltip("Radius of the sphere that is modelled from which the interaction forces are calculated.")]
|
||||
[@Range(0.01f, 50f)]
|
||||
[@GenerateAPI]
|
||||
|
||||
@@ -34,7 +34,10 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
internal override void RecalculateBounds()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
var transform = _Input.transform;
|
||||
var scale = transform.lossyScale.XZ();
|
||||
scale = Helpers.RotateAndEncapsulateXZ(scale, transform.rotation.eulerAngles.y);
|
||||
_Bounds = new(_Input.transform.position, scale.XNZ());
|
||||
}
|
||||
|
||||
internal override void Draw(Lod lod, Component component, CommandBuffer buffer, RenderTargetIdentifier target, int slices)
|
||||
@@ -45,7 +48,7 @@ namespace WaveHarmonic.Crest
|
||||
wrapper.SetVector(ShaderIDs.s_TextureSize, transform.lossyScale.XZ());
|
||||
wrapper.SetVector(ShaderIDs.s_TexturePosition, transform.position.XZ());
|
||||
wrapper.SetVector(ShaderIDs.s_TextureRotation, rotation);
|
||||
wrapper.SetVector(ShaderIDs.s_Resolution, new(_Texture.width, _Texture.height));
|
||||
wrapper.SetVector(ShaderIDs.s_TextureResolution, new(_Texture.width, _Texture.height));
|
||||
wrapper.SetVector(ShaderIDs.s_Multiplier, _Multiplier);
|
||||
wrapper.SetFloat(ShaderIDs.s_FeatherWidth, _Input.FeatherWidth);
|
||||
wrapper.SetTexture(ShaderIDs.s_Texture, _Texture);
|
||||
|
||||
@@ -52,14 +52,14 @@ namespace WaveHarmonic.Crest
|
||||
WatertightHullMode _Mode = WatertightHullMode.Displacement;
|
||||
|
||||
[Tooltip("Inverts the effect to remove clipping (ie add water).")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(WatertightHullMode.Clip), hide: true)]
|
||||
[@Show(nameof(_Mode), nameof(WatertightHullMode.Clip))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _Inverted;
|
||||
|
||||
[@Label("Use Clip")]
|
||||
[Tooltip("Whether to also to clip the surface when using displacement mode.\n\nDisplacement mode can have a leaky hull by allowing chop top push waves across the hull boundaries slightly. Clipping the surface will remove these interior leaks.")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(WatertightHullMode.Displacement), hide: true)]
|
||||
[@Show(nameof(_Mode), nameof(WatertightHullMode.Displacement))]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _UseClipWithDisplacement = true;
|
||||
@@ -248,28 +248,19 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
partial class WatertightHull : ISerializationCallbackReceiver
|
||||
partial class WatertightHull
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
private protected override int Version => Mathf.Max(base.Version, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
private protected override void OnMigrate()
|
||||
{
|
||||
base.OnMigrate();
|
||||
|
||||
if (_Version < 1)
|
||||
{
|
||||
// Keep clip for existing.
|
||||
_Mode = WatertightHullMode.Clip;
|
||||
_Version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace WaveHarmonic.Crest
|
||||
internal static readonly Color s_GizmoColor = new(75f / 255f, 0f, 130f / 255f, 0.5f);
|
||||
|
||||
internal override string ID => "Level";
|
||||
internal override string Name => "Water Level";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.black;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
@@ -41,6 +42,7 @@ namespace WaveHarmonic.Crest
|
||||
_OverrideResolution = false;
|
||||
_TextureFormatMode = LodTextureFormatMode.Automatic;
|
||||
_TextureFormat = GraphicsFormat.R16_SFloat;
|
||||
_BlurIterations = 4;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
#if !UNITY_2023_2_OR_NEWER
|
||||
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
|
||||
#endif
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
using Inputs = SortedList<int, ILodInput>;
|
||||
using Inputs = Utility.SortedList<int, ILodInput>;
|
||||
|
||||
/// <summary>
|
||||
/// Texture format preset.
|
||||
@@ -42,7 +39,7 @@ namespace WaveHarmonic.Crest
|
||||
/// Base class for data/behaviours created on each LOD.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract partial class Lod
|
||||
public abstract partial class Lod : Versioned
|
||||
{
|
||||
[Tooltip("Whether the simulation is enabled.")]
|
||||
[@GenerateAPI(Getter.Custom, Setter.Custom)]
|
||||
@@ -50,14 +47,13 @@ namespace WaveHarmonic.Crest
|
||||
internal bool _Enabled;
|
||||
|
||||
[Tooltip("Whether to override the resolution.\n\nIf not enabled, then the simulation will use the resolution defined on the Water Renderer.")]
|
||||
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
|
||||
[@Hide(typeof(AnimatedWavesLod))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@InlineToggle(fix: true), SerializeField]
|
||||
[@InlineToggle, SerializeField]
|
||||
internal bool _OverrideResolution = true;
|
||||
|
||||
[Tooltip("The resolution of the simulation data.\n\nSet higher for sharper results at the cost of higher memory usage.")]
|
||||
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
|
||||
[@Predicated(nameof(_OverrideResolution), hide: true)]
|
||||
[@Show(nameof(_OverrideResolution))]
|
||||
[@ShowComputedProperty(nameof(Resolution))]
|
||||
[@Delayed]
|
||||
[@GenerateAPI(Getter.Custom, Setter.Dirty)]
|
||||
@@ -72,25 +68,45 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[Tooltip("The render texture format used for this simulation data.\n\nIt will be overriden if the format is incompatible with the platform.")]
|
||||
[@ShowComputedProperty(nameof(RequestedTextureFormat))]
|
||||
[@Predicated(nameof(_TextureFormatMode), inverted: true, nameof(LodTextureFormatMode.Manual), hide: true)]
|
||||
[@Show(nameof(_TextureFormatMode), nameof(LodTextureFormatMode.Manual))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal GraphicsFormat _TextureFormat;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Blurs the output.\n\nEnable if blurring is desired or intolerable artifacts are present.\nThe blur is optimized to only run on inner LODs and at lower scales.")]
|
||||
[@Hide(typeof(AnimatedWavesLod))]
|
||||
[@Hide(typeof(DynamicWavesLod))]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
private protected bool _Blur;
|
||||
|
||||
[Tooltip("Number of blur iterations.\n\nBlur iterations are optimized to only run maximum iterations on the inner LODs.")]
|
||||
[@Hide(typeof(AnimatedWavesLod))]
|
||||
[@Hide(typeof(DynamicWavesLod))]
|
||||
[@Enable(nameof(_Blur))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
private protected int _BlurIterations = 1;
|
||||
|
||||
// NOTE: This MUST match the value in Constants.hlsl, as it
|
||||
// determines the size of the texture arrays in the shaders.
|
||||
internal const int k_MaximumSlices = 15;
|
||||
|
||||
// NOTE: these MUST match the values in Constants.hlsl
|
||||
// 64 recommended as a good common minimum: https://www.reddit.com/r/GraphicsProgramming/comments/aeyfkh/for_compute_shaders_is_there_an_ideal_numthreads/
|
||||
internal const int k_ThreadGroupSize = 8;
|
||||
internal const int k_ThreadGroupSizeX = k_ThreadGroupSize;
|
||||
internal const int k_ThreadGroupSizeY = k_ThreadGroupSize;
|
||||
|
||||
internal const string k_BlurField = nameof(_Blur);
|
||||
internal const string k_TextureFormatModeField = nameof(_TextureFormatMode);
|
||||
|
||||
internal static class ShaderIDs
|
||||
{
|
||||
public static readonly int s_LodIndex = Shader.PropertyToID("_Crest_LodIndex");
|
||||
public static readonly int s_LodChange = Shader.PropertyToID("_Crest_LodChange");
|
||||
public static readonly int s_TemporaryBlurLodTexture = Shader.PropertyToID("_Crest_TemporaryBlurLodTexture");
|
||||
}
|
||||
|
||||
// Used for creating shader property names etc.
|
||||
@@ -136,17 +152,10 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
static readonly GraphicsFormatUsage s_GraphicsFormatUsage =
|
||||
// Ensures a non compressed format is returned.
|
||||
GraphicsFormatUsage.LoadStore |
|
||||
// All these textures are sampled at some point.
|
||||
GraphicsFormatUsage.Sample |
|
||||
// Always use linear filtering.
|
||||
GraphicsFormatUsage.Linear;
|
||||
private protected bool Persistent => BufferCount > 1;
|
||||
internal virtual bool SkipEndOfFrame => false;
|
||||
|
||||
private protected BufferedData<RenderTexture> _Targets;
|
||||
internal RenderTexture DataTexture => _Targets.Current;
|
||||
internal RenderTexture GetDataTexture(int frameDelta) => _Targets.Previous(frameDelta);
|
||||
internal RenderTexture DataTexture { get; private protected set; }
|
||||
|
||||
private protected Matrix4x4[] _ViewMatrices = new Matrix4x4[k_MaximumSlices];
|
||||
private protected Cascade[] _Cascades = new Cascade[k_MaximumSlices];
|
||||
@@ -161,7 +170,7 @@ namespace WaveHarmonic.Crest
|
||||
internal WaterRenderer _Water;
|
||||
internal WaterRenderer Water => _Water;
|
||||
|
||||
private protected int _TargetsToClear;
|
||||
private protected bool _TargetsToClear;
|
||||
|
||||
private protected readonly int _TextureShaderID;
|
||||
private protected readonly int _TextureSourceShaderID;
|
||||
@@ -184,8 +193,10 @@ namespace WaveHarmonic.Crest
|
||||
_TextureName = $"_Crest_{ID}Lod";
|
||||
}
|
||||
|
||||
private protected RenderTexture CreateLodDataTextures()
|
||||
private protected RenderTexture CreateLodDataTextures(string postfix = null)
|
||||
{
|
||||
postfix ??= string.Empty;
|
||||
|
||||
RenderTexture result = new(Resolution, Resolution, 0, CompatibleTextureFormat)
|
||||
{
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
@@ -193,7 +204,7 @@ namespace WaveHarmonic.Crest
|
||||
filterMode = FilterMode.Bilinear,
|
||||
anisoLevel = 0,
|
||||
useMipMap = false,
|
||||
name = _TextureName,
|
||||
name = _TextureName + postfix,
|
||||
dimension = TextureDimension.Tex2DArray,
|
||||
volumeDepth = Slices,
|
||||
enableRandomWrite = NeedToReadWriteTextureData
|
||||
@@ -202,7 +213,7 @@ namespace WaveHarmonic.Crest
|
||||
return result;
|
||||
}
|
||||
|
||||
private protected void FlipBuffers()
|
||||
private protected void FlipBuffers(CommandBuffer commands)
|
||||
{
|
||||
if (_ReAllocateTexture)
|
||||
{
|
||||
@@ -214,11 +225,10 @@ namespace WaveHarmonic.Crest
|
||||
if (!UnityEditor.EditorApplication.isPaused || Time.deltaTime > 0)
|
||||
#endif
|
||||
{
|
||||
_Targets.Flip();
|
||||
_SamplingParameters.Flip();
|
||||
}
|
||||
|
||||
UpdateSamplingParameters();
|
||||
UpdateSamplingParameters(commands);
|
||||
}
|
||||
|
||||
private protected void Clear(RenderTexture target)
|
||||
@@ -226,29 +236,36 @@ namespace WaveHarmonic.Crest
|
||||
Helpers.ClearRenderTexture(target, ClearColor, depth: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears persistent LOD data. Some simulations have persistent data which can linger for a little while after
|
||||
/// being disabled. This will manually clear that data.
|
||||
/// </summary>
|
||||
internal virtual void ClearLodData()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
|
||||
private protected virtual bool AlwaysClear => false;
|
||||
|
||||
// Only works with input-only data (ie no simulation steps).
|
||||
internal virtual void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
FlipBuffers();
|
||||
FlipBuffers(buffer);
|
||||
|
||||
buffer.BeginSample(ID);
|
||||
|
||||
if (_TargetsToClear > 0 || AlwaysClear)
|
||||
if (_TargetsToClear || AlwaysClear)
|
||||
{
|
||||
// IMPORTANT:
|
||||
// We need both clears otherwise problems on PS5.
|
||||
// With only native clear, flow would clear to non black (ie constant flow).
|
||||
// With only custom clear, absorption/scattering inputs were not clearing.
|
||||
CoreUtils.SetRenderTarget(buffer, DataTexture, ClearFlag.Color, ClearColor);
|
||||
|
||||
_TargetsToClear--;
|
||||
// Custom clear because clear not working.
|
||||
if (Helpers.RequiresCustomClear && WaterResources.Instance.Compute._Clear != null)
|
||||
{
|
||||
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
|
||||
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTarget);
|
||||
compute.SetVariantForFormat(wrapper, CompatibleTextureFormat);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearMask, Color.white);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
|
||||
wrapper.Dispatch(Resolution / k_ThreadGroupSizeX, Resolution / k_ThreadGroupSizeY, Slices);
|
||||
}
|
||||
|
||||
_TargetsToClear = false;
|
||||
}
|
||||
|
||||
if (Inputs.Count > 0)
|
||||
@@ -256,9 +273,11 @@ namespace WaveHarmonic.Crest
|
||||
SubmitDraws(buffer, Inputs, DataTexture);
|
||||
|
||||
// Ensure all targets clear when there are no inputs.
|
||||
_TargetsToClear = _Targets.Size;
|
||||
_TargetsToClear = true;
|
||||
}
|
||||
|
||||
TryBlur(buffer);
|
||||
|
||||
if (RequiresClearBorder)
|
||||
{
|
||||
ClearBorder(buffer);
|
||||
@@ -354,14 +373,18 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
var size = Resolution / 8;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Clear, 1);
|
||||
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTargetBoundaryX);
|
||||
// Only need to be done once.
|
||||
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_TargetSlice, Slices - 1);
|
||||
wrapper.Dispatch(size, 1, 1);
|
||||
|
||||
wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Clear, 2);
|
||||
wrapper = new(buffer, compute._Shader, compute._KernelClearTargetBoundaryY);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
|
||||
@@ -369,7 +392,7 @@ namespace WaveHarmonic.Crest
|
||||
wrapper.Dispatch(1, size, 1);
|
||||
}
|
||||
|
||||
void UpdateSamplingParameters(bool initialize = false)
|
||||
void UpdateSamplingParameters(CommandBuffer commands, bool initialize = false)
|
||||
{
|
||||
var position = _Water.Position;
|
||||
var resolution = _Enabled ? Resolution : TextureArrayHelpers.k_SmallTextureSize;
|
||||
@@ -380,7 +403,7 @@ namespace WaveHarmonic.Crest
|
||||
for (var slice = 0; slice < levels; slice++)
|
||||
{
|
||||
// Find snap period.
|
||||
var texel = 2f * 2f * _Water._CascadeData.Current[slice].x / resolution;
|
||||
var texel = 2f * 2f * _Water.CascadeData.Current[slice].x / resolution;
|
||||
// Snap so that shape texels are stationary.
|
||||
var snapped = position - new Vector3(Mathf.Repeat(position.x, texel), 0, Mathf.Repeat(position.z, texel));
|
||||
|
||||
@@ -392,11 +415,20 @@ namespace WaveHarmonic.Crest
|
||||
_ViewMatrices[slice] = WaterRenderer.CalculateViewMatrixFromSnappedPositionRHS(snapped);
|
||||
}
|
||||
|
||||
if (initialize)
|
||||
if (!initialize)
|
||||
{
|
||||
Shader.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
|
||||
commands.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
|
||||
commands.SetGlobalVectorArray(_SamplingParametersCascadeShaderID, parameters);
|
||||
|
||||
if (BufferCount > 1)
|
||||
{
|
||||
commands.SetGlobalVectorArray(_SamplingParametersCascadeSourceShaderID, _SamplingParameters.Previous(1));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Shader.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
|
||||
Shader.SetGlobalVectorArray(_SamplingParametersCascadeShaderID, parameters);
|
||||
|
||||
if (BufferCount > 1)
|
||||
@@ -468,7 +500,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
// The smallest wavelengths should repeat no more than twice across the smaller
|
||||
// spatial length. Unless we're in the last LOD - then this is the best we can do.
|
||||
var minimumWavelength = _Water.MaximumWavelength(index) / 2f;
|
||||
var minimumWavelength = _Water.MaximumWavelength(index, Resolution) / 2f;
|
||||
if (minimumWavelength < minimumSpatialLength / 2f && index < count - 1)
|
||||
{
|
||||
continue;
|
||||
@@ -480,6 +512,48 @@ namespace WaveHarmonic.Crest
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Blurs the output if enabled.
|
||||
private protected void TryBlur(CommandBuffer commands)
|
||||
{
|
||||
if (!_Blur || _Water.Scale >= 32)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rt = DataTexture;
|
||||
|
||||
var compute = WaterResources.Instance._ComputeLibrary._BlurCompute;
|
||||
|
||||
var horizontal = new PropertyWrapperCompute(commands, compute._Shader, compute._KernelHorizontal);
|
||||
var vertical = new PropertyWrapperCompute(commands, compute._Shader, compute._KernelVertical);
|
||||
|
||||
var temporary = ShaderIDs.s_TemporaryBlurLodTexture;
|
||||
|
||||
commands.GetTemporaryRT(temporary, rt.descriptor);
|
||||
|
||||
// Applies to both.
|
||||
compute.SetVariantForFormat(horizontal, rt.graphicsFormat);
|
||||
horizontal.SetInteger(Crest.ShaderIDs.s_Resolution, rt.width);
|
||||
|
||||
horizontal.SetTexture(Crest.ShaderIDs.s_Source, rt);
|
||||
horizontal.SetTexture(Crest.ShaderIDs.s_Target, temporary);
|
||||
vertical.SetTexture(Crest.ShaderIDs.s_Source, temporary);
|
||||
vertical.SetTexture(Crest.ShaderIDs.s_Target, rt);
|
||||
|
||||
var x = rt.width / 8;
|
||||
var y = rt.height / 8;
|
||||
// Skip outer LODs.
|
||||
var z = Mathf.Min(rt.volumeDepth, 4);
|
||||
for (var i = 0; i < _BlurIterations; i++)
|
||||
{
|
||||
// Limit number of iterations for outer LODs.
|
||||
horizontal.Dispatch(x, y, Mathf.Max(z - i, 1));
|
||||
vertical.Dispatch(x, y, Mathf.Max(z - i, 1));
|
||||
}
|
||||
|
||||
commands.ReleaseTemporaryRT(temporary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind data needed to load or compute from this simulation.
|
||||
/// </summary>
|
||||
@@ -500,7 +574,7 @@ namespace WaveHarmonic.Crest
|
||||
// Validate textures.
|
||||
{
|
||||
// Find a compatible texture format.
|
||||
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, s_GraphicsFormatUsage, Name, NeedToReadWriteTextureData);
|
||||
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, Helpers.s_DataGraphicsFormatUsage, Name, NeedToReadWriteTextureData);
|
||||
|
||||
if (CompatibleTextureFormat == GraphicsFormat.None)
|
||||
{
|
||||
@@ -523,20 +597,15 @@ namespace WaveHarmonic.Crest
|
||||
// Bind/unbind data texture for all shaders.
|
||||
Shader.SetGlobalTexture(_TextureShaderID, enable && Enabled ? DataTexture : NullTexture);
|
||||
|
||||
if (BufferCount > 1)
|
||||
{
|
||||
Shader.SetGlobalTexture(_TextureSourceShaderID, enable && Enabled ? GetDataTexture(1) : NullTexture);
|
||||
}
|
||||
|
||||
if (_SamplingParameters == null || _SamplingParameters.Size != BufferCount)
|
||||
{
|
||||
_SamplingParameters = new(BufferCount, () => new Vector4[k_MaximumSlices]);
|
||||
}
|
||||
|
||||
// For safety. Disable to see if we are sampling outside of LOD chain.
|
||||
_SamplingParameters.RunLambda(x => System.Array.Fill(x, Vector4.zero));
|
||||
_SamplingParameters.RunLambda(x => System.Array.Fill(x, new(0, 0, 1, 0)));
|
||||
|
||||
UpdateSamplingParameters(initialize: true);
|
||||
UpdateSamplingParameters(null, initialize: true);
|
||||
}
|
||||
|
||||
internal virtual void Enable()
|
||||
@@ -553,11 +622,10 @@ namespace WaveHarmonic.Crest
|
||||
internal virtual void Destroy()
|
||||
{
|
||||
// Release resources and destroy object to avoid reference leak.
|
||||
_Targets?.RunLambda(x =>
|
||||
{
|
||||
if (x != null) x.Release();
|
||||
Helpers.Destroy(x);
|
||||
});
|
||||
if (DataTexture != null) DataTexture.Release();
|
||||
Helpers.Destroy(DataTexture);
|
||||
|
||||
_AdditionalCameraData.Clear();
|
||||
}
|
||||
|
||||
internal virtual void AfterExecute()
|
||||
@@ -567,10 +635,11 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
private protected virtual void Allocate()
|
||||
{
|
||||
_Targets = new(BufferCount, CreateLodDataTextures);
|
||||
_Targets.RunLambda(Clear);
|
||||
// Use per-camera data.
|
||||
DataTexture = CreateLodDataTextures();
|
||||
Clear(DataTexture);
|
||||
|
||||
// Bind globally once here on init, which will bind to all graphics shaders (not compute)
|
||||
// Bind globally once here on init, which will bind to all shaders.
|
||||
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
|
||||
_ReAllocateTexture = false;
|
||||
@@ -600,23 +669,21 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
int GetResolution() => _OverrideResolution || Water == null ? _Resolution : Water.LodResolution;
|
||||
|
||||
private protected void ReAllocate()
|
||||
private protected virtual void ReAllocate()
|
||||
{
|
||||
if (!Enabled) return;
|
||||
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, s_GraphicsFormatUsage, Name, NeedToReadWriteTextureData);
|
||||
var descriptor = _Targets.Current.descriptor;
|
||||
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, Helpers.s_DataGraphicsFormatUsage, Name, NeedToReadWriteTextureData);
|
||||
var descriptor = DataTexture.descriptor;
|
||||
descriptor.height = descriptor.width = Resolution;
|
||||
descriptor.graphicsFormat = CompatibleTextureFormat;
|
||||
_Targets.RunLambda(texture =>
|
||||
{
|
||||
texture.Release();
|
||||
texture.descriptor = descriptor;
|
||||
texture.Create();
|
||||
});
|
||||
descriptor.enableRandomWrite = NeedToReadWriteTextureData;
|
||||
DataTexture.Release();
|
||||
DataTexture.descriptor = descriptor;
|
||||
DataTexture.Create();
|
||||
|
||||
_ReAllocateTexture = false;
|
||||
|
||||
UpdateSamplingParameters(initialize: true);
|
||||
UpdateSamplingParameters(null, initialize: true);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -628,6 +695,7 @@ namespace WaveHarmonic.Crest
|
||||
case nameof(_Enabled):
|
||||
SetEnabled((bool)previousValue, _Enabled);
|
||||
break;
|
||||
case nameof(_Blur):
|
||||
case nameof(_Resolution):
|
||||
case nameof(_OverrideResolution):
|
||||
case nameof(_TextureFormat):
|
||||
@@ -639,6 +707,45 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
}
|
||||
|
||||
partial class Lod
|
||||
{
|
||||
readonly Dictionary<Camera, BufferedData<Vector4[]>> _AdditionalCameraData = new();
|
||||
|
||||
internal virtual void LoadCameraData(Camera camera)
|
||||
{
|
||||
Queryable?.Initialize(_Water);
|
||||
|
||||
// For non-persistent sims, we do not need to store per camera data.
|
||||
if (!Persistent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
_SamplingParameters = new(BufferCount, () => new Vector4[k_MaximumSlices]);
|
||||
_AdditionalCameraData.Add(camera, _SamplingParameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SamplingParameters = _AdditionalCameraData[camera];
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void StoreCameraData(Camera camera)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal virtual void RemoveCameraData(Camera camera)
|
||||
{
|
||||
if (_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
_AdditionalCameraData.Remove(camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
partial class Lod
|
||||
{
|
||||
@@ -657,29 +764,78 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
interface IQueryableLod<out T> where T : IQueryProvider
|
||||
{
|
||||
string Name { get; }
|
||||
bool Enabled { get; }
|
||||
WaterRenderer Water { get; }
|
||||
int MaximumQueryCount { get; }
|
||||
float Texel { get; }
|
||||
LodQuerySource QuerySource { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The source of collisions (ie water shape).
|
||||
/// </summary>
|
||||
[@GenerateDoc]
|
||||
public enum LodQuerySource
|
||||
{
|
||||
/// <inheritdoc cref="Generated.LodQuerySource.None"/>
|
||||
[Tooltip("No query source.")]
|
||||
None,
|
||||
|
||||
/// <inheritdoc cref="Generated.LodQuerySource.GPU"/>
|
||||
[Tooltip("Uses AsyncGPUReadback to retrieve data from GPU to CPU.\n\nThis is the most optimal approach.")]
|
||||
GPU,
|
||||
|
||||
/// <inheritdoc cref="Generated.LodQuerySource.CPU"/>
|
||||
[Tooltip("Computes data entirely on the CPU.")]
|
||||
CPU,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base type for simulations with a provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The query provider.</typeparam>
|
||||
[System.Serializable]
|
||||
public abstract class Lod<T> : Lod where T : IQueryProvider
|
||||
public abstract partial class Lod<T> : Lod, IQueryableLod<T> where T : IQueryProvider
|
||||
{
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Where to obtain water data on CPU for physics / gameplay.")]
|
||||
[@GenerateAPI(Setter.Internal)]
|
||||
[@Filtered]
|
||||
[SerializeField]
|
||||
private protected LodQuerySource _QuerySource = LodQuerySource.GPU;
|
||||
|
||||
[Tooltip("Maximum number of queries that can be performed when using GPU queries.")]
|
||||
[@Show(nameof(_QuerySource), nameof(LodQuerySource.GPU))]
|
||||
[@GenerateAPI(Setter.None)]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
private protected int _MaximumQueryCount = QueryBase.k_DefaultMaximumQueryCount;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data from the GPU to CPU.
|
||||
/// </summary>
|
||||
public T Provider { get; set; }
|
||||
private protected abstract T CreateProvider(bool enable);
|
||||
|
||||
internal override void SetGlobals(bool enable)
|
||||
WaterRenderer IQueryableLod<T>.Water => Water;
|
||||
string IQueryableLod<T>.Name => Name;
|
||||
float IQueryableLod<T>.Texel => _Cascades[0]._Texel;
|
||||
|
||||
private protected abstract T CreateProvider(bool onEnable);
|
||||
|
||||
internal override void SetGlobals(bool onEnable)
|
||||
{
|
||||
base.SetGlobals(enable);
|
||||
base.SetGlobals(onEnable);
|
||||
// We should always have a provider (null provider if disabled).
|
||||
InitializeProvider(enable);
|
||||
InitializeProvider(onEnable);
|
||||
}
|
||||
|
||||
private protected void InitializeProvider(bool enable)
|
||||
private protected void InitializeProvider(bool onEnable)
|
||||
{
|
||||
Provider = CreateProvider(enable);
|
||||
Provider = CreateProvider(onEnable);
|
||||
// None providers are not IQueryable.
|
||||
Queryable = Provider as IQueryable;
|
||||
}
|
||||
@@ -689,4 +845,30 @@ namespace WaveHarmonic.Crest
|
||||
Queryable?.SendReadBack(_Water);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
partial class Lod<T>
|
||||
{
|
||||
private protected void ResetQueryChange()
|
||||
{
|
||||
if (_Water == null || !_Water.isActiveAndEnabled || !_Enabled) return;
|
||||
Queryable?.CleanUp();
|
||||
InitializeProvider(true);
|
||||
}
|
||||
|
||||
[@OnChange]
|
||||
private protected override void OnChange(string path, object previous)
|
||||
{
|
||||
base.OnChange(path, previous);
|
||||
|
||||
switch (path)
|
||||
{
|
||||
case nameof(_QuerySource):
|
||||
case nameof(_MaximumQueryCount):
|
||||
ResetQueryChange();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ namespace WaveHarmonic.Crest
|
||||
[System.Serializable]
|
||||
public abstract partial class PersistentLod : Lod
|
||||
{
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Frequency to run the simulation, in updates per second.\n\nLower frequencies are more efficient but may lead to visible jitter or slowness.")]
|
||||
[@Range(15, 200, order = -1000)]
|
||||
[@Range(15, 200)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
private protected int _SimulationFrequency = 60;
|
||||
@@ -21,27 +23,27 @@ namespace WaveHarmonic.Crest
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_SimDeltaTime = Shader.PropertyToID("_Crest_SimDeltaTime");
|
||||
public static readonly int s_SimDeltaTimePrev = Shader.PropertyToID("_Crest_SimDeltaTimePrev");
|
||||
public static readonly int s_TemporaryPersistentTarget = Shader.PropertyToID("_Crest_TemporaryPersistentTarget");
|
||||
}
|
||||
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
internal override int BufferCount => 2;
|
||||
|
||||
float _PreviousSubstepDeltaTime = 1f / 60f;
|
||||
|
||||
// Is this the first step since being enabled?
|
||||
private protected bool _NeedsPrewarmingThisStep = true;
|
||||
|
||||
// This is how far the simulation time is behind Unity's time.
|
||||
private protected float _TimeToSimulate = 0f;
|
||||
|
||||
// Pristine historic data. Needed if using blur or multiple viewpoints. For the
|
||||
// latter, we cannot optimize the upstream data texture away due to camera filtering.
|
||||
private protected RenderTexture _PersistentDataTexture;
|
||||
|
||||
internal int LastUpdateSubstepCount { get; private set; }
|
||||
|
||||
private protected virtual int Kernel => 0;
|
||||
private protected virtual bool SkipFlipBuffers => false;
|
||||
private protected abstract ComputeShader SimulationShader { get; }
|
||||
private protected abstract void GetSubstepData(float timeToSimulate, out int substeps, out float delta);
|
||||
|
||||
internal override void Initialize()
|
||||
{
|
||||
@@ -56,28 +58,51 @@ namespace WaveHarmonic.Crest
|
||||
_NeedsPrewarmingThisStep = true;
|
||||
}
|
||||
|
||||
internal override void ClearLodData()
|
||||
private protected override void Allocate()
|
||||
{
|
||||
base.ClearLodData();
|
||||
_Targets.RunLambda(x => Clear(x));
|
||||
base.Allocate();
|
||||
|
||||
// Use per-camera data.
|
||||
if (!_Water.IsSingleViewpointMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Blur)
|
||||
{
|
||||
_PersistentDataTexture = CreateLodDataTextures("_Source");
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
|
||||
if (_PersistentDataTexture != null) _PersistentDataTexture.Release();
|
||||
Helpers.Destroy(_PersistentDataTexture);
|
||||
|
||||
foreach (var data in _AdditionalCameraData.Values)
|
||||
{
|
||||
var x = data._PersistentData;
|
||||
if (x != null) x.Release();
|
||||
Helpers.Destroy(x);
|
||||
}
|
||||
|
||||
_AdditionalCameraData.Clear();
|
||||
}
|
||||
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
buffer.BeginSample(ID);
|
||||
|
||||
if (!SkipFlipBuffers)
|
||||
{
|
||||
FlipBuffers();
|
||||
}
|
||||
|
||||
var slices = water.LodLevels;
|
||||
FlipBuffers(buffer);
|
||||
|
||||
// How far are we behind.
|
||||
_TimeToSimulate += water.DeltaTime;
|
||||
|
||||
// Do a set of substeps to catch up.
|
||||
GetSubstepData(_TimeToSimulate, out var substeps, out var delta);
|
||||
var substeps = Mathf.FloorToInt(_TimeToSimulate * _SimulationFrequency);
|
||||
var delta = substeps > 0 ? (1f / _SimulationFrequency) : 0f;
|
||||
|
||||
LastUpdateSubstepCount = substeps;
|
||||
|
||||
@@ -91,7 +116,10 @@ namespace WaveHarmonic.Crest
|
||||
delta = 0f;
|
||||
}
|
||||
|
||||
if (substeps > 1)
|
||||
// Use temporary if only storing one texture upstream which has the source.
|
||||
var useTemporary = _Water.IsSingleViewpointMode && !Blur;
|
||||
|
||||
if (useTemporary)
|
||||
{
|
||||
// No need to clear, as the update dispatch overwrites every pixel, but finding
|
||||
// artifacts if not and there is a renderer input. Happens for foam and dynamic
|
||||
@@ -100,9 +128,9 @@ namespace WaveHarmonic.Crest
|
||||
CoreUtils.SetRenderTarget(buffer, ShaderIDs.s_TemporaryPersistentTarget, ClearFlag.Color, ClearColor);
|
||||
}
|
||||
|
||||
var target = new RenderTargetIdentifier(DataTexture);
|
||||
var source = new RenderTargetIdentifier(ShaderIDs.s_TemporaryPersistentTarget);
|
||||
var current = target;
|
||||
var final = new RenderTargetIdentifier(DataTexture);
|
||||
var target = useTemporary ? new RenderTargetIdentifier(ShaderIDs.s_TemporaryPersistentTarget) : final;
|
||||
var source = useTemporary ? final : new RenderTargetIdentifier(_PersistentDataTexture);
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, SimulationShader, Kernel);
|
||||
|
||||
@@ -130,10 +158,9 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
// Both simulation update and input draws need delta time.
|
||||
buffer.SetGlobalFloat(ShaderIDs.s_SimDeltaTime, delta);
|
||||
buffer.SetGlobalFloat(ShaderIDs.s_SimDeltaTimePrev, _PreviousSubstepDeltaTime);
|
||||
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Source, source);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
|
||||
wrapper.SetTexture(_TextureSourceShaderID, isFirstStep ? _Targets.Previous(1) : source);
|
||||
|
||||
// Compute which LOD data we are sampling source data from. if a scale change has
|
||||
// happened this can be any LOD up or down the chain. This is only valid on the
|
||||
@@ -141,7 +168,7 @@ namespace WaveHarmonic.Crest
|
||||
// places.
|
||||
wrapper.SetFloat(Lod.ShaderIDs.s_LodChange, isFirstStep ? _Water.ScaleDifferencePower2 : 0);
|
||||
|
||||
wrapper.SetVectorArray(WaterRenderer.ShaderIDs.s_CascadeDataSource, _Water._CascadeData.Previous(frame));
|
||||
wrapper.SetVectorArray(WaterRenderer.ShaderIDs.s_CascadeDataSource, _Water.CascadeData.Previous(frame));
|
||||
wrapper.SetVectorArray(_SamplingParametersCascadeSourceShaderID, _SamplingParameters.Previous(frame));
|
||||
|
||||
SetAdditionalSimulationParameters(wrapper);
|
||||
@@ -157,22 +184,25 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
// The very first step since being enabled.
|
||||
_NeedsPrewarmingThisStep = false;
|
||||
_PreviousSubstepDeltaTime = delta;
|
||||
}
|
||||
|
||||
// Swap textures if needed.
|
||||
if (target != current)
|
||||
if (target != final)
|
||||
{
|
||||
buffer.CopyTexture(target, DataTexture);
|
||||
buffer.CopyTexture(target, final);
|
||||
}
|
||||
// Preserve non-blurred historic data.
|
||||
else if (!useTemporary)
|
||||
{
|
||||
buffer.CopyTexture(target, source);
|
||||
}
|
||||
|
||||
if (substeps > 1)
|
||||
if (useTemporary)
|
||||
{
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_TemporaryPersistentTarget);
|
||||
}
|
||||
|
||||
// Set the target texture as to make sure we catch the 'pong' each frame.
|
||||
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
TryBlur(buffer);
|
||||
|
||||
buffer.EndSample(ID);
|
||||
}
|
||||
@@ -184,5 +214,107 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private protected override void ReAllocate()
|
||||
{
|
||||
base.ReAllocate();
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var descriptor = DataTexture.descriptor;
|
||||
|
||||
if (_Water.IsMultipleViewpointMode)
|
||||
{
|
||||
foreach (var (key, data) in _AdditionalCameraData)
|
||||
{
|
||||
var texture = data._PersistentData;
|
||||
texture.Release();
|
||||
texture.descriptor = descriptor;
|
||||
texture.Create();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_PersistentDataTexture != null)
|
||||
{
|
||||
_PersistentDataTexture.Release();
|
||||
if (Blur)
|
||||
{
|
||||
_PersistentDataTexture.descriptor = descriptor;
|
||||
_PersistentDataTexture.Create();
|
||||
}
|
||||
else
|
||||
{
|
||||
Helpers.Destroy(_PersistentDataTexture);
|
||||
}
|
||||
}
|
||||
else if (Blur)
|
||||
{
|
||||
_PersistentDataTexture = CreateLodDataTextures("_Source");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class PersistentLod
|
||||
{
|
||||
sealed class AdditionalCameraData
|
||||
{
|
||||
public RenderTexture _PersistentData;
|
||||
public float _TimeToSimulate;
|
||||
}
|
||||
|
||||
readonly System.Collections.Generic.Dictionary<Camera, AdditionalCameraData> _AdditionalCameraData = new();
|
||||
|
||||
internal override void LoadCameraData(Camera camera)
|
||||
{
|
||||
base.LoadCameraData(camera);
|
||||
|
||||
AdditionalCameraData data;
|
||||
|
||||
if (!_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
data = new()
|
||||
{
|
||||
_PersistentData = CreateLodDataTextures("_Source"),
|
||||
_TimeToSimulate = _TimeToSimulate,
|
||||
};
|
||||
|
||||
_AdditionalCameraData.Add(camera, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = _AdditionalCameraData[camera];
|
||||
}
|
||||
|
||||
_PersistentDataTexture = data._PersistentData;
|
||||
_TimeToSimulate = data._TimeToSimulate;
|
||||
}
|
||||
|
||||
internal override void StoreCameraData(Camera camera)
|
||||
{
|
||||
base.StoreCameraData(camera);
|
||||
|
||||
if (_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
_AdditionalCameraData[camera]._TimeToSimulate = _TimeToSimulate;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void RemoveCameraData(Camera camera)
|
||||
{
|
||||
base.RemoveCameraData(camera);
|
||||
|
||||
if (_AdditionalCameraData.ContainsKey(camera))
|
||||
{
|
||||
var rt = _AdditionalCameraData[camera]._PersistentData;
|
||||
if (rt != null) rt.Release();
|
||||
Helpers.Destroy(rt);
|
||||
_AdditionalCameraData.Remove(camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,6 @@ namespace WaveHarmonic.Crest
|
||||
[AddComponentMenu(Constants.k_MenuPrefixDebug + "Collision Area Visualizer")]
|
||||
sealed class CollisionAreaVisualizer : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[Tooltip(ICollisionProvider.k_LayerTooltip)]
|
||||
[SerializeField]
|
||||
internal CollisionLayer _Layer;
|
||||
|
||||
@@ -38,12 +38,17 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
internal static ICollisionProvider Create(WaterRenderer water)
|
||||
{
|
||||
return water.IsMultipleViewpointMode ? new CollisionQueryPerCamera(water) : new CollisionQueryWithPasses(water);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives a flat, still water.
|
||||
/// </summary>
|
||||
internal sealed class NoneProvider : ICollisionProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything)
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything, Vector3? _4 = null)
|
||||
{
|
||||
if (result0 != null) System.Array.Fill(result0, Vector3.zero);
|
||||
if (result1 != null) System.Array.Fill(result1, Vector3.up);
|
||||
@@ -51,7 +56,7 @@ namespace WaveHarmonic.Crest
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Query(int _0, float _1, Vector3[] _2, float[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything)
|
||||
public int Query(int _0, float _1, Vector3[] _2, float[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything, Vector3? _4 = null)
|
||||
{
|
||||
if (result0 != null) System.Array.Fill(result0, WaterRenderer.Instance.SeaLevel);
|
||||
if (result1 != null) System.Array.Fill(result1, Vector3.up);
|
||||
@@ -66,12 +71,11 @@ namespace WaveHarmonic.Crest
|
||||
/// <param name="heights">Resulting heights (displacement Y + sea level) at the query positions. Pass null if this information is not required.</param>
|
||||
/// <param name="normals">Resulting normals at the query positions. Pass null if this information is not required.</param>
|
||||
/// <param name="velocities">Resulting velocities at the query positions. Pass null if this information is not required.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything);
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int, Vector3?)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null);
|
||||
|
||||
/// <param name="displacements">Resulting displacement vectors at the query positions. Add sea level to Y to get world space height.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
/// <inheritdoc cref="Query(int, float, Vector3[], float[], Vector3[], Vector3[], CollisionLayer)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything);
|
||||
/// <inheritdoc cref="Query(int, float, Vector3[], float[], Vector3[], Vector3[], CollisionLayer, Vector3?)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
@@ -10,10 +11,10 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
sealed class CollisionQuery : QueryBase, ICollisionProvider
|
||||
{
|
||||
public CollisionQuery(WaterRenderer water) : base(water) { }
|
||||
public CollisionQuery(WaterRenderer water) : base(water.AnimatedWavesLod) { }
|
||||
protected override int Kernel => 0;
|
||||
|
||||
public int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] resultDisplacements, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
public int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] resultDisplacements, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
@@ -35,7 +36,7 @@ namespace WaveHarmonic.Crest
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Query(int ownerHash, float minimumSpatialLength, Vector3[] queryPoints, float[] resultHeights, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
public int Query(int ownerHash, float minimumSpatialLength, Vector3[] queryPoints, float[] resultHeights, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
@@ -58,6 +59,102 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CollisionQueryPerCamera : QueryPerCamera<CollisionQueryWithPasses>, ICollisionProvider
|
||||
{
|
||||
public CollisionQueryPerCamera() : base(WaterRenderer.Instance) { }
|
||||
public CollisionQueryPerCamera(WaterRenderer water) : base(water) { }
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
|
||||
{
|
||||
if (_Water.IsSeparateViewpointCameraLoop)
|
||||
{
|
||||
return _Providers[_Water.CurrentCamera].Query(hash, minimumLength, points, heights, normals, velocities, layer, center);
|
||||
}
|
||||
|
||||
var lastStatus = -1;
|
||||
var lastDistance = Mathf.Infinity;
|
||||
|
||||
var newCenter = FindCenter(points, center);
|
||||
|
||||
foreach (var provider in _Providers)
|
||||
{
|
||||
var camera = provider.Key;
|
||||
|
||||
if (!_Water.ShouldExecuteQueries(camera))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var distance = (newCenter - camera.transform.position.XZ()).sqrMagnitude;
|
||||
|
||||
if (lastStatus == (int)QueryBase.QueryStatus.OK && lastDistance < distance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var status = provider.Value.Query(hash, minimumLength, points, heights, normals, velocities, layer, center);
|
||||
|
||||
if (lastStatus < 0 || status == (int)QueryBase.QueryStatus.OK)
|
||||
{
|
||||
lastStatus = status;
|
||||
lastDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return lastStatus;
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
|
||||
{
|
||||
if (_Water.IsSeparateViewpointCameraLoop)
|
||||
{
|
||||
return _Providers[_Water.CurrentCamera].Query(hash, minimumLength, points, displacements, normals, velocities, layer, center);
|
||||
}
|
||||
|
||||
var lastStatus = -1;
|
||||
var lastDistance = Mathf.Infinity;
|
||||
|
||||
var newCenter = FindCenter(points, center);
|
||||
|
||||
foreach (var provider in _Providers)
|
||||
{
|
||||
var camera = provider.Key;
|
||||
|
||||
if (!_Water.ShouldExecuteQueries(camera))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var distance = (newCenter - camera.transform.position.XZ()).sqrMagnitude;
|
||||
|
||||
if (lastStatus == (int)QueryBase.QueryStatus.OK && lastDistance < distance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var status = provider.Value.Query(hash, minimumLength, points, displacements, normals, velocities, layer, center);
|
||||
|
||||
if (lastStatus < 0 || status == (int)QueryBase.QueryStatus.OK)
|
||||
{
|
||||
lastStatus = status;
|
||||
lastDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return lastStatus;
|
||||
}
|
||||
|
||||
public void SendReadBack(WaterRenderer water, CollisionLayers layers)
|
||||
{
|
||||
_Providers[water.CurrentCamera].SendReadBack(water, layers);
|
||||
}
|
||||
|
||||
public void UpdateQueries(WaterRenderer water, CollisionLayer layer)
|
||||
{
|
||||
_Providers[water.CurrentCamera].UpdateQueries(water, layer);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CollisionQueryWithPasses : ICollisionProvider, IQueryable
|
||||
{
|
||||
readonly CollisionQuery _AnimatedWaves;
|
||||
@@ -69,6 +166,14 @@ namespace WaveHarmonic.Crest
|
||||
public int RequestCount => _AnimatedWaves.RequestCount + _DynamicWaves.RequestCount + _Displacement.RequestCount;
|
||||
public int QueryCount => _AnimatedWaves.QueryCount + _DynamicWaves.QueryCount + _Displacement.QueryCount;
|
||||
|
||||
public CollisionQueryWithPasses()
|
||||
{
|
||||
_Water = WaterRenderer.Instance;
|
||||
_AnimatedWaves = new(_Water);
|
||||
_DynamicWaves = new(_Water);
|
||||
_Displacement = new(_Water);
|
||||
}
|
||||
|
||||
public CollisionQueryWithPasses(WaterRenderer water)
|
||||
{
|
||||
_Water = water;
|
||||
@@ -107,12 +212,12 @@ namespace WaveHarmonic.Crest
|
||||
return _AnimatedWaves;
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
|
||||
{
|
||||
return GetProvider(layer).Query(hash, minimumLength, points, heights, normals, velocities);
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
|
||||
{
|
||||
return GetProvider(layer).Query(hash, minimumLength, points, displacements, normals, velocities);
|
||||
}
|
||||
@@ -151,13 +256,55 @@ namespace WaveHarmonic.Crest
|
||||
_DynamicWaves.CleanUp();
|
||||
_Displacement.CleanUp();
|
||||
}
|
||||
|
||||
public void Initialize(WaterRenderer water)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// These are required because of collision layer.
|
||||
static partial class Extensions
|
||||
{
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water, CollisionLayer layer) => (self as CollisionQueryWithPasses)?.UpdateQueries(water, layer);
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water, CollisionLayer layer)
|
||||
{
|
||||
if (self is CollisionQueryPerCamera a)
|
||||
{
|
||||
a.UpdateQueries(water, layer);
|
||||
}
|
||||
else if (self is CollisionQueryWithPasses b)
|
||||
{
|
||||
b.UpdateQueries(water, layer);
|
||||
}
|
||||
else if (self is ICollisionProvider.NoneProvider c)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Crest: no valid query provider. Report this to developers!");
|
||||
}
|
||||
}
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water) => (self as IQueryable)?.UpdateQueries(water);
|
||||
public static void SendReadBack(this ICollisionProvider self, WaterRenderer water, CollisionLayers layer) => (self as CollisionQueryWithPasses)?.SendReadBack(water, layer);
|
||||
public static void SendReadBack(this ICollisionProvider self, WaterRenderer water, CollisionLayers layer)
|
||||
{
|
||||
if (self is CollisionQueryPerCamera a)
|
||||
{
|
||||
a.SendReadBack(water, layer);
|
||||
}
|
||||
else if (self is CollisionQueryWithPasses b)
|
||||
{
|
||||
b.SendReadBack(water, layer);
|
||||
}
|
||||
else if (self is ICollisionProvider.NoneProvider c)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Crest: no valid query provider. Report this to developers!");
|
||||
}
|
||||
}
|
||||
public static void CleanUp(this ICollisionProvider self) => (self as IQueryable)?.CleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
distance = -1f;
|
||||
|
||||
Validate(allowMultipleCallsPerFrame: false);
|
||||
var id = GetHashCode();
|
||||
|
||||
Validate(allowMultipleCallsPerFrame: false, id);
|
||||
|
||||
var water = WaterRenderer.Instance;
|
||||
var provider = water == null ? null : water.AnimatedWavesLod.Provider;
|
||||
@@ -70,7 +72,7 @@ namespace WaveHarmonic.Crest
|
||||
_QueryPosition[i] = origin + i * _RayStepSize * direction;
|
||||
}
|
||||
|
||||
var status = provider.Query(GetHashCode(), _MinimumLength, _QueryPosition, _QueryResult, null, null, layer);
|
||||
var status = provider.Query(id, _MinimumLength, _QueryPosition, _QueryResult, null, null, layer);
|
||||
|
||||
if (!provider.RetrieveSucceeded(status))
|
||||
{
|
||||
|
||||
@@ -14,11 +14,6 @@ namespace WaveHarmonic.Crest
|
||||
[AddComponentMenu(Constants.k_MenuPrefixDebug + "Ray Cast Visualizer")]
|
||||
sealed class RayTraceVisualizer : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
readonly RayCastHelper _RayCast = new(50f, 2f);
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
|
||||
@@ -15,9 +15,14 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
internal static IDepthProvider Create(WaterRenderer water)
|
||||
{
|
||||
return water.IsMultipleViewpointMode ? new DepthQueryPerCamera(water) : new DepthQuery(water);
|
||||
}
|
||||
|
||||
internal sealed class NoneProvider : IDepthProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result, Vector3? _3 = null)
|
||||
{
|
||||
if (result != null) System.Array.Clear(result, 0, result.Length);
|
||||
return 0;
|
||||
@@ -28,7 +33,7 @@ namespace WaveHarmonic.Crest
|
||||
/// Query water depth data at a set of points.
|
||||
/// </summary>
|
||||
/// <param name="results">Water depth and distance to shoreline (XY respectively). Both are signed.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int, Vector3?)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results, Vector3? center = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class DepthQuery : QueryBase, IDepthProvider
|
||||
sealed class DepthQuery : QueryBaseSimple, IDepthProvider
|
||||
{
|
||||
public DepthQuery(WaterRenderer water) : base(water) { }
|
||||
public DepthQuery() : base(WaterRenderer.Instance.DepthLod) { }
|
||||
public DepthQuery(WaterRenderer water) : base(water.DepthLod) { }
|
||||
protected override int Kernel => 2;
|
||||
|
||||
public override int Query(int hash, float minimumSpatialLength, Vector3[] queries, Vector3[] results)
|
||||
public override int Query(int hash, float minimumSpatialLength, Vector3[] queries, Vector3[] results, Vector3? center = null)
|
||||
{
|
||||
var id = base.Query(hash, minimumSpatialLength, queries, results);
|
||||
var id = base.Query(hash, minimumSpatialLength, queries, results, center);
|
||||
|
||||
// Infinity will become NaN. Convert back to infinity.
|
||||
// Negative infinity should not happen.
|
||||
@@ -27,4 +28,9 @@ namespace WaveHarmonic.Crest
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DepthQueryPerCamera : QueryPerCameraSimple<DepthQuery>, IDepthProvider
|
||||
{
|
||||
public DepthQueryPerCamera(WaterRenderer water) : base(water) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,17 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
internal static IFlowProvider Create(WaterRenderer water)
|
||||
{
|
||||
return water.IsMultipleViewpointMode ? new FlowQueryPerCamera(water) : new FlowQuery(water);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives a stationary water (no horizontal flow).
|
||||
/// </summary>
|
||||
internal sealed class NoneProvider : IFlowProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result, Vector3? _3 = null)
|
||||
{
|
||||
if (result != null) System.Array.Clear(result, 0, result.Length);
|
||||
return 0;
|
||||
@@ -31,7 +36,7 @@ namespace WaveHarmonic.Crest
|
||||
/// Query water flow data (horizontal motion) at a set of points.
|
||||
/// </summary>
|
||||
/// <param name="results">Water surface flow velocities at the query positions.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int, Vector3?)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results, Vector3? center = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,15 @@ namespace WaveHarmonic.Crest
|
||||
/// <summary>
|
||||
/// Samples horizontal motion of water volume
|
||||
/// </summary>
|
||||
sealed class FlowQuery : QueryBase, IFlowProvider
|
||||
sealed class FlowQuery : QueryBaseSimple, IFlowProvider
|
||||
{
|
||||
public FlowQuery(WaterRenderer water) : base(water) { }
|
||||
public FlowQuery() : base(WaterRenderer.Instance.FlowLod) { }
|
||||
public FlowQuery(WaterRenderer water) : base(water.FlowLod) { }
|
||||
protected override int Kernel => 1;
|
||||
}
|
||||
|
||||
sealed class FlowQueryPerCamera : QueryPerCameraSimple<FlowQuery>, IFlowProvider
|
||||
{
|
||||
public FlowQueryPerCamera(WaterRenderer water) : base(water) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
@@ -22,8 +23,9 @@ namespace WaveHarmonic.Crest
|
||||
/// <param name="minimumLength">The minimum spatial length of the object, such as the width of a boat. Useful for filtering out detail when not needed. Set to zero to get full available detail.</param>
|
||||
/// <param name="points">The world space points that will be queried.</param>
|
||||
/// <param name="layer">The layer this query targets.</param>
|
||||
/// <param name="center">The center of all the query positions. Used to choose the closest query provider.</param>
|
||||
/// <returns>The status of the query.</returns>
|
||||
internal static int Query(int hash, float minimumLength, Vector3[] points, int layer) => 0;
|
||||
internal static int Query(int hash, float minimumLength, Vector3[] points, int layer, Vector3? center) => throw new System.NotImplementedException("Crest: this method is for documentation reuse only. Do not invoke.");
|
||||
|
||||
/// <summary>
|
||||
/// Check if the query results could be retrieved successfully using the return code
|
||||
@@ -45,6 +47,12 @@ namespace WaveHarmonic.Crest
|
||||
void UpdateQueries(WaterRenderer water);
|
||||
void SendReadBack(WaterRenderer water);
|
||||
void CleanUp();
|
||||
void Initialize(WaterRenderer water);
|
||||
}
|
||||
|
||||
interface IQueryableSimple : IQueryable
|
||||
{
|
||||
int Query(int hash, float minimumLength, Vector3[] queries, Vector3[] results, Vector3? center);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,6 +72,7 @@ namespace WaveHarmonic.Crest
|
||||
const int k_NormalAdditionalQueryCount = 2;
|
||||
|
||||
readonly WaterRenderer _Water;
|
||||
readonly IQueryableLod<IQueryProvider> _Lod;
|
||||
|
||||
readonly PropertyWrapperCompute _Wrapper;
|
||||
|
||||
@@ -85,8 +94,8 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
internal const int k_DefaultMaximumQueryCount = 4096;
|
||||
|
||||
readonly int _MaximumQueryCount = k_DefaultMaximumQueryCount;
|
||||
readonly Vector3[] _QueryPositionXZ_MinimumGridSize = new Vector3[k_DefaultMaximumQueryCount];
|
||||
readonly int _MaximumQueryCount;
|
||||
readonly Vector3[] _QueryPositionXZ_MinimumGridSize;
|
||||
|
||||
/// <summary>
|
||||
/// Holds information about all query points. Maps from unique hash code to position in point array.
|
||||
@@ -147,8 +156,9 @@ namespace WaveHarmonic.Crest
|
||||
// queries are often made from FixedUpdate(), and at high framerates this may not be called, which would mean
|
||||
// the query would get lost and this leads to stuttering and other artifacts.
|
||||
{
|
||||
_Segments[_SegmentAcquire]._QueryCount = 0;
|
||||
_Segments[_SegmentAcquire]._Segments.Clear();
|
||||
var registrar = _Segments[_SegmentAcquire];
|
||||
registrar._QueryCount = 0;
|
||||
registrar._Segments.Clear();
|
||||
|
||||
foreach (var segment in _Segments[lastIndex]._Segments)
|
||||
{
|
||||
@@ -160,11 +170,10 @@ namespace WaveHarmonic.Crest
|
||||
// Compute a new segment range - we may have removed some segments that were too old, so this ensures
|
||||
// we have a nice compact array of queries each frame rather than accumulating persistent air bubbles
|
||||
var newSegment = segment.Value;
|
||||
newSegment.x = _Segments[_SegmentAcquire]._QueryCount;
|
||||
newSegment.x = registrar._QueryCount;
|
||||
newSegment.y = newSegment.x + (segment.Value.y - segment.Value.x);
|
||||
_Segments[_SegmentAcquire]._QueryCount = newSegment.y + 1;
|
||||
|
||||
_Segments[_SegmentAcquire]._Segments.Add(segment.Key, newSegment);
|
||||
registrar._QueryCount = newSegment.y + 1;
|
||||
registrar._Segments.Add(segment.Key, newSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,17 +262,15 @@ namespace WaveHarmonic.Crest
|
||||
InvalidDtForVelocity = 16,
|
||||
}
|
||||
|
||||
public QueryBase(WaterRenderer water)
|
||||
public QueryBase(IQueryableLod<IQueryProvider> lod)
|
||||
{
|
||||
_Water = water;
|
||||
_Water = lod.Water;
|
||||
_Lod = lod;
|
||||
|
||||
_DataArrivedAction = new(DataArrived);
|
||||
|
||||
if (_MaximumQueryCount != water._AnimatedWavesLod.MaximumQueryCount)
|
||||
{
|
||||
_MaximumQueryCount = water._AnimatedWavesLod.MaximumQueryCount;
|
||||
_QueryPositionXZ_MinimumGridSize = new Vector3[_MaximumQueryCount];
|
||||
}
|
||||
_MaximumQueryCount = lod.MaximumQueryCount;
|
||||
_QueryPositionXZ_MinimumGridSize = new Vector3[_MaximumQueryCount];
|
||||
|
||||
_ComputeBufferQueries = new(_MaximumQueryCount, 12, ComputeBufferType.Default);
|
||||
_ComputeBufferResults = new(_MaximumQueryCount, 12, ComputeBufferType.Default);
|
||||
@@ -277,7 +284,13 @@ namespace WaveHarmonic.Crest
|
||||
Debug.LogError($"Crest: Could not load Query compute shader");
|
||||
return;
|
||||
}
|
||||
_Wrapper = new(water.SimulationBuffer, shader, Kernel);
|
||||
|
||||
_Wrapper = new(_Water.SimulationBuffer, shader, Kernel);
|
||||
}
|
||||
|
||||
void LogMaximumQueryCountExceededError()
|
||||
{
|
||||
Debug.LogError($"Crest: Maximum query count ({_MaximumQueryCount}) exceeded, increase the <i>{nameof(WaterRenderer)} > Simulations > {_Lod.Name} > {nameof(_Lod.MaximumQueryCount)}</i> to support a higher number of queries.", _Water);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -286,9 +299,11 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
protected bool UpdateQueryPoints(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] queryNormals)
|
||||
{
|
||||
if (queryPoints.Length + _SegmentRegistrarRingBuffer.Current._QueryCount > _MaximumQueryCount)
|
||||
var registrar = _SegmentRegistrarRingBuffer.Current;
|
||||
|
||||
if (queryPoints.Length + registrar._QueryCount > _MaximumQueryCount)
|
||||
{
|
||||
Debug.LogError($"Crest: Max query count ({_MaximumQueryCount}) exceeded, increase the max query count in the Animated Waves Settings to support a higher number of queries.");
|
||||
LogMaximumQueryCountExceededError();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -299,20 +314,20 @@ namespace WaveHarmonic.Crest
|
||||
var countNorms = queryNormals != null ? queryNormals.Length : 0;
|
||||
var countTotal = countPts + countNorms * k_NormalAdditionalQueryCount;
|
||||
|
||||
if (_SegmentRegistrarRingBuffer.Current._Segments.TryGetValue(ownerHash, out var segment))
|
||||
if (registrar._Segments.TryGetValue(ownerHash, out var segment))
|
||||
{
|
||||
var segmentSize = segment.y - segment.x + 1;
|
||||
if (segmentSize == countTotal)
|
||||
{
|
||||
// Update frame count
|
||||
segment.z = Time.frameCount;
|
||||
_SegmentRegistrarRingBuffer.Current._Segments[ownerHash] = segment;
|
||||
registrar._Segments[ownerHash] = segment;
|
||||
|
||||
segmentRetrieved = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_SegmentRegistrarRingBuffer.Current._Segments.Remove(ownerHash);
|
||||
registrar._Segments.Remove(ownerHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,18 +339,18 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
if (!segmentRetrieved)
|
||||
{
|
||||
if (_SegmentRegistrarRingBuffer.Current._Segments.Count >= k_MaximumGuids)
|
||||
if (registrar._Segments.Count >= k_MaximumGuids)
|
||||
{
|
||||
Debug.LogError("Crest: Too many guids registered with CollProviderCompute. Increase s_maxGuids.");
|
||||
return false;
|
||||
}
|
||||
|
||||
segment.x = _SegmentRegistrarRingBuffer.Current._QueryCount;
|
||||
segment.x = registrar._QueryCount;
|
||||
segment.y = segment.x + countTotal - 1;
|
||||
segment.z = Time.frameCount;
|
||||
_SegmentRegistrarRingBuffer.Current._Segments.Add(ownerHash, segment);
|
||||
registrar._Segments.Add(ownerHash, segment);
|
||||
|
||||
_SegmentRegistrarRingBuffer.Current._QueryCount += countTotal;
|
||||
registrar._QueryCount += countTotal;
|
||||
|
||||
//Debug.Log("Crest: Added points for " + guid);
|
||||
}
|
||||
@@ -346,9 +361,14 @@ namespace WaveHarmonic.Crest
|
||||
var samplesPerWave = 2f;
|
||||
var minGridSize = minWavelength / samplesPerWave;
|
||||
|
||||
// Displacements should not utilize the last slice which is used for transitioning
|
||||
// waves between sampling resolutions. While it might be ok to use the last slice
|
||||
// for other targets, we avoid using it to be consistent with displacements.
|
||||
var minimumSlice = Mathf.Clamp(Mathf.FloorToInt(Mathf.Log(Mathf.Max(minGridSize / _Lod.Texel, 1f), 2f)), 0, _Water.LodLevels - 2);
|
||||
|
||||
if (countPts + segment.x > _QueryPositionXZ_MinimumGridSize.Length)
|
||||
{
|
||||
Debug.LogError("Crest: Too many wave height queries. Increase Max Query Count in the Animated Waves Settings.");
|
||||
LogMaximumQueryCountExceededError();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -356,7 +376,7 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].x = queryPoints[pointi].x;
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].y = queryPoints[pointi].z;
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].z = minGridSize;
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].z = minimumSlice;
|
||||
}
|
||||
|
||||
// To compute each normal, post 2 query points (reuse point above)
|
||||
@@ -366,13 +386,13 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].x = queryNormals[normi].x + k_FiniteDifferenceDx;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].y = queryNormals[normi].z;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].z = minGridSize;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].z = minimumSlice;
|
||||
|
||||
arrIdx += 1;
|
||||
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].x = queryNormals[normi].x;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].y = queryNormals[normi].z + k_FiniteDifferenceDx;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].z = minGridSize;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].z = minimumSlice;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -590,10 +610,11 @@ namespace WaveHarmonic.Crest
|
||||
_QueryResultsTimeLast = _QueryResultsTime;
|
||||
_ResultSegmentsLast = _ResultSegments;
|
||||
|
||||
var data = _Requests[lastDoneIndex]._Request.GetData<Vector3>();
|
||||
var last = _Requests[lastDoneIndex];
|
||||
var data = last._Request.GetData<Vector3>();
|
||||
data.CopyTo(_QueryResults);
|
||||
_QueryResultsTime = _Requests[lastDoneIndex]._DataTimestamp;
|
||||
_ResultSegments = _Requests[lastDoneIndex]._Segments;
|
||||
_QueryResultsTime = last._DataTimestamp;
|
||||
_ResultSegments = last._Segments;
|
||||
}
|
||||
|
||||
// Remove all the requests up to the last completed one
|
||||
@@ -618,7 +639,23 @@ namespace WaveHarmonic.Crest
|
||||
_SegmentRegistrarRingBuffer.ClearAll();
|
||||
}
|
||||
|
||||
public virtual int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] results)
|
||||
public virtual void Initialize(WaterRenderer water)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public int ResultGuidCount => _ResultSegments != null ? _ResultSegments.Count : 0;
|
||||
|
||||
public int RequestCount => _Requests != null ? _Requests.Count : 0;
|
||||
|
||||
public int QueryCount => _SegmentRegistrarRingBuffer != null ? _SegmentRegistrarRingBuffer.Current._QueryCount : 0;
|
||||
}
|
||||
|
||||
abstract class QueryBaseSimple : QueryBase, IQueryableSimple
|
||||
{
|
||||
protected QueryBaseSimple(IQueryableLod<IQueryProvider> lod) : base(lod) { }
|
||||
|
||||
public virtual int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] results, Vector3? center)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
@@ -634,12 +671,159 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public int ResultGuidCount => _ResultSegments != null ? _ResultSegments.Count : 0;
|
||||
abstract class QueryPerCamera<T> : IQueryable where T : IQueryable, new()
|
||||
{
|
||||
internal readonly WaterRenderer _Water;
|
||||
internal readonly Dictionary<Camera, T> _Providers = new();
|
||||
|
||||
public int RequestCount => _Requests != null ? _Requests.Count : 0;
|
||||
public QueryPerCamera(WaterRenderer water)
|
||||
{
|
||||
_Water = water;
|
||||
Initialize(water);
|
||||
}
|
||||
|
||||
public int QueryCount => _SegmentRegistrarRingBuffer != null ? _SegmentRegistrarRingBuffer.Current._QueryCount : 0;
|
||||
public int ResultGuidCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var (camera, provider) in _Providers)
|
||||
{
|
||||
if (_Water.ShouldExecuteQueries(camera)) total += provider.ResultGuidCount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var (camera, provider) in _Providers)
|
||||
{
|
||||
if (_Water.ShouldExecuteQueries(camera)) total += provider.RequestCount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
public int QueryCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var (camera, provider) in _Providers)
|
||||
{
|
||||
if (_Water.ShouldExecuteQueries(camera)) total += provider.QueryCount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
foreach (var provider in _Providers.Values)
|
||||
{
|
||||
provider?.CleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(WaterRenderer water)
|
||||
{
|
||||
var camera = water.CurrentCamera;
|
||||
|
||||
if (camera == null)
|
||||
{
|
||||
camera = water.Viewer;
|
||||
}
|
||||
|
||||
if (camera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_Providers.ContainsKey(camera))
|
||||
{
|
||||
// Cannot use parameters. We could use System.Activator.CreateInstance to get
|
||||
// around that, but instead we just use WaterRenderer.Instance.
|
||||
_Providers.Add(camera, new());
|
||||
}
|
||||
}
|
||||
|
||||
public void SendReadBack(WaterRenderer water)
|
||||
{
|
||||
_Providers[water.CurrentCamera].SendReadBack(water);
|
||||
}
|
||||
|
||||
public void UpdateQueries(WaterRenderer water)
|
||||
{
|
||||
_Providers[water.CurrentCamera].UpdateQueries(water);
|
||||
}
|
||||
|
||||
public Vector2 FindCenter(Vector3[] queries, Vector3? center)
|
||||
{
|
||||
if (center != null)
|
||||
{
|
||||
return ((Vector3)center).XZ();
|
||||
}
|
||||
|
||||
// Calculate center if none provided.
|
||||
var sum = Vector2.zero;
|
||||
foreach (var point in queries)
|
||||
{
|
||||
sum += point.XZ();
|
||||
}
|
||||
|
||||
return new(sum.x / queries.Length, sum.y / queries.Length);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QueryPerCameraSimple<T> : QueryPerCamera<T>, IQueryableSimple where T : IQueryableSimple, new()
|
||||
{
|
||||
protected QueryPerCameraSimple(WaterRenderer water) : base(water) { }
|
||||
|
||||
public int Query(int id, float length, Vector3[] queries, Vector3[] results, Vector3? center = null)
|
||||
{
|
||||
if (_Water.IsSeparateViewpointCameraLoop)
|
||||
{
|
||||
return _Providers[_Water.CurrentCamera].Query(id, length, queries, results, center);
|
||||
}
|
||||
|
||||
var lastStatus = -1;
|
||||
var lastDistance = Mathf.Infinity;
|
||||
|
||||
var newCenter = FindCenter(queries, center);
|
||||
|
||||
foreach (var provider in _Providers)
|
||||
{
|
||||
var camera = provider.Key;
|
||||
|
||||
if (!_Water.ShouldExecuteQueries(camera))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var distance = (newCenter - camera.transform.position.XZ()).sqrMagnitude;
|
||||
|
||||
if (lastStatus == (int)QueryBase.QueryStatus.OK && lastDistance < distance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var status = provider.Value.Query(id, length, queries, results, center);
|
||||
|
||||
if (lastStatus < 0 || status == (int)QueryBase.QueryStatus.OK)
|
||||
{
|
||||
lastStatus = status;
|
||||
lastDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return lastStatus;
|
||||
}
|
||||
}
|
||||
|
||||
static partial class Extensions
|
||||
|
||||
@@ -29,17 +29,18 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/Events.html#query-events")]
|
||||
public sealed partial class QueryEvents : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
|
||||
[Tooltip("What transform should the queries be based on.\n\n\"Viewer\" will reuse queries already performed by the Water Renderer")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
QuerySource _Source;
|
||||
|
||||
[Tooltip("The viewer as the source of the queries.\n\nOnly needs to be set if using multiple viewpoints on the Water Renderer.")]
|
||||
[@Show(nameof(_Source), nameof(QuerySource.Viewer))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[SerializeField]
|
||||
Camera _Viewer;
|
||||
|
||||
[Tooltip(ICollisionProvider.k_LayerTooltip)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
@@ -49,14 +50,14 @@ namespace WaveHarmonic.Crest
|
||||
[Header("Distance From Water Surface")]
|
||||
|
||||
[Tooltip("The minimum wavelength for queries.\n\nThe higher the value, the more smaller waves will be ignored when sampling the water surface.")]
|
||||
[@Predicated(nameof(_Source), inverted: true, nameof(QuerySource.Transform))]
|
||||
[@Enable(nameof(_Source), nameof(QuerySource.Transform))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _MinimumWavelength = 1f;
|
||||
|
||||
[@Label("Signed")]
|
||||
[Tooltip("Whether to keep the sign of the value (ie positive/negative).\n\nA positive value means the query point is above the surface, while a negative means it below the surface.")]
|
||||
[@Predicated(nameof(_DistanceFromSurfaceUseCurve), inverted: true)]
|
||||
[@Disable(nameof(_DistanceFromSurfaceUseCurve))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DistanceFromSurfaceSigned;
|
||||
@@ -77,7 +78,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Curve")]
|
||||
[Tooltip("Apply a curve to the distance.\n\nValues towards \"one\" means closer to the water surface.")]
|
||||
[@Predicated(nameof(_DistanceFromSurfaceUseCurve))]
|
||||
[@Enable(nameof(_DistanceFromSurfaceUseCurve))]
|
||||
[@GenerateAPI]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_DistanceCurve")]
|
||||
[@DecoratedField, SerializeField]
|
||||
@@ -88,7 +89,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Signed")]
|
||||
[Tooltip("Whether to keep the sign of the value (ie positive/negative).\n\nA positive value means the query point is over water, while a negative means it is over land.")]
|
||||
[@Predicated(nameof(_DistanceFromEdgeUseCurve), inverted: true)]
|
||||
[@Disable(nameof(_DistanceFromEdgeUseCurve))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DistanceFromEdgeSigned;
|
||||
@@ -107,7 +108,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
[@Label("Curve")]
|
||||
[Tooltip("Apply a curve to the distance.\n\nValues towards \"one\" means closer to the water's edge.")]
|
||||
[@Predicated(nameof(_DistanceFromEdgeUseCurve))]
|
||||
[@Enable(nameof(_DistanceFromEdgeUseCurve))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
AnimationCurve _DistanceFromEdgeCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
@@ -174,6 +175,11 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
var distance = water.ViewerHeightAboveWater;
|
||||
|
||||
if (water.MultipleViewpoints && (_Viewer == null || !water.GetViewerHeightAboveWater(_Viewer, out distance)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_Source == QuerySource.Transform)
|
||||
{
|
||||
if (!_SampleHeightHelper.SampleHeight(transform.position, out var height, minimumLength: 2f * _MinimumWavelength, _Layer)) return;
|
||||
@@ -233,6 +239,11 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
var distance = water.ViewerDistanceToShoreline;
|
||||
|
||||
if (water.MultipleViewpoints && (_Viewer == null || !water.GetViewerDistanceToShoreline(_Viewer, out distance)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_Source == QuerySource.Transform)
|
||||
{
|
||||
if (!_SampleDepthHelper.SampleDistanceToWaterEdge(transform.position, out distance))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc plus defining own parameters.
|
||||
@@ -20,7 +21,7 @@ namespace WaveHarmonic.Crest.Internal
|
||||
private protected readonly Vector3[] _QueryPosition;
|
||||
private protected readonly Vector3[] _QueryResult;
|
||||
|
||||
int _LastFrame = -1;
|
||||
readonly Dictionary<int, int> _LastFrame = new();
|
||||
|
||||
private protected SampleHelper(int queryCount = 1)
|
||||
{
|
||||
@@ -29,15 +30,29 @@ namespace WaveHarmonic.Crest.Internal
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private protected void Validate(bool allowMultipleCallsPerFrame)
|
||||
private protected void Validate(bool allowMultipleCallsPerFrame, int id)
|
||||
{
|
||||
if (Application.isPlaying && !Time.inFixedTimeStep && !allowMultipleCallsPerFrame && _LastFrame == Time.frameCount)
|
||||
if (!_LastFrame.ContainsKey(id))
|
||||
{
|
||||
_LastFrame.Add(id, -1);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Prevent false positives spamming the console.
|
||||
if (!UnityEditor.EditorApplication.isFocused || (Application.isPlaying && UnityEditor.EditorApplication.isPaused))
|
||||
{
|
||||
_LastFrame[id] = Time.frameCount;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!Time.inFixedTimeStep && !allowMultipleCallsPerFrame && _LastFrame[id] == Time.frameCount)
|
||||
{
|
||||
var type = GetType().Name;
|
||||
Debug.LogWarning($"Crest: {type} sample called multiple times in one frame which is not expected. Each {type} object services a single sample per frame. To perform multiple queries, create multiple {type} objects or use the query provider API directly.");
|
||||
}
|
||||
|
||||
_LastFrame = Time.frameCount;
|
||||
_LastFrame[id] = Time.frameCount;
|
||||
}
|
||||
|
||||
// The first method is there just to get inheritdoc to work as it does not like
|
||||
@@ -103,7 +118,7 @@ namespace WaveHarmonic.Crest
|
||||
var isVelocity = (options & QueryOptions.Velocity) == QueryOptions.Velocity;
|
||||
var isNormal = (options & QueryOptions.Normal) == QueryOptions.Normal;
|
||||
|
||||
Validate(allowMultipleCallsPerFrame);
|
||||
Validate(allowMultipleCallsPerFrame, id);
|
||||
|
||||
_QueryPosition[0] = position;
|
||||
|
||||
@@ -115,7 +130,8 @@ namespace WaveHarmonic.Crest
|
||||
_QueryResult,
|
||||
isNormal ? _QueryResultNormal : null,
|
||||
isVelocity ? _QueryResultVelocity : null,
|
||||
layer
|
||||
layer,
|
||||
position
|
||||
);
|
||||
|
||||
if (!provider.RetrieveSucceeded(status))
|
||||
@@ -234,11 +250,13 @@ namespace WaveHarmonic.Crest
|
||||
return false;
|
||||
}
|
||||
|
||||
Validate(false);
|
||||
var id = GetHashCode();
|
||||
|
||||
Validate(false, id);
|
||||
|
||||
_QueryPosition[0] = position;
|
||||
|
||||
var status = flowProvider.Query(GetHashCode(), minimumLength, _QueryPosition, _QueryResult);
|
||||
var status = flowProvider.Query(id, minimumLength, _QueryPosition, _QueryResult, position);
|
||||
|
||||
if (!flowProvider.RetrieveSucceeded(status))
|
||||
{
|
||||
@@ -259,25 +277,25 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
public sealed class SampleDepthHelper : Internal.SampleHelper
|
||||
{
|
||||
bool Sample(Vector3 position, out Vector2 result)
|
||||
internal bool Sample(int id, Vector3 position, out Vector2 result, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
var water = WaterRenderer.Instance;
|
||||
var depthProvider = water == null ? null : water.DepthLod.Provider;
|
||||
|
||||
if (depthProvider == null)
|
||||
{
|
||||
result = Vector2.zero;
|
||||
result = new(Mathf.Infinity, Mathf.Infinity);
|
||||
return false;
|
||||
}
|
||||
|
||||
Validate(false);
|
||||
Validate(allowMultipleCallsPerFrame, id);
|
||||
|
||||
_QueryPosition[0] = position;
|
||||
|
||||
var status = depthProvider.Query(GetHashCode(), minimumLength: 0, _QueryPosition, _QueryResult);
|
||||
var status = depthProvider.Query(id, minimumLength: 0, _QueryPosition, _QueryResult, position);
|
||||
if (!depthProvider.RetrieveSucceeded(status))
|
||||
{
|
||||
result = Vector2.zero;
|
||||
result = new(Mathf.Infinity, Mathf.Infinity);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -285,13 +303,18 @@ namespace WaveHarmonic.Crest
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sample(Vector3 position, out Vector2 result)
|
||||
{
|
||||
return Sample(GetHashCode(), position, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample both the water depth and water edge distance.
|
||||
/// </summary>
|
||||
/// <param name="depth">Filled by the water depth at the query position.</param>
|
||||
/// <param name="distance">Filled by the distance to water edge at the query position.</param>
|
||||
/// <inheritdoc cref="Internal.SampleHelper.Sample" />
|
||||
bool Sample(Vector3 position, out float depth, out float distance)
|
||||
internal bool Sample(Vector3 position, out float depth, out float distance)
|
||||
{
|
||||
var success = Sample(position, out var result);
|
||||
depth = result.x;
|
||||
@@ -312,7 +335,12 @@ namespace WaveHarmonic.Crest
|
||||
/// <inheritdoc cref="Sample(Vector3, out float, out float)"/>
|
||||
public bool SampleDistanceToWaterEdge(Vector3 position, out float distance)
|
||||
{
|
||||
var success = Sample(position, out var result);
|
||||
return SampleDistanceToWaterEdge(GetHashCode(), position, out distance);
|
||||
}
|
||||
|
||||
internal bool SampleDistanceToWaterEdge(int id, Vector3 position, out float distance)
|
||||
{
|
||||
var success = Sample(id, position, out var result);
|
||||
distance = result.y;
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -19,22 +19,13 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
var water = _Water;
|
||||
|
||||
if (!water._ShadowLod.Enabled)
|
||||
if (!water._ActiveModules.HasFlag(WaterRenderer.ActiveModules.Shadows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!WaterRenderer.IsWithinEditorUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var camera = context.hdCamera.camera;
|
||||
|
||||
// Custom passes execute for every camera. We only support one camera for now.
|
||||
if (!ReferenceEquals(camera, water.Viewer)) return;
|
||||
// TODO: bail when not executing for main light or when no main light exists?
|
||||
// if (renderingData.lightData.mainLightIndex == -1) return;
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
#if URP_COMPATIBILITY_MODE
|
||||
[System.Obsolete]
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
|
||||
{
|
||||
@@ -54,6 +55,7 @@ namespace WaveHarmonic.Crest
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
CommandBufferPool.Release(buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,25 +34,6 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
var water = s_Instance._Water;
|
||||
|
||||
if (!water._ShadowLod.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!WaterRenderer.IsWithinEditorUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only sample shadows for the main camera.
|
||||
if (!ReferenceEquals(water.Viewer, camera))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera.TryGetComponent<UniversalAdditionalCameraData>(out var cameraData))
|
||||
{
|
||||
|
||||
@@ -12,11 +12,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/Waves.html#dynamic-waves-settings")]
|
||||
public sealed partial class DynamicWavesLodSettings : LodSettings
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[Header("Simulation")]
|
||||
|
||||
[Tooltip("How much energy is dissipated each frame.\n\nHelps simulation stability, but limits how far ripples will propagate. Set this as large as possible/acceptable. Default is 0.05.")]
|
||||
@@ -52,5 +47,14 @@ namespace WaveHarmonic.Crest
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
internal float _GravityMultiplier = 1f;
|
||||
|
||||
|
||||
[@Heading("Culling")]
|
||||
|
||||
[Tooltip("Adds padding to water chunk bounds.\n\nDynamic Waves displaces the surface which can push vertices outside of the chunk bounds leading to culling issues. This value adds padding to the chunk bounds to mitigate this.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField]
|
||||
[@SerializeField]
|
||||
internal float _VerticalDisplacementCullingContributions = 5f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,6 @@ namespace WaveHarmonic.Crest
|
||||
[@HelpURL("Manual/WaterAppearance.html#foam-settings")]
|
||||
public sealed partial class FoamLodSettings : LodSettings
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
|
||||
[Header("General settings")]
|
||||
|
||||
[Tooltip("Foam will not exceed this value in the simulation which can be used to prevent foam from accumulating too much.")]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user