还原水插件

This commit is contained in:
2026-03-05 00:14:42 +08:00
parent 0de35591e7
commit e82f2ea6b7
270 changed files with 2773 additions and 12445 deletions

View File

@@ -5,19 +5,10 @@ using System.Buffers;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
using WaveHarmonic.Crest.Utility;
namespace WaveHarmonic.Crest
{
enum WaterMeshType
{
[Tooltip("Chunks implemented as a clip-map.")]
Chunks,
[Tooltip("A single quad.\n\nOptimal for demanding platforms like mobile. Displacement will only contribute to normals.")]
Quad,
}
/// <summary>
/// Renders the water surface.
/// </summary>
@@ -36,12 +27,6 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
internal bool _Enabled = true;
[@Label("Mesh")]
[Tooltip("The meshing solution for the water surface.")]
[@DecoratedField]
[@SerializeField]
WaterMeshType _MeshType;
[Tooltip("The water chunk renderers will have this layer.")]
[@Layer]
[@GenerateAPI]
@@ -89,12 +74,6 @@ namespace WaveHarmonic.Crest
[@Heading("Advanced")]
[Tooltip("Rules to exclude cameras from surface rendering.\n\nThese are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.")]
[@DecoratedField]
[@GenerateAPI]
[SerializeField]
internal WaterCameraExclusion _CameraExclusions = WaterCameraExclusion.Hidden | WaterCameraExclusion.Reflection;
[Tooltip("How to handle self-intersections of the water surface.\n\nThey can be caused by choppy waves which can cause a flipped underwater effect. When not using the portals/volumes, this fix is only applied when within 2 metres of the water surface. Automatic will disable the fix if portals/volumes are used which is the recommend setting.")]
[@DecoratedField, SerializeField]
internal SurfaceSelfIntersectionFixMode _SurfaceSelfIntersectionFixMode = SurfaceSelfIntersectionFixMode.Automatic;
@@ -143,8 +122,8 @@ namespace WaveHarmonic.Crest
// Level of Detail
//
readonly MaterialPropertyBlock[] _PerCascadeMPB = new MaterialPropertyBlock[Lod.k_MaximumSlices];
internal MaterialPropertyBlock[] PerCascadeMPB { get; private set; }
// Extra frame is for motion vectors.
internal BufferedData<MaterialPropertyBlock[]> _PerCascadeMPB = new(2, () => new MaterialPropertyBlock[Lod.k_MaximumSlices]);
// We are computing these values to be optimal based on the base mesh vertex density.
float _LodAlphaBlackPointFade;
@@ -176,7 +155,6 @@ namespace WaveHarmonic.Crest
internal Material _MotionVectorMaterial;
internal Material AboveOrBelowSurfaceMaterial => _VolumeMaterial == null ? _Material : _VolumeMaterial;
internal bool IsQuadMesh => _MeshType == WaterMeshType.Quad;
//
@@ -222,23 +200,8 @@ namespace WaveHarmonic.Crest
public static readonly int s_ChunkGeometryGridWidth = Shader.PropertyToID("_Crest_ChunkGeometryGridWidth");
public static readonly int s_ChunkFarNormalsWeight = Shader.PropertyToID("_Crest_ChunkFarNormalsWeight");
public static readonly int s_ChunkNormalScrollSpeed = Shader.PropertyToID("_Crest_ChunkNormalScrollSpeed");
public static readonly int s_NormalMapParameters = Shader.PropertyToID("_Crest_NormalMapParameters");
}
bool _ForceRenderingOff;
internal bool ForceRenderingOff
{
get => _ForceRenderingOff;
set
{
_ForceRenderingOff = value;
if (_Enabled)
{
Root.gameObject.SetActive(!_ForceRenderingOff && !IsQuadMesh);
}
}
public static readonly int s_ChunkMeshScaleAlphaSource = Shader.PropertyToID("_Crest_ChunkMeshScaleAlphaSource");
public static readonly int s_ChunkGeometryGridWidthSource = Shader.PropertyToID("_Crest_ChunkGeometryGridWidthSource");
}
internal void Initialize()
@@ -248,10 +211,19 @@ namespace WaveHarmonic.Crest
Root.position = _Water.Position;
Root.localScale = new(_Water.Scale, 1f, _Water.Scale);
// Populate MPBs with defaults. Protects against null exceptions etc.
PerCascadeMPB = _PerCascadeMPB;
NormalMapParameters = _NormalMapParameters;
InitializeProperties();
// Populate MPBs with defaults.
for (var index = 0; index < _Water.LodLevels; index++)
{
for (var frame = 0; frame < 2; frame++)
{
var mpb = new MaterialPropertyBlock();
mpb.SetInteger(Lod.ShaderIDs.s_LodIndex, index);
mpb.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, 1f);
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, 0f);
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlphaSource, 0f);
_PerCascadeMPB.Previous(frame)[index] = mpb;
}
}
// Resolution is 4 tiles across.
var baseMeshDensity = _Water.LodResolution * 0.25f / _Water._GeometryDownSampleFactor;
@@ -325,11 +297,6 @@ namespace WaveHarmonic.Crest
return;
}
if (IsQuadMesh)
{
return;
}
GeometryUtility.CalculateFrustumPlanes(camera, _CameraFrustumPlanes);
foreach (var chunk in Chunks)
@@ -380,43 +347,20 @@ namespace WaveHarmonic.Crest
_Rebuild = false;
}
internal void DisableChunks()
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
foreach (var chunk in Chunks)
if (!WaterRenderer.ShouldRender(camera, Layer))
{
if (chunk.Rend != null) chunk.Rend.enabled = false;
}
}
internal bool ShouldRender(Camera camera)
{
if (!_Enabled)
{
return false;
}
if (!WaterRenderer.ShouldRender(camera, Layer, _CameraExclusions))
{
return false;
return;
}
// Our planar reflection camera must never render the surface.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return false;
return;
}
if (Material == null)
{
return false;
}
return true;
}
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
if (!ShouldRender(camera))
{
return;
}
@@ -424,7 +368,7 @@ namespace WaveHarmonic.Crest
WritePerCameraMaterialParameters(camera);
// Motion Vectors.
if (ShouldRenderMotionVectors(camera) && QueueMotionVectors)
if (ShouldRenderMotionVectors(camera) && _QueueMotionVectors)
{
UpdateChunkVisibility(camera);
@@ -434,7 +378,6 @@ namespace WaveHarmonic.Crest
}
}
#pragma warning disable format
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
@@ -450,26 +393,19 @@ namespace WaveHarmonic.Crest
{
OnBeginCameraRenderingLegacy(camera);
}
#pragma warning restore format
}
internal void OnEndCameraRendering(Camera camera)
{
_DoneChunkVisibility = false;
// Restore in case exclusion culling ran.
foreach (var chunk in Chunks)
{
if (chunk.Rend != null && !chunk._Culled) chunk.Rend.enabled = true;
}
if (!WaterRenderer.ShouldRender(camera, Layer))
{
return;
}
// Our planar reflection camera must never render the surface.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}
@@ -480,20 +416,6 @@ namespace WaveHarmonic.Crest
}
}
void InitializeProperties()
{
System.Array.Fill(NormalMapParameters, new Vector4(0, 0, 1, 0));
// Populate MPBs with defaults.
for (var index = 0; index < PerCascadeMPB.Length; index++)
{
var block = new MaterialPropertyBlock();
block.SetInteger(Lod.ShaderIDs.s_LodIndex, index);
block.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, 1f);
PerCascadeMPB[index] = block;
}
}
void WritePerCameraMaterialParameters(Camera camera)
{
if (Material == null)
@@ -520,16 +442,14 @@ namespace WaveHarmonic.Crest
var value = _SurfaceSelfIntersectionFixMode switch
{
SurfaceSelfIntersectionFixMode.On =>
!_Water._PerCameraHeightReady
? ForceFacing.None
: height < -2f
height < -2f
? ForceFacing.BelowWater
: height > 2f
? ForceFacing.AboveWater
: ForceFacing.None,
// Skip for portals as it is possible to see both sides of the surface at any position.
SurfaceSelfIntersectionFixMode.Automatic =>
_Water.Portaled || !_Water._PerCameraHeightReady
_Water.Portaled
? ForceFacing.None
: height < -2f
? ForceFacing.BelowWater
@@ -551,27 +471,12 @@ namespace WaveHarmonic.Crest
Rebuild();
}
if (_ForceRenderingOff)
{
return;
}
LoadCameraData(_Water.CurrentCamera);
Root.position = _Water.Position;
Root.localScale = new(_Water.Scale, 1f, _Water.Scale);
Root.gameObject.SetActive(!IsQuadMesh);
Material.SetKeyword(new(Material.shader, "_CREST_CUSTOM_MESH"), IsQuadMesh);
_PerCascadeMPB.Flip();
WritePerCascadeInstanceData();
if (IsQuadMesh)
{
LateUpdateQuadMesh();
return;
}
foreach (var chunk in Chunks)
{
chunk.UpdateMeshBounds(_Water, this);
@@ -601,48 +506,60 @@ namespace WaveHarmonic.Crest
{
var levels = _Water.LodLevels;
var texel = _Water.LodResolution * 0.25f / _Water._GeometryDownSampleFactor;
var mpbsCurrent = _PerCascadeMPB.Current;
var mpbsPrevious = _PerCascadeMPB.Previous(1);
// LOD 0
{
var mpb = mpbsCurrent[0];
if (_Water.WriteMotionVectors)
{
// NOTE: it may be more optimal to store in an array than fetching from MPB.
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlphaSource, mpbsPrevious[0].GetFloat(ShaderIDs.s_ChunkMeshScaleAlpha));
}
// Blend LOD 0 shape in/out to avoid pop, if scale could increase.
PerCascadeMPB[0].SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, _Water.ScaleCouldIncrease ? _Water.ViewerAltitudeLevelAlpha : 0f);
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, _Water.ScaleCouldIncrease ? _Water.ViewerAltitudeLevelAlpha : 0f);
}
// LOD N
{
var mpb = mpbsCurrent[levels - 1];
// Blend furthest normals scale in/out to avoid pop, if scale could reduce.
var weight = _Water.ScaleCouldDecrease ? _Water.ViewerAltitudeLevelAlpha : 1f;
PerCascadeMPB[levels - 1].SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, weight);
NormalMapParameters[levels - 1] = new(0, 0, weight, 0);
mpb.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, _Water.ScaleCouldDecrease ? _Water.ViewerAltitudeLevelAlpha : 1f);
}
for (var index = 0; index < levels; index++)
{
var mpb = PerCascadeMPB[index];
var mpbCurrent = mpbsCurrent[index];
var mpbPrevious = mpbsPrevious[index];
// geometry data
// compute grid size of geometry. take the long way to get there - make sure we land exactly on a power of two
// and not inherit any of the lossy-ness from lossyScale.
var scale = _Water.CascadeData.Current[index].x;
var scale = _Water._CascadeData.Current[index].x;
var width = scale / texel;
mpb.SetFloat(ShaderIDs.s_ChunkGeometryGridWidth, width);
if (_Water.WriteMotionVectors)
{
// NOTE: it may be more optimal to store in an array than fetching from MPB.
mpbPrevious.SetFloat(ShaderIDs.s_ChunkGeometryGridWidthSource, mpbCurrent.GetFloat(ShaderIDs.s_ChunkGeometryGridWidth));
}
mpbCurrent.SetFloat(ShaderIDs.s_ChunkGeometryGridWidth, width);
var mul = 1.875f; // fudge 1
var pow = 1.4f; // fudge 2
var texelWidth = width / _Water._GeometryDownSampleFactor;
var speed = new Vector2
mpbCurrent.SetVector(ShaderIDs.s_ChunkNormalScrollSpeed, new
(
Mathf.Pow(Mathf.Log(1f + 2f * texelWidth) * mul, pow),
Mathf.Pow(Mathf.Log(1f + 4f * texelWidth) * mul, pow)
);
mpb.SetVector(ShaderIDs.s_ChunkNormalScrollSpeed, speed);
var normals = NormalMapParameters[index];
normals.x = speed.x;
normals.y = speed.y;
NormalMapParameters[index] = normals;
Mathf.Pow(Mathf.Log(1f + 4f * texelWidth) * mul, pow),
0,
0
));
}
}
@@ -732,7 +649,7 @@ namespace WaveHarmonic.Crest
_CanSkipCulling = WaterBody.WaterBodies.Count == 0;
}
internal void Render(Camera camera, CommandBuffer buffer, Material material = null, int pass = 0, bool culled = false, MaterialPropertyBlock mpb = null)
internal void Render(Camera camera, CommandBuffer buffer, Material material = null, int pass = 0, bool culled = false)
{
var noMaterial = material == null;
@@ -741,12 +658,6 @@ namespace WaveHarmonic.Crest
return;
}
if (IsQuadMesh)
{
buffer.DrawMesh(Helpers.QuadMesh, Matrix4x4.TRS(Root.position, Quaternion.Euler(90f, 0, 0), new(10000, 10000, 1)), noMaterial ? Material : material, 0, shaderPass: pass, mpb);
return;
}
UpdateChunkVisibility(camera);
// Spends approx 0.2-0.3ms here on 2018 Dell XPS 15.
@@ -841,7 +752,6 @@ namespace WaveHarmonic.Crest
partial class SurfaceRenderer
{
bool _QueueMotionVectors;
bool QueueMotionVectors => _QueueMotionVectors && !IsQuadMesh;
bool ShouldRenderMotionVectors(Camera camera)
{
@@ -907,7 +817,7 @@ namespace WaveHarmonic.Crest
void UpdateMotionVectorsMaterial(Material surface, ref Material motion)
{
if (!QueueMotionVectors)
if (!_QueueMotionVectors)
{
return;
}
@@ -945,80 +855,4 @@ namespace WaveHarmonic.Crest
motion.SetFloat(ShaderIDs.s_BuiltShadowCasterZTest, 1); // ZTest Never
}
}
partial class SurfaceRenderer
{
internal Dictionary<Camera, MaterialPropertyBlock[]> _PerCameraPerCascadeMPB = new();
internal Dictionary<Camera, Vector4[]> _PerCameraNormalMapParameters = new();
void LoadCameraData(Camera camera)
{
if (!_Water.SeparateViewpoint)
{
return;
}
if (!_PerCameraPerCascadeMPB.ContainsKey(camera))
{
PerCascadeMPB = new MaterialPropertyBlock[Lod.k_MaximumSlices];
_PerCameraPerCascadeMPB.Add(camera, PerCascadeMPB);
NormalMapParameters = new Vector4[Lod.k_MaximumSlices];
_PerCameraNormalMapParameters.Add(camera, NormalMapParameters);
InitializeProperties();
}
else
{
PerCascadeMPB = _PerCameraPerCascadeMPB[camera];
NormalMapParameters = _PerCameraNormalMapParameters[camera];
}
}
internal void RemoveCameraData(Camera camera)
{
if (_PerCameraPerCascadeMPB.ContainsKey(camera))
{
_PerCameraPerCascadeMPB.Remove(camera);
_PerCameraNormalMapParameters.Remove(camera);
}
#if UNITY_EDITOR
RemoveCameraDataLDT(camera);
#endif
}
}
// Quad
partial class SurfaceRenderer
{
readonly Vector4[] _NormalMapParameters = new Vector4[Lod.k_MaximumSlices];
Vector4[] NormalMapParameters { get; set; }
void LateUpdateQuadMesh()
{
Shader.SetGlobalVectorArray(ShaderIDs.s_NormalMapParameters, NormalMapParameters);
var scale = new Vector3(10000 * _Water.Scale, 10000 * _Water.Scale, 1);
var bounds = Helpers.QuadMesh.bounds;
bounds.Expand(scale);
Graphics.RenderMesh
(
new()
{
motionVectorMode = MotionVectorGenerationMode.Camera,
material = Material,
worldBounds = Root.TransformBounds(bounds),
layer = Layer,
shadowCastingMode = CastShadows ? ShadowCastingMode.On : ShadowCastingMode.Off,
lightProbeUsage = LightProbeUsage.Off,
reflectionProbeUsage = ReflectionProbeUsage.BlendProbesAndSkybox,
renderingLayerMask = (uint)Layer,
},
Helpers.QuadMesh,
submeshIndex: 0,
Matrix4x4.TRS(Root.position, Quaternion.Euler(90f, 0, 0), scale)
);
UpdateMaterial(_Material, ref _MotionVectorMaterial);
}
}
}