升级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

@@ -22,6 +22,7 @@ namespace WaveHarmonic.Crest
public static int s_WaterLineSnappedPosition = Shader.PropertyToID("_Crest_WaterLineSnappedPosition");
public static int s_WaterLineResolution = Shader.PropertyToID("_Crest_WaterLineResolution");
public static int s_WaterLineTexel = Shader.PropertyToID("_Crest_WaterLineTexel");
public static int s_WaterLineFlatWater = Shader.PropertyToID("_Crest_WaterLineFlatWater");
}
RenderTexture _HeightRT;
@@ -50,6 +51,13 @@ namespace WaveHarmonic.Crest
internal void UpdateDisplacedSurfaceData(Camera camera)
{
Helpers.SetGlobalBoolean(ShaderIDs.s_WaterLineFlatWater, IsQuadMesh);
if (IsQuadMesh)
{
return;
}
// World size of the texture. Formula should effectively cover the camera.
var size = 1f + (camera.nearClipPlane * 2f);
@@ -76,6 +84,7 @@ namespace WaveHarmonic.Crest
"_Crest_WaterLine",
ref _HeightRT,
texel: 0.0125f,
maximumResolution: 2048,
out _SurfaceDataParameters
);
@@ -84,9 +93,11 @@ namespace WaveHarmonic.Crest
BindDisplacedSurfaceData(wrapper);
var lod = (int)Builder.PatchType.Interior;
var mpb = _PerCascadeMPB.Current[lod];
var mpb = PerCascadeMPB[lod];
if (_Water.Viewpoint != camera.transform && Vector3.Distance(_Water.Viewpoint.position, camera.transform.position) > 0.01f)
var viewpoint = _Water.Viewpoint;
if (viewpoint == null || (viewpoint != camera.transform && Vector3.Distance(viewpoint.position, camera.transform.position) > 0.01f))
{
foreach (var chunk in _Water.Surface.Chunks)
{
@@ -125,22 +136,32 @@ namespace WaveHarmonic.Crest
Graphics.ExecuteCommandBuffer(commands);
}
internal void UpdateDisplacedSurfaceData(CommandBuffer commands, Bounds bounds, string name, ref RenderTexture target, float texel, out SurfaceDataParameters parameters)
internal void UpdateDisplacedSurfaceData(CommandBuffer commands, Bounds bounds, string name, ref RenderTexture target, float texel, int maximumResolution, out SurfaceDataParameters parameters)
{
var size = bounds.size.XZ();
var position = bounds.center.XZ();
var scale = size;
// TODO: texel needs to be calculates is clamped
// TODO: aspect ratio
var resolution = new Vector2Int
(
// TODO: Floor, Ceil or Round?
Mathf.CeilToInt(size.x / texel),
Mathf.CeilToInt(size.y / texel)
);
var largest = Mathf.Max(resolution.x, resolution.y);
if (largest > maximumResolution)
{
texel = Mathf.Max(size.x, size.y) / maximumResolution;
resolution = new Vector2Int
(
Mathf.CeilToInt(size.x / texel),
Mathf.CeilToInt(size.y / texel)
);
}
// Snapping for spatial stability. Different results, but could not tell which is
// more accurate. At higher resolution, appears negligable anyway.
var snapped = position - new Vector2(Mathf.Repeat(position.x, texel), Mathf.Repeat(position.y, texel));
@@ -153,11 +174,6 @@ namespace WaveHarmonic.Crest
_Texel = texel,
};
if (resolution.x > 2048 || resolution.y > 2048)
{
return;
}
// FIXME: LOD scale less than two has cut off and fall off at edges.
var view = WaterRenderer.CalculateViewMatrixFromSnappedPositionRHS(snapped.XNZ());
var projection = Matrix4x4.Ortho(size.x * -0.5f, size.x * 0.5f, size.y * -0.5f, size.y * 0.5f, 1f, 10000f + 10000f);

View File

@@ -27,6 +27,9 @@ namespace WaveHarmonic.Crest
case nameof(_Layer):
SetLayer((int)previous, _Layer);
break;
case nameof(_MeshType):
_Water.Rebuild();
break;
case nameof(_ChunkTemplate):
// We have to rebuild, as we instantiate entire GO. If we restricted it to just a
// MeshRenderer, then we could just replace those.
@@ -42,6 +45,9 @@ namespace WaveHarmonic.Crest
case nameof(_Debug) + "." + nameof(DebugFields._UniformTiles):
Rebuild();
break;
case nameof(_Debug) + "." + nameof(DebugFields._DrawRendererBounds):
foreach (var chunk in Chunks) chunk._DrawRenderBounds = _Debug._DrawRendererBounds;
break;
}
}
}

View File

@@ -62,18 +62,7 @@ namespace WaveHarmonic.Crest
var hdCamera = context.hdCamera;
var camera = hdCamera.camera;
if (!WaterRenderer.ShouldRender(camera, _Water.Surface.Layer))
{
return;
}
// Our reflections do not need them.
if (camera == WaterReflections.CurrentCamera)
{
return;
}
if (_Water.Surface.Material == null)
if (!_Water._ActiveModules.HasFlag(WaterRenderer.ActiveModules.Surface))
{
return;
}
@@ -114,6 +103,7 @@ namespace WaveHarmonic.Crest
var rld = new RendererListDesc(_ShaderTagID, context.cullingResults, camera)
{
layerMask = 1 << _Water.Surface.Layer,
// Required to set the pass. Use shader to keep WB material overrides.
overrideShader = _Water.Surface.Material.shader,
overrideShaderPassIndex = _Water.Surface.Material.FindPass("Forward"),
renderQueueRange = RenderQueueRange.transparent,

View File

@@ -22,6 +22,7 @@ namespace WaveHarmonic.Crest
}
CommandBuffer _DrawWaterSurfaceBuffer;
MaterialPropertyBlock _QuadMeshMPB;
void OnBeginCameraRenderingLegacy(Camera camera)
{
@@ -40,8 +41,6 @@ namespace WaveHarmonic.Crest
return;
}
camera.depthTextureMode |= DepthTextureMode.Depth;
_DrawWaterSurfaceBuffer ??= new() { name = WaterRenderer.k_DrawWater };
_DrawWaterSurfaceBuffer.Clear();
@@ -111,6 +110,15 @@ namespace WaveHarmonic.Crest
// Always enabled.
commands.SetShaderKeyword("LIGHTPROBE_SH", true);
if (IsQuadMesh)
{
_QuadMeshMPB ??= new();
new PropertyWrapperMPB(_QuadMeshMPB).SetSHCoefficients(Root.position);
Render(camera, commands, Material, pass: 0, mpb: _QuadMeshMPB);
commands.EndSample(k_DrawWaterSurface);
return;
}
UpdateChunkVisibility(camera);
foreach (var chunk in Chunks)
@@ -137,9 +145,7 @@ namespace WaveHarmonic.Crest
chunk.Bind();
}
var mpb = new PropertyWrapperMPB(chunk._MaterialPropertyBlock);
mpb.SetSHCoefficients(chunk.transform.position);
commands.DrawMesh(chunk._Mesh, chunk.transform.localToWorldMatrix, renderer.sharedMaterial, 0, 0, chunk._MaterialPropertyBlock);
commands.DrawRenderer(chunk.Rend, renderer.sharedMaterial, 0, 0);
}
commands.EndSample(k_DrawWaterSurface);

