

📒 GaussianClusterRotation.cs
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class GaussianClusterRotation : MonoBehaviour
{
[Header("Structure")]
[Range(100, 20000)] public int count = 5000;
[Range(0.1f, 10f)] public float radius = 5f;
[Range(0.1f, 5f)] public float sigma = 1.5f;
[Header("Rotation")]
public float minSpeed = 0.2f;
public float maxSpeed = 2.5f;
[Tooltip("중심부 속도 배율")]
[Range(1f, 5f)] public float centerSpeedBoost = 2.5f;
[Header("Size")]
public float minSize = 0.03f;
public float maxSize = 0.08f;
[Header("Color (HSV Spectrum)")]
[Tooltip("느린 속도 Hue (0 = Red, 0.15 = Yellow 등)")]
[Range(0f, 1f)] public float hueSlow = 0.0f;
[Tooltip("빠른 속도 Hue")]
[Range(0f, 1f)] public float hueFast = 0.12f;
[Range(0f, 1f)] public float saturation = 0.9f;
[Tooltip("최소 밝기")]
[Range(0f, 1f)] public float valueMin = 0.6f;
[Tooltip("최대 밝기")]
[Range(0f, 2f)] public float valueMax = 1.4f;
[Tooltip("색 대비 (지각 보정)")]
[Range(0.5f, 5f)] public float contrastPower = 2.2f;
struct Particle
{
public Vector3 basePos;
public float radius;
public float angle;
public float speed;
public float size;
public float speed01;
}
Particle[] _p;
Mesh _mesh;
Material _mat;
MaterialPropertyBlock _mpb;
const int BATCH = 1023;
readonly Matrix4x4[] _mBuf = new Matrix4x4[BATCH];
readonly Vector4[] _cBuf = new Vector4[BATCH];
#if UNITY_EDITOR
bool _queued;
#endif
void OnEnable()
{
Init();
if (_p == null || _p.Length == 0)
Generate();
}
void Update()
{
if (_mesh == null || _mat == null || _p == null) return;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Render(0f);
return;
}
#endif
Render(Time.time);
}
#if UNITY_EDITOR
void OnValidate()
{
Init();
if (_queued) return;
_queued = true;
EditorApplication.delayCall += () =>
{
_queued = false;
if (this == null) return;
Generate();
};
}
#endif
void Generate()
{
_p = new Particle[count];
for (int i = 0; i < count; i++)
{
Vector3 dir = Random.onUnitSphere;
float g = Mathf.Sqrt(-2f * Mathf.Log(Mathf.Max(0.0001f, Random.value)))
* Mathf.Cos(Random.value * Mathf.PI * 2f);
float r = Mathf.Min(Mathf.Abs(g) * sigma, 1f) * radius;
float radial01 = r / radius;
float speed = Random.Range(minSpeed, maxSpeed);
float centerBoost = Mathf.Lerp(centerSpeedBoost, 1f, radial01);
speed *= centerBoost;
float speed01 = Mathf.InverseLerp(minSpeed, maxSpeed, speed);
speed01 = Mathf.Pow(speed01, contrastPower);
_p[i] = new Particle
{
basePos = dir * r,
radius = r,
angle = Random.value * Mathf.PI * 2f,
speed = speed,
size = Random.Range(minSize, maxSize),
speed01 = speed01
};
}
}
void Render(float t)
{
int total = _p.Length;
int offset = 0;
while (offset < total)
{
int n = Mathf.Min(BATCH, total - offset);
for (int i = 0; i < n; i++)
{
ref Particle p = ref _p[offset + i];
float a = p.angle + t * p.speed;
float cos = Mathf.Cos(a);
float sin = Mathf.Sin(a);
Vector3 pos = new Vector3(
p.basePos.x * cos - p.basePos.z * sin,
p.basePos.y,
p.basePos.x * sin + p.basePos.z * cos
);
_mBuf[i] = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one * p.size);
float hue = Mathf.Lerp(hueSlow, hueFast, p.speed01);
float value = Mathf.Lerp(valueMin, valueMax, p.speed01);
Color c = Color.HSVToRGB(hue, saturation, value);
c *= Mathf.Lerp(0.6f, 1.4f, p.speed01);
_cBuf[i] = new Vector4(c.r, c.g, c.b, 1f);
}
_mpb.Clear();
_mpb.SetVectorArray("_BaseColor", _cBuf);
Graphics.DrawMeshInstanced(_mesh, 0, _mat, _mBuf, n, _mpb);
offset += n;
}
}
void Init()
{
if (_mesh == null)
_mesh = Resources.GetBuiltinResource<Mesh>("Sphere.fbx");
if (_mat == null)
{
var sh = Shader.Find("Universal Render Pipeline/Unlit");
if (sh == null)
{
Debug.LogError("URP Unlit shader not found");
return;
}
_mat = new Material(sh);
_mat.enableInstancing = true;
}
_mpb ??= new MaterialPropertyBlock();
}
}