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>
357 lines
12 KiB
C#
357 lines
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using CultMath;
|
|
using CultMath.UnityBridge;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.Serialization;
|
|
using static CultMath.math;
|
|
using Random = UnityEngine.Random;
|
|
|
|
public class LightningCompute : MonoBehaviour
|
|
{
|
|
public Camera Camera;
|
|
|
|
[Header("Bolt Properties")]
|
|
public Vector3 StartPosition;
|
|
public Vector3 EndPosition;
|
|
public float StartWidth;
|
|
public float EndWidth;
|
|
public float BranchLength;
|
|
public float MorphSpeed;
|
|
public float NoiseAmplitude;
|
|
public float NoiseFrequency;
|
|
public float NoiseGain;
|
|
public float NoiseLacunarity;
|
|
public bool FixedEndpoint;
|
|
public bool Animate;
|
|
|
|
[Header("Animation Properties")]
|
|
public float LeaderWidth;
|
|
public float LeaderDuration;
|
|
public float LeaderIntensity;
|
|
public float PulseDuration;
|
|
public float PulseIntensity;
|
|
public float PulseWidth;
|
|
public AnimationCurve PulseTrunkIntensityCurve;
|
|
public AnimationCurve PulseBranchIntensityCurve;
|
|
public AnimationCurve PulseWidthCurve;
|
|
|
|
[Header("Buffer Sizes")]
|
|
public int TrunkNodeCount;
|
|
public int BranchCount;
|
|
public int BranchNodeCount;
|
|
|
|
[Header("Shaders")]
|
|
public Material RenderMaterial;
|
|
public ComputeShader ComputeShader;
|
|
|
|
public bool DebugDrawNodes;
|
|
public bool DebugDrawVertices;
|
|
|
|
public Action OnLeaderComplete;
|
|
public Action OnPulseComplete;
|
|
|
|
private Transform _cameraTransform;
|
|
|
|
private ComputeBuffer _nodeBuffer;
|
|
private ComputeBuffer _vertexBuffer;
|
|
private ComputeBuffer _indexBuffer;
|
|
private ComputeBuffer _boltBuffer;
|
|
private Bolt[] _bolts;
|
|
private Vector3[] _branchEndpoints;
|
|
|
|
private const int GROUP_SIZE = 64;
|
|
private int _updateKernel;
|
|
private int _createWidthKernel;
|
|
private float _animationStartTime;
|
|
private float _time;
|
|
private bool _leaderComplete;
|
|
private bool _pulseComplete;
|
|
private bool _initialized;
|
|
private Material _materialInstance;
|
|
|
|
private int NodeBufferSize => TrunkNodeCount + BranchCount * BranchNodeCount;
|
|
private int VertexBufferSize => NodeBufferSize * 2;
|
|
private int IndexBufferSize => (NodeBufferSize - (BranchCount + 1)) * 6;
|
|
|
|
public struct Node
|
|
{
|
|
public Vector3 pos;
|
|
public int bolt;
|
|
public float time;
|
|
}
|
|
|
|
public struct Vertex
|
|
{
|
|
public Vector3 pos;
|
|
public Vector2 uv;
|
|
}
|
|
|
|
public struct Bolt
|
|
{
|
|
public Vector3 startPos;
|
|
public Vector3 endPos;
|
|
public float startTime;
|
|
public int startIndex;
|
|
public Vector3 startOffset;
|
|
public Vector3 endOffset;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
Initialize();
|
|
_materialInstance = Instantiate(RenderMaterial);
|
|
}
|
|
|
|
void Initialize()
|
|
{
|
|
if (_initialized) return;
|
|
_initialized = true;
|
|
|
|
if (!Camera)
|
|
Camera = Camera.main;
|
|
_cameraTransform = Camera.transform;
|
|
_updateKernel = ComputeShader.FindKernel("UpdateNodes");
|
|
_createWidthKernel = ComputeShader.FindKernel("CreateWidth");
|
|
|
|
_nodeBuffer = new ComputeBuffer(NodeBufferSize, Marshal.SizeOf(typeof(Node)));
|
|
var nodeList = new List<Node>();
|
|
for (int i = 0; i < TrunkNodeCount; i++)
|
|
{
|
|
nodeList.Add(new Node{bolt = 0, pos = Vector3.zero, time = (float)i/(TrunkNodeCount-1)});
|
|
}
|
|
|
|
for (int branch = 0; branch < BranchCount; branch++)
|
|
{
|
|
for (int branchNode = 0; branchNode < BranchNodeCount; branchNode++)
|
|
{
|
|
nodeList.Add(new Node{bolt = branch+1, pos = Vector3.zero, time = (float)branchNode/(BranchNodeCount-1)});
|
|
}
|
|
}
|
|
|
|
_nodeBuffer.SetData(nodeList.ToArray());
|
|
|
|
|
|
_boltBuffer = new ComputeBuffer(BranchCount + 1, Marshal.SizeOf(typeof(Bolt)));
|
|
_branchEndpoints = new Vector3[BranchCount];
|
|
_bolts = new Bolt[BranchCount + 1];
|
|
_bolts[0] = new Bolt
|
|
{
|
|
startPos = StartPosition,
|
|
endPos = EndPosition,
|
|
startTime = 0
|
|
};
|
|
|
|
_boltBuffer.SetData(_bolts);
|
|
|
|
_vertexBuffer = new ComputeBuffer(VertexBufferSize, Marshal.SizeOf(typeof(Vertex))); // 1 node to 2 vtx(left,right)
|
|
_vertexBuffer.SetData(Enumerable.Repeat(default(Vertex), _vertexBuffer.count).ToArray());
|
|
|
|
var indexData = new int[IndexBufferSize];
|
|
var index = 0;
|
|
for (var iNode = 0; iNode < TrunkNodeCount - 1; iNode++)
|
|
{
|
|
var offset = iNode * 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;
|
|
}
|
|
for (int iBranch = 0; iBranch < BranchCount; iBranch++)
|
|
{
|
|
for (int iBranchNode = 0; iBranchNode < BranchNodeCount - 1; iBranchNode++)
|
|
{
|
|
var offset = iBranchNode * 2 + (TrunkNodeCount) * 2 + iBranch * (BranchNodeCount) * 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 node to 2 triangles(6vertexs)
|
|
_indexBuffer.SetData(indexData);
|
|
}
|
|
|
|
public void StartAnimation()
|
|
{
|
|
Initialize();
|
|
_animationStartTime = Time.time;
|
|
_time = Random.value * 1000;
|
|
_leaderComplete = false;
|
|
_pulseComplete = false;
|
|
|
|
for (int i = 0; i < BranchCount; i++)
|
|
{
|
|
_branchEndpoints[i] = Random.insideUnitSphere * BranchLength;
|
|
_bolts[i+1] = new Bolt
|
|
{
|
|
startTime = Random.value * .8f
|
|
};
|
|
}
|
|
}
|
|
|
|
float fbm(float2 p)
|
|
{
|
|
float freq = NoiseFrequency, amp = NoiseAmplitude;
|
|
float sum = 0;
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
sum += snoise(p * freq) * amp;
|
|
freq *= NoiseLacunarity;
|
|
amp *= NoiseGain;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
float3 fbm3(float2 p)
|
|
{
|
|
return float3(
|
|
fbm(p),
|
|
fbm(p+10),
|
|
fbm(p+20));
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
_time += Time.deltaTime * MorphSpeed;
|
|
|
|
if(Animate)
|
|
{
|
|
var leaderLerp = (Time.time - _animationStartTime) / LeaderDuration;
|
|
if (leaderLerp > 1 && !_leaderComplete)
|
|
{
|
|
OnLeaderComplete?.Invoke();
|
|
_leaderComplete = true;
|
|
}
|
|
|
|
var pulseLerp = (Time.time - _animationStartTime - LeaderDuration) / PulseDuration;
|
|
if (pulseLerp > 1 && !_pulseComplete)
|
|
{
|
|
OnPulseComplete?.Invoke();
|
|
_pulseComplete = true;
|
|
}
|
|
ComputeShader.SetFloat("_StartWidth", leaderLerp < 1 ? LeaderWidth : StartWidth * PulseWidthCurve.Evaluate(pulseLerp)*PulseWidth);
|
|
ComputeShader.SetFloat("_EndWidth", leaderLerp < 1 ? LeaderWidth : EndWidth * PulseWidthCurve.Evaluate(pulseLerp)*PulseWidth);
|
|
|
|
_materialInstance.SetFloat("_LeaderProgress", leaderLerp);
|
|
_materialInstance.SetFloat("_BranchIntensity", leaderLerp < 1 ? LeaderIntensity : PulseBranchIntensityCurve.Evaluate(pulseLerp) * PulseIntensity);
|
|
_materialInstance.SetFloat("_Intensity", leaderLerp < 1 ? LeaderIntensity : PulseTrunkIntensityCurve.Evaluate(pulseLerp) * PulseIntensity);
|
|
}
|
|
else
|
|
{
|
|
ComputeShader.SetFloat("_StartWidth", StartWidth);
|
|
ComputeShader.SetFloat("_EndWidth", EndWidth);
|
|
}
|
|
var diff = EndPosition - StartPosition;
|
|
var dist = diff.magnitude;
|
|
_bolts[0].startPos = StartPosition;
|
|
_bolts[0].startOffset = -fbm3(float2(0, _time)).ToUnity();
|
|
_bolts[0].endPos = EndPosition;
|
|
_bolts[0].endOffset = FixedEndpoint ? -fbm3(float2(dist, _time)).ToUnity() : Vector3.zero;
|
|
|
|
for (int i = 0; i < BranchCount; i++)
|
|
{
|
|
var root = Vector3.Lerp(StartPosition, EndPosition, _bolts[i + 1].startTime);
|
|
var rootDist = _bolts[i + 1].startTime * dist;
|
|
_bolts[i + 1].startPos =
|
|
root + fbm3(float2(rootDist, _time)).ToUnity() +
|
|
_bolts[0].startOffset * (max(.5f - _bolts[i + 1].startTime, 0) * 2) +
|
|
_bolts[0].endOffset * (max(_bolts[i + 1].startTime - .5f, 0) * 2);
|
|
_bolts[i + 1].startOffset = -fbm3(float2(0, _time + (i + 1) * 10)).ToUnity();
|
|
_bolts[i + 1].endPos = root + _branchEndpoints[i];
|
|
}
|
|
|
|
_boltBuffer.SetData(_bolts);
|
|
|
|
ComputeShader.SetBuffer(_updateKernel, "_NodeBuffer", _nodeBuffer);
|
|
ComputeShader.SetBuffer(_updateKernel, "_BoltBuffer", _boltBuffer);
|
|
|
|
ComputeShader.SetFloat("_Time", _time);
|
|
ComputeShader.SetFloat("_NoiseAmplitude", NoiseAmplitude);
|
|
ComputeShader.SetFloat("_NoiseFrequency", NoiseFrequency);
|
|
ComputeShader.SetFloat("_NoiseGain", NoiseGain);
|
|
ComputeShader.SetFloat("_NoiseLacunarity", NoiseLacunarity);
|
|
|
|
ComputeShader.Dispatch(_updateKernel, NodeBufferSize/GROUP_SIZE, 1, 1);
|
|
|
|
ComputeShader.SetInt("_TrunkNodeCount", TrunkNodeCount);
|
|
ComputeShader.SetInt("_BranchNodeCount", BranchNodeCount);
|
|
ComputeShader.SetVector("_CameraPos", _cameraTransform.position);
|
|
|
|
ComputeShader.SetBuffer(_createWidthKernel, "_NodeBuffer", _nodeBuffer);
|
|
ComputeShader.SetBuffer(_createWidthKernel, "_BoltBuffer", _boltBuffer);
|
|
ComputeShader.SetBuffer(_createWidthKernel, "_VertexBuffer", _vertexBuffer);
|
|
|
|
ComputeShader.Dispatch(_createWidthKernel, NodeBufferSize/GROUP_SIZE, 1, 1);
|
|
|
|
_materialInstance.SetBuffer("_IndexBuffer", _indexBuffer);
|
|
_materialInstance.SetBuffer("_VertexBuffer", _vertexBuffer);
|
|
Graphics.DrawProcedural(
|
|
_materialInstance,
|
|
new Bounds((EndPosition + StartPosition) / 2, (EndPosition - StartPosition).magnitude * Vector3.one),
|
|
MeshTopology.Triangles,
|
|
IndexBufferSize,
|
|
1,
|
|
Camera,
|
|
null,
|
|
ShadowCastingMode.Off,
|
|
false,
|
|
0);
|
|
//
|
|
// if(pulseLerp > 2)
|
|
// StartAnimation();
|
|
}
|
|
|
|
#region cleanup
|
|
void OnDestroy()
|
|
{
|
|
if(_indexBuffer!=null)
|
|
{
|
|
_indexBuffer.Release();
|
|
_boltBuffer.Release();
|
|
_nodeBuffer.Release();
|
|
_vertexBuffer.Release();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
public void OnDrawGizmosSelected()
|
|
{
|
|
if (DebugDrawNodes && _nodeBuffer != null)
|
|
{
|
|
Gizmos.color = Color.magenta;
|
|
var data = new Node[_nodeBuffer.count];
|
|
_nodeBuffer.GetData(data);
|
|
foreach(var node in data)
|
|
{
|
|
Gizmos.DrawWireSphere(node.pos, StartWidth);
|
|
};
|
|
}
|
|
|
|
if (DebugDrawVertices && _vertexBuffer != null)
|
|
{
|
|
Gizmos.color = Color.yellow;
|
|
var data = new Vertex[_vertexBuffer.count];
|
|
_vertexBuffer.GetData(data);
|
|
|
|
var num = _vertexBuffer.count / 2;
|
|
for(var i=0; i< num; ++i)
|
|
{
|
|
var v0 = data[2*i];
|
|
var v1 = data[2*i +1];
|
|
|
|
Gizmos.DrawLine(v0.pos, v1.pos);
|
|
}
|
|
}
|
|
}
|
|
}
|