using System;
using Unity.Mathematics;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.Experimental.Rendering;
namespace UnityEngine.Rendering.HighDefinition
{
public partial class WaterSurface
{
/// Fade modes
public enum FadeMode
{
/// No fading
None,
/// Automatic fading
Automatic,
/// Custom fading
Custom
}
#region Swell/Agitation
///
///
///
[Range(WaterConsts.k_SwellMinPatchSize, WaterConsts.k_SwellMaxPatchSize)]
public float repetitionSize = 500.0f;
///
///
///
public float largeOrientationValue = 0.0f;
///
///
///
[Range(0, WaterConsts.k_SwellMaximumWindSpeed)]
public float largeWindSpeed = 30.0f;
///
///
///
[Range(0, 1.0f)]
public float largeChaos = 0.8f;
///
///
///
[Range(0, 1.0f)]
public float largeBand0Multiplier = 1.0f;
///
///
///
public FadeMode largeBand0FadeMode = FadeMode.Automatic;
///
///
///
public float largeBand0FadeStart = 1500.0f;
///
///
///
public float largeBand0FadeDistance = 3000.0f;
///
///
///
[Range(0, 1.0f)]
public float largeBand1Multiplier = 1.0f;
///
///
///
public FadeMode largeBand1FadeMode = FadeMode.Automatic;
///
///
///
public float largeBand1FadeStart = 300.0f;
///
///
///
public float largeBand1FadeDistance = 800.0f;
#endregion
#region Ripples
///
/// When enabled, the water system allows you to simulate and render a ripples simulation for finer details.
///
public bool ripples = true;
///
///
///
public WaterPropertyOverrideMode ripplesMotionMode = WaterPropertyOverrideMode.Inherit;
///
///
///
public float ripplesOrientationValue = 0.0f;
///
///
///
[Range(0, WaterConsts.k_RipplesMaxWindSpeed)]
public float ripplesWindSpeed = 8.0f;
///
///
///
[Range(0.0f, 1.0f)]
public float ripplesChaos = 0.8f;
///
///
///
public FadeMode ripplesFadeMode = FadeMode.Automatic;
///
///
///
public float ripplesFadeStart = 50.0f;
///
///
///
public float ripplesFadeDistance = 200.0f;
#endregion
/// Used to sync different water surfaces simulation time, for example across network.
public DateTime simulationStart
{
get
{
float timeScale = Time.timeScale * timeMultiplier;
if (timeScale == 0.0f) timeScale = 1.0f;
return DateTime.Now - TimeSpan.FromSeconds(simulation != null ? simulation.simulationTime / timeScale : 0.0f);
}
set
{
TimeSpan elapsed = DateTime.Now - value;
if (simulation != null)
simulation.simulationTime = (float)elapsed.TotalSeconds * Time.timeScale * timeMultiplier;
}
}
/// Current simulation time in seconds.
public float simulationTime
{
get
{
return simulation?.simulationTime ?? 0.0f;
}
set
{
if (simulation != null)
simulation.simulationTime = value;
}
}
internal int numActiveBands => WaterSystem.EvaluateBandCount(surfaceType, ripples);
// Optional CPU simulation data
internal AsyncTextureSynchronizer displacementBufferSynchronizer = new AsyncTextureSynchronizer(GraphicsFormat.R16G16B16A16_SFloat);
// Internal simulation data
internal WaterSimulationResources simulation = null;
internal void CheckResources(int bandResolution, bool gpuReadback)
{
int bandCount = numActiveBands;
bool foam = HasSimulationFoam();
// If the previously existing resources are not valid, just release them
if (simulation != null && !simulation.ValidResources(bandResolution, bandCount, foam))
{
simulation.ReleaseSimulationResources();
simulation = null;
}
// Will we need to enable the CPU simulation?
bool cpuSimulationActive = scriptInteractions && !gpuReadback;
// If the resources have not been allocated for this water surface, allocate them
if (simulation == null)
{
// Create the simulation resources
simulation = new WaterSimulationResources();
// Initialize for the allocation
simulation.InitializeSimulationResources(bandResolution, bandCount, foam);
// GPU buffers should always be allocated
simulation.AllocateSimulationBuffersGPU();
// CPU buffers should be allocated only if required
if (cpuSimulationActive)
simulation.AllocateSimulationBuffersCPU();
CreatePropertyBlock();
}
// If the resources are no longer used, release them
if (!cpuSimulationActive && simulation.cpuBuffers != null)
{
simulation.ReleaseSimulationBuffersCPU();
simulation.cpuSpectrumValid = false;
}
// One more case that we need check here is that if the CPU became required
if (cpuSimulationActive && simulation.cpuBuffers == null)
{
simulation.AllocateSimulationBuffersCPU();
simulation.cpuSpectrumValid = false;
}
// Evaluate the spectrum parameters
WaterSpectrumParameters spectrum = EvaluateSpectrumParams(surfaceType);
// If the spectrum defining data changed, we need to invalidate the buffers
if (simulation.spectrum != spectrum)
{
// Mark the spectrums as invalid and assign the new one
simulation.gpuSpectrumValid = false;
simulation.cpuSpectrumValid = false;
simulation.spectrum = spectrum;
}
// TODO: Handle properly the change of resolution to be able to not do this every frame.
simulation.cpuSpectrumValid = false;
// Re-evaluate the simulation data
simulation.rendering = EvaluateRenderingParams(surfaceType);
}
internal static void EvaluateWaterSurfaceMatrices(bool quad, bool customMesh, Vector3 position, Quaternion rotation, ref float4x4 waterToWorld, ref float4x4 worldToWater, ref float4x4 worldToWater2)
{
// Evaluate the right transform based on the type of surface
if (customMesh)
{
waterToWorld = worldToWater = Matrix4x4.identity;
worldToWater2 = math.inverse(Matrix4x4.TRS(position, rotation, Vector3.one));
}
else
{
waterToWorld = Matrix4x4.TRS(position, rotation, Vector3.one);
worldToWater = worldToWater2 = math.inverse(waterToWorld);
}
}
// Function that evaluates the spectrum data for the ocean/sea/lake case
WaterSpectrumParameters EvaluateSpectrumParams(WaterSurfaceType type)
{
WaterSpectrumParameters spectrum = new WaterSpectrumParameters();
switch (type)
{
case WaterSurfaceType.OceanSeaLake:
{
// Compute the patch size of the biggest swell band
float swellPatchSize = repetitionSize;
// We need to evaluate the radio between the first and second band
float swellSecondBandRatio = WaterSystem.EvaluateSwellSecondPatchSize(swellPatchSize);
// Set the patch groups
spectrum.patchGroup.x = 0;
spectrum.patchGroup.y = 0;
spectrum.patchGroup.z = ripplesMotionMode == WaterPropertyOverrideMode.Inherit ? 0 : 1;
// Deduce the patch sizes from the max patch size for the swell
spectrum.patchSizes.x = swellPatchSize;
spectrum.patchSizes.y = swellPatchSize / swellSecondBandRatio;
spectrum.patchSizes.z = WaterConsts.k_RipplesBandSize;
// Keep track of the directionality is used
float largeAngle = WaterSystem.NormalizeAngle(largeOrientationValue);
float ripplesAngle = WaterSystem.NormalizeAngle(ripplesOrientationValue);
spectrum.patchOrientation.x = largeAngle;
spectrum.patchOrientation.y = largeAngle;
spectrum.patchOrientation.z = ripplesMotionMode == WaterPropertyOverrideMode.Inherit ? largeAngle : ripplesAngle;
// Set the patch groups
spectrum.groupOrientation.x = spectrum.patchOrientation.x;
spectrum.groupOrientation.y = spectrum.patchOrientation.z;
// Wind speed per band
spectrum.patchWindSpeed.x = largeWindSpeed * WaterConsts.k_KilometerPerHourToMeterPerSecond;
spectrum.patchWindSpeed.y = largeWindSpeed * WaterConsts.k_KilometerPerHourToMeterPerSecond;
spectrum.patchWindSpeed.z = ripplesWindSpeed * WaterConsts.k_KilometerPerHourToMeterPerSecond;
// Direction dampener
spectrum.patchWindDirDampener.x = largeChaos;
spectrum.patchWindDirDampener.y = largeChaos;
spectrum.patchWindDirDampener.z = ripplesChaos;
}
break;
case WaterSurfaceType.River:
{
// Set the patch groups
spectrum.patchGroup.x = 0;
spectrum.patchGroup.y = ripplesMotionMode == WaterPropertyOverrideMode.Inherit ? 0 : 1;
// Deduce the patch sizes from the max patch size for the swell
spectrum.patchSizes.x = repetitionSize;
spectrum.patchSizes.y = WaterConsts.k_RipplesBandSize;
// Wind speed per band
spectrum.patchWindSpeed.x = largeWindSpeed * WaterConsts.k_KilometerPerHourToMeterPerSecond;
spectrum.patchWindSpeed.y = ripplesWindSpeed * WaterConsts.k_KilometerPerHourToMeterPerSecond;
// Keep track of the directionality is used
float largeAngle = WaterSystem.NormalizeAngle(largeOrientationValue);
float ripplesAngle = WaterSystem.NormalizeAngle(ripplesOrientationValue);
spectrum.patchOrientation.x = largeAngle;
spectrum.patchOrientation.y = ripplesMotionMode == WaterPropertyOverrideMode.Inherit ? largeAngle : ripplesAngle;
// Set the patch groups
spectrum.groupOrientation.x = spectrum.patchOrientation.x;
spectrum.groupOrientation.y = spectrum.patchOrientation.y;
// Direction dampener
spectrum.patchWindDirDampener.x = largeChaos;
spectrum.patchWindDirDampener.y = ripplesChaos;
}
break;
case WaterSurfaceType.Pool:
{
// Set the patch groups
spectrum.patchGroup.x = 1;
// Deduce the patch sizes from the max patch size for the swell
spectrum.patchSizes.x = WaterConsts.k_RipplesBandSize;
// Wind speed per band
spectrum.patchWindSpeed.x = ripplesWindSpeed * WaterConsts.k_KilometerPerHourToMeterPerSecond;
// Keep track of the directionality is used
spectrum.patchOrientation.x = WaterSystem.NormalizeAngle(ripplesOrientationValue);
// Set the patch groups
spectrum.groupOrientation.x = spectrum.patchOrientation.x;
spectrum.groupOrientation.y = spectrum.patchOrientation.x;
// Direction dampener
spectrum.patchWindDirDampener.x = ripplesChaos;
}
break;
}
return spectrum;
}
void ComputeDistanceFade(ref WaterRenderingParameters rendering, int index, FadeMode mode, float customStart, float customDistance)
{
if (mode == FadeMode.None)
{
rendering.patchFadeA[index] = 0.0f;
rendering.patchFadeB[index] = 1.0f;
rendering.maxFadeDistance = float.MaxValue;
}
else
{
if (mode == FadeMode.Automatic)
{
// For an ocean with repetition size 500, will give following results
// band0: start = 1500 / distance = 6000
// band1: start = 339 / distance = 1357
// ripples: start = 67 / distance = 271
float factor = (index == 0) ? 3 : (index == 1 ? 5 : simulation.spectrum.patchSizes[1] / WaterConsts.k_RipplesBandSize);
customStart = factor * simulation.spectrum.patchSizes[index];
customDistance = 4 * customStart;
}
rendering.patchFadeA[index] = -1.0f / Mathf.Max(customDistance, 0.001f);
rendering.patchFadeB[index] = 1.0f - customStart * rendering.patchFadeA[index];
rendering.maxFadeDistance = Mathf.Max(rendering.maxFadeDistance, customStart + customDistance);
}
}
WaterRenderingParameters EvaluateRenderingParams(WaterSurfaceType type)
{
WaterRenderingParameters rendering = new WaterRenderingParameters();
// Propagate the simulation time to the rendering structure
rendering.simulationTime = simulation.simulationTime;
rendering.maxFadeDistance = 0.0f;
switch (type)
{
case WaterSurfaceType.OceanSeaLake:
{
// Deduce the patch sizes from the max patch size for the swell
rendering.patchAmplitudeMultiplier.x = largeBand0Multiplier;
rendering.patchAmplitudeMultiplier.y = largeBand1Multiplier;
rendering.patchAmplitudeMultiplier.z = 1.0f;
// Keep track of the directionality is used
float swellCurrentSpeed = largeCurrentSpeedValue * WaterConsts.k_KilometerPerHourToMeterPerSecond;
rendering.patchCurrentSpeed.x = swellCurrentSpeed;
rendering.patchCurrentSpeed.y = swellCurrentSpeed;
rendering.patchCurrentSpeed.z = ripplesMotionMode == WaterPropertyOverrideMode.Inherit ? swellCurrentSpeed : ripplesCurrentSpeedValue * WaterConsts.k_KilometerPerHourToMeterPerSecond;
// Fade parameters
ComputeDistanceFade(ref rendering, 0, largeBand0FadeMode, largeBand0FadeStart, largeBand0FadeDistance);
ComputeDistanceFade(ref rendering, 1, largeBand1FadeMode, largeBand1FadeStart, largeBand1FadeDistance);
if (ripples) ComputeDistanceFade(ref rendering, 2, ripplesFadeMode, ripplesFadeStart, ripplesFadeDistance);
}
break;
case WaterSurfaceType.River:
{
// Deduce the patch sizes from the max patch size for the swell
rendering.patchAmplitudeMultiplier.x = largeBand0Multiplier;
rendering.patchAmplitudeMultiplier.y = ripples ? 1.0f : 0.0f;
// Keep track of the directionality is used
rendering.patchCurrentSpeed.x = largeCurrentSpeedValue * WaterConsts.k_KilometerPerHourToMeterPerSecond;
rendering.patchCurrentSpeed.y = ripplesMotionMode == WaterPropertyOverrideMode.Inherit ? rendering.patchCurrentSpeed.x : ripplesCurrentSpeedValue * WaterConsts.k_KilometerPerHourToMeterPerSecond;
// Fade parameters
ComputeDistanceFade(ref rendering, 0, largeBand0FadeMode, largeBand0FadeStart, largeBand0FadeDistance);
if (ripples) ComputeDistanceFade(ref rendering, 1, ripplesFadeMode, ripplesFadeStart, ripplesFadeDistance);
}
break;
case WaterSurfaceType.Pool:
{
// Deduce the patch sizes from the max patch size for the swell
rendering.patchAmplitudeMultiplier.x = 1.0f;
rendering.patchAmplitudeMultiplier.y = 0.0f;
// Keep track of the directionality is used
rendering.patchCurrentSpeed.x = ripplesCurrentSpeedValue * WaterConsts.k_KilometerPerHourToMeterPerSecond;
// Fade parameters
ComputeDistanceFade(ref rendering, 0, ripplesFadeMode, ripplesFadeStart, ripplesFadeDistance);
}
break;
}
// Make sure the matrices are evaluated
EvaluateWaterSurfaceMatrices(IsQuad(), IsCustomMesh(), transform.position, transform.rotation, ref rendering.waterToWorldMatrix, ref rendering.worldToWaterMatrix, ref rendering.worldToWaterMatrixCustom);
return rendering;
}
internal void ReleaseSimulationResources()
{
displacementBufferSynchronizer.ReleaseATSResources();
// Make sure to release the resources if they have been created (before HDRP destroys them)
if (simulation != null)
simulation.ReleaseSimulationResources();
simulation = null;
}
}
}