🫧Art_007 GenArt Arrangements

BamgasiJMΒ·2026λ…„ 3μ›” 18일

Unity GenArt

λͺ©λ‘ 보기
18/41
post-thumbnail

λ‹€μ–‘ν•œ λ°°μ—΄ λͺ¨λ“œλ₯Ό μ μš©ν•  수 μžˆλŠ” λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ

πŸ“„GenArtArrangements.cs

using UnityEngine;
using System.Collections.Generic;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class GenArtArrangements : MonoBehaviour
{
    // ============================================================
    // λ°°μ—΄ λͺ¨λ“œ
    // ============================================================
    public enum ArrangementMode
    {
        Grid,           // 직ꡐ 격자
        HexGrid,        // 윑각 격자
        SineWave,       // XμΆ• μ‚¬μΈνŒŒ λ³€μœ„
        NoiseField,     // Perlin noise 3D λ³€μœ„
        Circle,         // 동심원
        Spiral,         // μ•„λ₯΄ν‚€λ©”λ°μŠ€ λ‚˜μ„ 
        Phyllotaxis,    // ν”Όλ³΄λ‚˜μΉ˜/해바라기 λ°°μ—΄
        Lissajous,      // 리사주 곑선
    }

    // ============================================================
    // μΈμŠ€νŽ™ν„° νŒŒλΌλ―Έν„°
    // ============================================================

    [Header("λ°°μ—΄ λͺ¨λ“œ")]
    public ArrangementMode mode = ArrangementMode.Grid;

    // ----- ꡬ쑰 νŒŒλΌλ―Έν„° (λ³€κ²½ μ‹œ μž¬μƒμ„±) -------------------------
    [Header("ꡬ쑰 μ„€μ • (λ³€κ²½ μ‹œ 였브젝트 μž¬μƒμ„±)")]
    public int countX = 20;
    public int countY = 20;
    public float spacing = 1.0f;
    public float objectSize = 0.3f;

    [Header("Noise Field μ„€μ •")]
    public float noiseScale = 0.25f;
    public float noiseAmplitude = 2.5f;

    [Header("Sine Wave μ„€μ •")]
    public float sineFrequency = 1.0f;
    public float sineAmplitude = 2.0f;

    [Header("Phyllotaxis μ„€μ •")]
    [Tooltip("ν™©κΈˆκ° = 137.5077Β°. 살짝 λ°”κΎΈλ©΄ μ „ν˜€ λ‹€λ₯Έ νŒ¨ν„΄μ΄ λ‚˜μ˜΅λ‹ˆλ‹€")]
    public float phyllotaxisAngle = 137.5077f;
    public float phyllotaxisRadius = 0.4f;

    [Header("Lissajous μ„€μ •")]
    public float lissA = 3f;
    public float lissB = 2f;
    public float lissDelta = 0f;   // μœ„μƒμ°¨ (λΌλ””μ•ˆ)
    public float lissScale = 8f;

    // ----- μ• λ‹ˆλ©”μ΄μ…˜ νŒŒλΌλ―Έν„° -----------------------------------
    [Header("μ• λ‹ˆλ©”μ΄μ…˜ μ„€μ • (μ‹€μ‹œκ°„ 반영)")]
    public float animSpeed = 1.0f;
    public float waveHeight = 1.5f;
    public float waveFrequency = 0.6f;

    // ----- 재질 μ„€μ • --------------------------------------------
    [Header("재질 μ„€μ •")]
    public Mesh instanceMesh;
    public Material instanceMaterial;

    // ============================================================
    // λ‚΄λΆ€ 데이터
    // ============================================================
    struct InstanceData
    {
        public Vector3 basePos;
        public float noiseOffset; // μΈμŠ€ν„΄μŠ€λ³„ μœ„μƒ (noise/wave λ³€μ£Όμš©)
    }

    List<InstanceData> _instances = new List<InstanceData>();
    Matrix4x4[] _matrices;
    int _totalCount;

    const int BATCH_SIZE = 1023;
    bool _generated = false;

    enum RenderPipeline { BuiltIn, URP, HDRP }
    RenderPipeline _pipeline;

    // ꡬ쑰 νŒŒλΌλ―Έν„° μΊμ‹œ
    ArrangementMode _cachedMode;
    int _cachedCountX, _cachedCountY;
    float _cachedSpacing, _cachedObjectSize;
    float _cachedNoiseScale, _cachedNoiseAmplitude;
    float _cachedSineFrequency, _cachedSineAmplitude;
    float _cachedPhylAngle, _cachedPhylRadius;
    float _cachedLissA, _cachedLissB, _cachedLissDelta, _cachedLissScale;

#if UNITY_EDITOR
    bool _generateQueued = false;
#endif

    // ============================================================
    // Unity 이벀트
    // ============================================================
    void OnEnable()
    {
        _pipeline = DetectPipeline();
        EnsureResources();
        Generate();
        CacheStructureParams();
    }

    void OnValidate()
    {
#if UNITY_EDITOR
        _pipeline = DetectPipeline();
        EnsureResources();
        if (StructureParamsChanged())
        {
            CacheStructureParams();
            QueueGenerate();
        }
        else
        {
            Animate(0f);
        }
#endif
    }

#if UNITY_EDITOR
    void QueueGenerate()
    {
        if (_generateQueued) return;
        _generateQueued = true;
        EditorApplication.delayCall += () =>
        {
            _generateQueued = false;
            if (this == null) return;
            Generate();
        };
    }
#endif

    void Update()
    {
        if (!_generated) return;

        if (!Application.isPlaying)
        {
            Animate(0f);
            RenderInstances();
            return;
        }

        Animate(Time.time * animSpeed);
        RenderInstances();
    }

    // ============================================================
    // [μ•„νŠΈμ›Œν¬ 둜직] Generate β€” λ°°μ—΄ 곡식
    // ============================================================
    void Generate()
    {
        _instances.Clear();
        _totalCount = countX * countY;

        for (int y = 0; y < countY; y++)
            for (int x = 0; x < countX; x++)
            {
                int idx = y * countX + x;
                float fx = x - (countX - 1) * 0.5f;   // 쀑심 κΈ°μ€€ μ •κ·œν™”
                float fy = y - (countY - 1) * 0.5f;

                Vector3 pos = Vector3.zero;

                switch (mode)
                {
                    // ── 직ꡐ 격자 ─────────────────────────────────────────
                    // κ°€μž₯ κΈ°λ³Έ. spacing으둜 XZ 평면에 κ· λ“± 배치
                    case ArrangementMode.Grid:
                        pos = new Vector3(fx * spacing, 0f, fy * spacing);
                        break;

                    // ── 윑각 격자 ────────────────────────────────────────
                    // 짝수 행을 spacing * 0.5 만큼 X μ˜€ν”„μ…‹ν•˜κ³ 
                    // Y 간격을 √3/2 배둜 쀄여 μ •μ‚Όκ°ν˜• 타일을 ꡬ성
                    case ArrangementMode.HexGrid:
                        {
                            float xOffset = (y % 2 == 0) ? 0f : spacing * 0.5f;
                            pos = new Vector3(
                                fx * spacing + xOffset,
                                0f,
                                fy * spacing * 0.866f);   // 0.866 β‰ˆ √3/2
                            break;
                        }

                    // ── XμΆ• μ‚¬μΈνŒŒ ───────────────────────────────────────
                    // X μœ„μΉ˜λ₯Ό 인수둜 μ‚¬μΈνŒŒλ₯Ό 계산해 Y λ°©ν–₯으둜 λ³€μœ„
                    // Generate μ‹œμ—” t=0 μŠ€λƒ…μƒ·, Animateμ—μ„œ μ‹€μ‹œκ°„ κ°±μ‹ 
                    case ArrangementMode.SineWave:
                        pos = new Vector3(fx * spacing, 0f, fy * spacing);
                        break;

                    // ── Perlin Noise ν•„λ“œ ────────────────────────────────
                    // 각 μΈμŠ€ν„΄μŠ€ μœ„μΉ˜λ₯Ό seed둜 λ…Έμ΄μ¦ˆλ₯Ό μƒ˜ν”Œλ§ν•΄ Y λ³€μœ„
                    // noiseOffset에 μƒ˜ν”Œ μ’Œν‘œλ₯Ό μ €μž₯ν•΄ Animateμ—μ„œ μž¬μ‚¬μš©
                    case ArrangementMode.NoiseField:
                        pos = new Vector3(fx * spacing, 0f, fy * spacing);
                        break;

                    // ── 동심원 ───────────────────────────────────────────
                    // y = λ°˜μ§€λ¦„ 인덱슀, x = 각도 인덱슀
                    // 각 링에 countX개 였브젝트λ₯Ό κ· λ“± 배치
                    case ArrangementMode.Circle:
                        {
                            float radius = (y + 1) * spacing;
                            float angle = (float)x / countX * Mathf.PI * 2f;
                            pos = new Vector3(
                                Mathf.Cos(angle) * radius,
                                0f,
                                Mathf.Sin(angle) * radius);
                            break;
                        }

                    // ── μ•„λ₯΄ν‚€λ©”λ°μŠ€ λ‚˜μ„  ─────────────────────────────────
                    // 각도와 λ°˜μ§€λ¦„μ΄ λͺ¨λ‘ μΈλ±μŠ€μ— λΉ„λ‘€ν•˜μ—¬ 증가
                    // countYλ₯Ό κ°λŠ” 횟수둜 μ‚¬μš©
                    case ArrangementMode.Spiral:
                        {
                            float t = (float)idx / _totalCount;
                            float angle = t * Mathf.PI * 2f * countY;
                            float r = t * spacing * countX * 0.5f;
                            pos = new Vector3(Mathf.Cos(angle) * r, 0f, Mathf.Sin(angle) * r);
                            break;
                        }

                    // ── ν”Όλ³΄λ‚˜μΉ˜ / 해바라기 (Phyllotaxis) ───────────────
                    // ν™©κΈˆκ°(137.5Β°)을 νšŒμ „ν•˜λ©΄μ„œ λ°˜μ§€λ¦„μ€ √n 에 λΉ„λ‘€
                    // phyllotaxisAngle을 μ‘°κΈˆμ”© λ°”κΎΈλ©΄ μ™„μ „νžˆ λ‹€λ₯Έ νŒ¨ν„΄
                    case ArrangementMode.Phyllotaxis:
                        {
                            float angle = idx * phyllotaxisAngle * Mathf.Deg2Rad;
                            float r = phyllotaxisRadius * Mathf.Sqrt(idx);
                            pos = new Vector3(Mathf.Cos(angle) * r, 0f, Mathf.Sin(angle) * r);
                            break;
                        }

                    // ── 리사주 곑선 (Lissajous) ──────────────────────────
                    // x(t) = sin(aΒ·t + Ξ΄),  z(t) = sin(bΒ·t)
                    // lissA, lissB λΉ„μœ¨μ— 따라 λ‹€μ–‘ν•œ 곑선 νŒ¨ν„΄ 생성
                    // countX * countY 개의 점을 곑선 μœ„μ— κ· λ“± 뢄포
                    case ArrangementMode.Lissajous:
                        {
                            float t = (float)idx / (_totalCount - 1) * Mathf.PI * 2f;
                            float px = Mathf.Sin(lissA * t + lissDelta) * lissScale;
                            float pz = Mathf.Sin(lissB * t) * lissScale;
                            // Y λ°©ν–₯으둜 살짝 λ¬΄μž‘μœ„ λ³€μœ„λ₯Ό μ£Όλ©΄ μž…μ²΄κ°μ΄ 생김
                            float py = Mathf.Cos((lissA + lissB) * t) * lissScale * 0.2f;
                            pos = new Vector3(px, py, pz);
                            break;
                        }
                }

                _instances.Add(new InstanceData
                {
                    basePos = pos,
                    noiseOffset = x * noiseScale + y * noiseScale * 1.3f  // λ…Έμ΄μ¦ˆ μƒ˜ν”Œ μ’Œν‘œ
                });
            }

        // GPU 전솑 λ°°μ—΄ μ΄ˆκΈ°ν™”
        _matrices = new Matrix4x4[_totalCount];
        _generated = true;

        Animate(0f);  // 초기 μŠ€λƒ…μƒ·
    }

    // ============================================================
    // [μ•„νŠΈμ›Œν¬ 둜직] Animate β€” λ§€ ν”„λ ˆμž„ λ³€μœ„ 곡식
    // ============================================================
    void Animate(float t)
    {
        if (!_generated || _instances.Count == 0) return;

        for (int i = 0; i < _instances.Count; i++)
        {
            InstanceData inst = _instances[i];
            Vector3 pos = inst.basePos;
            Vector3 scale = Vector3.one * objectSize;

            switch (mode)
            {
                case ArrangementMode.Grid:
                case ArrangementMode.HexGrid:
                case ArrangementMode.Circle:
                case ArrangementMode.Spiral:
                case ArrangementMode.Phyllotaxis:
                case ArrangementMode.Lissajous:
                    // κΈ°λ³Έ λ°°μ—΄λ“€: YμΆ•μœΌλ‘œ μž”μž”ν•œ μ‚¬μΈνŒŒ λ¦¬ν”Œ
                    float ripple = Mathf.Sin(
                        (pos.x + pos.z) * waveFrequency + t) * waveHeight * 0.3f;
                    pos.y += ripple;
                    break;

                case ArrangementMode.SineWave:
                    // X μœ„μΉ˜λ₯Ό 인수둜 μ‚¬μΈνŒŒ Y λ³€μœ„
                    pos.y = Mathf.Sin(pos.x * waveFrequency + t) * sineAmplitude;
                    break;

                case ArrangementMode.NoiseField:
                    // Perlin noise둜 Y λ³€μœ„ + μŠ€μΌ€μΌ λ³€μ‘°
                    float n = Mathf.PerlinNoise(inst.noiseOffset + t * 0.4f,
                                                inst.noiseOffset * 0.7f + t * 0.2f);
                    pos.y = (n - 0.5f) * 2f * noiseAmplitude;
                    scale = Vector3.one * objectSize * Mathf.Lerp(0.3f, 1.4f, n);
                    break;
            }

            Vector3 worldPos = transform.TransformPoint(pos);
            _matrices[i] = Matrix4x4.TRS(worldPos, transform.rotation, scale);
        }
    }

    // ============================================================
    // 인프라 μ½”λ“œ β€” μˆ˜μ • λΆˆν•„μš”
    // ============================================================
    void RenderInstances()
    {
        if (instanceMesh == null || instanceMaterial == null) return;

        int total = _instances.Count;
        int index = 0;
        while (total > 0)
        {
            int batch = Mathf.Min(BATCH_SIZE, total);
            var batchMatrices = new Matrix4x4[batch];
            System.Array.Copy(_matrices, index, batchMatrices, 0, batch);

            Graphics.DrawMeshInstanced(
                instanceMesh, 0, instanceMaterial, batchMatrices, batch);

            index += batch;
            total -= batch;
        }
    }

    void EnsureResources()
    {
        if (instanceMesh == null)
        {
            var tmp = GameObject.CreatePrimitive(PrimitiveType.Cube);
            instanceMesh = tmp.GetComponent<MeshFilter>().sharedMesh;
            DestroyImmediate(tmp);
        }
        if (instanceMaterial == null)
        {
            Shader shader = null;
            switch (_pipeline)
            {
                case RenderPipeline.URP: shader = Shader.Find("Universal Render Pipeline/Lit"); break;
                case RenderPipeline.HDRP: shader = Shader.Find("HDRP/Lit"); break;
                default: shader = Shader.Find("Standard"); break;
            }
            instanceMaterial = new Material(shader);
            instanceMaterial.enableInstancing = true;
        }
    }

    bool StructureParamsChanged() =>
        mode != _cachedMode ||
        countX != _cachedCountX ||
        countY != _cachedCountY ||
        spacing != _cachedSpacing ||
        objectSize != _cachedObjectSize ||
        noiseScale != _cachedNoiseScale ||
        noiseAmplitude != _cachedNoiseAmplitude ||
        sineFrequency != _cachedSineFrequency ||
        sineAmplitude != _cachedSineAmplitude ||
        phyllotaxisAngle != _cachedPhylAngle ||
        phyllotaxisRadius != _cachedPhylRadius ||
        lissA != _cachedLissA ||
        lissB != _cachedLissB ||
        lissDelta != _cachedLissDelta ||
        lissScale != _cachedLissScale;

    void CacheStructureParams()
    {
        _cachedMode = mode;
        _cachedCountX = countX;
        _cachedCountY = countY;
        _cachedSpacing = spacing;
        _cachedObjectSize = objectSize;
        _cachedNoiseScale = noiseScale;
        _cachedNoiseAmplitude = noiseAmplitude;
        _cachedSineFrequency = sineFrequency;
        _cachedSineAmplitude = sineAmplitude;
        _cachedPhylAngle = phyllotaxisAngle;
        _cachedPhylRadius = phyllotaxisRadius;
        _cachedLissA = lissA;
        _cachedLissB = lissB;
        _cachedLissDelta = lissDelta;
        _cachedLissScale = lissScale;
    }

    RenderPipeline DetectPipeline()
    {
        var rp = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
        if (rp == null) return RenderPipeline.BuiltIn;
        string name = rp.GetType().Name;
        if (name.Contains("Universal")) return RenderPipeline.URP;
        if (name.Contains("HighDefinition") || name.Contains("HDRP")) return RenderPipeline.HDRP;
        return RenderPipeline.BuiltIn;
    }
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0개의 λŒ“κΈ€