// Crest Water System // Copyright © 2024 Wave Harmonic. All rights reserved. using UnityEngine; namespace WaveHarmonic.Crest { /// /// Renders the meniscus (waterline). /// [System.Serializable] public sealed partial class Meniscus { [@Space(10)] [Tooltip("Whether the meniscus is enabled.")] [@GenerateAPI(Getter.Custom, Setter.Custom)] [@DecoratedField] [SerializeField] internal bool _Enabled = true; [Tooltip("Any camera with this layer in its culling mask will render the meniscus.")] [@Layer] [@GenerateAPI] [SerializeField] int _Layer = 4; // Water [Tooltip("The meniscus material.")] [@AttachMaterialEditor(order: 2)] [@MaterialField("Crest/Meniscus", name: "Meniscus", title: "Create Meniscus Material")] [@GenerateAPI(Setter.Custom)] [SerializeField] internal Material _Material; WaterRenderer _Water; internal MeniscusRenderer Renderer { get; private set; } internal bool RequiresOpaqueTexture => Enabled && Material != null && Material.IsKeywordEnabled("d_Crest_Refraction"); /// /// Disables rendering without de-allocating. /// public bool ForceRenderingOff { get; set; } internal void Enable() { Initialize(_Water); Renderer?.Enable(); } internal void Disable() { Renderer?.Disable(); } internal void Destroy() { Renderer?.Destroy(); Renderer = null; } internal void OnActiveRenderPipelineTypeChanged() { Destroy(); Initialize(_Water); } internal void Initialize(WaterRenderer water) { _Water = water; if (!Enabled) { return; } #if d_UnityHDRP if (RenderPipelineHelper.IsHighDefinition) { Renderer ??= new MeniscusRendererHDRP(water, this); } else #endif #if d_UnityURP if (RenderPipelineHelper.IsUniversal) { Renderer ??= new MeniscusRendererURP(water, this); } else #endif // Legacy { Renderer ??= new MeniscusRendererBIRP(water, this); } } } // Getters/Setters partial class Meniscus { bool GetEnabled() { return _Enabled && _Material != null; } void SetEnabled(bool previous, bool current) { if (previous == current) return; if (_Water == null || !_Water.isActiveAndEnabled) return; if (_Enabled) Enable(); else Disable(); } void SetMaterial(Material previous, Material current) { if (previous == current) return; if (_Water == null || !_Water.isActiveAndEnabled) return; if (previous == null) Enable(); else if (current == null) Disable(); } } partial class Meniscus { internal abstract partial class MeniscusRenderer { private protected const string k_Draw = "Crest.DrawWater/Meniscus"; private protected readonly WaterRenderer _Water; internal readonly Meniscus _Meniscus; static partial class ShaderIDs { public static readonly int s_HorizonNormal = Shader.PropertyToID("_Crest_HorizonNormal"); } public abstract void OnBeginCameraRendering(Camera camera); public abstract void OnEndCameraRendering(Camera camera); public MeniscusRenderer(WaterRenderer water, Meniscus meniscus) { _Water = water; _Meniscus = meniscus; } public virtual void Enable() { } public virtual void Disable() { } public virtual void Destroy() { } internal bool ShouldExecute(Camera camera) { #if UNITY_EDITOR if (GL.wireframe) { return false; } #endif if (_Meniscus.ForceRenderingOff) { return false; } // Meniscus is a product of the water surface. if (!_Water.Surface.Enabled) { return false; } if (camera.cameraType is not CameraType.Game and not CameraType.SceneView) { return false; } if (!WaterRenderer.ShouldRender(camera, _Meniscus.Layer)) { return false; } #if d_CrestPortals if (_Water.Portals.Active) { // Near surface check not compatible with portals. return true; } #endif _Water.UpdatePerCameraHeight(camera); // Only execute if near the surface. if (_Water._ViewerHeightAboveWaterPerCamera is > 2f or < -8f) { return false; } return true; } internal void Execute(Camera camera, T commands) where T : ICommandWrapper { // Project water normal onto camera plane. _Meniscus.Material.SetVector(ShaderIDs.s_HorizonNormal, new Vector2 ( Vector3.Dot(Vector3.up, camera.transform.right), Vector3.Dot(Vector3.up, camera.transform.up) )); var isFullScreenRequired = true; var isMasked = false; var passOffset = 1; #if d_CrestPortals passOffset = (int)Portals.PortalRenderer.MeniscusPass.Length; if (_Water.Portals.Active) { isMasked = isFullScreenRequired = _Water._Portals.RenderMeniscus(commands, _Meniscus.Material); } #endif if (isFullScreenRequired) { var pass = isMasked ? 1 : 0; var mpb = _Water.Surface._SurfaceDataMPB; if (_Water._Underwater.UseLegacyMask) { pass += passOffset; mpb = null; } commands.DrawFullScreenTriangle(_Meniscus.Material, pass, mpb); } } } } }