Files
Fishing2/Packages/com.waveharmonic.crest/Runtime/Scripts/Meniscus/Meniscus.cs
2026-03-05 00:14:42 +08:00

251 lines
6.7 KiB
C#

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
/// Renders the meniscus (waterline).
/// </summary>
[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");
/// <summary>
/// Disables rendering without de-allocating.
/// </summary>
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<T>(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);
}
}
}
}
}