Files
2026-03-04 10:03:45 +08:00

600 lines
17 KiB
C#

using System;
using UltimateWater.Internal;
using UnityEngine;
namespace UltimateWater
{
public sealed class WavesRendererFFT
{
[Serializable]
public sealed class Data
{
[Tooltip("Determines if GPU partial derivatives or Fast Fourier Transform (high quality) should be used to compute normal map (Recommended: on). Works only if displacement map rendering is enabled.")]
public bool HighQualityNormalMaps = true;
[Tooltip("Check this option, if your water is flat or game crashes instantly on a DX11 GPU (in editor or build). Compute shaders are very fast, so use this as a last resort.")]
public bool ForcePixelShader;
[Tooltip("Fixes crest artifacts during storms, but lowers overall quality. Enabled by default when used with additive water volumes as it is actually needed and disabled in all other cases.")]
public FlattenMode FlattenMode;
[Tooltip("Sea state will be cached in the specified frame count for extra performance, if LoopLength on WindWaves is set to a value greater than zero.")]
public int CachedFrameCount = 180;
}
public enum SpectrumType
{
Phillips = 0,
Unified = 1
}
[Flags]
public enum MapType
{
Displacement = 1,
Normal = 2
}
public enum FlattenMode
{
Auto = 0,
ForcedOn = 1,
ForcedOff = 2
}
private Shader _FFTShader;
private Shader _FFTUtilitiesShader;
private readonly Water _Water;
private readonly WindWaves _WindWaves;
private readonly Data _Data;
private readonly RenderTexture[][] _NormalMapsCache;
private readonly RenderTexture[][] _DisplacementMapsCache;
private readonly bool[] _IsCachedFrameValid;
private RenderTexture[] _NormalMaps;
private RenderTexture[] _DisplacementMaps;
private RenderTexturesCache _SingleTargetCache;
private RenderTexturesCache _DoubleTargetCache;
private GpuFFT _HeightFFT;
private GpuFFT _NormalFFT;
private GpuFFT _DisplacementFFT;
private Material _FFTUtilitiesMaterial;
private ComputeShader _Dx11FFT;
private MapType _RenderedMaps;
private bool _FinalHighQualityNormalMaps;
private bool _CopyModeDirty;
private int _WaveMapsFrame;
private Water _LastCopyFrom;
private static readonly Vector4[] _Offsets = new Vector4[4]
{
new Vector4(0f, 0f, 0f, 0f),
new Vector4(0.5f, 0f, 0f, 0f),
new Vector4(0f, 0.5f, 0f, 0f),
new Vector4(0.5f, 0.5f, 0f, 0f)
};
private static readonly Vector4[] _OffsetsDual = new Vector4[2]
{
new Vector4(0f, 0f, 0.5f, 0f),
new Vector4(0f, 0.5f, 0.5f, 0.5f)
};
public MapType RenderedMaps
{
get
{
return _RenderedMaps;
}
set
{
_RenderedMaps = value;
if (Enabled && Application.isPlaying)
{
Dispose(total: false);
ValidateResources();
}
}
}
public bool Enabled { get; private set; }
public RenderTexture[] NormalMaps => _NormalMaps;
public event Action ResourcesChanged;
public void OnCopyModeChanged()
{
_CopyModeDirty = true;
if (_LastCopyFrom != null)
{
_LastCopyFrom.WindWaves.WaterWavesFFT.ResourcesChanged -= ValidateResources;
}
if (_WindWaves.CopyFrom != null)
{
_WindWaves.CopyFrom.WindWaves.WaterWavesFFT.ResourcesChanged += ValidateResources;
}
_LastCopyFrom = _WindWaves.CopyFrom;
Dispose(total: false);
}
public Texture GetDisplacementMap(int index)
{
if (_DisplacementMaps == null)
{
return null;
}
return _DisplacementMaps[index];
}
public Texture GetNormalMap(int index)
{
return _NormalMaps[index];
}
public void OnWaterRender(Camera camera)
{
if (!(_FFTUtilitiesMaterial == null))
{
ValidateWaveMaps();
}
}
public WavesRendererFFT(Water water, WindWaves windWaves, Data data)
{
_Water = water;
_WindWaves = windWaves;
_Data = data;
if (windWaves.LoopDuration != 0f)
{
_NormalMapsCache = new RenderTexture[data.CachedFrameCount][];
_DisplacementMapsCache = new RenderTexture[data.CachedFrameCount][];
_IsCachedFrameValid = new bool[data.CachedFrameCount];
water.ProfilesManager.Changed.AddListener(OnProfilesChanged);
}
Validate();
}
private void RenderSpectra(float time, out Texture heightSpectrum, out Texture normalSpectrum, out Texture displacementSpectrum)
{
if (_RenderedMaps == MapType.Normal)
{
heightSpectrum = null;
displacementSpectrum = null;
normalSpectrum = _WindWaves.SpectrumResolver.RenderNormalsSpectrumAt(time);
}
else if ((_RenderedMaps & MapType.Normal) == 0 || !_FinalHighQualityNormalMaps)
{
normalSpectrum = null;
_WindWaves.SpectrumResolver.RenderDisplacementsSpectraAt(time, out heightSpectrum, out displacementSpectrum);
}
else
{
_WindWaves.SpectrumResolver.RenderCompleteSpectraAt(time, out heightSpectrum, out normalSpectrum, out displacementSpectrum);
}
}
private void RenderMaps(float time, RenderTexture[] displacementMaps, RenderTexture[] normalMaps)
{
RenderSpectra(time, out var heightSpectrum, out var normalSpectrum, out var displacementSpectrum);
if ((_RenderedMaps & MapType.Displacement) != 0)
{
TemporaryRenderTexture temporary = _SingleTargetCache.GetTemporary();
TemporaryRenderTexture temporary2 = _DoubleTargetCache.GetTemporary();
_HeightFFT.ComputeFFT(heightSpectrum, temporary);
_DisplacementFFT.ComputeFFT(displacementSpectrum, temporary2);
_FFTUtilitiesMaterial.SetTexture(ShaderVariables.HeightTex, (RenderTexture)temporary);
_FFTUtilitiesMaterial.SetTexture(ShaderVariables.DisplacementTex, (RenderTexture)temporary2);
_FFTUtilitiesMaterial.SetFloat(ShaderVariables.HorizontalDisplacementScale, _Water.Materials.HorizontalDisplacementScale);
for (int i = 0; i < 4; i++)
{
_FFTUtilitiesMaterial.SetFloat(ShaderVariables.JacobianScale, _Water.Materials.HorizontalDisplacementScale * 0.1f * (float)displacementMaps[i].width / _WindWaves.TileSizes[i]);
_FFTUtilitiesMaterial.SetVector(ShaderVariables.Offset, _Offsets[i]);
Graphics.Blit(null, displacementMaps[i], _FFTUtilitiesMaterial, 1);
}
temporary.Dispose();
temporary2.Dispose();
}
if ((_RenderedMaps & MapType.Normal) == 0)
{
return;
}
if (!_FinalHighQualityNormalMaps)
{
for (int j = 0; j < 2; j++)
{
int finalResolution = _WindWaves.FinalResolution;
_FFTUtilitiesMaterial.SetFloat("_Intensity1", 0.58f * (float)finalResolution / _WindWaves.TileSizes[j * 2]);
_FFTUtilitiesMaterial.SetFloat("_Intensity2", 0.58f * (float)finalResolution / _WindWaves.TileSizes[j * 2 + 1]);
_FFTUtilitiesMaterial.SetTexture("_MainTex", displacementMaps[j << 1]);
_FFTUtilitiesMaterial.SetTexture("_SecondTex", displacementMaps[(j << 1) + 1]);
_FFTUtilitiesMaterial.SetFloat("_MainTex_Texel_Size", 1f / (float)displacementMaps[j << 1].width);
Graphics.Blit(null, normalMaps[j], _FFTUtilitiesMaterial, 0);
}
}
else
{
TemporaryRenderTexture temporary3 = _DoubleTargetCache.GetTemporary();
_NormalFFT.ComputeFFT(normalSpectrum, temporary3);
for (int k = 0; k < 2; k++)
{
_FFTUtilitiesMaterial.SetVector(ShaderVariables.Offset, _OffsetsDual[k]);
Graphics.Blit((RenderTexture)temporary3, normalMaps[k], _FFTUtilitiesMaterial, 3);
}
temporary3.Dispose();
}
}
private void RetrieveCachedFrame(int frameIndex, out RenderTexture[] displacementMaps, out RenderTexture[] normalMaps)
{
float time = (float)frameIndex / (float)_Data.CachedFrameCount * _WindWaves.LoopDuration;
if (!_IsCachedFrameValid[frameIndex])
{
_IsCachedFrameValid[frameIndex] = true;
if ((_RenderedMaps & MapType.Displacement) != 0 && _DisplacementMapsCache[frameIndex] == null)
{
CreateRenderTextures(ref _DisplacementMapsCache[frameIndex], "[UWS] WavesRendererFFT - Water Displacement Map", RenderTextureFormat.ARGBHalf, 4, mipMaps: true);
}
if ((_RenderedMaps & MapType.Normal) != 0 && _NormalMapsCache[frameIndex] == null)
{
CreateRenderTextures(ref _NormalMapsCache[frameIndex], "[UWS] WavesRendererFFT - Water Normal Map", RenderTextureFormat.ARGBHalf, 2, mipMaps: true);
}
RenderMaps(time, _DisplacementMapsCache[frameIndex], _NormalMapsCache[frameIndex]);
}
displacementMaps = _DisplacementMapsCache[frameIndex];
normalMaps = _NormalMapsCache[frameIndex];
}
private void RenderMapsFromCache(float time, RenderTexture[] displacementMaps, RenderTexture[] normalMaps)
{
float num = (float)_Data.CachedFrameCount * FastMath.FracAdditive(time / _WindWaves.LoopDuration);
int num2 = (int)num;
float value = num - (float)num2;
RetrieveCachedFrame(num2, out var displacementMaps2, out var normalMaps2);
int num3 = num2 + 1;
if (num3 >= _Data.CachedFrameCount)
{
num3 = 0;
}
RetrieveCachedFrame(num3, out var displacementMaps3, out var normalMaps3);
_FFTUtilitiesMaterial.SetFloat("_BlendFactor", value);
for (int i = 0; i < 4; i++)
{
_FFTUtilitiesMaterial.SetTexture("_Texture1", displacementMaps2[i]);
_FFTUtilitiesMaterial.SetTexture("_Texture2", displacementMaps3[i]);
Graphics.Blit(null, displacementMaps[i], _FFTUtilitiesMaterial, 6);
}
for (int j = 0; j < 2; j++)
{
_FFTUtilitiesMaterial.SetTexture("_Texture1", normalMaps2[j]);
_FFTUtilitiesMaterial.SetTexture("_Texture2", normalMaps3[j]);
Graphics.Blit(null, normalMaps[j], _FFTUtilitiesMaterial, 6);
}
}
private void ValidateWaveMaps()
{
int frameCount = Time.frameCount;
if (_WaveMapsFrame == frameCount || !Application.isPlaying)
{
return;
}
if (_LastCopyFrom != null)
{
if (_CopyModeDirty)
{
_CopyModeDirty = false;
ValidateResources();
}
}
else
{
_WaveMapsFrame = frameCount;
if (_WindWaves.LoopDuration == 0f)
{
RenderMaps(_Water.Time, _DisplacementMaps, _NormalMaps);
}
else
{
RenderMapsFromCache(_Water.Time, _DisplacementMaps, _NormalMaps);
}
}
}
private void OnResolutionChanged(WindWaves windWaves)
{
Dispose(total: false);
ValidateResources();
}
private void Dispose(bool total)
{
_WaveMapsFrame = -1;
if (_HeightFFT != null)
{
_HeightFFT.Dispose();
_HeightFFT = null;
}
if (_NormalFFT != null)
{
_NormalFFT.Dispose();
_NormalFFT = null;
}
if (_DisplacementFFT != null)
{
_DisplacementFFT.Dispose();
_DisplacementFFT = null;
}
if (_NormalMaps != null)
{
RenderTexture[] normalMaps = _NormalMaps;
for (int i = 0; i < normalMaps.Length; i++)
{
normalMaps[i].Destroy();
}
_NormalMaps = null;
}
if (_DisplacementMaps != null)
{
RenderTexture[] normalMaps = _DisplacementMaps;
for (int i = 0; i < normalMaps.Length; i++)
{
normalMaps[i].Destroy();
}
_DisplacementMaps = null;
}
if (_NormalMapsCache != null)
{
for (int num = _NormalMapsCache.Length - 1; num >= 0; num--)
{
RenderTexture[] array = _NormalMapsCache[num];
if (array != null)
{
for (int num2 = array.Length - 1; num2 >= 0; num2--)
{
array[num2].Destroy();
}
_NormalMapsCache[num] = null;
}
}
}
if (_DisplacementMapsCache != null)
{
for (int num3 = _DisplacementMapsCache.Length - 1; num3 >= 0; num3--)
{
RenderTexture[] array2 = _DisplacementMapsCache[num3];
if (array2 != null)
{
for (int num4 = array2.Length - 1; num4 >= 0; num4--)
{
array2[num4].Destroy();
}
_DisplacementMapsCache[num3] = null;
}
}
}
if (_IsCachedFrameValid != null)
{
for (int num5 = _IsCachedFrameValid.Length - 1; num5 >= 0; num5--)
{
_IsCachedFrameValid[num5] = false;
}
}
if (total && _FFTUtilitiesMaterial != null)
{
_FFTUtilitiesMaterial.Destroy();
_FFTUtilitiesMaterial = null;
}
}
private void OnProfilesChanged(Water water)
{
for (int num = _IsCachedFrameValid.Length - 1; num >= 0; num--)
{
_IsCachedFrameValid[num] = false;
}
}
internal void Validate()
{
_Dx11FFT = _Water.ShaderSet.GetComputeShader("DX11 FFT");
if (_FFTShader == null)
{
_FFTShader = Shader.Find("UltimateWater/Base/FFT");
}
if (_FFTUtilitiesShader == null)
{
_FFTUtilitiesShader = Shader.Find("UltimateWater/Utilities/FFT Utilities");
}
if (Application.isPlaying && Enabled)
{
ResolveFinalSettings(WaterQualitySettings.Instance.CurrentQualityLevel);
}
}
internal void ResolveFinalSettings(WaterQualityLevel qualityLevel)
{
_FinalHighQualityNormalMaps = _Data.HighQualityNormalMaps;
if (!qualityLevel.AllowHighQualityNormalMaps)
{
_FinalHighQualityNormalMaps = false;
}
if ((_RenderedMaps & MapType.Displacement) == 0)
{
_FinalHighQualityNormalMaps = true;
}
}
private GpuFFT ChooseBestFFTAlgorithm(bool twoChannels)
{
int finalResolution = _WindWaves.FinalResolution;
GpuFFT gpuFFT = ((_Data.ForcePixelShader || !(_Dx11FFT != null) || !SystemInfo.supportsComputeShaders || finalResolution > 512) ? ((GpuFFT)new PixelShaderFFT(_FFTShader, finalResolution, _WindWaves.FinalHighPrecision || finalResolution >= 2048, twoChannels)) : ((GpuFFT)new Dx11FFT(_Dx11FFT, finalResolution, _WindWaves.FinalHighPrecision || finalResolution >= 2048, twoChannels)));
gpuFFT.SetupMaterials();
return gpuFFT;
}
private void ValidateFFT(ref GpuFFT fft, bool present, bool twoChannels)
{
if (present)
{
if (fft == null)
{
fft = ChooseBestFFTAlgorithm(twoChannels);
}
}
else if (fft != null)
{
fft.Dispose();
fft = null;
}
}
private RenderTexture CreateRenderTexture(string name, RenderTextureFormat format, bool mipMaps)
{
RenderTexture renderTexture = new RenderTexture(_WindWaves.FinalResolution, _WindWaves.FinalResolution, 0, format, RenderTextureReadWrite.Linear)
{
name = name,
hideFlags = HideFlags.DontSave,
wrapMode = TextureWrapMode.Repeat
};
if (mipMaps && WaterProjectSettings.Instance.AllowFloatingPointMipMaps)
{
renderTexture.filterMode = FilterMode.Trilinear;
renderTexture.useMipMap = true;
renderTexture.autoGenerateMips = true;
}
else
{
renderTexture.filterMode = FilterMode.Bilinear;
}
return renderTexture;
}
private void CreateRenderTextures(ref RenderTexture[] renderTextures, string name, RenderTextureFormat format, int count, bool mipMaps)
{
renderTextures = new RenderTexture[count];
for (int i = 0; i < count; i++)
{
renderTextures[i] = CreateRenderTexture(name, format, mipMaps);
}
}
private void ValidateResources()
{
if (_WindWaves.CopyFrom == null)
{
ValidateFFT(ref _HeightFFT, (_RenderedMaps & MapType.Displacement) != 0, twoChannels: false);
ValidateFFT(ref _DisplacementFFT, (_RenderedMaps & MapType.Displacement) != 0, twoChannels: true);
ValidateFFT(ref _NormalFFT, (_RenderedMaps & MapType.Normal) != 0, twoChannels: true);
}
if (_DisplacementMaps != null && _NormalMaps != null)
{
return;
}
RenderTexture[] displacementMaps;
RenderTexture[] normalMaps;
if (_WindWaves.CopyFrom == null)
{
int num = _WindWaves.FinalResolution << 1;
_SingleTargetCache = RenderTexturesCache.GetCache(num, num, 0, RenderTextureFormat.RHalf, linear: true, _HeightFFT is Dx11FFT);
_DoubleTargetCache = RenderTexturesCache.GetCache(num, num, 0, RenderTextureFormat.RGHalf, linear: true, _DisplacementFFT is Dx11FFT);
if (_DisplacementMaps == null && (_RenderedMaps & MapType.Displacement) != 0)
{
CreateRenderTextures(ref _DisplacementMaps, "[UWS] WavesRendererFFT - Water Displacement Map", RenderTextureFormat.ARGBHalf, 4, mipMaps: true);
}
if (_NormalMaps == null && (_RenderedMaps & MapType.Normal) != 0)
{
CreateRenderTextures(ref _NormalMaps, "[UWS] WavesRendererFFT - Water Normal Map", RenderTextureFormat.ARGBHalf, 2, mipMaps: true);
}
displacementMaps = _DisplacementMaps;
normalMaps = _NormalMaps;
}
else
{
Water copyFrom = _WindWaves.CopyFrom;
if (copyFrom.WindWaves.WaterWavesFFT._WindWaves == null)
{
copyFrom.WindWaves.ResolveFinalSettings(WaterQualitySettings.Instance.CurrentQualityLevel);
}
copyFrom.WindWaves.WaterWavesFFT.ValidateResources();
displacementMaps = copyFrom.WindWaves.WaterWavesFFT._DisplacementMaps;
normalMaps = copyFrom.WindWaves.WaterWavesFFT._NormalMaps;
}
for (int i = 0; i < 4; i++)
{
string text = ((i != 0) ? i.ToString() : "");
if (displacementMaps != null)
{
string name = "_GlobalDisplacementMap" + text;
_Water.Renderer.PropertyBlock.SetTexture(name, displacementMaps[i]);
}
if (i < 2 && normalMaps != null)
{
string name2 = "_GlobalNormalMap" + text;
_Water.Renderer.PropertyBlock.SetTexture(name2, normalMaps[i]);
}
}
if (this.ResourcesChanged != null)
{
this.ResourcesChanged();
}
}
internal void Enable()
{
if (Enabled)
{
return;
}
Enabled = true;
OnCopyModeChanged();
if (Application.isPlaying)
{
if (_LastCopyFrom == null)
{
ValidateResources();
}
_WindWaves.ResolutionChanged.AddListener(OnResolutionChanged);
}
_FFTUtilitiesMaterial = new Material(_FFTUtilitiesShader)
{
hideFlags = HideFlags.DontSave
};
}
internal void Disable()
{
if (Enabled)
{
Enabled = false;
Dispose(total: false);
}
}
internal void OnDestroy()
{
Dispose(total: true);
}
}
}