Fluid, Slime, SlimeTestSpawner, LightningCompute and MouseRotate still imported Unity.Mathematics and compiled only because Burst and Collections pull the package in. Fluid, Slime and LightningCompute now use CultMath (LightningCompute's snoise included, which CultMath carries as the same Ashima simplex noise); SlimeTestSpawner and MouseRotate imported it without using it, so the usings are gone. LightningCompute converts its float3 offsets with ToUnity() instead of the Unity.Mathematics implicit cast. None of these are Burst-compiled or jobs. Their ComputeBuffer structs are built from UnityEngine Vector2/Vector3 and floats, not math types, so no buffer layout changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
219 lines
No EOL
6.9 KiB
C#
219 lines
No EOL
6.9 KiB
C#
// StableFluids - A GPU implementation of Jos Stam's Stable Fluids on Unity
|
|
// https://github.com/keijiro/StableFluids
|
|
|
|
using System.Collections.Generic;
|
|
using CultMath;
|
|
using UnityEngine;
|
|
using static CultMath.math;
|
|
using Random = UnityEngine.Random;
|
|
|
|
public class Fluid : MonoBehaviour
|
|
{
|
|
#region Editable attributes
|
|
|
|
[SerializeField] int _resolution = 512;
|
|
[SerializeField] float _viscosity = 1e-6f;
|
|
[SerializeField] float _damping = .05f;
|
|
[SerializeField] float _range = 512;
|
|
[SerializeField] Transform _camera;
|
|
|
|
#endregion
|
|
|
|
#region Internal resources
|
|
|
|
[SerializeField, HideInInspector] ComputeShader _compute;
|
|
[SerializeField, HideInInspector] Shader _shader;
|
|
|
|
#endregion
|
|
|
|
#region Private members
|
|
|
|
Material _shaderSheet;
|
|
int2 _previousCameraIntPosition = int2.zero;
|
|
private List<Force> _forces = new List<Force>();
|
|
|
|
struct Force
|
|
{
|
|
public float2 Position;
|
|
public float2 Vector;
|
|
public float Exponent;
|
|
}
|
|
|
|
static class Kernels
|
|
{
|
|
public const int Advect = 0;
|
|
public const int Force = 1;
|
|
public const int PSetup = 2;
|
|
public const int PFinish = 3;
|
|
public const int Jacobi1 = 4;
|
|
public const int Jacobi2 = 5;
|
|
public const int Adjust = 6;
|
|
}
|
|
|
|
int ThreadCount { get { return (_resolution + 7) / 8; } }
|
|
|
|
int Resolution { get { return ThreadCount * 8; } }
|
|
|
|
// Vector field buffers
|
|
static class VFB
|
|
{
|
|
public static RenderTexture V1;
|
|
public static RenderTexture V2;
|
|
public static RenderTexture V3;
|
|
public static RenderTexture P1;
|
|
public static RenderTexture P2;
|
|
}
|
|
|
|
RenderTexture AllocateBuffer(int componentCount)
|
|
{
|
|
var format = RenderTextureFormat.ARGBHalf;
|
|
if (componentCount == 1) format = RenderTextureFormat.RHalf;
|
|
if (componentCount == 2) format = RenderTextureFormat.RGHalf;
|
|
|
|
var rt = new RenderTexture(Resolution, Resolution, 0, format);
|
|
rt.enableRandomWrite = true;
|
|
rt.Create();
|
|
return rt;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region MonoBehaviour implementation
|
|
|
|
public void AddForce(float2 position, float2 vector, float exponent)
|
|
{
|
|
_forces.Add(new Force { Position = position, Vector = vector, Exponent = exponent });
|
|
}
|
|
|
|
void OnValidate()
|
|
{
|
|
_resolution = Mathf.Max(_resolution, 8);
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
_shaderSheet = new Material(_shader);
|
|
|
|
VFB.V1 = AllocateBuffer(2);
|
|
VFB.V2 = AllocateBuffer(2);
|
|
VFB.V3 = AllocateBuffer(2);
|
|
VFB.P1 = AllocateBuffer(1);
|
|
VFB.P2 = AllocateBuffer(1);
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
Destroy(_shaderSheet);
|
|
|
|
Destroy(VFB.V1);
|
|
Destroy(VFB.V2);
|
|
Destroy(VFB.V3);
|
|
Destroy(VFB.P1);
|
|
Destroy(VFB.P2);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
var dt = max(Time.deltaTime, .01f);
|
|
var dx = 1.0f / Resolution;
|
|
|
|
var snapLength = _range / _resolution;
|
|
|
|
// Input point
|
|
var cameraIntPosition = int2(
|
|
(int)(_camera.position.x / snapLength),
|
|
(int)(_camera.position.z / snapLength)
|
|
);
|
|
var cameraPosition = float2(
|
|
cameraIntPosition.x * snapLength,
|
|
cameraIntPosition.y * snapLength
|
|
);
|
|
Shader.SetGlobalVector("_FluidTransform", new Vector4(cameraPosition.x, cameraPosition.y, _range));
|
|
Shader.SetGlobalTexture("_FluidVelocity", VFB.V1);
|
|
|
|
var delta = (float2)(cameraIntPosition - _previousCameraIntPosition) / Resolution;
|
|
_compute.SetVector("Delta", new Vector4(delta.x,delta.y));
|
|
_compute.SetFloat("Damping", _damping);
|
|
_compute.SetTexture(Kernels.Adjust, "U_in", VFB.V1);
|
|
_compute.SetTexture(Kernels.Adjust, "W_out", VFB.V3);
|
|
_compute.Dispatch(Kernels.Adjust, ThreadCount, ThreadCount, 1);
|
|
|
|
// Common variables
|
|
_compute.SetFloat("Time", Time.time);
|
|
_compute.SetFloat("DeltaTime", dt);
|
|
|
|
// Advection
|
|
_compute.SetTexture(Kernels.Advect, "U_in", VFB.V3);
|
|
_compute.SetTexture(Kernels.Advect, "W_out", VFB.V2);
|
|
_compute.Dispatch(Kernels.Advect, ThreadCount, ThreadCount, 1);
|
|
|
|
// Diffuse setup
|
|
var dif_alpha = dx * dx / (_viscosity * dt);
|
|
_compute.SetFloat("Alpha", dif_alpha);
|
|
_compute.SetFloat("Beta", 4 + dif_alpha);
|
|
Graphics.CopyTexture(VFB.V2, VFB.V1);
|
|
_compute.SetTexture(Kernels.Jacobi2, "B2_in", VFB.V1);
|
|
|
|
// Jacobi iteration
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
_compute.SetTexture(Kernels.Jacobi2, "X2_in", VFB.V2);
|
|
_compute.SetTexture(Kernels.Jacobi2, "X2_out", VFB.V3);
|
|
_compute.Dispatch(Kernels.Jacobi2, ThreadCount, ThreadCount, 1);
|
|
|
|
_compute.SetTexture(Kernels.Jacobi2, "X2_in", VFB.V3);
|
|
_compute.SetTexture(Kernels.Jacobi2, "X2_out", VFB.V2);
|
|
_compute.Dispatch(Kernels.Jacobi2, ThreadCount, ThreadCount, 1);
|
|
}
|
|
|
|
float2 getUV(float2 pos, float2 camPos, float scale)
|
|
{
|
|
return -(pos-camPos)/scale + float2(.5f,.5f);
|
|
}
|
|
|
|
// Add external forces
|
|
foreach(var force in _forces)
|
|
{
|
|
var uv = getUV(force.Position, cameraIntPosition, _range);
|
|
_compute.SetVector("ForceOrigin", new Vector4(uv.x,uv.y));
|
|
_compute.SetFloat("ForceExponent", force.Exponent);
|
|
_compute.SetTexture(Kernels.Force, "W_in", VFB.V2);
|
|
_compute.SetTexture(Kernels.Force, "W_out", VFB.V3);
|
|
_compute.SetVector("ForceVector", new Vector4(force.Vector.x,force.Vector.y));
|
|
|
|
_compute.Dispatch(Kernels.Force, ThreadCount, ThreadCount, 1);
|
|
}
|
|
|
|
// Projection setup
|
|
_compute.SetTexture(Kernels.PSetup, "W_in", VFB.V3);
|
|
_compute.SetTexture(Kernels.PSetup, "DivW_out", VFB.V2);
|
|
_compute.SetTexture(Kernels.PSetup, "P_out", VFB.P1);
|
|
_compute.Dispatch(Kernels.PSetup, ThreadCount, ThreadCount, 1);
|
|
|
|
// Jacobi iteration
|
|
_compute.SetFloat("Alpha", -dx * dx);
|
|
_compute.SetFloat("Beta", 4);
|
|
_compute.SetTexture(Kernels.Jacobi1, "B1_in", VFB.V2);
|
|
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
_compute.SetTexture(Kernels.Jacobi1, "X1_in", VFB.P1);
|
|
_compute.SetTexture(Kernels.Jacobi1, "X1_out", VFB.P2);
|
|
_compute.Dispatch(Kernels.Jacobi1, ThreadCount, ThreadCount, 1);
|
|
|
|
_compute.SetTexture(Kernels.Jacobi1, "X1_in", VFB.P2);
|
|
_compute.SetTexture(Kernels.Jacobi1, "X1_out", VFB.P1);
|
|
_compute.Dispatch(Kernels.Jacobi1, ThreadCount, ThreadCount, 1);
|
|
}
|
|
|
|
// Projection finish
|
|
_compute.SetTexture(Kernels.PFinish, "W_in", VFB.V3);
|
|
_compute.SetTexture(Kernels.PFinish, "P_in", VFB.P1);
|
|
_compute.SetTexture(Kernels.PFinish, "U_out", VFB.V1);
|
|
_compute.Dispatch(Kernels.PFinish, ThreadCount, ThreadCount, 1);
|
|
|
|
_previousCameraIntPosition = cameraIntPosition;
|
|
}
|
|
|
|
#endregion
|
|
} |