Files
2026-02-21 18:55:27 +08:00

597 lines
16 KiB
C#

using System;
using UltimateWater.Internal;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
namespace UltimateWater
{
[RequireComponent(typeof(Water))]
[AddComponentMenu("Ultimate Water/Spray", 1)]
public sealed class Spray : MonoBehaviour, IOverlaysRenderer
{
public struct Particle
{
public Vector3 Position;
public Vector3 Velocity;
public Vector2 Lifetime;
public float Offset;
public float MaxIntensity;
public Particle(Vector3 position, Vector3 velocity, float lifetime, float offset, float maxIntensity)
{
Position = position;
Velocity = velocity;
Lifetime = new Vector2(lifetime, lifetime);
Offset = offset;
MaxIntensity = maxIntensity;
}
}
[HideInInspector]
[SerializeField]
[FormerlySerializedAs("sprayTiledGeneratorShader")]
private Shader _SprayTiledGeneratorShader;
[FormerlySerializedAs("sprayLocalGeneratorShader")]
[SerializeField]
[HideInInspector]
private Shader _SprayLocalGeneratorShader;
[FormerlySerializedAs("sprayToFoamShader")]
[SerializeField]
[HideInInspector]
private Shader _SprayToFoamShader;
[FormerlySerializedAs("sprayControllerShader")]
[SerializeField]
[HideInInspector]
private ComputeShader _SprayControllerShader;
[FormerlySerializedAs("sprayMaterial")]
[SerializeField]
private Material _SprayMaterial;
[FormerlySerializedAs("maxParticles")]
[SerializeField]
[Range(16f, 327675f)]
private int _MaxParticles = 65535;
[FormerlySerializedAs("sprayToFoam")]
[SerializeField]
private bool _SprayToFoam = true;
private float _SpawnThreshold = 1f;
private float _SpawnSkipRatio = 0.9f;
private float _Scale = 1f;
private Water _Water;
private WindWaves _WindWaves;
private DynamicWater _Overlays;
private Material _SprayTiledGeneratorMaterial;
private Material _SprayLocalGeneratorMaterial;
private Material _SprayToFoamMaterial;
private Transform _ProbeAnchor;
private RenderTexture _BlankOutput;
private Texture2D _BlankWhiteTex;
private ComputeBuffer _ParticlesA;
private ComputeBuffer _ParticlesB;
private ComputeBuffer _ParticlesInfo;
private ComputeBuffer _SpawnBuffer;
private int _Resolution;
private Mesh _Mesh;
private bool _Supported;
private bool _ResourcesReady;
private Vector2 _LastSurfaceOffset;
private readonly int[] _CountBuffer = new int[4];
private float _SkipRatioPrecomp;
private Particle[] _ParticlesToSpawn = new Particle[10];
private int _NumParticlesToSpawn;
private MaterialPropertyBlock[] _PropertyBlocks;
public int MaxParticles
{
get
{
return _MaxParticles;
}
}
public int SpawnedParticles
{
get
{
if (_ParticlesA != null)
{
ComputeBuffer.CopyCount(_ParticlesA, _ParticlesInfo, 0);
_ParticlesInfo.GetData(_CountBuffer);
return _CountBuffer[0];
}
return 0;
}
}
public ComputeBuffer ParticlesBuffer
{
get
{
return _ParticlesA;
}
}
public void SpawnCustomParticle(Particle particle)
{
if (base.enabled)
{
if (_ParticlesToSpawn.Length <= _NumParticlesToSpawn)
{
Array.Resize(ref _ParticlesToSpawn, _ParticlesToSpawn.Length << 1);
}
_ParticlesToSpawn[_NumParticlesToSpawn] = particle;
_NumParticlesToSpawn++;
}
}
public void SpawnCustomParticles(Particle[] particles, int numParticles)
{
if (!base.enabled)
{
return;
}
CheckResources();
if (_SpawnBuffer == null || _SpawnBuffer.count < particles.Length)
{
if (_SpawnBuffer != null)
{
_SpawnBuffer.Release();
}
_SpawnBuffer = new ComputeBuffer(particles.Length, 40);
}
_SpawnBuffer.SetData(particles);
_SprayControllerShader.SetFloat("particleCount", numParticles);
_SprayControllerShader.SetBuffer(2, "SourceParticles", _SpawnBuffer);
_SprayControllerShader.SetBuffer(2, "TargetParticles", _ParticlesA);
_SprayControllerShader.Dispatch(2, 1, 1, 1);
}
public void RenderOverlays(DynamicWaterCameraData overlays)
{
}
public void RenderFoam(DynamicWaterCameraData overlays)
{
if (base.enabled)
{
CheckResources();
if (_SprayToFoam)
{
GenerateLocalFoam(overlays);
}
}
}
private void Start()
{
_Water = GetComponent<Water>();
_WindWaves = _Water.WindWaves;
_Overlays = _Water.DynamicWater;
_WindWaves.ResolutionChanged.AddListener(OnResolutionChanged);
_Supported = CheckSupport();
_LastSurfaceOffset = _Water.SurfaceOffset;
if (!_Supported)
{
base.enabled = false;
}
}
private void OnEnable()
{
_Water = GetComponent<Water>();
_Water.ProfilesManager.Changed.AddListener(OnProfilesChanged);
OnProfilesChanged(_Water);
Camera.onPreCull = (Camera.CameraCallback)Delegate.Remove(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraPreCull));
Camera.onPreCull = (Camera.CameraCallback)Delegate.Combine(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraPreCull));
}
private void OnDisable()
{
if (_Water != null)
{
_Water.ProfilesManager.Changed.RemoveListener(OnProfilesChanged);
}
Camera.onPreCull = (Camera.CameraCallback)Delegate.Remove(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraPreCull));
Dispose();
}
private void LateUpdate()
{
if (Time.frameCount >= 10)
{
if (!_ResourcesReady)
{
CheckResources();
}
SwapParticleBuffers();
ClearParticles();
UpdateParticles();
if (Camera.main != null)
{
SpawnWindWavesParticlesTiled(Camera.main.transform);
}
if (_NumParticlesToSpawn != 0)
{
SpawnCustomParticles(_ParticlesToSpawn, _NumParticlesToSpawn);
_NumParticlesToSpawn = 0;
}
}
}
private void OnValidate()
{
_MaxParticles = Mathf.RoundToInt((float)_MaxParticles / 65535f) * 65535;
if (_SprayTiledGeneratorShader == null)
{
_SprayTiledGeneratorShader = Shader.Find("UltimateWater/Spray/Generator (Tiles)");
}
if (_SprayLocalGeneratorShader == null)
{
_SprayLocalGeneratorShader = Shader.Find("UltimateWater/Spray/Generator (Local)");
}
if (_SprayToFoamShader == null)
{
_SprayToFoamShader = Shader.Find("UltimateWater/Spray/Spray To Foam");
}
UpdatePrecomputedParams();
}
private void OnSomeCameraPreCull(Camera cameraComponent)
{
if (!_ResourcesReady)
{
return;
}
WaterCamera waterCamera = WaterCamera.GetWaterCamera(cameraComponent);
if (waterCamera != null && waterCamera.Type == WaterCamera.CameraType.Normal)
{
_SprayMaterial.SetBuffer("_Particles", _ParticlesA);
_SprayMaterial.SetVector("_CameraUp", cameraComponent.transform.up);
_SprayMaterial.SetVector("_WrapSubsurfaceScatteringPack", _Water.Renderer.PropertyBlock.GetVector("_WrapSubsurfaceScatteringPack"));
_SprayMaterial.SetFloat("_UniformWaterScale", _Water.UniformWaterScale);
if (_ProbeAnchor == null)
{
GameObject gameObject = new GameObject("Spray Probe Anchor");
gameObject.hideFlags = HideFlags.HideAndDontSave;
GameObject gameObject2 = gameObject;
_ProbeAnchor = gameObject2.transform;
}
_ProbeAnchor.position = cameraComponent.transform.position;
int num = _PropertyBlocks.Length;
for (int i = 0; i < num; i++)
{
Graphics.DrawMesh(_Mesh, Matrix4x4.identity, _SprayMaterial, 0, cameraComponent, 0, _PropertyBlocks[i], ShadowCastingMode.Off, false, _ProbeAnchor);
}
}
}
private void SpawnWindWavesParticlesTiled(Transform origin)
{
Vector3 position = origin.position;
float num = 400f / (float)_BlankOutput.width;
_SprayTiledGeneratorMaterial.CopyPropertiesFromMaterial(_Water.Materials.SurfaceMaterial);
_SprayTiledGeneratorMaterial.SetVector("_SurfaceOffset", new Vector3(_Water.SurfaceOffset.x, _Water.transform.position.y, _Water.SurfaceOffset.y));
_SprayTiledGeneratorMaterial.SetVector("_Params", new Vector4(_SpawnThreshold * 0.25835f, _SkipRatioPrecomp, 0f, _Scale * 0.455f));
_SprayTiledGeneratorMaterial.SetVector("_Coordinates", new Vector4(position.x - 200f + UnityEngine.Random.value * num, position.z - 200f + UnityEngine.Random.value * num, 400f, 400f));
if (_Overlays == null)
{
_SprayTiledGeneratorMaterial.SetTexture("_LocalNormalMap", GetBlankWhiteTex());
}
Graphics.SetRandomWriteTarget(1, _ParticlesA);
GraphicsUtilities.Blit(null, _BlankOutput, _SprayTiledGeneratorMaterial, 0, _Water.Renderer.PropertyBlock);
Graphics.ClearRandomWriteTargets();
}
private void GenerateLocalFoam(DynamicWaterCameraData data)
{
RenderTexture temporary = RenderTexture.GetTemporary(512, 512, 0, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear);
Graphics.SetRenderTarget(temporary);
GL.Clear(false, true, new Color(0f, 0f, 0f, 0f));
_SprayToFoamMaterial.SetBuffer("_Particles", _ParticlesA);
_SprayToFoamMaterial.SetVector("_LocalMapsCoords", data.Camera.LocalMapsShaderCoords);
_SprayToFoamMaterial.SetFloat("_UniformWaterScale", 50f * _Water.UniformWaterScale / data.Camera.LocalMapsRect.width);
Vector4 vector = _SprayMaterial.GetVector("_ParticleParams");
vector.x *= 8f;
vector.z = 1f;
_SprayToFoamMaterial.SetVector("_ParticleParams", vector);
int num = _PropertyBlocks.Length;
for (int i = 0; i < num; i++)
{
_SprayToFoamMaterial.SetFloat("_ParticleOffset", i * 65535);
if (_SprayToFoamMaterial.SetPass(0))
{
Graphics.DrawMeshNow(_Mesh, Matrix4x4.identity, 0);
}
}
Camera planeProjectorCamera = data.Camera.PlaneProjectorCamera;
Rect localMapsRect = data.Camera.LocalMapsRect;
Vector2 center = localMapsRect.center;
float num2 = localMapsRect.width * 0.5f;
Matrix4x4 matrix = new Matrix4x4
{
m03 = center.x,
m13 = _Water.transform.position.y,
m23 = center.y,
m00 = num2,
m11 = num2,
m22 = num2,
m33 = 1f
};
GL.PushMatrix();
GL.modelview = planeProjectorCamera.worldToCameraMatrix;
GL.LoadProjectionMatrix(planeProjectorCamera.projectionMatrix);
Graphics.SetRenderTarget(data.FoamMap);
_SprayToFoamMaterial.mainTexture = temporary;
if (_SprayToFoamMaterial.SetPass(1))
{
Graphics.DrawMeshNow(Quads.BipolarXZ, matrix, 0);
}
GL.PopMatrix();
RenderTexture.ReleaseTemporary(temporary);
}
private void UpdateParticles()
{
Vector2 vector = _WindWaves.WindSpeed * 0.0008f;
Vector3 gravity = Physics.gravity;
float deltaTime = Time.deltaTime;
if (_Overlays != null)
{
DynamicWaterCameraData cameraOverlaysData = _Overlays.GetCameraOverlaysData(Camera.main, false);
if (cameraOverlaysData != null)
{
_SprayControllerShader.SetTexture(0, "TotalDisplacementMap", cameraOverlaysData.TotalDisplacementMap);
WaterCamera waterCamera = WaterCamera.GetWaterCamera(Camera.main);
if (waterCamera != null)
{
_SprayControllerShader.SetVector("localMapsCoords", waterCamera.LocalMapsShaderCoords);
}
}
else
{
_SprayControllerShader.SetTexture(0, "TotalDisplacementMap", GetBlankWhiteTex());
}
}
else
{
_SprayControllerShader.SetTexture(0, "TotalDisplacementMap", GetBlankWhiteTex());
}
Vector2 surfaceOffset = _Water.SurfaceOffset;
_SprayControllerShader.SetVector("deltaTime", new Vector4(deltaTime, 1f - deltaTime * 0.2f, 0f, 0f));
_SprayControllerShader.SetVector("externalForces", new Vector3((vector.x + gravity.x) * deltaTime, gravity.y * deltaTime, (vector.y + gravity.z) * deltaTime));
_SprayControllerShader.SetVector("surfaceOffsetDelta", new Vector3(_LastSurfaceOffset.x - surfaceOffset.x, 0f, _LastSurfaceOffset.y - surfaceOffset.y));
_SprayControllerShader.SetFloat("surfaceOffsetY", base.transform.position.y);
_SprayControllerShader.SetVector("waterTileSizesInv", _WindWaves.TileSizesInv);
_SprayControllerShader.SetBuffer(0, "SourceParticles", _ParticlesB);
_SprayControllerShader.SetBuffer(0, "TargetParticles", _ParticlesA);
_SprayControllerShader.Dispatch(0, _MaxParticles / 256, 1, 1);
_LastSurfaceOffset = surfaceOffset;
}
private Texture2D GetBlankWhiteTex()
{
if (_BlankWhiteTex == null)
{
_BlankWhiteTex = new Texture2D(2, 2, TextureFormat.ARGB32, false, true);
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
_BlankWhiteTex.SetPixel(i, j, new Color(1f, 1f, 1f, 1f));
}
}
_BlankWhiteTex.Apply(false, true);
}
return _BlankWhiteTex;
}
private void ClearParticles()
{
_SprayControllerShader.SetBuffer(1, "TargetParticlesFlat", _ParticlesA);
_SprayControllerShader.Dispatch(1, _MaxParticles / 256, 1, 1);
}
private void SwapParticleBuffers()
{
ComputeBuffer particlesB = _ParticlesB;
_ParticlesB = _ParticlesA;
_ParticlesA = particlesB;
}
private void OnResolutionChanged(WindWaves windWaves)
{
if (_BlankOutput != null)
{
UnityEngine.Object.Destroy(_BlankOutput);
_BlankOutput = null;
}
_ResourcesReady = false;
}
private void OnProfilesChanged(Water water)
{
Water.WeightedProfile[] profiles = water.ProfilesManager.Profiles;
_SpawnThreshold = 0f;
_SpawnSkipRatio = 0f;
_Scale = 0f;
if (profiles != null)
{
for (int i = 0; i < profiles.Length; i++)
{
Water.WeightedProfile weightedProfile = profiles[i];
WaterProfileData profile = weightedProfile.Profile;
float weight = weightedProfile.Weight;
_SpawnThreshold += profile.SprayThreshold * weight;
_SpawnSkipRatio += profile.SpraySkipRatio * weight;
_Scale += profile.SpraySize * weight;
}
}
}
private void UpdatePrecomputedParams()
{
if (_Water != null)
{
_Resolution = _WindWaves.FinalResolution;
}
_SkipRatioPrecomp = Mathf.Pow(_SpawnSkipRatio, 1024f / (float)_Resolution);
}
private bool CheckSupport()
{
return SystemInfo.supportsComputeShaders && _SprayTiledGeneratorShader != null && _SprayTiledGeneratorShader.isSupported;
}
private void CheckResources()
{
if (_SprayTiledGeneratorMaterial == null)
{
_SprayTiledGeneratorMaterial = new Material(_SprayTiledGeneratorShader)
{
hideFlags = HideFlags.DontSave
};
}
if (_SprayLocalGeneratorMaterial == null)
{
_SprayLocalGeneratorMaterial = new Material(_SprayLocalGeneratorShader)
{
hideFlags = HideFlags.DontSave
};
}
if (_SprayToFoamMaterial == null)
{
_SprayToFoamMaterial = new Material(_SprayToFoamShader)
{
hideFlags = HideFlags.DontSave
};
}
if (_BlankOutput == null)
{
UpdatePrecomputedParams();
_BlankOutput = new RenderTexture(_Resolution, _Resolution, 0, SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.R8) ? RenderTextureFormat.R8 : RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear)
{
name = "[UWS] Spray - WaterSpray Blank Output Texture",
filterMode = FilterMode.Point
};
_BlankOutput.Create();
}
if (_Mesh == null)
{
int num = Mathf.Min(_MaxParticles, 65535);
_Mesh = new Mesh
{
name = "Spray",
hideFlags = HideFlags.DontSave,
vertices = new Vector3[num]
};
int[] array = new int[num];
for (int i = 0; i < num; i++)
{
array[i] = i;
}
_Mesh.SetIndices(array, MeshTopology.Points, 0);
_Mesh.bounds = new Bounds(Vector3.zero, new Vector3(10000000f, 10000000f, 10000000f));
}
if (_PropertyBlocks == null)
{
int num2 = Mathf.CeilToInt((float)_MaxParticles / 65535f);
_PropertyBlocks = new MaterialPropertyBlock[num2];
for (int j = 0; j < num2; j++)
{
(_PropertyBlocks[j] = new MaterialPropertyBlock()).SetFloat("_ParticleOffset", j * 65535);
}
}
if (_ParticlesA == null)
{
_ParticlesA = new ComputeBuffer(_MaxParticles, 40, ComputeBufferType.Append);
}
if (_ParticlesB == null)
{
_ParticlesB = new ComputeBuffer(_MaxParticles, 40, ComputeBufferType.Append);
}
if (_ParticlesInfo == null)
{
_ParticlesInfo = new ComputeBuffer(1, 16, ComputeBufferType.IndirectArguments);
_ParticlesInfo.SetData(new int[4] { 0, 1, 0, 0 });
}
_ResourcesReady = true;
}
private void Dispose()
{
if (_BlankOutput != null)
{
UnityEngine.Object.Destroy(_BlankOutput);
_BlankOutput = null;
}
if (_ParticlesA != null)
{
_ParticlesA.Dispose();
_ParticlesA = null;
}
if (_ParticlesB != null)
{
_ParticlesB.Dispose();
_ParticlesB = null;
}
if (_ParticlesInfo != null)
{
_ParticlesInfo.Release();
_ParticlesInfo = null;
}
if (_Mesh != null)
{
UnityEngine.Object.Destroy(_Mesh);
_Mesh = null;
}
if (_ProbeAnchor != null)
{
UnityEngine.Object.Destroy(_ProbeAnchor.gameObject);
_ProbeAnchor = null;
}
if (_SpawnBuffer != null)
{
_SpawnBuffer.Release();
_SpawnBuffer = null;
}
_ResourcesReady = false;
}
}
}