diff --git a/crest/Assets/Crest/Crest/Scripts/Collision/QueryDisplacements.cs b/crest/Assets/Crest/Crest/Scripts/Collision/QueryDisplacements.cs index ee348388b..c6af4a99e 100644 --- a/crest/Assets/Crest/Crest/Scripts/Collision/QueryDisplacements.cs +++ b/crest/Assets/Crest/Crest/Scripts/Collision/QueryDisplacements.cs @@ -20,6 +20,8 @@ public class QueryDisplacements : QueryBase, ICollProvider protected override void BindInputsAndOutputs(PropertyWrapperComputeStandalone wrapper, ComputeBuffer resultsBuffer) { LodDataMgrAnimWaves.Bind(wrapper); + LodDataMgrSeaFloorDepth.Bind(wrapper); + ShaderProcessQueries.SetTexture(_kernelHandle, sp_LD_TexArray_AnimatedWaves, OceanRenderer.Instance._lodDataAnimWaves.DataTexture); ShaderProcessQueries.SetBuffer(_kernelHandle, sp_ResultDisplacements, resultsBuffer); diff --git a/crest/Assets/Crest/Crest/Scripts/Helpers/Editor/WindowCrestWaterBody.cs b/crest/Assets/Crest/Crest/Scripts/Helpers/Editor/WindowCrestWaterBody.cs deleted file mode 100644 index 02a334ef4..000000000 --- a/crest/Assets/Crest/Crest/Scripts/Helpers/Editor/WindowCrestWaterBody.cs +++ /dev/null @@ -1,308 +0,0 @@ -// Crest Ocean System - -// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE) - -#if UNITY_EDITOR - -using UnityEditor; -using UnityEngine; - -namespace Crest -{ - public class WindowCrestWaterBody : EditorWindow - { - enum State - { - Idle, - Placing - } - State _state; - - // This is required because gizmos don't intersect with scene, which makes them useless as a guide when placing - GameObject _proxyObject; - - // Placement - Vector3 _position = Vector3.zero; - float _sizeX = 100f; - float _sizeZ = 100f; - float _rotation = 0f; - - bool _createDepthCache = true; - LayerMask _depthCacheLayers = 1; // Default - - bool _createGerstnerWaves = false; - float _gerstnerWindDirection = 0f; - OceanWaveSpectrum _gerstnerWaveSpectrum = null; - Material _gerstnerMaterial = null; - - bool _createClipArea = false; - Material _clipMaterial = null; - - private void OnGUI() - { - if (EditorApplication.isPlaying == true) - { - EditorGUILayout.HelpBox("Exit play mode to create water bodies.", MessageType.Info); - return; - } - - if (OceanRenderer.Instance == null) - { - EditorGUILayout.HelpBox( - "No water object in loaded scenes, so no water will appear. To add one create a new GameObject and attach a OceanRenderer component to it.", - MessageType.Warning - ); - } - - switch (_state) - { - case State.Idle: - OnGUIIdle(); - break; - case State.Placing: - OnGUIPlacing(); - break; - } - } - - private void OnGUIIdle() - { - if (GUILayout.Button("Create Water Body")) - { - _state = State.Placing; - - // Refresh scene view - GetWindow(); - } - } - Vector2 _scrollPosition; - - private void OnGUIPlacing() - { - _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); - - EditorGUI.BeginChangeCheck(); - - EditorGUILayout.LabelField("Placement", EditorStyles.boldLabel); - _position = EditorGUILayout.Vector3Field("Center position", _position); - _sizeX = EditorGUILayout.FloatField("Size X", _sizeX); - _sizeZ = EditorGUILayout.FloatField("Size Z", _sizeZ); - _rotation = EditorGUILayout.FloatField("Rotation", _rotation); - - EditorGUILayout.Space(); - - _createDepthCache = EditorGUILayout.BeginToggleGroup("Create Depth Cache", _createDepthCache); - _depthCacheLayers = EditorHelpers.EditorHelpers.LayerMaskField("Layers for cache", _depthCacheLayers); - EditorGUILayout.EndToggleGroup(); - - EditorGUILayout.Space(); - - _createGerstnerWaves = EditorGUILayout.BeginToggleGroup("Create Gerstner Waves", _createGerstnerWaves); - _gerstnerWindDirection = EditorGUILayout.FloatField("Wind direction angle", _gerstnerWindDirection); - _gerstnerWaveSpectrum = EditorGUILayout.ObjectField("Wave spectrum", _gerstnerWaveSpectrum, typeof(OceanWaveSpectrum), false) as OceanWaveSpectrum; - _gerstnerMaterial = EditorGUILayout.ObjectField("Gerstner material", _gerstnerMaterial, typeof(Material), false) as Material; - EditorGUILayout.EndToggleGroup(); - - EditorGUILayout.Space(); - - _createClipArea = EditorGUILayout.BeginToggleGroup("Create Clip Area", _createClipArea); - _clipMaterial = EditorGUILayout.ObjectField("Clip material", _clipMaterial, typeof(Material), false) as Material; - if (_createClipArea) - { - EditorGUILayout.HelpBox("Create Clip Surface Data should be enabled on the OceanRnederer component, and the Default Clipping State should be set to Everything Clipped.", MessageType.Info); - } - EditorGUILayout.EndToggleGroup(); - - if (EditorGUI.EndChangeCheck()) - { - UpdateProxy(); - SceneView.RepaintAll(); - } - - EditorGUILayout.Space(); - - var created = false; - if (GUILayout.Button("Create")) - { - CreateWaterBody(); - created = true; - } - - if (created || GUILayout.Button("Done")) - { - _state = State.Idle; - - // Refresh scene view - GetWindow(); - } - - EditorGUILayout.EndScrollView(); - } - - [MenuItem("Window/Crest/Create Water Body")] - public static void ShowWindow() - { - GetWindow("Crest Create Water Body"); - } - - private void OnEnable() - { - PopulateResources(); - - var ocean = FindObjectOfType(); - _position.y = (ocean != null && ocean.Root != null) ? ocean.Root.position.y : 0f; - } - - private void OnFocus() - { - // Remove delegate listener if it has previously - // been assigned. - SceneView.duringSceneGui -= OnSceneGUI; - // Add (or re-add) the delegate. - SceneView.duringSceneGui += OnSceneGUI; - - if (_proxyObject == null) - { - CreateProxyObject(); - } - } - - private void OnDestroy() - { - SceneView.duringSceneGui -= OnSceneGUI; - - if (_proxyObject != null) - { - DestroyImmediate(_proxyObject); - } - } - - void OnSceneGUI(SceneView sceneView) - { - UpdateProxy(); - - if (_state != State.Placing) - { - return; - } - - _position = Handles.DoPositionHandle(_position, Quaternion.identity); - } - - void CreateProxyObject() - { - _proxyObject = GameObject.CreatePrimitive(PrimitiveType.Plane); - _proxyObject.name = "_HIDDEN_WaterBodyProxy"; - _proxyObject.hideFlags = HideFlags.HideAndDontSave; - UpdateProxy(); - } - - void UpdateProxy() - { - _proxyObject.transform.position = _position; - _proxyObject.transform.rotation = Quaternion.AngleAxis(_rotation, Vector3.up); - var planeScaleFactor = 10f; - _proxyObject.transform.localScale = new Vector3(_sizeX / planeScaleFactor, 1f, _sizeZ / planeScaleFactor); - _proxyObject.SetActive(_state == State.Placing); - } - - bool PopulateResources() - { - if (_clipMaterial == null) - { - _clipMaterial = AssetDatabase.LoadAssetAtPath("Assets/Crest/Crest/Materials/OceanInputs/ClipSurfaceIncludeArea.mat"); - if (_clipMaterial == null) return false; - } - - if (_gerstnerMaterial == null) - { - _gerstnerMaterial = AssetDatabase.LoadAssetAtPath("Assets/Crest/Crest/Materials/OceanInputs/WaterBodyGerstnerPatch.mat"); - if (_gerstnerMaterial == null) return false; - } - - return true; - } - - void CreateWaterBody() - { - var waterBodyGO = new GameObject("WaterBody"); - Undo.RegisterCreatedObjectUndo(waterBodyGO, "Add Crest WaterBody"); - waterBodyGO.transform.position = _position; - waterBodyGO.transform.rotation = Quaternion.AngleAxis(_rotation, Vector3.up); - waterBodyGO.transform.localScale = new Vector3(_sizeX, 1f, _sizeZ); - - waterBodyGO.AddComponent(); - - if (_createDepthCache) - { - var depthCacheGO = new GameObject("DepthCache"); - depthCacheGO.transform.parent = waterBodyGO.transform; - depthCacheGO.transform.localRotation = Quaternion.identity; - depthCacheGO.transform.localPosition = Vector3.zero; - depthCacheGO.transform.localScale = Vector3.one; - - var depthCache = depthCacheGO.AddComponent(); - var res = Mathf.FloorToInt(Mathf.Max(_sizeX, _sizeZ) / 0.5f); - // I think multiple-of-4 is typical requirement for texture compression - if (res % 4 > 0) res += 4 - (res % 4); - depthCache._resolution = Mathf.Clamp(res, 16, 512); - depthCache._layers = _depthCacheLayers; - } - - if (_createGerstnerWaves) - { - var gerstnerGO = GameObject.CreatePrimitive(PrimitiveType.Quad); - gerstnerGO.name = "GerstnerWaves"; - DestroyImmediate(gerstnerGO.GetComponent()); - gerstnerGO.transform.parent = waterBodyGO.transform; - gerstnerGO.transform.localEulerAngles = 90f * Vector3.right; - gerstnerGO.transform.localScale = Vector3.one; - gerstnerGO.transform.localPosition = Vector3.zero; - - var gerstner = gerstnerGO.AddComponent(); - gerstner._mode = ShapeGerstnerBatched.GerstnerMode.Geometry; - gerstner._windDirectionAngle = _gerstnerWindDirection; - gerstner._spectrum = _gerstnerWaveSpectrum; - - var rend = gerstnerGO.GetComponent(); - rend.sharedMaterial = _gerstnerMaterial; - } - - if (_createClipArea) - { - var clipGO = GameObject.CreatePrimitive(PrimitiveType.Quad); - clipGO.name = "SurfaceClip"; - DestroyImmediate(clipGO.GetComponent()); - clipGO.transform.parent = waterBodyGO.transform; - clipGO.transform.localEulerAngles = 90f * Vector3.right; - clipGO.transform.localScale = Vector3.one; - clipGO.transform.localPosition = Vector3.zero; - - var input = clipGO.AddComponent(); - input._mode = RegisterClipSurfaceInput.Mode.Geometry; - - var rend = clipGO.GetComponent(); - rend.sharedMaterial = _clipMaterial; - } - } - - bool CheckResources() - { - if (_createClipArea && _clipMaterial == null) - { - Debug.LogError("Crest: A material for the clip shader must be provided. This is typically a material using shader 'Crest/Inputs/Clip Surface/Include Area'"); - return false; - } - - if (_createGerstnerWaves && _gerstnerMaterial == null) - { - Debug.LogError("Crest: A material for the Gerstner waves must be specified in the Create Water Body window. This is typically a material using shader 'Crest/Inputs/Animated Waves/Gerstner Batch Geometry'"); - return false; - } - - return true; - } - } -} - -#endif diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/LodDataMgrSeaFloorDepth.cs b/crest/Assets/Crest/Crest/Scripts/LodData/LodDataMgrSeaFloorDepth.cs index 715692468..506840f99 100644 --- a/crest/Assets/Crest/Crest/Scripts/LodData/LodDataMgrSeaFloorDepth.cs +++ b/crest/Assets/Crest/Crest/Scripts/LodData/LodDataMgrSeaFloorDepth.cs @@ -8,16 +8,18 @@ namespace Crest { + using SettingsType = SimSettingsSeaFloorDepth; + /// /// Data that gives depth of the ocean (height of sea level above ocean floor). Stores terrain height and water level - /// offset. + /// offset in x & y channels. /// public class LodDataMgrSeaFloorDepth : LodDataMgr { - public override string SimName { get { return "SeaFloorDepth"; } } - protected override GraphicsFormat RequestedTextureFormat => GraphicsFormat.R16_SFloat; - protected override bool NeedToReadWriteTextureData { get { return false; } } - // We want the clear colour to be the min terrain height (-1000m) + public override string SimName => "SeaFloorDepth"; + protected override GraphicsFormat RequestedTextureFormat => Settings._allowVaryingWaterLevel ? GraphicsFormat.R32G32_SFloat : GraphicsFormat.R16_SFloat; + protected override bool NeedToReadWriteTextureData => false; + // We want the clear colour to be the min terrain height (-1000m) in X, and sea level offset 0m in Y. readonly static Color s_nullColor = Color.red * -1000f; static Texture2DArray s_nullTexture; protected override Texture2DArray NullTexture => s_nullTexture; @@ -29,6 +31,9 @@ public class LodDataMgrSeaFloorDepth : LodDataMgr public const string ShaderName = "Crest/Inputs/Depth/Cached Depths"; + public override SimSettingsBase SettingsBase => Settings; + public SettingsType Settings => _ocean._simSettingsSeaFloorDepth != null ? _ocean._simSettingsSeaFloorDepth : GetDefaultSettings(); + public LodDataMgrSeaFloorDepth(OceanRenderer ocean) : base(ocean) { Start(); diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs b/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs index c141e6b6b..03195ace1 100644 --- a/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs +++ b/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs @@ -300,13 +300,6 @@ public void PopulateCache(bool updateComponents = false) _copyDepthMaterial = new Material(Shader.Find("Crest/Copy Depth Buffer Into Cache")); } - // Shader needs sea level to determine water depth. Ocean instance might not be available in prefabs. - var centerPoint = Vector3.zero; - centerPoint.y = OceanRenderer.Instance != null - ? OceanRenderer.Instance.Root.position.y : transform.position.y; - - _copyDepthMaterial.SetVector("_OceanCenterPosWorld", centerPoint); - _copyDepthMaterial.SetTexture("_CamDepthBuffer", _camDepthCache.targetTexture); // Zbuffer params @@ -489,23 +482,6 @@ void FixScale(SerializedObject depthCache) } } - void FixHeight(SerializedObject depthCache) - { - var dc = depthCache.targetObject as OceanDepthCache; - - Undo.RecordObject(dc.transform, "Fix depth cache scale"); - EditorUtility.SetDirty(dc.transform); - - var pos = dc.transform.position; - pos.y = OceanRenderer.Instance.transform.position.y; - dc.transform.position = pos; - - if (dc.Type == OceanDepthCacheType.Realtime) - { - dc.PopulateCache(true); - } - } - void FixRotation(SerializedObject depthCache) { var dc = depthCache.targetObject as OceanDepthCache; @@ -637,30 +613,6 @@ public bool Validate(OceanRenderer ocean, ValidatedHelper.ShowMessage showMessag isValid = false; } - if (ocean == null) - { - showMessage - ( - "The Ocean Depth Cache uses the Ocean Renderer height which is not present. " + - "The transform height will be used instead.", - "", // Leave fix message blank as this could be a valid option. - ValidatedHelper.MessageType.Info, this - ); - } - - if (ocean != null && ocean.Root != null && !Mathf.Approximately(transform.position.y, ocean.Root.position.y)) - { - showMessage - ( - "It is recommended that the cache is placed at the same height (y component of position) as the ocean, i.e. at the sea level. If the cache is created before the ocean is present, the cache height will inform the sea level.", - "Set the Y position to the same height as the ocean object.", - ValidatedHelper.MessageType.Warning, this, - FixHeight - ); - - isValid = false; - } - if (!Mathf.Approximately(transform.eulerAngles.x, 0f) || !Mathf.Approximately(transform.eulerAngles.z, 0f)) { showMessage diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/RegisterHeightInput.cs b/crest/Assets/Crest/Crest/Scripts/LodData/RegisterHeightInput.cs index 34ab506e0..cfec184ca 100644 --- a/crest/Assets/Crest/Crest/Scripts/LodData/RegisterHeightInput.cs +++ b/crest/Assets/Crest/Crest/Scripts/LodData/RegisterHeightInput.cs @@ -2,9 +2,7 @@ // This file is subject to the MIT License as seen in the root of this folder structure (LICENSE) -using UnityEditor; using UnityEngine; -using UnityEngine.Experimental.Rendering; namespace Crest { @@ -13,7 +11,7 @@ namespace Crest /// [ExecuteAlways] [AddComponentMenu(MENU_PREFIX + "Height Input")] - public partial class RegisterHeightInput : RegisterLodDataInputWithSplineSupport + public partial class RegisterHeightInput : RegisterLodDataInputWithSplineSupport { /// /// The version of this asset. Can be used to migrate across versions. This value should @@ -31,9 +29,9 @@ public partial class RegisterHeightInput : RegisterLodDataInputWithSplineSupport public readonly static Color s_gizmoColor = new Color(0f, 1f, 0f, 0.5f); protected override Color GizmoColor => s_gizmoColor; - protected override string ShaderPrefix => "Crest/Inputs/Animated Waves"; + protected override string ShaderPrefix => "Crest/Inputs/Sea Floor Depth"; - protected override string SplineShaderName => "Crest/Inputs/Animated Waves/Set Base Water Height Using Geometry"; + protected override string SplineShaderName => "Crest/Inputs/Sea Floor Depth/Set Base Water Height Using Geometry"; protected override Vector2 DefaultCustomData => Vector2.zero; protected override bool FollowHorizontalMotion => true; @@ -61,6 +59,13 @@ protected override void Update() var seaLevel = OceanRenderer.Instance.SeaLevel; maxDispVert = Mathf.Max(maxDispVert, Mathf.Abs(seaLevel - minY), Mathf.Abs(seaLevel - maxY)); } + else if (_splineMaterial != null && + ShapeGerstnerSplineHandling.MinMaxHeightValid(_splinePointHeightMin, _splinePointHeightMax)) + { + var seaLevel = OceanRenderer.Instance.SeaLevel; + maxDispVert = Mathf.Max(maxDispVert, + Mathf.Abs(seaLevel - _splinePointHeightMin), Mathf.Abs(seaLevel - _splinePointHeightMax)); + } if (maxDispVert > 0f) { @@ -73,30 +78,4 @@ protected override void Update() protected override bool FeatureEnabled(OceanRenderer ocean) => true; #endif // UNITY_EDITOR } - -#if UNITY_EDITOR - public partial class RegisterHeightInput - { - public override bool Validate(OceanRenderer ocean, ValidatedHelper.ShowMessage showMessage) - { - var isValid = base.Validate(ocean, showMessage); - - if (isValid) - { - if (ocean != null && ocean._simSettingsAnimatedWaves._renderTextureGraphicsFormat != GraphicsFormat.R32G32B32A32_SFloat) - { - showMessage( - "Changing the height of the ocean can reduce precision leading to artefacts like tearing or incorrect normals. " + - $"{ocean._simSettingsAnimatedWaves._renderTextureGraphicsFormat} may not have enough precision.", - "Change graphics format to R32G32B32A32_SFloat.", - ValidatedHelper.MessageType.Warning, - ocean - ); - } - } - - return isValid; - } - } -#endif } diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/RegisterLodDataInput.cs b/crest/Assets/Crest/Crest/Scripts/LodData/RegisterLodDataInput.cs index a40c931d8..a43d594c3 100644 --- a/crest/Assets/Crest/Crest/Scripts/LodData/RegisterLodDataInput.cs +++ b/crest/Assets/Crest/Crest/Scripts/LodData/RegisterLodDataInput.cs @@ -300,6 +300,9 @@ public abstract partial class RegisterLodDataInputWithSplineSupport _spline == null; + protected float _splinePointHeightMin; + protected float _splinePointHeightMax; + void Awake() { if (TryGetComponent(out _spline)) @@ -307,7 +310,8 @@ void Awake() var radius = _overrideSplineSettings ? _radius : _spline.Radius; var subdivs = _overrideSplineSettings ? _subdivisions : _spline.Subdivisions; var smooth = _overrideSplineSettings ? _smoothingIterations : _spline.SmoothingIterations; - ShapeGerstnerSplineHandling.GenerateMeshFromSpline(_spline, transform, subdivs, radius, smooth, DefaultCustomData, ref _splineMesh); + ShapeGerstnerSplineHandling.GenerateMeshFromSpline(_spline, transform, subdivs, radius, smooth, DefaultCustomData, + ref _splineMesh, out _splinePointHeightMin, out _splinePointHeightMax); if (_splineMaterial == null) { @@ -369,7 +373,8 @@ protected override void Update() var radius = _overrideSplineSettings ? _radius : _spline.Radius; var subdivs = _overrideSplineSettings ? _subdivisions : _spline.Subdivisions; var smooth = _overrideSplineSettings ? _smoothingIterations : _spline.SmoothingIterations; - ShapeGerstnerSplineHandling.GenerateMeshFromSpline(_spline, transform, subdivs, radius, smooth, DefaultCustomData, ref _splineMesh); + ShapeGerstnerSplineHandling.GenerateMeshFromSpline(_spline, transform, subdivs, radius, smooth, DefaultCustomData, + ref _splineMesh, out _splinePointHeightMin, out _splinePointHeightMax); if (_splineMaterial == null) { diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/Settings/SimSettingsSeaFloorDepth.cs b/crest/Assets/Crest/Crest/Scripts/LodData/Settings/SimSettingsSeaFloorDepth.cs new file mode 100644 index 000000000..92a4ac09f --- /dev/null +++ b/crest/Assets/Crest/Crest/Scripts/LodData/Settings/SimSettingsSeaFloorDepth.cs @@ -0,0 +1,22 @@ +// Crest Ocean System + +// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE) + +using UnityEngine; + +namespace Crest +{ + [CreateAssetMenu(fileName = "SimSettingsSeaFloorDepth", menuName = "Crest/Sea Floor Depth Settings", order = 10000)] + public class SimSettingsSeaFloorDepth : SimSettingsBase + { + [Tooltip("Allow varying water level, to support water bodies at different heights and rivers to run down slopes. Disabling this will save some memory and may improve performance on some platforms.")] + public bool _allowVaryingWaterLevel = true; + + public override void AddToSettingsHash(ref int settingsHash) + { + base.AddToSettingsHash(ref settingsHash); + + Hashy.AddBool(_allowVaryingWaterLevel, ref settingsHash); + } + } +} diff --git a/crest/Assets/Crest/Crest/Scripts/Helpers/Editor/WindowCrestWaterBody.cs.meta b/crest/Assets/Crest/Crest/Scripts/LodData/Settings/SimSettingsSeaFloorDepth.cs.meta similarity index 83% rename from crest/Assets/Crest/Crest/Scripts/Helpers/Editor/WindowCrestWaterBody.cs.meta rename to crest/Assets/Crest/Crest/Scripts/LodData/Settings/SimSettingsSeaFloorDepth.cs.meta index 2ae9d20e3..dd5002aa8 100644 --- a/crest/Assets/Crest/Crest/Scripts/Helpers/Editor/WindowCrestWaterBody.cs.meta +++ b/crest/Assets/Crest/Crest/Scripts/LodData/Settings/SimSettingsSeaFloorDepth.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0375f6e71461f794eb0699352808e4f4 +guid: d5f9ff9027780ff45a85fc1e233fbd14 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/Shadows/LodDataMgrShadow.cs b/crest/Assets/Crest/Crest/Scripts/LodData/Shadows/LodDataMgrShadow.cs index 919a4ae8e..a3ebc195a 100644 --- a/crest/Assets/Crest/Crest/Scripts/LodData/Shadows/LodDataMgrShadow.cs +++ b/crest/Assets/Crest/Crest/Scripts/LodData/Shadows/LodDataMgrShadow.cs @@ -224,12 +224,14 @@ public override void UpdateLodData() _renderProperties.SetMatrix(sp_MainCameraProjectionMatrix, camera.projectionMatrix * camera.worldToCameraMatrix); _renderProperties.SetFloat(sp_SimDeltaTime, OceanRenderer.Instance.DeltaTimeDynamics); - _renderProperties.SetTexture(GetParamIdSampler(true), (Texture)_sources); + _renderProperties.SetTexture(GetParamIdSampler(true), _sources); _renderProperties.SetTexture(sp_LD_TexArray_Target, _targets); _renderProperties.SetBuffer(sp_cascadeDataSrc, OceanRenderer.Instance._bufCascadeDataSrc); + LodDataMgrSeaFloorDepth.Bind(_renderProperties); + var lt = OceanRenderer.Instance._lodTransform; for (var lodIdx = lt.LodCount - 1; lodIdx >= 0; lodIdx--) { diff --git a/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs b/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs index 522d5e9d2..2a4677bf7 100644 --- a/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs +++ b/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs @@ -212,6 +212,8 @@ public void PopTimeProvider(ITimeProvider tp) [Tooltip("Water depth information used for shallow water, shoreline foam, wave attenuation, among others."), SerializeField] bool _createSeaFloorDepthData = true; public bool CreateSeaFloorDepthData { get { return _createSeaFloorDepthData; } } + [Predicated("_createSeaFloorDepthData"), Embedded] + public SimSettingsSeaFloorDepth _simSettingsSeaFloorDepth; [Tooltip("Simulation of foam created in choppy water and dissipating over time."), SerializeField] bool _createFoamSim = true; @@ -334,6 +336,12 @@ public enum DefaultClippingState List _oceanChunkRenderers = new List(); public List Tiles => _oceanChunkRenderers; + /// + /// Smoothly varying version of viewer height to combat sudden changes in water level that are possible + /// when there are local bodies of water + /// + float _viewerHeightAboveWaterSmooth = 0f; + SampleHeightHelper _sampleHeightHelper = new SampleHeightHelper(); public static OceanRenderer Instance { get; private set; } @@ -889,8 +897,8 @@ void RunUpdate() if (_followViewpoint && Viewpoint != null) { LateUpdatePosition(); - LateUpdateScale(); LateUpdateViewerHeight(); + LateUpdateScale(); } CreateDestroySubSystems(); @@ -1000,11 +1008,14 @@ void LateUpdatePosition() void LateUpdateScale() { - // reach maximum detail at slightly below sea level. this should combat cases where visual range can be lost + var viewerHeight = _viewerHeightAboveWaterSmooth; + + // Reach maximum detail at slightly below sea level. this should combat cases where visual range can be lost // when water height is low and camera is suspended in air. i tried a scheme where it was based on difference // to water height but this does help with the problem of horizontal range getting limited at bad times. - float maxDetailY = SeaLevel - _maxVertDispFromWaves * _dropDetailHeightBasedOnWaves; - float camDistance = Mathf.Abs(Viewpoint.position.y - maxDetailY); + viewerHeight += _maxVertDispFromWaves * _dropDetailHeightBasedOnWaves; + + var camDistance = Mathf.Abs(viewerHeight); // offset level of detail to keep max detail in a band near the surface camDistance = Mathf.Max(camDistance - 4f, 0f); @@ -1033,6 +1044,10 @@ void LateUpdateViewerHeight() _sampleHeightHelper.Sample(out var waterHeight); ViewerHeightAboveWater = camera.transform.position.y - waterHeight; + + // Smoothly varying version of viewer height to combat sudden changes in water level that are possible + // when there are local bodies of water + _viewerHeightAboveWaterSmooth = Mathf.Lerp(_viewerHeightAboveWaterSmooth, ViewerHeightAboveWater, 0.05f); } void LateUpdateLods() diff --git a/crest/Assets/Crest/Crest/Scripts/Shapes/FFT/ShapeFFT.cs b/crest/Assets/Crest/Crest/Scripts/Shapes/FFT/ShapeFFT.cs index 5fb3d9b9e..d151eddbf 100644 --- a/crest/Assets/Crest/Crest/Scripts/Shapes/FFT/ShapeFFT.cs +++ b/crest/Assets/Crest/Crest/Scripts/Shapes/FFT/ShapeFFT.cs @@ -269,7 +269,8 @@ void InitBatches() var radius = _overrideSplineSettings ? _radius : splineForWaves.Radius; var subdivs = _overrideSplineSettings ? _subdivisions : splineForWaves.Subdivisions; var smooth = _overrideSplineSettings ? _smoothingIterations : splineForWaves.SmoothingIterations; - if (ShapeGerstnerSplineHandling.GenerateMeshFromSpline(splineForWaves, transform, subdivs, radius, smooth, Vector2.one, ref _meshForDrawingWaves)) + if (ShapeGerstnerSplineHandling.GenerateMeshFromSpline(splineForWaves, transform, subdivs, radius, smooth, Vector2.one, + ref _meshForDrawingWaves, out _, out _)) { _meshForDrawingWaves.name = gameObject.name + "_mesh"; } diff --git a/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstner.cs b/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstner.cs index 9e133d9f6..8e2e80139 100644 --- a/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstner.cs +++ b/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstner.cs @@ -628,7 +628,8 @@ void InitBatches() var subdivs = _overrideSplineSettings ? _subdivisions : splineForWaves.Subdivisions; var smooth = _overrideSplineSettings ? _smoothingIterations : splineForWaves.SmoothingIterations; - if (ShapeGerstnerSplineHandling.GenerateMeshFromSpline(splineForWaves, transform, subdivs, radius, smooth, Vector2.one, ref _meshForDrawingWaves)) + if (ShapeGerstnerSplineHandling.GenerateMeshFromSpline(splineForWaves, transform, subdivs, radius, smooth, Vector2.one, + ref _meshForDrawingWaves, out _, out _)) { _meshForDrawingWaves.name = gameObject.name + "_mesh"; } diff --git a/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerSplineHandling.cs b/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerSplineHandling.cs index 1d2a56544..35133a7ce 100644 --- a/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerSplineHandling.cs +++ b/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerSplineHandling.cs @@ -12,15 +12,25 @@ namespace Crest /// public static class ShapeGerstnerSplineHandling { - public static bool GenerateMeshFromSpline(Spline.Spline spline, Transform transform, int subdivisions, float radius, int smoothingIterations, Vector2 customDataDefault, ref Mesh mesh) + public static bool GenerateMeshFromSpline(Spline.Spline spline, Transform transform, int subdivisions, float radius, int smoothingIterations, Vector2 customDataDefault, ref Mesh mesh, out float minHeight, out float maxHeight) { - return GenerateMeshFromSpline(spline, transform, subdivisions, radius, smoothingIterations, customDataDefault, ref mesh); + return GenerateMeshFromSpline(spline, transform, subdivisions, radius, smoothingIterations, customDataDefault, ref mesh, out minHeight, out maxHeight); } - public static bool GenerateMeshFromSpline(Spline.Spline spline, Transform transform, int subdivisions, float radius, int smoothingIterations, Vector2 customDataDefault, ref Mesh mesh) + public static bool GenerateMeshFromSpline(Spline.Spline spline, Transform transform, int subdivisions, float radius, int smoothingIterations, Vector2 customDataDefault, ref Mesh mesh, out float minHeight, out float maxHeight) where SplinePointCustomData : ISplinePointCustomData { + minHeight = 10000f; + maxHeight = -10000f; + var splinePoints = spline.GetComponentsInChildren(); + + foreach (var sp in splinePoints) + { + minHeight = Mathf.Min(minHeight, sp.transform.position.y); + maxHeight = Mathf.Max(maxHeight, sp.transform.position.y); + } + if (splinePoints.Length < 2) return false; var splinePointCount = splinePoints.Length; @@ -278,5 +288,7 @@ static bool UpdateMesh(Transform transform, Vector3[] sampledPtsOnSpline, Vector return true; } + + public static bool MinMaxHeightValid(float minHeight, float maxHeight) => maxHeight >= minHeight; } } diff --git a/crest/Assets/Crest/Crest/Scripts/WaterBody.cs b/crest/Assets/Crest/Crest/Scripts/WaterBody.cs index 2e87f3693..d9d323d0e 100644 --- a/crest/Assets/Crest/Crest/Scripts/WaterBody.cs +++ b/crest/Assets/Crest/Crest/Scripts/WaterBody.cs @@ -84,10 +84,6 @@ private void OnDrawGizmosSelected() var oldColor = Gizmos.color; Gizmos.color = new Color(1f, 1f, 1f, 0.5f); var center = AABB.center; - if (OceanRenderer.Instance != null && OceanRenderer.Instance.Root != null) - { - center.y = OceanRenderer.Instance.Root.position.y; - } Gizmos.DrawCube(center, 2f * new Vector3(AABB.extents.x, 1f, AABB.extents.z)); Gizmos.color = oldColor; } diff --git a/crest/Assets/Crest/Crest/Shaders/Ocean.shader b/crest/Assets/Crest/Crest/Shaders/Ocean.shader index 4b30325b1..bc8a76104 100644 --- a/crest/Assets/Crest/Crest/Shaders/Ocean.shader +++ b/crest/Assets/Crest/Crest/Shaders/Ocean.shader @@ -299,6 +299,7 @@ Shader "Crest/Ocean" half3 debugtint : TEXCOORD8; #endif half4 grabPos : TEXCOORD9; + float2 seaLevelDerivs : TEXCOORD10; UNITY_FOG_COORDS(3) @@ -383,16 +384,16 @@ Shader "Crest/Ocean" } // Data that needs to be sampled at the displaced position + half seaLevelOffset = 0.0; + o.seaLevelDerivs = 0.0; if (wt_smallerLod > 0.0001) { const float3 uv_slice_smallerLodDisp = WorldToUV(o.worldPos.xz, cascadeData0, _LD_SliceIndex); - #if _SUBSURFACESHALLOWCOLOUR_ON - // The minimum sampling weight is lower (0.0001) than others to fix shallow water colour popping. - SampleSeaDepth(_LD_TexArray_SeaFloorDepth, uv_slice_smallerLodDisp, wt_smallerLod, o.lodAlpha_worldXZUndisplaced_oceanDepth.w); - #endif + SampleSeaDepth(_LD_TexArray_SeaFloorDepth, uv_slice_smallerLodDisp, wt_smallerLod, o.lodAlpha_worldXZUndisplaced_oceanDepth.w, seaLevelOffset, cascadeData0, o.seaLevelDerivs); #if _SHADOWS_ON + // The minimum sampling weight is lower than others to fix shallow water colour popping. if (wt_smallerLod > 0.001) { SampleShadow(_LD_TexArray_Shadow, uv_slice_smallerLodDisp, wt_smallerLod, o.flow_shadow.zw); @@ -403,12 +404,10 @@ Shader "Crest/Ocean" { const float3 uv_slice_biggerLodDisp = WorldToUV(o.worldPos.xz, cascadeData1, _LD_SliceIndex + 1); - #if _SUBSURFACESHALLOWCOLOUR_ON - // The minimum sampling weight is lower (0.0001) than others to fix shallow water colour popping. - SampleSeaDepth(_LD_TexArray_SeaFloorDepth, uv_slice_biggerLodDisp, wt_biggerLod, o.lodAlpha_worldXZUndisplaced_oceanDepth.w); - #endif + SampleSeaDepth(_LD_TexArray_SeaFloorDepth, uv_slice_biggerLodDisp, wt_biggerLod, o.lodAlpha_worldXZUndisplaced_oceanDepth.w, seaLevelOffset, cascadeData1, o.seaLevelDerivs); #if _SHADOWS_ON + // The minimum sampling weight is lower than others to fix shallow water colour popping. if (wt_biggerLod > 0.001) { SampleShadow(_LD_TexArray_Shadow, uv_slice_biggerLodDisp, wt_biggerLod, o.flow_shadow.zw); @@ -416,6 +415,8 @@ Shader "Crest/Ocean" #endif } + o.worldPos.y += seaLevelOffset; + // debug tinting to see which shape textures are used #if _DEBUGVISUALISESHAPESAMPLE_ON #define TINT_COUNT (uint)7 @@ -531,6 +532,8 @@ Shader "Crest/Ocean" #endif #endif + n_pixel.xz += float2(-input.seaLevelDerivs.x, -input.seaLevelDerivs.y); + // Finalise normal n_pixel.xz *= _NormalsStrengthOverall; n_pixel = normalize( n_pixel ); diff --git a/crest/Assets/Crest/Crest/Shaders/OceanEmission.hlsl b/crest/Assets/Crest/Crest/Shaders/OceanEmission.hlsl index e4a613f29..047bffd36 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanEmission.hlsl +++ b/crest/Assets/Crest/Crest/Shaders/OceanEmission.hlsl @@ -74,7 +74,8 @@ void ApplyCaustics // this gives height at displaced position, not exactly at query position.. but it helps. i cant pass this from vert shader // because i dont know it at scene pos. SampleDisplacements(_LD_TexArray_AnimatedWaves, scenePosUV, 1.0, disp); - half waterHeight = _OceanCenterPosWorld.y + disp.y; + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, scenePosUV, 0.0).y; + half waterHeight = _OceanCenterPosWorld.y + disp.y + seaLevelOffset; half sceneDepth = waterHeight - i_scenePos.y; // Compute mip index manually, with bias based on sea floor depth. We compute it manually because if it is computed automatically it produces ugly patches // where samples are stretched/dilated. The bias is to give a focusing effect to caustics - they are sharpest at a particular depth. This doesn't work amazingly diff --git a/crest/Assets/Crest/Crest/Shaders/OceanHelpersNew.hlsl b/crest/Assets/Crest/Crest/Shaders/OceanHelpersNew.hlsl index 19c0256e4..bfd7546a2 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanHelpersNew.hlsl +++ b/crest/Assets/Crest/Crest/Shaders/OceanHelpersNew.hlsl @@ -108,10 +108,35 @@ void SampleFlow(in Texture2DArray i_oceanFlowSampler, in float3 i_uv_slice, in f void SampleSeaDepth(in Texture2DArray i_oceanDepthSampler, in float3 i_uv_slice, in float i_wt, inout half io_oceanDepth) { - const float waterDepth = _OceanCenterPosWorld.y - i_oceanDepthSampler.SampleLevel(LODData_linear_clamp_sampler, i_uv_slice, 0.0).x; + const half2 terrainHeight_seaLevelOffset = i_oceanDepthSampler.SampleLevel(LODData_linear_clamp_sampler, i_uv_slice.xyz, 0.0).xy; + const half waterDepth = _OceanCenterPosWorld.y - terrainHeight_seaLevelOffset.x + terrainHeight_seaLevelOffset.y; io_oceanDepth += i_wt * (waterDepth - CREST_OCEAN_DEPTH_BASELINE); } +void SampleSeaDepth(in Texture2DArray i_oceanDepthSampler, in float3 i_uv_slice, in float i_wt, inout half io_oceanDepth, inout half io_seaLevelOffset, const CascadeParams i_cascadeParams, inout float2 io_seaLevelDerivs) +{ + const half2 terrainHeight_seaLevelOffset = i_oceanDepthSampler.SampleLevel( LODData_linear_clamp_sampler, i_uv_slice, 0.0 ); + io_oceanDepth += i_wt * (terrainHeight_seaLevelOffset.x - CREST_OCEAN_DEPTH_BASELINE); + io_seaLevelOffset += i_wt * terrainHeight_seaLevelOffset.y; + + { + // Compute derivative of sea level - needed to get base normal of water. Gerstner normal / normal map + // normal is then added to base normal. + const float seaLevelOffset_x = i_oceanDepthSampler.SampleLevel( + LODData_linear_clamp_sampler, i_uv_slice + float3(i_cascadeParams._oneOverTextureRes, 0.0, 0.0), 0.0 ).y; + const float seaLevelOffset_z = i_oceanDepthSampler.SampleLevel( + LODData_linear_clamp_sampler, i_uv_slice + float3(0.0, i_cascadeParams._oneOverTextureRes, 0.0), 0.0 ).y; + + io_seaLevelDerivs.x += i_wt * (seaLevelOffset_x - terrainHeight_seaLevelOffset.y) / i_cascadeParams._texelWidth; + io_seaLevelDerivs.y += i_wt * (seaLevelOffset_z - terrainHeight_seaLevelOffset.y) / i_cascadeParams._texelWidth; + } +} + +void SampleSeaLevelOffset(in Texture2DArray i_oceanDepthSampler, in float3 i_uv_slice, in float i_wt, inout half io_seaLevelOffset) +{ + io_seaLevelOffset += i_wt * i_oceanDepthSampler.SampleLevel( LODData_linear_clamp_sampler, i_uv_slice, 0.0 ).y; +} + void SampleShadow(in Texture2DArray i_oceanShadowSampler, in float3 i_uv_slice, in float i_wt, inout half2 io_shadow) { io_shadow += i_wt * i_oceanShadowSampler.SampleLevel(LODData_linear_clamp_sampler, i_uv_slice, 0.0).xy; diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesRemoveGeometry.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesRemoveGeometry.shader index 53d2be1ce..e700e2e38 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesRemoveGeometry.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesRemoveGeometry.shader @@ -20,6 +20,8 @@ Shader "Crest/Inputs/Animated Waves/Push Water Under Convex Hull" #include "UnityCG.cginc" #include "../OceanGlobals.hlsl" + #include "../OceanInputsDriven.hlsl" + #include "../OceanHelpersNew.hlsl" CBUFFER_START(CrestPerOceanInput) float _Weight; @@ -44,7 +46,7 @@ Shader "Crest/Inputs/Animated Waves/Push Water Under Convex Hull" o.worldPos = mul(unity_ObjectToWorld, float4(input.positionOS, 1.0)).xyz; // Correct for displacement o.worldPos.xz -= _DisplacementAtInputPosition.xz; - + o.positionCS = mul(UNITY_MATRIX_VP, float4(o.worldPos, 1.0)); return o; @@ -56,7 +58,10 @@ Shader "Crest/Inputs/Animated Waves/Push Water Under Convex Hull" // Write large XZ components - using min blending so this should not affect them. - return half4(10000.0, _Weight * (input.worldPos.y - _OceanCenterPosWorld.y), 10000.0, 1.0); + float3 uv = WorldToUV(input.worldPos.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).y; + + return half4(10000.0, _Weight * (input.worldPos.y - _OceanCenterPosWorld.y - seaLevelOffset), 10000.0, 1.0); } ENDCG } diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesSetHeightToGeometry.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesSetHeightToGeometry.shader index 46beecd28..e32051b16 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesSetHeightToGeometry.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/AnimWavesSetHeightToGeometry.shader @@ -25,6 +25,8 @@ Shader "Crest/Inputs/Animated Waves/Set Water Height Using Geometry" #include "UnityCG.cginc" #include "../OceanGlobals.hlsl" + #include "../OceanInputsDriven.hlsl" + #include "../OceanHelpersNew.hlsl" CBUFFER_START(CrestPerOceanInput) float _Weight; @@ -45,11 +47,11 @@ Shader "Crest/Inputs/Animated Waves/Set Water Height Using Geometry" Varyings Vert(Attributes input) { Varyings o; - + o.worldPos = mul(unity_ObjectToWorld, float4(input.positionOS, 1.0)).xyz; // Correct for displacement o.worldPos.xz -= _DisplacementAtInputPosition.xz; - + o.positionCS = mul(UNITY_MATRIX_VP, float4(o.worldPos, 1.0)); return o; @@ -57,8 +59,11 @@ Shader "Crest/Inputs/Animated Waves/Set Water Height Using Geometry" half4 Frag(Varyings input) : SV_Target { + float3 uv = WorldToUV(input.worldPos.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).y; + // Write displacement to get from sea level of ocean to the y value of this geometry - float height = input.worldPos.y - _OceanCenterPosWorld.y; + float height = input.worldPos.y - _OceanCenterPosWorld.y - seaLevelOffset; return half4(0.0, _Weight * height, 0.0, 0.0); } ENDCG diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/GerstnerShared.hlsl b/crest/Assets/Crest/Crest/Shaders/OceanInputs/GerstnerShared.hlsl index 320fab036..c37c52714 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/GerstnerShared.hlsl +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/GerstnerShared.hlsl @@ -26,7 +26,8 @@ CBUFFER_END half3 ComputeGerstner(float2 worldPosXZ, float3 uv_slice) { // sample ocean depth (this render target should 1:1 match depth texture, so UVs are trivial) - const half depth = _OceanCenterPosWorld.y - _LD_TexArray_SeaFloorDepth.Sample(LODData_linear_clamp_sampler, uv_slice).x; + const half2 terrainHeight_seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv_slice, 0.0).xy; + const half depth = _OceanCenterPosWorld.y - terrainHeight_seaLevelOffset.x + terrainHeight_seaLevelOffset.y; // Preferred wave directions #if CREST_DIRECT_TOWARDS_POINT_INTERNAL diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstner.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstner.shader index 5066d528a..2523b9f04 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstner.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstner.shader @@ -72,7 +72,9 @@ Shader "Hidden/Crest/Inputs/Animated Waves/Gerstner Global" float wt = _Weight; // Attenuate if depth is less than half of the average wavelength - const half depth = _OceanCenterPosWorld.y - _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, float3(input.uv_uvWaves.xy, _LD_SliceIndex), 0.0).x; + const half2 terrainHeight_seaLevelOffset = + _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, float3(input.uv_uvWaves.xy, _LD_SliceIndex), 0.0).xy; + const half depth = _OceanCenterPosWorld.y - terrainHeight_seaLevelOffset.x + terrainHeight_seaLevelOffset.y; half depth_wt = saturate(2.0 * depth / _AverageWavelength); const float attenuationAmount = _AttenuationInShallows * _RespectShallowWaterAttenuation; wt *= attenuationAmount * depth_wt + (1.0 - attenuationAmount); diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstnerGeometry.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstnerGeometry.shader index cc8bfc7aa..9c42942e4 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstnerGeometry.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesGerstnerGeometry.shader @@ -87,7 +87,7 @@ Shader "Crest/Inputs/Animated Waves/Gerstner Geometry" const float3 worldPos = mul( unity_ObjectToWorld, float4(positionOS, 1.0) ).xyz; // UV coordinate into the cascade we are rendering into - o.uv_slice.xyz = WorldToUV(worldPos.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + o.uv_slice = WorldToUV(worldPos.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); // World pos prescaled by wave buffer size, suitable for using as UVs in fragment shader const float waveBufferSize = 0.5f * (1 << _WaveBufferSliceIndex); @@ -107,7 +107,8 @@ Shader "Crest/Inputs/Animated Waves/Gerstner Geometry" float wt = input.invNormDistToShoreline_weight.y; // Attenuate if depth is less than half of the average wavelength - const half depth = _OceanCenterPosWorld.y - _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, input.uv_slice.xyz, 0.0).x; + const half2 terrainHeight_seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, input.uv_slice, 0.0).xy; + const half depth = _OceanCenterPosWorld.y - terrainHeight_seaLevelOffset.x + terrainHeight_seaLevelOffset.y; const half depth_wt = saturate(2.0 * depth / _AverageWavelength); const float attenuationAmount = _AttenuationInShallows * _RespectShallowWaterAttenuation; wt *= attenuationAmount * depth_wt + (1.0 - attenuationAmount); diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesSetBaseWaterHeightUsingGeometry.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesSetBaseWaterHeightUsingGeometry.shader index d0779b27b..a29a39c7d 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesSetBaseWaterHeightUsingGeometry.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/AnimWavesSetBaseWaterHeightUsingGeometry.shader @@ -9,6 +9,7 @@ Shader "Crest/Inputs/Animated Waves/Set Base Water Height Using Geometry" { Properties { + [HideInInspector] _ObsoleteMessage( "Use Crest/Inputs/Sea Floor Depth/Set Base Water Height Using Geometry instead.", Float ) = 0 [Enum(BlendOp)] _BlendOp("Blend Op", Int) = 0 [Enum(UnityEngine.Rendering.BlendMode)] _BlendModeSrc("Src Blend Mode", Int) = 1 [Enum(UnityEngine.Rendering.BlendMode)] _BlendModeTgt("Tgt Blend Mode", Int) = 1 @@ -29,6 +30,8 @@ Shader "Crest/Inputs/Animated Waves/Set Base Water Height Using Geometry" #include "UnityCG.cginc" #include "../../OceanGlobals.hlsl" + #include "../../OceanInputsDriven.hlsl" + #include "../../OceanHelpersNew.hlsl" CBUFFER_START(CrestPerOceanInput) float _Weight; @@ -61,11 +64,16 @@ Shader "Crest/Inputs/Animated Waves/Set Base Water Height Using Geometry" half4 Frag(Varyings input) : SV_Target { + float3 uv = WorldToUV(input.worldPos.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).y; + // Write displacement to get from sea level of ocean to the y value of this geometry - float addHeight = input.worldPos.y - _OceanCenterPosWorld.y; + float addHeight = input.worldPos.y - _OceanCenterPosWorld.y - seaLevelOffset; return _Weight * half4(0.0, addHeight, 0.0, 0.0); } ENDCG } } + + CustomEditor "Crest.ObsoleteShaderGUI" } diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceConvexHull.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceConvexHull.shader index 3ceb462de..97deb0850 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceConvexHull.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceConvexHull.shader @@ -64,8 +64,11 @@ Shader "Crest/Inputs/Clip Surface/Convex Hull" _DisplacementSamplingIterations ); + float3 uv = WorldToUV(input.positionWS.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).y; + // Move to sea level - surfacePositionWS.y += _OceanCenterPosWorld.y; + surfacePositionWS.y += _OceanCenterPosWorld.y + seaLevelOffset; // Write red if underwater if (input.positionWS.y > surfacePositionWS.y) @@ -126,8 +129,11 @@ Shader "Crest/Inputs/Clip Surface/Convex Hull" _DisplacementSamplingIterations ); + float3 uv = WorldToUV(input.positionWS.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).y; + // Move to sea level - surfacePositionWS.y += _OceanCenterPosWorld.y; + surfacePositionWS.y += _OceanCenterPosWorld.y + seaLevelOffset; // Write black if underwater if (input.positionWS.y > surfacePositionWS.y) diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceSignedDistance.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceSignedDistance.shader index 7fd67f2ed..8b6a68a61 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceSignedDistance.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/ClipSurfaceSignedDistance.shader @@ -84,6 +84,11 @@ Shader "Hidden/Crest/Inputs/Clip Surface/Signed Distance" // We only need the height as clip surface is sampled at the displaced position in the ocean shader. positionWS.y += surfacePositionWS.y; + // The sea level is baked into the matrix but not the sea level offset. + float3 uv = WorldToUV(input.positionWS.xz, _CrestCascadeData[_LD_SliceIndex], _LD_SliceIndex); + half seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).y; + positionWS.y += seaLevelOffset; + #if _CUBE float signedDistance = signedDistanceBox(positionWS); #else diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/DepthSetBaseWaterHeightUsingGeometry.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/DepthSetBaseWaterHeightUsingGeometry.shader new file mode 100644 index 000000000..caa2d96f1 --- /dev/null +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/DepthSetBaseWaterHeightUsingGeometry.shader @@ -0,0 +1,63 @@ +// Crest Ocean System + +// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE) + +// This sets base water height to Y value of geometry. + +Shader "Crest/Inputs/Sea Floor Depth/Set Base Water Height Using Geometry" +{ + SubShader + { + Pass + { + Blend Off + + // Second channel is sea level offset + ColorMask G + + CGPROGRAM + #pragma vertex Vert + #pragma fragment Frag + + #include "UnityCG.cginc" + #include "../../OceanGlobals.hlsl" + + CBUFFER_START(CrestPerOceanInput) + float _Weight; + float3 _DisplacementAtInputPosition; + CBUFFER_END + + struct Attributes + { + float3 positionOS : POSITION; + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float3 worldPos : TEXCOORD0; + }; + + Varyings Vert(Attributes input) + { + Varyings o; + + o.worldPos = mul(unity_ObjectToWorld, float4(input.positionOS, 1.0)).xyz; + // Correct for displacement + o.worldPos.xz -= _DisplacementAtInputPosition.xz; + + o.positionCS = mul(UNITY_MATRIX_VP, float4(o.worldPos, 1.0)); + + return o; + } + + half4 Frag(Varyings input) : SV_Target + { + // Write displacement to get from sea level of ocean to the y value of this geometry + const float heightOffset = input.worldPos.y - _OceanCenterPosWorld.y; + return half4(0.0, heightOffset, 0.0, _Weight); + } + ENDCG + } + } +} diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/DepthSetBaseWaterHeightUsingGeometry.shader.meta b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/DepthSetBaseWaterHeightUsingGeometry.shader.meta new file mode 100644 index 000000000..36ea1a452 --- /dev/null +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/DepthSetBaseWaterHeightUsingGeometry.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 767cdc0829b400c4ba3cc8aad8df86df +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/OceanDepthsCache.shader b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/OceanDepthsCache.shader index b6b8b9db9..c0d7775b2 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/OceanDepthsCache.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanInputs/Resources/OceanDepthsCache.shader @@ -51,9 +51,9 @@ Shader "Crest/Inputs/Depth/Cached Depths" return output; } - half4 Frag(Varyings input) : SV_Target + float2 Frag(Varyings input) : SV_Target { - return half4(tex2D(_MainTex, input.uv).x, 0.0, 0.0, 0.0); + return float2(tex2D(_MainTex, input.uv).x, 0.0); } ENDCG } diff --git a/crest/Assets/Crest/Crest/Shaders/OceanSurfaceAlpha.shader b/crest/Assets/Crest/Crest/Shaders/OceanSurfaceAlpha.shader index a98aa0b96..b9d11ad42 100644 --- a/crest/Assets/Crest/Crest/Shaders/OceanSurfaceAlpha.shader +++ b/crest/Assets/Crest/Crest/Shaders/OceanSurfaceAlpha.shader @@ -90,24 +90,31 @@ Shader "Crest/Ocean Surface Alpha" // sample displacement textures, add results to current world pos / normal / foam half foam = 0.0; // sample weight. params.z allows shape to be faded out (used on last lod to support pop-less scale transitions) - const float cascadeWt0 = cascadeData0._weight; - float wt_smallerLod = (1.0 - lodAlpha) * cascadeWt0; + float wt_smallerLod = (1.0 - lodAlpha) * cascadeData0._weight; { const float3 uv_slice = WorldToUV(worldPos.xz, cascadeData0, _LD_SliceIndex); half variance = 0.0; SampleDisplacements(_LD_TexArray_AnimatedWaves, uv_slice, wt_smallerLod, worldPos, variance); } + const float wt_biggerLod = (1.0 - wt_smallerLod) * cascadeData1._weight; { // sample weight. params.z allows shape to be faded out (used on last lod to support pop-less scale transitions) - const float cascadeWt1 = cascadeData1._weight; - const float wt_biggerLod = (1.0 - wt_smallerLod) * cascadeWt1; const float3 uv_slice = WorldToUV(worldPos.xz, cascadeData1, _LD_SliceIndex + 1); half variance = 0.0; SampleDisplacements(_LD_TexArray_AnimatedWaves, uv_slice, wt_biggerLod, worldPos, variance); } + // Data that needs to be sampled at the displaced position. + float seaLevelOffset = 0.0; + { + const float3 uv_slice_smallerLodDisp = WorldToUV(worldPos.xz, cascadeData0, _LD_SliceIndex); + const float3 uv_slice_biggerLodDisp = WorldToUV( worldPos.xz, cascadeData1, _LD_SliceIndex + 1 ); + SampleSeaLevelOffset(_LD_TexArray_SeaFloorDepth, uv_slice_smallerLodDisp, wt_smallerLod, seaLevelOffset); + SampleSeaLevelOffset(_LD_TexArray_SeaFloorDepth, uv_slice_biggerLodDisp, wt_biggerLod, seaLevelOffset); + } + // move to sea level - worldPos.y += _OceanCenterPosWorld.y; + worldPos.y += _OceanCenterPosWorld.y + seaLevelOffset; // view-projection o.positionCS = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0)); diff --git a/crest/Assets/Crest/Crest/Shaders/Resources/QueryDisplacements.compute b/crest/Assets/Crest/Crest/Shaders/Resources/QueryDisplacements.compute index 29a54e2e3..857762d99 100644 --- a/crest/Assets/Crest/Crest/Shaders/Resources/QueryDisplacements.compute +++ b/crest/Assets/Crest/Crest/Shaders/Resources/QueryDisplacements.compute @@ -28,9 +28,17 @@ float3 ComputeDisplacement(float2 undispPos, float minSlice, const float baseSca const float wt_0 = (1. - lodAlpha) * _CrestCascadeData[slice0]._weight; const float wt_1 = (1. - wt_0) * _CrestCascadeData[slice1]._weight; - return + // Wave displacement + float3 disp = wt_0 * _LD_TexArray_AnimatedWaves.SampleLevel(LODData_linear_clamp_sampler, uv0, 0).xyz + wt_1 * _LD_TexArray_AnimatedWaves.SampleLevel(LODData_linear_clamp_sampler, uv1, 0).xyz; + + // Sea level offset + disp.y += + wt_0 * _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv0, 0).y + + wt_1 * _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv1, 0).y; + + return disp; } [numthreads(GROUP_SIZE, 1, 1)] diff --git a/crest/Assets/Crest/Crest/Shaders/Resources/UpdateDynWaves.compute b/crest/Assets/Crest/Crest/Shaders/Resources/UpdateDynWaves.compute index 299c71e34..bc650e610 100644 --- a/crest/Assets/Crest/Crest/Shaders/Resources/UpdateDynWaves.compute +++ b/crest/Assets/Crest/Crest/Shaders/Resources/UpdateDynWaves.compute @@ -62,7 +62,8 @@ void UpdateDynWaves(uint3 id : SV_DispatchThreadID) const float3 uv_slice = float3(input_uv, sliceIndex); - const float waterDepth = _OceanCenterPosWorld.y - SampleLod(_LD_TexArray_SeaFloorDepth, uv_slice).x; + const float2 terrainHeight_seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv_slice, 0.0).xy; + const float waterDepth = _OceanCenterPosWorld.y - terrainHeight_seaLevelOffset.x + terrainHeight_seaLevelOffset.y; // Wave reflections off geometry. if (waterDepth <= 0.0) diff --git a/crest/Assets/Crest/Crest/Shaders/Resources/UpdateFoam.compute b/crest/Assets/Crest/Crest/Shaders/Resources/UpdateFoam.compute index cec1cc7ef..33acfd813 100644 --- a/crest/Assets/Crest/Crest/Shaders/Resources/UpdateFoam.compute +++ b/crest/Assets/Crest/Crest/Shaders/Resources/UpdateFoam.compute @@ -92,7 +92,8 @@ void UpdateFoam(uint3 id : SV_DispatchThreadID) // Add foam in shallow water. use the displaced position to ensure we add foam where world objects are. const float3 uv_slice_displaced = WorldToUV(worldPosXZ + disp.xz, cascadeData, sliceIndex); - const float signedOceanDepth = _OceanCenterPosWorld.y - SampleLodLevel(_LD_TexArray_SeaFloorDepth, uv_slice_displaced, 0.0).x + disp.y; + const half2 terrainHeight_seaLevelOffset = _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, uv_slice_displaced, 0.0).xy; + const half signedOceanDepth = _OceanCenterPosWorld.y - terrainHeight_seaLevelOffset.x + terrainHeight_seaLevelOffset.y + disp.y; foam += _ShorelineFoamStrength * _SimDeltaTime * saturate(1.0 - signedOceanDepth / _ShorelineFoamMaxDepth); _LD_TexArray_Target[id] = saturate(foam); diff --git a/crest/Assets/Crest/Crest/Shaders/Resources/UpdateShadow.compute b/crest/Assets/Crest/Crest/Shaders/Resources/UpdateShadow.compute index 7b44ce7fc..5597050e1 100644 --- a/crest/Assets/Crest/Crest/Shaders/Resources/UpdateShadow.compute +++ b/crest/Assets/Crest/Crest/Shaders/Resources/UpdateShadow.compute @@ -101,12 +101,15 @@ void UpdateShadow(uint3 id : SV_DispatchThreadID) // of rendering real geometry from a worldspace camera. ShadowCoords shadowCoords; { - // world pos from [0,1] texture - float4 wpos = float4(float3(((id.x + 0.5)/width) - 0.5, 0.0, ((id.y + 0.5)/height) -0.5) * _Scale * 4.0 + _CenterPos, 1.0); + // World pos from [0,1] texture + const float2 uv = float2((id.x + 0.5) / width, (id.y + 0.5) / height); + float4 wpos = float4(float3(uv.x - 0.5, 0.0, uv.y - 0.5) * _Scale * 4.0 + _CenterPos, 1.0); - // this could add wave height/disp?? wpos.y = _OceanCenterPosWorld.y; + // Offset world position by sea level offset + wpos.y += _LD_TexArray_SeaFloorDepth.SampleLevel(LODData_linear_clamp_sampler, float3(uv, _LD_SliceIndex), 0.0).y; + shadowCoords._WorldPosViewZ.xyz = wpos.xyz; shadowCoords._WorldPosViewZ.w = dot(wpos.xyz - _CamPos, _CamForward); @@ -115,7 +118,7 @@ void UpdateShadow(uint3 id : SV_DispatchThreadID) shadowCoords._ShadowCoord2 = mul(unity_WorldToShadow[2], wpos).xyz; shadowCoords._ShadowCoord3 = mul(unity_WorldToShadow[3], wpos).xyz; - // working hard to get derivatives for shadow uvs, so that i can jitter the world position in the fragment shader. this + // Working hard to get derivatives for shadow uvs, so that i can jitter the world position in the fragment shader. this // enables per-fragment noise (required to avoid wobble), and is required because each cascade has a different scale etc. shadowCoords.ShadowCoord0_dxdz.xy = mul(unity_WorldToShadow[0], wpos + float4(1.0, 0.0, 0.0, 0.0)).xz - shadowCoords._ShadowCoord0.xz; shadowCoords.ShadowCoord0_dxdz.zw = mul(unity_WorldToShadow[0], wpos + float4(0.0, 0.0, 1.0, 0.0)).xz - shadowCoords._ShadowCoord0.xz; diff --git a/crest/Assets/Crest/Crest/Shaders/Underwater/UnderwaterMaskShared.hlsl b/crest/Assets/Crest/Crest/Shaders/Underwater/UnderwaterMaskShared.hlsl index 7eb655a24..579bad1de 100644 --- a/crest/Assets/Crest/Crest/Shaders/Underwater/UnderwaterMaskShared.hlsl +++ b/crest/Assets/Crest/Crest/Shaders/Underwater/UnderwaterMaskShared.hlsl @@ -91,6 +91,21 @@ Varyings Vert(Attributes v) SampleDisplacements(_LD_TexArray_AnimatedWaves, uv_slice_biggerLod, wt_biggerLod, worldPos, sss); } + // Data that needs to be sampled at the displaced position. + half seaLevelOffset = 0.0; + if (wt_smallerLod > 0.0001) + { + const float3 uv_slice_smallerLodDisp = WorldToUV(worldPos.xz, cascadeData0, _LD_SliceIndex); + SampleSeaLevelOffset(_LD_TexArray_SeaFloorDepth, uv_slice_smallerLodDisp, wt_smallerLod, seaLevelOffset); + } + if (wt_biggerLod > 0.0001) + { + const float3 uv_slice_biggerLodDisp = WorldToUV(worldPos.xz, cascadeData1, _LD_SliceIndex + 1); + SampleSeaLevelOffset(_LD_TexArray_SeaFloorDepth, uv_slice_biggerLodDisp, wt_biggerLod, seaLevelOffset); + } + + worldPos.y += seaLevelOffset; + #if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0) worldPos.xz -= _WorldSpaceCameraPos.xz; #endif diff --git a/docs/about/history.rst b/docs/about/history.rst index 9abd0b1a8..51633b247 100644 --- a/docs/about/history.rst +++ b/docs/about/history.rst @@ -23,6 +23,8 @@ Changed - *Sphere Water Interaction* component simplified - no mesh renderer/shader setup required, and no 'register' component required. - *Sphere Water Interaction* produces more consistent results at different radii/scales. - Improve `FFT` wave quality by doubling the sampling from two to four. + - *RegisterHeightInput* can be used in conjunction with our *Spline* component to offset the water level. This can be used to create water bodies at different altitudes, and to create rivers that flow between them. + - All water features updated to support varying water level. Fixed ^^^^^ @@ -45,6 +47,7 @@ Removed .. bullet_list:: - Remove *Texels Per Wave* parameter from Ocean Renderer and hard-code to Nyquist limit as it is required for `FFT`\ s to work well. + - Removed *Create Water Body* wizard window. The water body setup has been simplified and works without this additional tooling. Performance ^^^^^^^^^^^ diff --git a/docs/user/water-bodies.rst b/docs/user/water-bodies.rst index 1d3757cf3..058e54acb 100644 --- a/docs/user/water-bodies.rst +++ b/docs/user/water-bodies.rst @@ -1,18 +1,21 @@ .. _water-bodies: -Water Bodies -============ +Oceans, Rivers and Lakes +======================== -.. youtube:: jXphUy__J0o +.. note:: - Water Bodies and Surface Clipping + The features described in this section are in preview and may evolve in future versions. -.. note:: +Oceans +------ - *Water Bodies* as a complete feature is a work-in-progress. +By default Crest generates an infinite body of water at a fixed sea level, suitable for oceans and very large lakes. -By default the system generates a water surface that expands our to the horizon in every direction. -There are mechanisms to limit the area: +Lakes +----- + +Crest can be configured to efficiently generate smaller bodies of water, using the following mechanisms. - The waves can be generated in a limited area - see the :ref:`wave-splines-section` section. - The *WaterBody* component, if present, marks areas of the scene where water should be present. @@ -21,21 +24,17 @@ There are mechanisms to limit the area: - The *WaterBody* component turns off tiles that do not overlap the desired area. The *Clip Surface* feature can be used to precisely remove any remaining water outside the intended area. Additionally, the clipping system can be configured to clip everything by default, and then areas can be defined where water should be included. See the :ref:`clip-surface-section` section. +- If the lake altitude differs from the global sea level, create a spline that covers the area of the lake and attach the *RegisterHeightInput* component which will set the water level to match the spline. + It is recommended to cover a larger area than the lake itself, to give a protective margin against LOD effects in the distance. Another advantage of the *WaterBody* component is it allows an optional override material to be provided, to change the appearance of the water. This currently only changes the appearance of the water surface, it does not currently affect the underwater effect. +Rivers +------ -Wizard (preview) ----------------- - -We recently added a 'wizard' to help create this setup. -It can be used as follows: +Splines can also be used to create rivers, by creating a spline at the water surface of the river, and attaching the following components: -- Open the wizard window by selecting *Window/Crest/Create Water Body* -- Click *Create Water Body*. A white plane should appear in the Scene View visualising the location and size -- Set the position using the translation gizmo in the Scene View, or using the *Center position* input -- Set the size using the *Size X* and *Size Z* inputs -- Each of the above components are available via the *Create ...* toggles -- Click *Create* and a water body should be created in the scene -- Click *Done* to close the wizard +- *RegisterHeightInput* can be used to set the water level to match the spline. +- *RegisterFlowInput* can be used to make the water move along the spline. +- *ShapeFFT* can be used to generate waves that propagate along the river.