升级6.4.升级水,升级天气

This commit is contained in:
2026-04-05 00:26:54 +08:00
parent 63bc9b5536
commit 5f7cbfb713
635 changed files with 34718 additions and 22567 deletions

View File

@@ -5,21 +5,25 @@ using System.Buffers;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Utility;
using WaveHarmonic.Crest.Internal;
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>
[System.Serializable]
public sealed partial class SurfaceRenderer
public sealed partial class SurfaceRenderer : Versioned
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[@Space(10)]
[Tooltip("Whether the underwater effect is enabled.\n\nAllocates/releases resources if state has changed.")]
@@ -33,6 +37,27 @@ namespace WaveHarmonic.Crest
[SerializeField]
internal int _Layer = 4; // Water
[@Space(10)]
[@Label("Mesh")]
[Tooltip("The meshing solution for the water surface.")]
[@DecoratedField]
[@SerializeField]
WaterMeshType _MeshType;
[Tooltip("Template for water chunks as a prefab.\n\nThe only requirements are that the prefab must contain a MeshRenderer at the root and not a MeshFilter or WaterChunkRenderer. MR values will be overwritten where necessary and the prefabs are linked in edit mode.")]
[@PrefabField(title: "Create Chunk Prefab", name: "Water Chunk")]
[SerializeField]
internal GameObject _ChunkTemplate;
[Tooltip("Whether to support using the surface material with other renderers.\n\nAlso requires enabling Custom Mesh on the material.")]
[@GenerateAPI]
[@DecoratedField]
[@SerializeField]
bool _SupportCustomRenderers = true;
[@Space(10)]
[Tooltip("Material to use for the water surface.")]
[@AttachMaterialEditor(order: 0)]
[@MaterialField("Crest/Water", name: "Water", title: "Create Water Material")]
@@ -47,11 +72,6 @@ namespace WaveHarmonic.Crest
[SerializeField]
internal Material _VolumeMaterial = null;
[Tooltip("Template for water chunks as a prefab.\n\nThe only requirements are that the prefab must contain a MeshRenderer at the root and not a MeshFilter or WaterChunkRenderer. MR values will be overwritten where necessary and the prefabs are linked in edit mode.")]
[@PrefabField(title: "Create Chunk Prefab", name: "Water Chunk")]
[SerializeField]
internal GameObject _ChunkTemplate;
[@Space(10)]
[Tooltip("Have the water surface cast shadows for albedo (both foam and custom).")]
@@ -74,12 +94,18 @@ 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;
[Tooltip("Whether to allow sorting using the render queue.\n\nIf you need to change the minor part of the render queue (eg +100), then enable this option. As a side effect, it will also disable the front-to-back rendering optimization for Crest. This option does not affect changing the major part of the render queue (eg AlphaTest, Transparent), as that is always allowed.\n\nRender queue sorting is required for some third-party integrations.")]
[@Predicated(RenderPipeline.HighDefinition, inverted: true, hide: true)]
[@Hide(RenderPipeline.HighDefinition)]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _AllowRenderQueueSorting;
@@ -108,6 +134,13 @@ namespace WaveHarmonic.Crest
[Tooltip("Disable generating a wide strip of triangles at the outer edge to extend water to edge of view frustum.")]
[@DecoratedField, SerializeField]
public bool _DisableSkirt;
#if !CREST_DEBUG
[HideInInspector]
#endif
[Tooltip("Toggle the Draw Renderer Bounds on each chunk.")]
[@DecoratedField, SerializeField]
public bool _DrawRendererBounds;
}
const string k_DrawWaterSurface = "Surface";
@@ -116,14 +149,15 @@ namespace WaveHarmonic.Crest
internal Transform Root { get; private set; }
internal List<WaterChunkRenderer> Chunks { get; } = new();
internal bool _Rebuild;
Renderer _RendererTemplate;
//
// Level of Detail
//
// Extra frame is for motion vectors.
internal BufferedData<MaterialPropertyBlock[]> _PerCascadeMPB = new(2, () => new MaterialPropertyBlock[Lod.k_MaximumSlices]);
readonly MaterialPropertyBlock[] _PerCascadeMPB = new MaterialPropertyBlock[Lod.k_MaximumSlices];
internal MaterialPropertyBlock[] PerCascadeMPB { get; private set; }
// We are computing these values to be optimal based on the base mesh vertex density.
float _LodAlphaBlackPointFade;
@@ -155,6 +189,7 @@ namespace WaveHarmonic.Crest
internal Material _MotionVectorMaterial;
internal Material AboveOrBelowSurfaceMaterial => _VolumeMaterial == null ? _Material : _VolumeMaterial;
internal bool IsQuadMesh => _MeshType == WaterMeshType.Quad;
//
@@ -200,30 +235,63 @@ 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_ChunkMeshScaleAlphaSource = Shader.PropertyToID("_Crest_ChunkMeshScaleAlphaSource");
public static readonly int s_ChunkGeometryGridWidthSource = Shader.PropertyToID("_Crest_ChunkGeometryGridWidthSource");
public static readonly int s_NormalMapParameters = Shader.PropertyToID("_Crest_NormalMapParameters");
// Visualizer
public static readonly int s_DataType = Shader.PropertyToID("_Crest_DataType");
public static readonly int s_Exposure = Shader.PropertyToID("_Crest_Exposure");
public static readonly int s_Range = Shader.PropertyToID("_Crest_Range");
public static readonly int s_Saturate = Shader.PropertyToID("_Crest_Saturate");
}
bool _ForceRenderingOff;
internal bool ForceRenderingOff
{
get => _ForceRenderingOff;
set
{
_ForceRenderingOff = value;
if (_Enabled)
{
Root.gameObject.SetActive(!_ForceRenderingOff && !IsQuadMesh);
}
}
}
Material _VisualizeDataMaterial;
internal Material VisualizeDataMaterial
{
get
{
if (_VisualizeDataMaterial == null)
{
_VisualizeDataMaterial = new(Shader.Find("Hidden/Crest/Debug/Visualize Data"));
}
return _VisualizeDataMaterial;
}
}
internal void Initialize()
{
Root = Builder.GenerateMesh(_Water, this, Chunks, _Water.LodResolution, _Water._GeometryDownSampleFactor, _Water.LodLevels);
if (_ChunkTemplate != null)
{
_RendererTemplate = _ChunkTemplate.GetComponent<Renderer>();
}
Root.position = _Water.Position;
Root.localScale = new(_Water.Scale, 1f, _Water.Scale);
// 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;
}
}
// Populate MPBs with defaults. Protects against null exceptions etc.
PerCascadeMPB = _PerCascadeMPB;
NormalMapParameters = _NormalMapParameters;
_PreviousObjectToWorld = new Matrix4x4[Chunks.Count];
PreviousObjectToWorld = _PreviousObjectToWorld;
InitializeProperties();
// Resolution is 4 tiles across.
var baseMeshDensity = _Water.LodResolution * 0.25f / _Water._GeometryDownSampleFactor;
@@ -260,12 +328,18 @@ namespace WaveHarmonic.Crest
for (var i = 0; i < _Meshes?.Length; i++)
{
Helpers.Destroy(_Meshes[i]);
_Meshes[i] = null;
}
Chunks.Clear();
CoreUtils.Destroy(_MotionVectorMaterial);
CoreUtils.Destroy(_DisplacedMaterial);
// Clear camera data.
_PerCameraPerCascadeMPB.Clear();
_PerCameraNormalMapParameters.Clear();
_PerCameraPreviousObjectToWorld.Clear();
if (Root != null)
{
CoreUtils.Destroy(Root.gameObject);
@@ -297,6 +371,11 @@ namespace WaveHarmonic.Crest
return;
}
if (IsQuadMesh)
{
return;
}
GeometryUtility.CalculateFrustumPlanes(camera, _CameraFrustumPlanes);
foreach (var chunk in Chunks)
@@ -347,28 +426,38 @@ namespace WaveHarmonic.Crest
_Rebuild = false;
}
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
internal bool ShouldRender(Camera camera)
{
if (!WaterRenderer.ShouldRender(camera, Layer))
if (!_Enabled)
{
return;
return false;
}
if (!WaterRenderer.ShouldRender(camera, Layer, _CameraExclusions))
{
return false;
}
// Our planar reflection camera must never render the surface.
if (camera == WaterReflections.CurrentCamera)
if (camera == _Water.Reflections.ReflectionCamera)
{
return;
return false;
}
if (Material == null)
{
return;
return false;
}
return true;
}
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
WritePerCameraMaterialParameters(camera);
// Motion Vectors.
if (ShouldRenderMotionVectors(camera) && _QueueMotionVectors)
if (ShouldRenderMotionVectors(camera) && QueueMotionVectors)
{
UpdateChunkVisibility(camera);
@@ -378,6 +467,7 @@ namespace WaveHarmonic.Crest
}
}
#pragma warning disable format
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
@@ -393,29 +483,38 @@ namespace WaveHarmonic.Crest
{
OnBeginCameraRenderingLegacy(camera);
}
#pragma warning restore format
}
internal void OnEndCameraRendering(Camera camera)
{
_DoneChunkVisibility = false;
if (!WaterRenderer.ShouldRender(camera, Layer))
{
return;
}
// Our planar reflection camera must never render the surface.
if (camera == WaterReflections.CurrentCamera)
{
return;
}
if (RenderPipelineHelper.IsLegacy)
{
OnEndCameraRenderingLegacy(camera);
}
}
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;
}
foreach (var chunk in Chunks)
{
PreviousObjectToWorld[chunk._SiblingIndex] = chunk.transform.localToWorldMatrix;
}
}
void WritePerCameraMaterialParameters(Camera camera)
{
if (Material == null)
@@ -424,7 +523,7 @@ namespace WaveHarmonic.Crest
}
// If no underwater, then no need for underwater surface.
if (!_Water.Underwater.Enabled)
if (!_Water._ActiveModules.HasFlag(WaterRenderer.ActiveModules.Volume) && _SurfaceSelfIntersectionFixMode == SurfaceSelfIntersectionFixMode.Automatic)
{
Shader.SetGlobalInteger(ShaderIDs.s_ForceUnderwater, (int)ForceFacing.AboveWater);
return;
@@ -442,14 +541,16 @@ namespace WaveHarmonic.Crest
var value = _SurfaceSelfIntersectionFixMode switch
{
SurfaceSelfIntersectionFixMode.On =>
height < -2f
!_Water._PerCameraHeightReady
? ForceFacing.None
: 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._ActiveModules.HasFlag(WaterRenderer.ActiveModules.Portal) || !_Water._PerCameraHeightReady
? ForceFacing.None
: height < -2f
? ForceFacing.BelowWater
@@ -471,12 +572,55 @@ namespace WaveHarmonic.Crest
Rebuild();
}
if (_ForceRenderingOff)
{
return;
}
LoadCameraData(_Water.CurrentCamera);
Root.position = _Water.Position;
Root.localScale = new(_Water.Scale, 1f, _Water.Scale);
_PerCascadeMPB.Flip();
Root.gameObject.SetActive(!IsQuadMesh);
#if CREST_DEBUG
if (_Water._Debug._VisualizeData)
{
var material = _Water.Surface.VisualizeDataMaterial;
material.SetInteger(ShaderIDs.s_DataType, (int)_Water._Debug._VisualizeDataType);
material.SetBoolean(ShaderIDs.s_Saturate, _Water._Debug._VisualizeDataSaturate);
material.SetFloat(ShaderIDs.s_Exposure, _Water._Debug._VisualizeDataExposure);
material.SetFloat(ShaderIDs.s_Range, _Water._Debug._VisualizeDataRange);
}
#endif
if (Material != null)
{
// Cannot cache or receive the following on shader recompilation:
// Local keyword … comes from a different shader.
var keyword = Material.shader.keywordSpace.FindKeyword("_CREST_CUSTOM_MESH");
if (keyword.isValid)
{
Material.SetKeyword(keyword, IsQuadMesh);
}
}
WritePerCascadeInstanceData();
if (IsQuadMesh || _SupportCustomRenderers)
{
// For simple and custom meshes.
Shader.SetGlobalVectorArray(ShaderIDs.s_NormalMapParameters, NormalMapParameters);
}
if (IsQuadMesh)
{
LateUpdateQuadMesh();
return;
}
foreach (var chunk in Chunks)
{
chunk.UpdateMeshBounds(_Water, this);
@@ -506,60 +650,48 @@ 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.
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, _Water.ScaleCouldIncrease ? _Water.ViewerAltitudeLevelAlpha : 0f);
PerCascadeMPB[0].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.
mpb.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, _Water.ScaleCouldDecrease ? _Water.ViewerAltitudeLevelAlpha : 1f);
var weight = _Water.ScaleCouldDecrease ? _Water.ViewerAltitudeLevelAlpha : 1f;
PerCascadeMPB[levels - 1].SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, weight);
NormalMapParameters[levels - 1] = new(0, 0, weight, 0);
}
for (var index = 0; index < levels; index++)
{
var mpbCurrent = mpbsCurrent[index];
var mpbPrevious = mpbsPrevious[index];
var mpb = PerCascadeMPB[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;
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);
mpb.SetFloat(ShaderIDs.s_ChunkGeometryGridWidth, width);
var mul = 1.875f; // fudge 1
var pow = 1.4f; // fudge 2
var texelWidth = width / _Water._GeometryDownSampleFactor;
mpbCurrent.SetVector(ShaderIDs.s_ChunkNormalScrollSpeed, new
var speed = new Vector2
(
Mathf.Pow(Mathf.Log(1f + 2f * texelWidth) * mul, pow),
Mathf.Pow(Mathf.Log(1f + 4f * texelWidth) * mul, pow),
0,
0
));
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;
}
}
@@ -649,7 +781,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)
internal void Render(Camera camera, CommandBuffer buffer, Material material = null, int pass = 0, bool culled = false, MaterialPropertyBlock mpb = null)
{
var noMaterial = material == null;
@@ -658,6 +790,12 @@ 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.
@@ -751,7 +889,11 @@ namespace WaveHarmonic.Crest
// Motion Vectors
partial class SurfaceRenderer
{
// Mostly to update the motion vector material only once.
bool _QueueMotionVectors;
bool QueueMotionVectors => _QueueMotionVectors && !IsQuadMesh;
Matrix4x4[] _PreviousObjectToWorld;
internal Matrix4x4[] PreviousObjectToWorld { get; private set; }
bool ShouldRenderMotionVectors(Camera camera)
{
@@ -799,7 +941,7 @@ namespace WaveHarmonic.Crest
{
var camera = cameras[i];
if (!WaterRenderer.ShouldRender(camera, _Layer))
if (!ShouldRender(camera))
{
continue;
}
@@ -817,7 +959,7 @@ namespace WaveHarmonic.Crest
void UpdateMotionVectorsMaterial(Material surface, ref Material motion)
{
if (!_QueueMotionVectors)
if (!QueueMotionVectors)
{
return;
}
@@ -855,4 +997,79 @@ 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();
internal Dictionary<Camera, Matrix4x4[]> _PerCameraPreviousObjectToWorld = new();
void LoadCameraData(Camera camera)
{
if (_Water.IsSingleViewpointMode)
{
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);
PreviousObjectToWorld = new Matrix4x4[Chunks.Count];
_PerCameraPreviousObjectToWorld.Add(camera, PreviousObjectToWorld);
InitializeProperties();
}
else
{
PerCascadeMPB = _PerCameraPerCascadeMPB[camera];
NormalMapParameters = _PerCameraNormalMapParameters[camera];
PreviousObjectToWorld = _PerCameraPreviousObjectToWorld[camera];
}
}
internal void RemoveCameraData(Camera camera)
{
if (_PerCameraPerCascadeMPB.ContainsKey(camera))
{
_PerCameraPerCascadeMPB.Remove(camera);
_PerCameraNormalMapParameters.Remove(camera);
_PerCameraPreviousObjectToWorld.Remove(camera);
}
}
}
// Quad
partial class SurfaceRenderer
{
readonly Vector4[] _NormalMapParameters = new Vector4[Lod.k_MaximumSlices];
Vector4[] NormalMapParameters { get; set; }
void LateUpdateQuadMesh()
{
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 = _RendererTemplate != null ? _RendererTemplate.renderingLayerMask : 1,
},
Helpers.QuadMesh,
submeshIndex: 0,
Matrix4x4.TRS(Root.position, Quaternion.Euler(90f, 0, 0), scale)
);
UpdateMaterial(_Material, ref _MotionVectorMaterial);
}
}
}