View File

@@ -1,6 +1,7 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
#if d_UnityHDRP
using UnityEngine;
@@ -56,7 +57,12 @@ namespace WaveHarmonic.Crest
return;
}
if (camera.cameraType != CameraType.SceneView || camera != _Water.Viewer)
if (!_Water._CurrentSceneCameraHasMouseHover)
{
return;
}
if (camera.cameraType != CameraType.SceneView || (_Water.IsSingleViewpointMode && camera != _Water.Viewer))
{
return;
}
@@ -68,3 +74,4 @@ namespace WaveHarmonic.Crest
}
#endif // d_UnityHDRP
#endif

View File

@@ -1,6 +1,8 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.Rendering;
@@ -12,7 +14,12 @@ namespace WaveHarmonic.Crest
void OnPreRenderWaterLevelDepthTexture(Camera camera)
{
if (camera.cameraType != CameraType.SceneView || camera != _Water.Viewer)
if (!_Water._CurrentSceneCameraHasMouseHover)
{
return;
}
if (camera.cameraType != CameraType.SceneView || (_Water.IsSingleViewpointMode && camera != _Water.Viewer))
{
return;
}
@@ -38,3 +45,5 @@ namespace WaveHarmonic.Crest
}
}
}
#endif

View File

@@ -1,6 +1,8 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
#if d_UnityURP
using UnityEngine;
@@ -37,7 +39,12 @@ namespace WaveHarmonic.Crest
return;
}
if (camera.cameraType != CameraType.SceneView || camera != _Water.Viewer)
if (camera.cameraType != CameraType.SceneView || (_Water.IsSingleViewpointMode && camera != _Water.Viewer))
{
return;
}
if (!_Water._CurrentSceneCameraHasMouseHover)
{
return;
}
@@ -69,7 +76,10 @@ namespace WaveHarmonic.Crest
});
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
@@ -79,8 +89,10 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}
}
#endif // d_UnityURP
#endif

View File

@@ -1,7 +1,7 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
// FIXME: Broken for BIRP on MacOS. Either platform specific problem or bug in Unity.
#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.Rendering;
@@ -19,8 +19,18 @@ namespace WaveHarmonic.Crest
void ExecuteWaterLevelDepthTexture(Camera camera, CommandBuffer buffer)
{
Helpers.CreateRenderTargetTextureReference(ref _WaterLevelDepthTexture, ref _WaterLevelDepthTarget);
_WaterLevelDepthTexture.name = k_WaterLevelDepthTextureName;
// Currently, only used for painting which means only when mouse is over the view.
if (!_Water._CurrentSceneCameraHasMouseHover)
{
return;
}
if (_WaterLevelDepthTexture == null)
{
_WaterLevelDepthTexture = new(0, 0, 0);
}
WaterLevelDepthTexture.name = k_WaterLevelDepthTextureName;
if (_WaterLevelDepthMaterial == null)
{
@@ -41,11 +51,19 @@ namespace WaveHarmonic.Crest
// Depth texture.
// Always release to handle screen size changes.
_WaterLevelDepthTexture.Release();
WaterLevelDepthTexture.Release();
descriptor.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat;
descriptor.depthBufferBits = 0;
Helpers.SafeCreateRenderTexture(ref _WaterLevelDepthTexture, descriptor);
_WaterLevelDepthTexture.Create();
WaterLevelDepthTexture.descriptor = descriptor;
WaterLevelDepthTexture.Create();
_WaterLevelDepthTarget = new
(
WaterLevelDepthTexture,
mipLevel: 0,
CubemapFace.Unknown,
depthSlice: -1 // Bind all XR slices.
);
// Convert.
Helpers.Blit(buffer, _WaterLevelDepthTarget, Rendering.BIRP.UtilityMaterial, (int)Rendering.BIRP.UtilityPass.Copy);
@@ -82,3 +100,5 @@ namespace WaveHarmonic.Crest
}
}
}
#endif

View File

@@ -24,9 +24,7 @@ namespace WaveHarmonic.Crest
{
_Water = water;
renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
// Copy color happens between "after skybox" and "before transparency".
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
ConfigureInput(ScriptableRenderPassInput.None);
}
public static void Enable(WaterRenderer water)
@@ -52,25 +50,18 @@ namespace WaveHarmonic.Crest
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
if (!WaterRenderer.ShouldRender(camera, Instance._Water.Surface.Layer))
if (!IsTransparent(_Water.Surface.Material))
{
return;
}
// Our reflections do not need them.
if (camera == WaterReflections.CurrentCamera)
{
return;
}
if (Instance._Water.Surface.Material == null)
{
return;
}
if (!IsTransparent(Instance._Water.Surface.Material))
{
return;
// Copy color happens between "after skybox" and "before transparency".
var pass = ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth;
#if d_Crest_SimpleTransparency
pass = ScriptableRenderPassInput.None;
#endif
ConfigureInput(pass);
}
camera.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(Instance);
@@ -113,6 +104,7 @@ namespace WaveHarmonic.Crest
var rld = new RendererListDesc(_ShaderTagID, renderingData.cullResults, cameraData.camera)
{
layerMask = 1 << _Water.Surface.Layer,
// Required to set the pass. Use shader to keep WB material overrides.
overrideShader = _Water.Surface.Material.shader,
overrideShaderPassIndex = 0, // UniversalForward
renderQueueRange = RenderQueueRange.transparent,
@@ -129,7 +121,10 @@ namespace WaveHarmonic.Crest
});
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
@@ -144,6 +139,7 @@ namespace WaveHarmonic.Crest
var rld = new RendererListDesc(_ShaderTagID, renderingData.cullResults, renderingData.cameraData.camera)
{
layerMask = 1 << _Water.Surface.Layer,
// Required to set the pass. Use shader to keep WB material overrides.
overrideShader = _Water.Surface.Material.shader,
overrideShaderPassIndex = 0, // UniversalForward
renderQueueRange = RenderQueueRange.transparent,
@@ -156,6 +152,7 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}
}

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);
}
}
}

View File

@@ -19,11 +19,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/WaterBodies.html")]
public sealed partial class WaterBody : ManagedBehaviour<WaterRenderer>
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[Tooltip("Makes sure this water body is not clipped.\n\nIf clipping is enabled and set to clip everywhere by default, this option will register this water body to ensure its area does not get clipped.")]
[@GenerateAPI(name: "Clipped")]
[SerializeField]

View File

