280 lines
7.1 KiB
C#
280 lines
7.1 KiB
C#
using UltimateWater.Internal;
|
|
using UnityEngine;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace UltimateWater
|
|
{
|
|
public class WaveParticlesSystemGPU : MonoBehaviour, IOverlaysRenderer
|
|
{
|
|
public struct ParticleData
|
|
{
|
|
public Vector2 Position;
|
|
|
|
public Vector2 Direction;
|
|
|
|
public float Wavelength;
|
|
|
|
public float Amplitude;
|
|
|
|
public float InitialLifetime;
|
|
|
|
public float Lifetime;
|
|
|
|
public float UvOffsetPack;
|
|
|
|
public float Foam;
|
|
|
|
public float TrailCalming;
|
|
|
|
public float TrailFoam;
|
|
}
|
|
|
|
[FormerlySerializedAs("maxParticles")]
|
|
[SerializeField]
|
|
private int _MaxParticles = 80000;
|
|
|
|
[FormerlySerializedAs("controllerShader")]
|
|
[SerializeField]
|
|
private ComputeShader _ControllerShader;
|
|
|
|
[FormerlySerializedAs("particlesRenderShader")]
|
|
[SerializeField]
|
|
private Shader _ParticlesRenderShader;
|
|
|
|
[FormerlySerializedAs("foamTexture")]
|
|
[SerializeField]
|
|
private Texture _FoamTexture;
|
|
|
|
[FormerlySerializedAs("foamOverlayTexture")]
|
|
[SerializeField]
|
|
private Texture _FoamOverlayTexture;
|
|
|
|
[FormerlySerializedAs("foamAtlasWidth")]
|
|
[SerializeField]
|
|
private int _FoamAtlasWidth = 8;
|
|
|
|
[SerializeField]
|
|
[FormerlySerializedAs("foamAtlasHeight")]
|
|
private int _FoamAtlasHeight = 4;
|
|
|
|
private Material _ParticlesRenderMaterial;
|
|
|
|
private ComputeBuffer _ParticlesA;
|
|
|
|
private ComputeBuffer _ParticlesB;
|
|
|
|
private ComputeBuffer _SpawnBuffer;
|
|
|
|
private ComputeBuffer _ParticlesRenderInfo;
|
|
|
|
private ComputeBuffer _ParticlesUpdateInfo;
|
|
|
|
private Vector2 _LastSurfaceOffset;
|
|
|
|
private uint _ParticlesToSpawnCount;
|
|
|
|
private Water _Water;
|
|
|
|
private readonly ParticleData[] _ParticlesToSpawn = new ParticleData[16];
|
|
|
|
private readonly RenderBuffer[] _RenderBuffers = new RenderBuffer[2];
|
|
|
|
public ComputeBuffer ParticlesBuffer
|
|
{
|
|
get
|
|
{
|
|
return _ParticlesA;
|
|
}
|
|
}
|
|
|
|
public int FoamAtlasWidth
|
|
{
|
|
get
|
|
{
|
|
return _FoamAtlasWidth;
|
|
}
|
|
}
|
|
|
|
public int FoamAtlasHeight
|
|
{
|
|
get
|
|
{
|
|
return _FoamAtlasHeight;
|
|
}
|
|
}
|
|
|
|
public void EmitParticle(ParticleData particleData)
|
|
{
|
|
if (_ParticlesToSpawnCount != _ParticlesToSpawn.Length)
|
|
{
|
|
_ParticlesToSpawn[_ParticlesToSpawnCount++] = particleData;
|
|
}
|
|
}
|
|
|
|
public void RenderOverlays(DynamicWaterCameraData overlays)
|
|
{
|
|
Spray component = GetComponent<Spray>();
|
|
if (component != null && component.ParticlesBuffer != null)
|
|
{
|
|
Graphics.SetRandomWriteTarget(3, component.ParticlesBuffer);
|
|
}
|
|
_RenderBuffers[0] = overlays.DynamicDisplacementMap.colorBuffer;
|
|
_RenderBuffers[1] = overlays.NormalMap.colorBuffer;
|
|
_ParticlesRenderMaterial.SetBuffer("_Particles", _ParticlesA);
|
|
_ParticlesRenderMaterial.SetMatrix("_ParticlesVP", GL.GetGPUProjectionMatrix(overlays.Camera.PlaneProjectorCamera.projectionMatrix, true) * overlays.Camera.PlaneProjectorCamera.worldToCameraMatrix);
|
|
if (_ParticlesRenderMaterial.SetPass(0))
|
|
{
|
|
Graphics.SetRenderTarget(_RenderBuffers, overlays.DynamicDisplacementMap.depthBuffer);
|
|
Graphics.DrawProceduralIndirect(MeshTopology.Points, _ParticlesRenderInfo);
|
|
Graphics.ClearRandomWriteTargets();
|
|
}
|
|
if (_ParticlesRenderMaterial.SetPass(2))
|
|
{
|
|
Graphics.SetRenderTarget(overlays.DisplacementsMask);
|
|
Graphics.DrawProceduralIndirect(MeshTopology.Points, _ParticlesRenderInfo);
|
|
}
|
|
Graphics.SetRenderTarget(null);
|
|
}
|
|
|
|
public void RenderFoam(DynamicWaterCameraData overlays)
|
|
{
|
|
if (_ParticlesRenderMaterial.SetPass(1))
|
|
{
|
|
Graphics.SetRenderTarget(overlays.FoamMap);
|
|
Graphics.DrawProceduralIndirect(MeshTopology.Points, _ParticlesRenderInfo);
|
|
}
|
|
if (_ParticlesRenderMaterial.SetPass(3))
|
|
{
|
|
Graphics.DrawProceduralIndirect(MeshTopology.Points, _ParticlesRenderInfo);
|
|
}
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (_ParticlesRenderShader == null)
|
|
{
|
|
_ParticlesRenderShader = Shader.Find("UltimateWater/Particles/GPU_Render");
|
|
}
|
|
if (_ParticlesRenderMaterial != null)
|
|
{
|
|
SetMaterialProperties();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
CheckResources();
|
|
UpdateParticles();
|
|
SpawnParticles();
|
|
SwapBuffers();
|
|
ComputeBuffer.CopyCount(_ParticlesA, _ParticlesRenderInfo, 0);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
_Water = GetComponentInParent<Water>();
|
|
OnValidate();
|
|
_ParticlesRenderMaterial = new Material(_ParticlesRenderShader)
|
|
{
|
|
hideFlags = HideFlags.DontSave
|
|
};
|
|
SetMaterialProperties();
|
|
_LastSurfaceOffset = _Water.SurfaceOffset;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_ParticlesA != null)
|
|
{
|
|
_ParticlesA.Release();
|
|
_ParticlesA = null;
|
|
}
|
|
if (_ParticlesB != null)
|
|
{
|
|
_ParticlesB.Release();
|
|
_ParticlesB = null;
|
|
}
|
|
if (_ParticlesRenderInfo != null)
|
|
{
|
|
_ParticlesRenderInfo.Release();
|
|
_ParticlesRenderInfo = null;
|
|
}
|
|
if (_ParticlesUpdateInfo != null)
|
|
{
|
|
_ParticlesUpdateInfo.Release();
|
|
_ParticlesUpdateInfo = null;
|
|
}
|
|
if (_SpawnBuffer != null)
|
|
{
|
|
_SpawnBuffer.Release();
|
|
_SpawnBuffer = null;
|
|
}
|
|
}
|
|
|
|
private void UpdateParticles()
|
|
{
|
|
_ParticlesB.SetCounterValue(0u);
|
|
Vector2 surfaceOffset = _Water.SurfaceOffset;
|
|
ComputeBuffer.CopyCount(_ParticlesA, _ParticlesUpdateInfo, 0);
|
|
_ControllerShader.SetFloat("deltaTime", Time.deltaTime);
|
|
_ControllerShader.SetVector("surfaceOffsetDelta", new Vector4(_LastSurfaceOffset.x - surfaceOffset.x, _LastSurfaceOffset.y - surfaceOffset.y, 0f, 0f));
|
|
_ControllerShader.SetBuffer(0, "Particles", _ParticlesB);
|
|
_ControllerShader.SetBuffer(0, "SourceParticles", _ParticlesA);
|
|
_ControllerShader.DispatchIndirect(0, _ParticlesUpdateInfo);
|
|
_LastSurfaceOffset = surfaceOffset;
|
|
}
|
|
|
|
private void SpawnParticles()
|
|
{
|
|
if (_ParticlesToSpawnCount != 0)
|
|
{
|
|
_SpawnBuffer.SetData(_ParticlesToSpawn);
|
|
_ControllerShader.SetBuffer(1, "Particles", _ParticlesB);
|
|
_ControllerShader.SetBuffer(1, "SpawnedParticles", _SpawnBuffer);
|
|
_ControllerShader.Dispatch(1, 1, 1, 1);
|
|
for (int i = 0; i < _ParticlesToSpawnCount; i++)
|
|
{
|
|
_ParticlesToSpawn[i].Lifetime = 0f;
|
|
}
|
|
_ParticlesToSpawnCount = 0u;
|
|
}
|
|
}
|
|
|
|
private void SwapBuffers()
|
|
{
|
|
ComputeBuffer particlesA = _ParticlesA;
|
|
_ParticlesA = _ParticlesB;
|
|
_ParticlesB = particlesA;
|
|
}
|
|
|
|
private void CheckResources()
|
|
{
|
|
if (_ParticlesA == null)
|
|
{
|
|
_ParticlesA = new ComputeBuffer(_MaxParticles, 48, ComputeBufferType.Append);
|
|
_ParticlesA.SetCounterValue(0u);
|
|
_ParticlesB = new ComputeBuffer(_MaxParticles, 48, ComputeBufferType.Append);
|
|
_ParticlesB.SetCounterValue(0u);
|
|
_SpawnBuffer = new ComputeBuffer(16, 48, ComputeBufferType.Default);
|
|
}
|
|
if (_ParticlesRenderInfo == null)
|
|
{
|
|
_ParticlesRenderInfo = new ComputeBuffer(1, 16, ComputeBufferType.IndirectArguments);
|
|
_ParticlesRenderInfo.SetData(new int[4] { 0, 1, 0, 0 });
|
|
}
|
|
if (_ParticlesUpdateInfo == null)
|
|
{
|
|
_ParticlesUpdateInfo = new ComputeBuffer(1, 12, ComputeBufferType.IndirectArguments);
|
|
_ParticlesUpdateInfo.SetData(new int[3] { 0, 1, 1 });
|
|
}
|
|
}
|
|
|
|
private void SetMaterialProperties()
|
|
{
|
|
_ParticlesRenderMaterial.SetVector("_FoamAtlasParams", new Vector4(1f / (float)_FoamAtlasWidth, 1f / (float)_FoamAtlasHeight, 0f, 0f));
|
|
_ParticlesRenderMaterial.SetTexture("_FoamAtlas", _FoamTexture);
|
|
_ParticlesRenderMaterial.SetTexture("_FoamOverlayTexture", _FoamOverlayTexture);
|
|
}
|
|
}
|
|
}
|