Aetheria/Assets/Shaders/Compute/Slime/Slime.cs
2026-06-28 01:18:21 +01:00

372 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using Random = UnityEngine.Random;
using static Unity.Mathematics.math;
public class Slime : MonoBehaviour
{
#region variables
[Header("Scene Links")]
public Camera TargetCamera;
[Header("Materials")]
public ComputeShader SlimeCompute;
public Material AccumulationMaterial;
public Material ParticleMaterial;
public RenderTexture GravityTexture;
[Header("Performance")]
public int TextureSize = 1024;
public float ZoneRadius = 512;
public int AgentCount = 524288;
public int ParticleCount = 65536;
public int TrailPoints = 8;
[Header("Parameters")]
public SlimeSettings SlimeSettings;
public float ZoneCeiling;
public float ZoneFloor;
public float NoiseFrequency = 1;
public float NoiseAmplitude = 5;
public float ParticleHeightRange;
public float ParticleHeightOffset;
public float TrailStartWidth = .5f;
public float TrailEndWidth = .1f;
public float TrailDamping = .05f;
public Vector2[] SpawnPositions;
private const int GROUP_SIZE = 64;
private int _updateAgentsKernel;
private int _diffuseKernel;
private int _particleKernel;
private int _trailKernel;
private int _geometryKernel;
private bool _settingsInitialized;
private SlimeSettings _currentSettings;
private int _spawnBufferSize = 1;
private int IndexBufferSize => ParticleCount * 6 * (TrailPoints - 1);
#endregion
#region buffers
private ComputeBuffer _agentsBuffer;
private RenderTexture _accumulationTexture;
private RenderTexture _previousAccumulationTexture;
private ComputeBuffer _spawnBuffer;
private ComputeBuffer _parameterBuffer;
private ComputeBuffer _particlesBuffer;
private ComputeBuffer _trailBuffer;
private ComputeBuffer _trailPreviousBuffer;
private ComputeBuffer _indexBuffer;
private ComputeBuffer _vertexBuffer;
#endregion
#region Structs
struct Agent
{
public Vector2 Position;
public float Angle;
}
struct Particle
{
public Vector3 Start;
public float Intensity;
}
struct Vertex
{
public Vector3 Position;
public Vector2 UV;
public float Intensity;
}
#endregion
void Start()
{
_updateAgentsKernel = SlimeCompute.FindKernel("UpdateAgents");
_diffuseKernel = SlimeCompute.FindKernel("Diffuse");
_particleKernel = SlimeCompute.FindKernel("CreateAgentParticles");
_trailKernel = SlimeCompute.FindKernel("UpdateTrailPoints");
_geometryKernel = SlimeCompute.FindKernel("CreateTrailGeometry");
_accumulationTexture = new RenderTexture(TextureSize, TextureSize, 0, RenderTextureFormat.RFloat);
_accumulationTexture.enableRandomWrite = true;
_previousAccumulationTexture = new RenderTexture(TextureSize, TextureSize, 0, RenderTextureFormat.RFloat);
_previousAccumulationTexture.enableRandomWrite = true;
AccumulationMaterial.SetTexture("_MainTex", _accumulationTexture);
_agentsBuffer = new ComputeBuffer(AgentCount, Marshal.SizeOf(typeof(Agent)));
var agents = new Agent[AgentCount];
for (var i = 0; i < agents.Length; i++)
{
agents[i] = new Agent
{
Position = Random.insideUnitCircle * ZoneRadius,
Angle = Random.value * Mathf.PI * 2
};
}
_agentsBuffer.SetData(agents);
_parameterBuffer = new ComputeBuffer(1, Marshal.SizeOf(typeof(SlimeSettings)));
_spawnBuffer = new ComputeBuffer(1, Marshal.SizeOf(typeof(Vector2)));
_spawnBuffer.SetData(new [] {Vector2.zero});
_trailBuffer = new ComputeBuffer(ParticleCount * TrailPoints, Marshal.SizeOf(typeof(Vector3)));
_trailPreviousBuffer = new ComputeBuffer(ParticleCount * TrailPoints, Marshal.SizeOf(typeof(Vector3)));
var trailData = Enumerable.Repeat(Vector3.zero, _trailBuffer.count).ToArray();
_trailBuffer.SetData(trailData);
_trailPreviousBuffer.SetData(trailData);
_particlesBuffer = new ComputeBuffer(ParticleCount, Marshal.SizeOf(typeof(Particle)));
_vertexBuffer = new ComputeBuffer(ParticleCount * 2 * TrailPoints, Marshal.SizeOf(typeof(Vertex)));
var indexData = new int[IndexBufferSize];
var index = 0;
for (int iTrail = 0; iTrail < ParticleCount; iTrail++)
{
var trailOffset = iTrail * TrailPoints * 2;
for (int iTrailPoint = 0; iTrailPoint < TrailPoints - 1; iTrailPoint++)
{
var offset = trailOffset + iTrailPoint * 2;
indexData[index++] = 0 + offset;
indexData[index++] = 1 + offset;
indexData[index++] = 2 + offset;
indexData[index++] = 2 + offset;
indexData[index++] = 1 + offset;
indexData[index++] = 3 + offset;
}
}
_indexBuffer = new ComputeBuffer(indexData.Length, Marshal.SizeOf(typeof(uint))); // 1 trail point to 2 triangles (6 vertices)
_indexBuffer.SetData(indexData);
}
void UpdateSettings()
{
if (!_settingsInitialized || !SlimeSettings.Equals(_currentSettings))
{
_settingsInitialized = true;
_currentSettings = SlimeSettings;
_parameterBuffer.SetData(new[] {SlimeSettings});
}
}
// Update is called once per frame
void Update()
{
UpdateSettings();
if (SpawnPositions != null && SpawnPositions.Length > 0)
{
if (SpawnPositions.Length != _spawnBufferSize)
{
_spawnBuffer.Release();
_spawnBuffer = new ComputeBuffer(SpawnPositions.Length, Marshal.SizeOf(typeof(Vector2)));
_spawnBufferSize = SpawnPositions.Length;
}
_spawnBuffer.SetData(SpawnPositions);
}
SlimeCompute.SetInt("randomOffset", Random.Range(0,65536));
SlimeCompute.SetInt("textureSize", TextureSize);
SlimeCompute.SetInt("gravityTextureSize", GravityTexture.width);
SlimeCompute.SetInt("numAgents", AgentCount);
SlimeCompute.SetFloat("zoneRadius", ZoneRadius);
SlimeCompute.SetFloat("deltaTime", Time.deltaTime);
SlimeCompute.SetInt("spawnPositionCount", _spawnBufferSize);
SlimeCompute.SetBuffer(_updateAgentsKernel, "agents", _agentsBuffer);
SlimeCompute.SetBuffer(_updateAgentsKernel, "spawnPositions", _spawnBuffer);
SlimeCompute.SetBuffer(_updateAgentsKernel, "parameters", _parameterBuffer);
SlimeCompute.SetTexture(_updateAgentsKernel, "accumulation", _accumulationTexture);
SlimeCompute.SetTexture(_updateAgentsKernel, "Heightmap", GravityTexture);
var numberOfGroups = Mathf.CeilToInt((float)AgentCount / GROUP_SIZE);
SlimeCompute.Dispatch(_updateAgentsKernel, numberOfGroups, 1, 1);
Graphics.Blit(_accumulationTexture, _previousAccumulationTexture);
SlimeCompute.SetBuffer(_diffuseKernel, "parameters", _parameterBuffer);
SlimeCompute.SetTexture(_diffuseKernel, "Heightmap", GravityTexture);
SlimeCompute.SetTexture(_diffuseKernel, "accumulation", _previousAccumulationTexture);
SlimeCompute.SetTexture(_diffuseKernel, "diffusedAccumulation", _accumulationTexture);
numberOfGroups = Mathf.CeilToInt((float)TextureSize / 8);
SlimeCompute.Dispatch(_diffuseKernel, numberOfGroups, numberOfGroups, 1);
SlimeCompute.SetBuffer(_particleKernel, "parameters", _parameterBuffer);
SlimeCompute.SetTexture(_particleKernel, "Heightmap", GravityTexture);
SlimeCompute.SetBuffer(_particleKernel, "agents", _agentsBuffer);
SlimeCompute.SetBuffer(_particleKernel, "particles", _particlesBuffer);
SlimeCompute.SetFloat("time", Time.time * NoiseFrequency);
SlimeCompute.SetFloat("noiseAmplitude", NoiseAmplitude);
SlimeCompute.SetFloat("heightRange", ParticleHeightRange);
SlimeCompute.SetFloat("heightOffset", ParticleHeightOffset);
SlimeCompute.SetFloat("trailDamping", TrailDamping);
SlimeCompute.SetInt("particleCount", ParticleCount);
numberOfGroups = ParticleCount / GROUP_SIZE;
SlimeCompute.Dispatch(_particleKernel, numberOfGroups, 1, 1);
SlimeCompute.SetInt("trailPoints", TrailPoints);
numberOfGroups = ParticleCount * TrailPoints / GROUP_SIZE;
SlimeCompute.SetBuffer(_trailKernel, "previousTrailBuffer", _trailPreviousBuffer);
SlimeCompute.SetBuffer(_trailKernel, "particles", _particlesBuffer);
SlimeCompute.SetBuffer(_trailKernel, "trailBuffer", _trailBuffer);
SlimeCompute.Dispatch(_trailKernel, numberOfGroups, 1, 1);
var tempTrailBuffer = _trailBuffer;
_trailBuffer = _trailPreviousBuffer;
_trailPreviousBuffer = tempTrailBuffer;
SlimeCompute.SetFloat("trailStartWidth", TrailStartWidth);
SlimeCompute.SetFloat("trailEndWidth", TrailEndWidth);
SlimeCompute.SetVector("cameraPos", TargetCamera.transform.position);
SlimeCompute.SetBuffer(_geometryKernel, "particles", _particlesBuffer);
SlimeCompute.SetBuffer(_geometryKernel, "trailBuffer", _trailBuffer);
SlimeCompute.SetBuffer(_geometryKernel, "vertexBuffer", _vertexBuffer);
SlimeCompute.Dispatch(_geometryKernel, numberOfGroups, 1, 1);
ParticleMaterial.SetBuffer("_IndexBuffer", _indexBuffer);
ParticleMaterial.SetBuffer("_VertexBuffer", _vertexBuffer);
Graphics.DrawProcedural(
ParticleMaterial,
new Bounds(Vector3.zero, ZoneRadius * 2 * Vector3.one),
MeshTopology.Triangles,
IndexBufferSize,
1,
TargetCamera,
null,
ShadowCastingMode.Off,
false,
0);
}
private void OnDisable()
{
_agentsBuffer.Release();
_accumulationTexture.Release();
_previousAccumulationTexture.Release();
_spawnBuffer.Release();
_parameterBuffer.Release();
_particlesBuffer.Release();
_trailBuffer.Release();
_trailPreviousBuffer.Release();
_indexBuffer.Release();
_vertexBuffer.Release();
}
}
[Serializable]
public struct SlimeSettings : IEquatable<SlimeSettings>
{
public SlimeParameter Deposition;
public SlimeParameter Diffusion;
public SlimeParameter Decay;
public SlimeParameter TurnSpeed;
public SlimeParameter Speed;
public SlimeParameter Drive;
public SlimeParameter SensorDistance;
public SlimeParameter SensorSpread;
public SlimeParameter TrailDamping;
public SlimeParameter Intensity;
public bool Equals(SlimeSettings other)
{
return Deposition.Equals(other.Deposition)
&& Diffusion.Equals(other.Diffusion)
&& Decay.Equals(other.Decay)
&& TurnSpeed.Equals(other.TurnSpeed)
&& Speed.Equals(other.Speed)
&& Drive.Equals(other.Drive)
&& SensorDistance.Equals(other.SensorDistance)
&& SensorSpread.Equals(other.SensorSpread)
&& TrailDamping.Equals(other.TrailDamping)
&& Intensity.Equals(other.Intensity);
}
public override bool Equals(object obj)
{
return obj is SlimeSettings other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Deposition.GetHashCode();
hashCode = (hashCode * 397) ^ Diffusion.GetHashCode();
hashCode = (hashCode * 397) ^ Decay.GetHashCode();
hashCode = (hashCode * 397) ^ TurnSpeed.GetHashCode();
hashCode = (hashCode * 397) ^ Speed.GetHashCode();
hashCode = (hashCode * 397) ^ Drive.GetHashCode();
hashCode = (hashCode * 397) ^ SensorDistance.GetHashCode();
hashCode = (hashCode * 397) ^ SensorSpread.GetHashCode();
hashCode = (hashCode * 397) ^ TrailDamping.GetHashCode();
hashCode = (hashCode * 397) ^ Intensity.GetHashCode();
return hashCode;
}
}
}
[Serializable]
public struct SlimeParameter : IEquatable<SlimeParameter>
{
public float SlopeThreshold;
public float SlopeBlend;
public float HighValue;
public float LowValue;
public float Exponent;
public float Evaluate(float slope)
{
return lerp(LowValue, HighValue, pow(smoothstep(SlopeThreshold - SlopeBlend, SlopeThreshold + SlopeBlend, slope), Exponent));
}
public bool Equals(SlimeParameter other)
{
return SlopeThreshold.Equals(other.SlopeThreshold)
&& SlopeBlend.Equals(other.SlopeBlend)
&& HighValue.Equals(other.HighValue)
&& LowValue.Equals(other.LowValue)
&& Exponent.Equals(other.Exponent);
}
public override bool Equals(object obj)
{
return obj is SlimeParameter other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = SlopeThreshold.GetHashCode();
hashCode = (hashCode * 397) ^ SlopeBlend.GetHashCode();
hashCode = (hashCode * 397) ^ HighValue.GetHashCode();
hashCode = (hashCode * 397) ^ LowValue.GetHashCode();
hashCode = (hashCode * 397) ^ Exponent.GetHashCode();
return hashCode;
}
}
}