@@ -6,6 +6,7 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -455,11 +456,12 @@ namespace WaveHarmonic.Crest
var order = -lodCount + (patchTypes[i] == PatchType.Interior ? -1 : lodIndex);
var chunk = patch.AddComponent<WaterChunkRenderer>();
var mesh = meshData[(int)patchTypes[i]];
{
var mesh = meshData[(int)patchTypes[i]];
patch.AddComponent<MeshFilter>().sharedMesh = mesh;
var chunk = patch.AddComponent<WaterChunkRenderer>();
chunk._Water = water;
chunk._SortingOrder = order;
chunk._SiblingIndex = s_SiblingIndex++;
@@ -470,6 +472,8 @@ namespace WaveHarmonic.Crest
// be optimally sorted. We statically sort by LOD. Sub-sort is only done for LOD0,
// where interior tiles are placed first. Further sorting must be done dynamically.
tiles.Add(chunk);
chunk._DrawRenderBounds = water.Surface._Debug._DrawRendererBounds;
}
// Sorting order to stop unity drawing it back to front. Make the innermost four tiles draw first,
@@ -532,6 +536,14 @@ namespace WaveHarmonic.Crest
else
patch.transform.localRotation = Quaternion.FromToRotation(from, to);
}
// Pre-rotate bounds.
{
var bounds = mesh.bounds;
bounds = bounds.Rotate(chunk.transform.rotation);
chunk._LocalBounds = bounds;
chunk._LocalScale = chunk.transform.localScale.x;
}
}
}
}

View File

@@ -1,7 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using UnityEngine;
using WaveHarmonic.Crest.Internal;
@@ -9,12 +8,12 @@ namespace WaveHarmonic.Crest
{
interface IReportsHeight
{
bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum);
bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum);
}
interface IReportsDisplacement
{
bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical);
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical);
}
/// <summary>
@@ -24,7 +23,7 @@ namespace WaveHarmonic.Crest
[AddComponentMenu("")]
#endif
[@ExecuteDuringEditMode]
sealed class WaterChunkRenderer : ManagedBehaviour<WaterRenderer>
sealed partial class WaterChunkRenderer : ManagedBehaviour<WaterRenderer>
{
[SerializeField]
internal bool _DrawRenderBounds = false;
@@ -46,7 +45,13 @@ namespace WaveHarmonic.Crest
internal Rect _UnexpandedBoundsXZ = new();
public Rect UnexpandedBoundsXZ => _UnexpandedBoundsXZ;
internal Bounds _LocalBounds;
internal float _LocalScale;
// WaterBody culling.
internal bool _Culled;
// Frustum visibility.
internal bool _Visible;
internal WaterRenderer _Water;
@@ -59,10 +64,8 @@ namespace WaveHarmonic.Crest
// contiguous surface.
internal bool _WaterDataHasBeenBound = true;
int _LodIndex = -1;
internal int _LodIndex = -1;
public static List<IReportsHeight> HeightReporters { get; } = new();
public static List<IReportsDisplacement> DisplacementReporters { get; } = new();
// There is a 1-frame delay with Initialized in edit mode due to setting
// enableInEditMode in EditorApplication.update. This only really affect this
@@ -73,7 +76,6 @@ namespace WaveHarmonic.Crest
_LodIndex = index;
Rend = renderer;
_Mesh = mesh;
_PreviousObjectToWorld = _CurrentObjectToWorld = transform.localToWorldMatrix;
_Transform = transform;
}
@@ -118,8 +120,9 @@ namespace WaveHarmonic.Crest
internal void OnLateUpdate()
{
_PreviousObjectToWorld = _CurrentObjectToWorld;
_PreviousObjectToWorld = _Water.Surface.PreviousObjectToWorld[_SiblingIndex];
_CurrentObjectToWorld = _Transform.localToWorldMatrix;
_Water.Surface.PreviousObjectToWorld[_SiblingIndex] = _CurrentObjectToWorld;
}
internal void RenderMotionVectors(SurfaceRenderer surface, Camera camera)
@@ -144,6 +147,7 @@ namespace WaveHarmonic.Crest
matProps = _MaterialPropertyBlock,
worldBounds = Rend.bounds,
layer = surface.Layer,
renderingLayerMask = (uint)surface.Layer,
receiveShadows = false,
shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off,
lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off,
@@ -158,37 +162,26 @@ namespace WaveHarmonic.Crest
{
s_UpdateMeshBoundsMarker.Begin(this);
var bounds = _Mesh.bounds;
var bounds = _LocalBounds;
if (WaterBody.WaterBodies.Count > 0)
bounds = ComputeBounds(_Transform, bounds);
_UnexpandedBoundsXZ = new(0, 0, bounds.size.x, bounds.size.z)
{
_UnexpandedBoundsXZ = ComputeBoundsXZ(_Transform, bounds);
}
center = bounds.center.XZ(),
};
bounds = ExpandBoundsForDisplacements(_Transform, bounds);
Rend.localBounds = bounds;
Rend.bounds = bounds;
s_UpdateMeshBoundsMarker.End();
}
public static Rect ComputeBoundsXZ(Transform transform, Bounds bounds)
{
// Since chunks are axis-aligned it is safe to rotate the bounds.
var center = transform.rotation * bounds.center * transform.lossyScale.x + transform.position;
var size = transform.rotation * bounds.size * transform.lossyScale.x;
// Rotation can make size negative.
return new(0, 0, Mathf.Abs(size.x), Mathf.Abs(size.z))
{
center = center.XZ(),
};
}
// Used by the water mask system if we need to render the water mask in situations
// where the water itself doesn't need to be rendered or has otherwise been disabled
internal void Bind()
{
_MaterialPropertyBlock = _Water.Surface._PerCascadeMPB.Current[_LodIndex];
_MaterialPropertyBlock = _Water.Surface.PerCascadeMPB[_LodIndex];
new PropertyWrapperMPB(_MaterialPropertyBlock).SetSHCoefficients(_Transform.position);
Rend.SetPropertyBlock(_MaterialPropertyBlock);
_WaterDataHasBeenBound = true;
@@ -197,6 +190,7 @@ namespace WaveHarmonic.Crest
void OnDestroy()
{
Helpers.Destroy(_Mesh);
_Mesh = null;
}
// Called when visible to a camera
@@ -207,6 +201,14 @@ namespace WaveHarmonic.Crest
return;
}
#if CREST_DEBUG
if (_Water._Debug._VisualizeData)
{
Rend.sharedMaterial = _Water.Surface.VisualizeDataMaterial;
MaterialOverridden = true;
}
#endif
if (!MaterialOverridden && Rend.sharedMaterial != _Water.Surface.Material)
{
Rend.sharedMaterial = _Water.Surface.Material;
@@ -217,13 +219,28 @@ namespace WaveHarmonic.Crest
{
Bind();
}
if (_DrawRenderBounds)
{
Rend.bounds.DebugDraw();
}
}
public Bounds ComputeBounds(Transform transform, Bounds bounds)
{
var extents = bounds.extents;
var center = bounds.center;
// Apply transform. Rotation already done.
var scale = _LocalScale * _Water.Scale;
extents.x *= scale;
extents.z *= scale;
center.x *= scale;
center.z *= scale;
center += transform.position;
bounds.center = center;
bounds.extents = extents;
return bounds;
}
// this is called every frame because the bounds are given in world space and depend on the transform scale, which
// can change depending on view altitude
public Bounds ExpandBoundsForDisplacements(Transform transform, Bounds bounds)
@@ -231,99 +248,53 @@ namespace WaveHarmonic.Crest
var extents = bounds.extents;
var center = bounds.center;
var scale = transform.lossyScale;
var rotation = transform.rotation;
var boundsPadding = _Water.MaximumHorizontalDisplacement;
var expandXZ = boundsPadding / scale.x;
var boundsY = _Water.MaximumVerticalDisplacement;
var rect = _UnexpandedBoundsXZ;
// Extend the kinematic bounds slightly to give room for dynamic waves.
if (_Water._DynamicWavesLod.Enabled)
{
boundsY += 5f;
}
// Extend bounds by global waves.
extents.x += expandXZ;
extents.y += boundsY;
extents.z += expandXZ;
// Get XZ bounds. Doing this manually bypasses updating render bounds call.
Rect rect;
{
var p1 = transform.position;
var p2 = rotation * new Vector3(center.x, 0f, center.z);
var s1 = scale;
var s2 = rotation * (extents.XNZ(0f) * 2f);
rect = new(0, 0, Mathf.Abs(s1.x * s2.x), Mathf.Abs(s1.z * s2.z))
{
center = new(p1.x + p2.x, p1.z + p2.z)
};
var settings = _Water.DynamicWavesLod.Settings;
extents.x += settings._HorizontalDisplace;
extents.y += settings._VerticalDisplacementCullingContributions;
extents.z += settings._HorizontalDisplace;
}
// Extend bounds by local waves.
{
var totalHorizontal = 0f;
var totalVertical = 0f;
var horizontal = 0f;
var vertical = 0f;
foreach (var reporter in DisplacementReporters)
foreach (var (key, input) in AnimatedWavesLod.s_Inputs)
{
var horizontal = 0f;
var vertical = 0f;
if (reporter.ReportDisplacement(ref rect, ref horizontal, ref vertical))
{
totalHorizontal += horizontal;
totalVertical += vertical;
}
input.DisplacementReporter?.ReportDisplacement(_Water, ref rect, ref horizontal, ref vertical);
}
boundsPadding = totalHorizontal;
expandXZ = boundsPadding / scale.x;
boundsY = totalVertical;
extents.x += expandXZ;
extents.y += boundsY;
extents.z += expandXZ;
extents.x += horizontal;
extents.y += vertical;
extents.z += horizontal;
}
// Expand and offset bounds by height.
{
var minimumWaterLevelBounds = 0f;
var maximumWaterLevelBounds = 0f;
var minimum = 0f;
var maximum = 0f;
foreach (var reporter in HeightReporters)
foreach (var (key, input) in LevelLod.s_Inputs)
{
var minimum = 0f;
var maximum = 0f;
if (reporter.ReportHeight(ref rect, ref minimum, ref maximum))
{
minimumWaterLevelBounds = Mathf.Max(minimumWaterLevelBounds, Mathf.Abs(Mathf.Min(minimum, _Water.SeaLevel) - _Water.SeaLevel));
maximumWaterLevelBounds = Mathf.Max(maximumWaterLevelBounds, Mathf.Abs(Mathf.Max(maximum, _Water.SeaLevel) - _Water.SeaLevel));
}
input.HeightReporter?.ReportHeight(_Water, ref rect, ref minimum, ref maximum);
}
minimumWaterLevelBounds *= 0.5f;
maximumWaterLevelBounds *= 0.5f;
extents.y += Mathf.Abs((minimum - maximum) * 0.5f);
boundsY = minimumWaterLevelBounds + maximumWaterLevelBounds;
extents.y += boundsY;
bounds.extents = extents;
var offset = maximumWaterLevelBounds - minimumWaterLevelBounds;
var offset = Mathf.Lerp(minimum, maximum, 0.5f);
center.y += offset;
bounds.center = center;
}
return bounds;
}
// Get XZ bounds. Doing this manually bypasses updating render bounds call.
bounds.center = center;
bounds.extents = extents;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void InitStatics()
{
HeightReporters.Clear();
DisplacementReporters.Clear();
return bounds;
}
}

View File

@@ -0,0 +1,117 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
partial class WaterReflections
{
CopyDepthRenderPass _CopyTargetsRenderPass;
void CaptureTargetDepth(ScriptableRenderContext context, Camera camera)
{
if (camera != ReflectionCamera)
{
return;
}
if (!RenderPipelineHelper.IsUniversal)
{
return;
}
#if URP_COMPATIBILITY_MODE
#if !UNITY_6000_4_OR_NEWER
if (GraphicsSettings.GetRenderPipelineSettings<RenderGraphSettings>().enableRenderCompatibilityMode)
{
return;
}
#endif
#endif
_CopyTargetsRenderPass ??= new(this);
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;
renderer.EnqueuePass(_CopyTargetsRenderPass);
}
sealed class CopyDepthRenderPass : ScriptableRenderPass
{
readonly WaterReflections _Renderer;
RTHandle _Wrapper;
class CopyPassData
{
public TextureHandle _Source;
public TextureHandle _Target;
public int _Slice;
}
public CopyDepthRenderPass(WaterReflections renderer)
{
_Renderer = renderer;
renderPassEvent = RenderPassEvent.AfterRendering;
}
public void Dispose()
{
_Wrapper?.Release();
_Wrapper = null;
}
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
{
var resources = frame.Get<UniversalResourceData>();
var source = resources.cameraDepth;
if (!source.IsValid())
{
return;
}
// Create a wrapper. Does not appear to be anything heavy in here.
_Wrapper ??= RTHandles.Alloc(_Renderer._DepthTexture);
_Wrapper.SetRenderTexture(_Renderer._DepthTexture);
var texture = graph.ImportTexture(_Wrapper);
using var builder = graph.AddUnsafePass<CopyPassData>("Crest.CopyDepth", out var data);
data._Source = source;
data._Target = graph.ImportTexture(_Wrapper);
data._Slice = _Renderer._ActiveSlice;
builder.UseTexture(data._Source, AccessFlags.Read);
builder.UseTexture(data._Target, AccessFlags.Write);
// Unity's AddCopyPass cannot handle this it seems.
builder.SetRenderFunc((CopyPassData data, UnsafeGraphContext context) =>
{
RTHandle source = data._Source;
RTHandle target = data._Target;
// Just in case. Planar Reflections will work mostly without it.
if (source.rt == null)
{
return;
}
if (source.rt.graphicsFormat == target.rt.graphicsFormat && source.rt.depthStencilFormat == target.rt.depthStencilFormat)
{
context.cmd.m_WrappedCommandBuffer.CopyTexture(source.rt, 0, 0, target.rt, data._Slice, 0);
}
});
}
}
}
}
#endif
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 79f370973399f49f38382da0814a25cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 205
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -10,6 +10,7 @@ using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering.Universal;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -36,13 +37,8 @@ namespace WaveHarmonic.Crest
/// Renders reflections for water. Currently on planar reflections.
/// </summary>
[Serializable]
public sealed partial class WaterReflections
public sealed partial class WaterReflections : Versioned
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[@Space(10)]
[@Label("Enable")]
@@ -55,7 +51,7 @@ namespace WaveHarmonic.Crest
[@Heading("Capture")]
[Tooltip("What side of the water surface to render planar reflections for.")]
[@GenerateAPI(name: "ReflectionSide")]
[@GenerateAPI(Setter.Custom, name: "ReflectionSide")]
[@DecoratedField, SerializeField]
internal WaterReflectionSide _Mode = WaterReflectionSide.Above;
@@ -69,10 +65,11 @@ namespace WaveHarmonic.Crest
[@Delayed, SerializeField]
int _Resolution = 256;
[Tooltip("Whether to render to the viewer camera only.\n\nWhen disabled, reflections will render for all cameras rendering the water layer, which currently this prevents Refresh Rate from working. Enabling will unlock the Refresh Rate heading.")]
[Tooltip("Overscan amount to capture off-screen content.\n\nRenders the reflections at a larger viewport size to capture off-screen content when the surface reflects off-screen. This avoids a category of artifacts - especially when looking down. This can be expensive, as the value is a multiplier to the capture size.")]
[@Range(1, 2)]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _RenderOnlySingleCamera;
[SerializeField]
float _Overscan = 1.5f;
[@Space(10)]
@@ -88,7 +85,7 @@ namespace WaveHarmonic.Crest
#pragma warning disable 414
[Tooltip("Disables shadows.")]
[@GenerateAPI]
[@GenerateAPI(Setter.Custom)]
[@DecoratedField, SerializeField]
bool _DisableShadows = true;
#pragma warning restore 414
@@ -103,11 +100,6 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
bool _Stencil = false;
[Tooltip("Whether to allow MSAA.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _AllowMSAA = false;
[@Space(10)]
[Tooltip("Overrides global quality settings.")]
@@ -144,11 +136,11 @@ namespace WaveHarmonic.Crest
[@Heading("Refresh Rate")]
[Tooltip("Refresh reflection every x frames (one is every frame)")]
[@Predicated(nameof(_RenderOnlySingleCamera))]
[@Enable(nameof(_RenderOnlySingleCamera))]
[@DecoratedField, SerializeField]
int _RefreshPerFrames = 1;
[@Predicated(nameof(_RenderOnlySingleCamera))]
[@Enable(nameof(_RenderOnlySingleCamera))]
[@DecoratedField, SerializeField]
int _FrameRefreshOffset = 0;
@@ -162,19 +154,34 @@ namespace WaveHarmonic.Crest
bool _UseObliqueMatrix = true;
[Tooltip("Planar relfections using an oblique frustum for better performance.\n\nThis can cause depth issues for TIRs, especially near the surface.")]
[@Predicated(nameof(_UseObliqueMatrix))]
[@Enable(nameof(_UseObliqueMatrix))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _NonObliqueNearSurface;
[Tooltip("If within this distance from the surface, disable the oblique matrix.")]
[@Predicated(nameof(_NonObliqueNearSurface))]
[@Predicated(nameof(_UseObliqueMatrix))]
[@Enable(nameof(_NonObliqueNearSurface))]
[@Enable(nameof(_UseObliqueMatrix))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _NonObliqueNearSurfaceThreshold = 0.05f;
[@Heading("Advanced")]
[Tooltip("Whether to render to the viewer camera only.\n\nWhen disabled, reflections will render for all cameras rendering the water layer, which currently this prevents Refresh Rate from working. Enabling will unlock the Refresh Rate heading.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _RenderOnlySingleCamera;
[Tooltip("Renderer index for the reflection camera.")]
[@Show(RenderPipeline.Universal)]
[@Minimum(0)]
[@GenerateAPI(Setter.Custom)]
[@DecoratedField]
[@SerializeField]
int _RendererIndex;
[@Space(10)]
[@DecoratedField, SerializeField]
@@ -189,66 +196,91 @@ namespace WaveHarmonic.Crest
[Tooltip("Rendering reflections per-camera requires recursive rendering. Check this toggle if experiencing issues. The other downside without it is a one-frame delay.")]
[@DecoratedField, SerializeField]
internal bool _DisableRecursiveRendering;
[Tooltip("Whether to create a context more compatible for planar reflections camera. Try enabling this if you are getting exceptions.")]
[@Show(RenderPipeline.Universal)]
[@DecoratedField]
[SerializeField]
internal bool _ForceCompatibility;
}
/// <summary>
/// What side of the water surface to render planar reflections for.
/// </summary>
public WaterReflectionSide Mode { get => _Mode; set => _Mode = value; }
static class ShaderIDs
{
public static int s_ReflectionTexture = Shader.PropertyToID("_Crest_ReflectionTexture");
public static int s_ReflectionColorTexture = Shader.PropertyToID("_Crest_ReflectionColorTexture");
public static int s_ReflectionDepthTexture = Shader.PropertyToID("_Crest_ReflectionDepthTexture");
public static int s_ReflectionPositionNormal = Shader.PropertyToID("_Crest_ReflectionPositionNormal");
}
public static readonly int s_ReflectionMatrixIVP = Shader.PropertyToID("_Crest_ReflectionMatrixIVP");
public static readonly int s_ReflectionMatrixV = Shader.PropertyToID("_Crest_ReflectionMatrixV");
public static readonly int s_Crest_ReflectionOverscan = Shader.PropertyToID("_Crest_ReflectionOverscan");
// Checked in underwater to filter cameras.
internal static Camera CurrentCamera { get; private set; }
public static readonly int s_PlanarReflectionsApplySmoothness = Shader.PropertyToID("_Crest_PlanarReflectionsApplySmoothness");
}
internal WaterRenderer _Water;
internal UnderwaterRenderer _UnderWater;
RenderTexture _ReflectionTexture;
internal RenderTexture ReflectionTexture => _ReflectionTexture;
bool _ApplySmoothness;
RenderTexture _ColorTexture;
RenderTexture _DepthTexture;
internal RenderTexture ColorTexture => _ColorTexture;
internal RenderTexture DepthTexture => _DepthTexture;
readonly Vector4[] _ReflectionPositionNormal = new Vector4[2];
readonly Matrix4x4[] _ReflectionMatrixIVP = new Matrix4x4[2];
readonly Matrix4x4[] _ReflectionMatrixV = new Matrix4x4[2];
internal int _ActiveSlice;
Camera _CameraViewpoint;
Skybox _CameraViewpointSkybox;
Camera _CameraReflections;
Skybox _CameraReflectionsSkybox;
internal Camera ReflectionCamera => _CameraReflections;
int RefreshPerFrames => _RenderOnlySingleCamera ? _RefreshPerFrames : 1;
long _LastRefreshOnFrame = -1;
internal bool SupportsRecursiveRendering =>
#if !UNITY_6000_0_OR_NEWER
// HDRP cannot recursive render for 2022.
!RenderPipelineHelper.IsHighDefinition &&
#endif
!_Debug._DisableRecursiveRendering;
internal bool SupportsRecursiveRendering => _Water.SupportsRecursiveRendering && !_Debug._DisableRecursiveRendering;
readonly float[] _CullDistances = new float[32];
Texture _CameraDepthTexture;
/// <summary>
/// Invoked when the reflection camera is created.
/// </summary>
public static Action<Camera> OnCameraAdded { get; set; }
bool RequireTemporaryTargets =>
#if UNITY_6000_0_OR_NEWER && d_UnityURP
// As of Unity 6 we can write directly to a slice for URP.
!RenderPipelineHelper.IsUniversal &&
#endif
true;
internal void OnEnable()
{
_CameraViewpoint = _Water.Viewer;
_CameraViewpointSkybox = _CameraViewpoint.GetComponent<Skybox>();
// We initialized here previously to fix the first frame being black, but could not
// replicate anymore.
// This is called also called every frame, but was required here as there was a
// black reflection for a frame without this earlier setup call.
CreateWaterObjects(_CameraViewpoint);
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
RenderPipelineManager.beginCameraRendering -= CaptureTargetDepth;
RenderPipelineManager.beginCameraRendering += CaptureTargetDepth;
#endif
#endif
}
internal void OnDisable()
{
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionTexture, Texture2D.blackTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionColorTexture, Texture2D.blackTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionDepthTexture, Texture2D.blackTexture);
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
RenderPipelineManager.beginCameraRendering -= CaptureTargetDepth;
#endif
#endif
}
internal void OnDestroy()
@@ -259,18 +291,30 @@ namespace WaveHarmonic.Crest
_CameraReflections = null;
}
if (_ReflectionTexture)
if (_ColorTexture)
{
_ReflectionTexture.Release();
Helpers.Destroy(_ReflectionTexture);
_ReflectionTexture = null;
_ColorTexture.Release();
Helpers.Destroy(_ColorTexture);
_ColorTexture = null;
}
if (_DepthTexture)
{
_DepthTexture.Release();
Helpers.Destroy(_DepthTexture);
_DepthTexture = null;
}
}
bool ShouldRender(Camera camera)
internal bool ShouldRender(Camera camera)
{
if (!_Enabled)
{
return false;
}
// If no surface, then do not execute the reflection camera.
if (!WaterRenderer.ShouldRender(camera, _Water.Surface.Layer))
if (!_Water._ActiveModules.HasFlag(WaterRenderer.ActiveModules.Surface))
{
return false;
}
@@ -278,7 +322,7 @@ namespace WaveHarmonic.Crest
// This method could be executed twice: once by the camera rendering the surface,
// and once again by the planar reflection camera. For the latter, we do not want
// to proceed or infinite recursion. For safety.
if (camera == CurrentCamera)
if (camera == _CameraReflections)
{
return false;
}
@@ -294,11 +338,6 @@ namespace WaveHarmonic.Crest
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
if (!ShouldRender(camera))
{
return;
}
if (SupportsRecursiveRendering)
{
// This option only valid for recursive, otherwise, it is always single camera.
@@ -314,18 +353,44 @@ namespace WaveHarmonic.Crest
if (camera == _CameraViewpoint)
{
// TODO: Emit an event instead so WBs can listen.
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionTexture, _ReflectionTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionColorTexture, _ColorTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionDepthTexture, _DepthTexture);
}
}
internal void OnEndReflectionCameraRendering(Camera camera)
{
if (camera == ReflectionCamera)
{
// Appears to be the only reasonable way to get camera depth separately for SRPs.
_CameraDepthTexture = Shader.GetGlobalTexture(Crest.ShaderIDs.Unity.s_CameraDepthTexture);
}
}
internal void OnEndCameraRendering(Camera camera)
{
if (!ShouldRender(camera))
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionColorTexture, Texture2D.blackTexture);
}
internal void LateUpdate()
{
// Check if enabled for at least one material every frame.
_ApplySmoothness = false;
CheckSurfaceMaterial(_Water.Surface.Material);
foreach (var wb in WaterBody.WaterBodies)
{
CheckSurfaceMaterial(wb._Material);
}
if (SupportsRecursiveRendering)
{
return;
}
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionTexture, Texture2D.blackTexture);
// Passing a struct.
LateUpdate(new());
}
internal void LateUpdate(ScriptableRenderContext context)
@@ -376,19 +441,18 @@ namespace WaveHarmonic.Crest
UpdateCameraModes();
ForceDistanceCulling(_FarClipPlane);
_CameraReflections.targetTexture = _ReflectionTexture;
// TODO: Do not do this every frame.
if (_Mode != WaterReflectionSide.Both)
{
Helpers.ClearRenderTexture(_ReflectionTexture, Color.clear, depth: false);
Helpers.ClearRenderTexture(_ColorTexture, Color.clear, depth: true);
Helpers.ClearRenderTexture(_DepthTexture, Color.clear, depth: true);
}
var isActive = _Water.Surface.Root.gameObject.activeSelf;
// We do not want the water plane when rendering planar reflections.
_Water.Surface.Root.gameObject.SetActive(false);
CurrentCamera = _CameraReflections;
// Optionally disable pixel lights for reflection/refraction
var oldPixelLightCount = QualitySettings.pixelLightCount;
if (_DisablePixelLights)
@@ -436,8 +500,7 @@ namespace WaveHarmonic.Crest
_QualitySettingsOverride.Restore();
CurrentCamera = null;
_Water.Surface.Root.gameObject.SetActive(true);
_Water.Surface.Root.gameObject.SetActive(isActive);
// Remember this frame as last refreshed.
_LastRefreshOnFrame = Time.renderedFrameCount;
@@ -446,41 +509,60 @@ namespace WaveHarmonic.Crest
void Render(ScriptableRenderContext context)
{
#if UNITY_6000_0_OR_NEWER && d_UnityURP
_CameraReflections.targetTexture = _ReflectionTexture;
#else
var descriptor = _ReflectionTexture.descriptor;
descriptor.dimension = TextureDimension.Tex2D;
descriptor.volumeDepth = 1;
descriptor.useMipMap = false;
// No need to clear, as camera clears using the skybox.
var target = RenderTexture.GetTemporary(descriptor);
_CameraReflections.targetTexture = target;
#endif
var colorTarget = _ColorTexture;
var depthTarget = _DepthTexture;
if (RequireTemporaryTargets)
{
var descriptor = _ColorTexture.descriptor;
descriptor.dimension = TextureDimension.Tex2D;
descriptor.volumeDepth = 1;
descriptor.useMipMap = false;
// No need to clear, as camera clears using the skybox.
colorTarget = RenderTexture.GetTemporary(descriptor);
if (RenderPipelineHelper.IsLegacy)
{
descriptor = _DepthTexture.descriptor;
descriptor.dimension = TextureDimension.Tex2D;
descriptor.volumeDepth = 1;
descriptor.useMipMap = false;
// No need to clear, as camera clears using the skybox.
depthTarget = RenderTexture.GetTemporary(descriptor);
}
}
if (RenderPipelineHelper.IsLegacy)
{
// Not documented, but does not work for SRPs.
_CameraReflections.SetTargetBuffers(colorTarget.colorBuffer, depthTarget.depthBuffer);
}
else
{
_CameraReflections.targetTexture = colorTarget;
}
if (_Mode != WaterReflectionSide.Below)
{
_ReflectionPositionNormal[0] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, 0.05f, false);
if (_UnderWater._Enabled)
{
// Disable underwater layer. It is the only way to exclude probes.
_CameraReflections.cullingMask = _Layers & ~(1 << _UnderWater.Layer);
}
_ActiveSlice = 0;
RenderCamera(context, _CameraReflections, Vector3.up, false, 0);
#if !(UNITY_6000_0_OR_NEWER && d_UnityURP)
Graphics.CopyTexture(target, 0, 0, _ReflectionTexture, 0, 0);
#endif
CopyTargets(colorTarget, depthTarget, 0);
_ReflectionPositionNormal[0] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, 0.5f / _Resolution, false);
_CameraReflections.ResetProjectionMatrix();
}
if (_Mode != WaterReflectionSide.Above)
{
_ReflectionPositionNormal[1] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, -0.05f, true);
if (_UnderWater._Enabled)
{
// Enable underwater layer.
@@ -489,22 +571,34 @@ namespace WaveHarmonic.Crest
_CameraReflections.depthTextureMode = DepthTextureMode.Depth;
}
_ActiveSlice = 1;
RenderCamera(context, _CameraReflections, Vector3.down, _NonObliqueNearSurface, 1);
#if !(UNITY_6000_0_OR_NEWER && d_UnityURP)
Graphics.CopyTexture(target, 0, 0, _ReflectionTexture, 1, 0);
#endif
CopyTargets(colorTarget, depthTarget, 1);
_ReflectionPositionNormal[1] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, -0.05f, true);
_CameraReflections.ResetProjectionMatrix();
}
#if !(UNITY_6000_0_OR_NEWER && d_UnityURP)
RenderTexture.ReleaseTemporary(target);
if (RequireTemporaryTargets)
{
RenderTexture.ReleaseTemporary(colorTarget);
if (RenderPipelineHelper.IsLegacy) RenderTexture.ReleaseTemporary(depthTarget);
}
#if !d_Crest_DisablePlanarReflectionApplySmoothness
if (_ApplySmoothness)
{
// We are only using mip-maps if applying smoothness/roughness.
_ColorTexture.GenerateMips();
}
#endif
_ReflectionTexture.GenerateMips();
Shader.SetGlobalVectorArray(ShaderIDs.s_ReflectionPositionNormal, _ReflectionPositionNormal);
Shader.SetGlobalMatrixArray(ShaderIDs.s_ReflectionMatrixIVP, _ReflectionMatrixIVP);
Shader.SetGlobalMatrixArray(ShaderIDs.s_ReflectionMatrixV, _ReflectionMatrixV);
}
void RenderCamera(ScriptableRenderContext context, Camera camera, Vector3 planeNormal, bool nonObliqueNearSurface, int slice)
@@ -517,10 +611,9 @@ namespace WaveHarmonic.Crest
var viewpoint = _CameraViewpoint.transform;
if (offset == 0f && viewpoint.position.y == planePosition.y)
{
// Minor offset to prevent "Screen position out of view frustum". Smallest number
// to work with both above and below. Smallest number to work with both above and
// below. Could be BIRP only.
offset = 0.00001f;
// Minor offset to prevent "Screen position out of view frustum". Needs to scale
// with distance from center.
offset = viewpoint.position.magnitude >= 15000f ? 0.01f : 0.001f;
}
}
@@ -539,7 +632,12 @@ namespace WaveHarmonic.Crest
if (_UseObliqueMatrix && (!nonObliqueNearSurface || Mathf.Abs(_CameraViewpoint.transform.position.y - planePosition.y) > _NonObliqueNearSurfaceThreshold))
{
camera.projectionMatrix = _CameraViewpoint.CalculateObliqueMatrix(clipPlane);
var matrix = _CameraViewpoint.CalculateObliqueMatrix(clipPlane);
// Overscan.
var overscan = 1f - (_Overscan - 1f) * 0.5f;
matrix[0, 0] *= overscan;
matrix[1, 1] *= overscan;
camera.projectionMatrix = matrix;
}
// Set custom culling matrix from the current camera
@@ -550,9 +648,12 @@ namespace WaveHarmonic.Crest
camera.transform.eulerAngles = new(-euler.x, euler.y, euler.z);
camera.cullingMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
_ReflectionMatrixV[slice] = camera.worldToCameraMatrix;
_ReflectionMatrixIVP[slice] = (GL.GetGPUProjectionMatrix(camera.projectionMatrix, true) * camera.worldToCameraMatrix).inverse;
if (SupportsRecursiveRendering)
{
Helpers.RenderCamera(camera, context, slice);
Helpers.RenderCamera(camera, context, slice, _Debug._ForceCompatibility);
}
else
{
@@ -560,6 +661,35 @@ namespace WaveHarmonic.Crest
}
}
void CopyTargets(Texture color, Texture depth, int slice)
{
if (RequireTemporaryTargets)
{
Graphics.CopyTexture(color, 0, 0, 0, 0, _Resolution, _Resolution, _ColorTexture, slice, 0, 0, 0);
}
if (!RenderPipelineHelper.IsLegacy)
{
depth = _CameraDepthTexture;
}
if (Rendering.IsRenderGraph)
{
return;
}
// This can change between depth and R32 based on settings.
if (depth != null && depth.graphicsFormat != _DepthTexture.graphicsFormat)
{
RecreateDepth(depth);
}
if (depth != null && depth.width >= _Resolution)
{
Graphics.CopyTexture(depth, 0, 0, 0, 0, _Resolution, _Resolution, _DepthTexture, slice, 0, 0, 0);
}
}
/// <summary>
/// Limit render distance for reflection camera for first 32 layers
/// </summary>
@@ -611,6 +741,7 @@ namespace WaveHarmonic.Crest
{
// Destroy otherwise skybox will not render if empty.
Helpers.Destroy(_CameraViewpointSkybox);
_CameraViewpointSkybox = null;
}
}
@@ -623,54 +754,125 @@ namespace WaveHarmonic.Crest
_CameraReflections.orthographic = _CameraViewpoint.orthographic;
_CameraReflections.fieldOfView = _CameraViewpoint.fieldOfView;
_CameraReflections.orthographicSize = _CameraViewpoint.orthographicSize;
_CameraReflections.allowMSAA = _AllowMSAA;
_CameraReflections.allowMSAA = false;
_CameraReflections.aspect = _CameraViewpoint.aspect;
_CameraReflections.useOcclusionCulling = !_DisableOcclusionCulling && _CameraViewpoint.useOcclusionCulling;
_CameraReflections.depthTextureMode = _CameraViewpoint.depthTextureMode;
// Overscan
{
_CameraReflections.usePhysicalProperties = _Overscan > 1f;
var baseSensor = new Vector2(36f, 24f);
var focal = (baseSensor.y * 0.5f) / Mathf.Tan(_CameraViewpoint.fieldOfView * 0.5f * Mathf.Deg2Rad);
var overscan = 1f - (_Overscan - 1f) * 0.5f;
_CameraReflections.sensorSize = baseSensor / overscan;
_CameraReflections.focalLength = focal;
Shader.SetGlobalFloat(ShaderIDs.s_Crest_ReflectionOverscan, overscan);
}
}
void RecreateDepth(Texture depth)
{
if (_DepthTexture != null && _DepthTexture.IsCreated())
{
_DepthTexture.Release();
_DepthTexture.descriptor = depth.GetDescriptor();
}
else
{
_DepthTexture = new(depth.GetDescriptor());
}
_DepthTexture.name = "_Crest_ReflectionDepth";
_DepthTexture.width = _DepthTexture.height = _Resolution;
_DepthTexture.isPowerOfTwo = true;
_DepthTexture.useMipMap = false;
_DepthTexture.autoGenerateMips = false;
_DepthTexture.filterMode = FilterMode.Point;
_DepthTexture.volumeDepth = 2;
_DepthTexture.dimension = TextureDimension.Tex2DArray;
_DepthTexture.Create();
}
// On-demand create any objects we need for water
void CreateWaterObjects(Camera currentCamera)
{
var format = _HDR ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32;
var stencil = _Stencil ? 24 : 16;
// We cannot exclude stencil for URP, as the depth texture format always has it.
var colorFormat = Rendering.GetDefaultColorFormat(_HDR);
var depthFormat = Rendering.GetDefaultDepthFormat(_Stencil || RenderPipelineHelper.IsUniversal);
// Reflection render texture
if (!_ReflectionTexture || _ReflectionTexture.width != _Resolution || _ReflectionTexture.format != format || _ReflectionTexture.depth != stencil)
if (!_ColorTexture || _ColorTexture.width != _Resolution || _ColorTexture.graphicsFormat != colorFormat || _ColorTexture.depthStencilFormat != depthFormat)
{
if (_ReflectionTexture)
if (_ColorTexture)
{
Helpers.Destroy(_ReflectionTexture);
Helpers.Destroy(_ColorTexture);
Helpers.Destroy(_DepthTexture);
}
Debug.Assert(SystemInfo.SupportsRenderTextureFormat(format), "Crest: The graphics device does not support the render texture format " + format.ToString());
_ReflectionTexture = new(_Resolution, _Resolution, stencil, format)
var descriptor = new RenderTextureDescriptor(_Resolution, _Resolution)
{
name = "_Crest_WaterReflection",
isPowerOfTwo = true,
dimension = TextureDimension.Tex2DArray,
volumeDepth = 2,
depthStencilFormat = depthFormat,
msaaSamples = 1,
useMipMap = false,
};
_ColorTexture = new(descriptor)
{
name = "_Crest_ReflectionColor",
graphicsFormat = colorFormat,
isPowerOfTwo = true,
#if !d_Crest_DisablePlanarReflectionApplySmoothness
useMipMap = true,
#endif
autoGenerateMips = false,
filterMode = FilterMode.Trilinear,
};
_ReflectionTexture.Create();
_ColorTexture.Create();
_DepthTexture = new(descriptor)
{
name = "_Crest_ReflectionDepth",
graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None,
isPowerOfTwo = true,
useMipMap = false,
autoGenerateMips = false,
filterMode = FilterMode.Point,
};
if (RenderPipelineHelper.IsHighDefinition)
{
_DepthTexture.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat;
_DepthTexture.depthStencilFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None;
}
_DepthTexture.Create();
}
var create = _CameraReflections == null;
// Camera for reflection
if (!_CameraReflections)
if (create)
{
var go = new GameObject("_Crest_WaterReflectionCamera");
go.transform.SetParent(_Water.Container.transform, worldPositionStays: true);
_CameraReflections = go.AddComponent<Camera>();
_CameraReflections.enabled = false;
_CameraReflections.cullingMask = _Layers;
_CameraReflections.cameraType = CameraType.Reflection;
_CameraReflections.backgroundColor = Color.clear;
if (RenderPipelineHelper.IsLegacy)
{
#pragma warning disable IDE0079
#pragma warning disable CS0618 // Type or member is obsolete
_CameraReflections.gameObject.AddComponent<FlareLayer>();
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore IDE0079
}
#if d_UnityHDRP
@@ -690,15 +892,34 @@ namespace WaveHarmonic.Crest
if (RenderPipelineHelper.IsUniversal)
{
var additionalCameraData = _CameraReflections.gameObject.AddComponent<UniversalAdditionalCameraData>();
additionalCameraData.renderShadows = !_DisableShadows;
additionalCameraData.requiresColorTexture = false;
additionalCameraData.requiresDepthTexture = false;
additionalCameraData.requiresDepthTexture = true;
}
#endif
OnCameraAdded?.Invoke(_CameraReflections);
_UpdateCamera = true;
}
_CameraReflections.gameObject.hideFlags = _Debug._ShowHiddenObjects ? HideFlags.DontSave : HideFlags.HideAndDontSave;
if (_UpdateCamera)
{
_CameraReflections.gameObject.hideFlags = _Debug._ShowHiddenObjects ? HideFlags.DontSave : HideFlags.HideAndDontSave;
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
var additionalCameraData = _CameraReflections.GetUniversalAdditionalCameraData();
additionalCameraData.SetRenderer(_RendererIndex);
additionalCameraData.renderShadows = !_DisableShadows; // Does not appear to work!
additionalCameraData.requiresColorTexture = _Mode != WaterReflectionSide.Above; // or incur assertions
}
#endif
_UpdateCamera = false;
}
if (create)
{
OnCameraAdded?.Invoke(_CameraReflections);
}
}
// Given position/normal of the plane, calculates plane in camera space.
@@ -856,14 +1077,34 @@ namespace WaveHarmonic.Crest
positionWS.Dispose();
}
normal = normal.normalized;
if (flipped)
{
normal = -normal;
}
else if (position.y == 0f)
{
// Sample anywhere if pointing downwards.
position.y = 1f;
}
return new(position.x, position.y, normal.x, normal.y);
}
void CheckSurfaceMaterial(Material material)
{
if (material == null)
{
return;
}
if (!_ApplySmoothness)
{
_ApplySmoothness = material.GetBoolean(ShaderIDs.s_PlanarReflectionsApplySmoothness);
}
}
void SetEnabled(bool previous, bool current)
{
if (previous == current) return;
@@ -871,6 +1112,26 @@ namespace WaveHarmonic.Crest
if (_Enabled) OnEnable(); else OnDisable();
}
bool _UpdateCamera;
void SetReflectionSide(WaterReflectionSide previous, WaterReflectionSide current)
{
if (previous == current) return;
_UpdateCamera = true;
}
void SetDisableShadows(bool previous, bool current)
{
if (previous == current) return;
_UpdateCamera = true;
}
void SetRendererIndex(int previous, int current)
{
if (previous == current) return;
_UpdateCamera = true;
}
#if UNITY_EDITOR
[@OnChange]
void OnChange(string propertyPath, object previousValue)
@@ -880,8 +1141,37 @@ namespace WaveHarmonic.Crest
case nameof(_Enabled):
SetEnabled((bool)previousValue, _Enabled);
break;
case nameof(_Debug) + "." + nameof(DebugFields._ShowHiddenObjects):
_UpdateCamera = true;
break;
case nameof(_Mode):
SetReflectionSide((WaterReflectionSide)previousValue, _Mode);
break;
case nameof(_DisableShadows):
SetDisableShadows((bool)previousValue, _DisableShadows);
break;
case nameof(_RendererIndex):
SetRendererIndex((int)previousValue, _RendererIndex);
break;
}
}
#endif
}
partial class WaterReflections
{
// MSAA would require separate textures to resolve to. Not worth the expense.
[HideInInspector]
[Obsolete("MSAA for the planar reflection camera is no longer supported. This setting will be ignored.")]
[Tooltip("Whether to allow MSAA.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _AllowMSAA;
/// <summary>
/// What side of the water surface to render planar reflections for.
/// </summary>
[Obsolete("Please use ReflectionSide instead.")]
public WaterReflectionSide Mode { get => _Mode; set => _Mode = value; }
}
}