๐ŸซงArt_003 Perlin Noise Surface

BamgasiJMยท2026๋…„ 3์›” 13์ผ

Unity GenArt

๋ชฉ๋ก ๋ณด๊ธฐ
12/41
post-thumbnail

๐Ÿ“„Art_003_Perlin_Noise_Surface.cs

using UnityEngine;
using System.Collections.Generic;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class PerlinNoiseSurface : MonoBehaviour
{
    [Header("๊ทธ๋ฆฌ๋“œ ๊ตฌ์กฐ (๋ณ€๊ฒฝ ์‹œ ์˜ค๋ธŒ์ ํŠธ ์žฌ์ƒ์„ฑ)")]
    public int   gridSize = 20;
    public float spacing  = 1.2f;

    [Header("๋…ธ์ด์ฆˆ ์„ค์ •")]
    public float noiseScale  = 0.15f;
    public float heightScale = 3f;
    public float noiseSpeed  = 0.5f;

    [Header("ํฌ๊ธฐ ์„ค์ •")]
    public float sphereSize = 0.5f;

    [Header("์ƒ‰์ƒ ์„ค์ •")]
    public Gradient gradient;

    [Header("์žฌ์งˆ ์„ค์ •")]
    public Material baseMaterial;

    // ---------------------------------------------------
    // ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์บ์‹œ
    // ---------------------------------------------------
    private int   _cachedGridSize;
    private float _cachedSpacing;

    // ---------------------------------------------------
    // ๊ตฌ์ฒด ๋ฐ์ดํ„ฐ
    // ---------------------------------------------------
    struct SphereData
    {
        public Transform tr;
        public Material  mat;
    }

    List<SphereData> _spheres = new List<SphereData>();

#if UNITY_EDITOR
    bool _generateQueued = false;
#endif

    // ---------------------------------------------------
    // Unity ์ด๋ฒคํŠธ
    // ---------------------------------------------------

    void OnEnable()
    {
        if (gradient == null || gradient.colorKeys.Length == 0)
            gradient = CreateDefaultGradient();

        Generate();
        CacheStructureParams();
    }

    void OnValidate()
    {
#if UNITY_EDITOR
        if (gradient == null || gradient.colorKeys.Length == 0)
            gradient = CreateDefaultGradient();

        if (StructureParamsChanged())
        {
            CacheStructureParams();
            QueueGenerate();
        }
        else
        {
            ApplyAnimation(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 UNITY_EDITOR
        if (!Application.isPlaying) return;
#endif
        ApplyAnimation(Time.time * noiseSpeed);
    }

    // ---------------------------------------------------
    // ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ ๊ฐ์ง€
    // ---------------------------------------------------

    bool StructureParamsChanged() =>
        gridSize != _cachedGridSize || spacing != _cachedSpacing;

    void CacheStructureParams()
    {
        _cachedGridSize = gridSize;
        _cachedSpacing  = spacing;
    }

    // ---------------------------------------------------
    // ์ƒ์„ฑ
    // ---------------------------------------------------

    void Generate()
    {
        Clear();

        float offset = (gridSize - 1) * spacing * 0.5f;

        for (int x = 0; x < gridSize; x++)
        for (int z = 0; z < gridSize; z++)
        {
            Vector3 pos = new Vector3(x * spacing - offset, 0f, z * spacing - offset);
            SpawnSphere(pos);
        }

        ApplyAnimation(0f);
    }

    void SpawnSphere(Vector3 pos)
    {
        if (baseMaterial == null)
        {
            Debug.LogWarning("[PerlinNoiseSurface] baseMaterial์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Inspector์—์„œ URP/Lit ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ํ• ๋‹นํ•ด์ฃผ์„ธ์š”.");
            return;
        }

        GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        go.transform.SetParent(transform);
        go.transform.localPosition = pos;
        go.transform.localScale    = Vector3.one * sphereSize;

        Material mat = Instantiate(baseMaterial);
        mat.EnableKeyword("_EMISSION");
        mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;

        go.GetComponent<Renderer>().material = mat;
        _spheres.Add(new SphereData { tr = go.transform, mat = mat });
    }

    void Clear()
    {
        _spheres.Clear();
        while (transform.childCount > 0)
            DestroyImmediate(transform.GetChild(0).gameObject);
    }

    // ---------------------------------------------------
    // ์• ๋‹ˆ๋ฉ”์ด์…˜
    // ---------------------------------------------------

    void ApplyAnimation(float t)
    {
        for (int i = 0; i < _spheres.Count; i++)
        {
            SphereData s = _spheres[i];
            if (s.tr == null || s.mat == null) continue;

            Vector3 p = s.tr.localPosition;

            float n = Mathf.PerlinNoise(
                p.x * noiseScale + t,
                p.z * noiseScale + t * 0.8f
            );

            p.y = n * heightScale;
            s.tr.localPosition = p;
            s.tr.localScale    = Vector3.one * (sphereSize * (0.6f + n * 0.8f));

            Color albedo   = gradient.Evaluate(n);
            Color emission = albedo * (n * 0.6f);

            s.mat.SetColor("_BaseColor",     albedo);
            s.mat.SetColor("_EmissionColor", emission);
        }
    }

    // ---------------------------------------------------
    // ์œ ํ‹ธ๋ฆฌํ‹ฐ
    // ---------------------------------------------------

    Gradient CreateDefaultGradient()
    {
        Gradient g = new Gradient();
        g.SetKeys(
            new GradientColorKey[] {
                new GradientColorKey(Color.blue,   0.0f),
                new GradientColorKey(Color.cyan,   0.4f),
                new GradientColorKey(Color.green,  0.7f),
                new GradientColorKey(Color.yellow, 1.0f)
            },
            new GradientAlphaKey[] {
                new GradientAlphaKey(1f, 0f),
                new GradientAlphaKey(1f, 1f)
            }
        );
        return g;
    }
}

์ฃผ์„

using UnityEngine;
using System.Collections.Generic;

// UNITY_EDITOR: ์—๋””ํ„ฐ ์ „์šฉ ๊ธฐ๋Šฅ(EditorApplication ๋“ฑ)์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด๋ถ€ ๋„ค์ž„์ŠคํŽ˜์ด์Šค
#if UNITY_EDITOR
using UnityEditor;
#endif

// [ExecuteAlways]: ํ”Œ๋ ˆ์ด ๋ชจ๋“œ๋ฟ ์•„๋‹ˆ๋ผ ์—๋””ํ„ฐ ์”ฌ ๋ทฐ์—์„œ๋„ Update/OnEnable ๋“ฑ์ด ์‹คํ–‰๋˜๋„๋ก ํ—ˆ์šฉ
[ExecuteAlways]
public class PerlinNoiseSurface : MonoBehaviour
{
    // -------------------------------------------------------
    // Inspector ๋…ธ์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ
    // -------------------------------------------------------

    [Header("๊ทธ๋ฆฌ๋“œ ๊ตฌ์กฐ (๋ณ€๊ฒฝ ์‹œ ์˜ค๋ธŒ์ ํŠธ ์žฌ์ƒ์„ฑ)")]
    // gridSize: ๊ฐ€๋กœยท์„ธ๋กœ ๋ฐฉํ–ฅ์— ๋ฐฐ์น˜ํ•  ๊ตฌ์ฒด์˜ ์ˆ˜. ์ „์ฒด ๊ตฌ์ฒด ์ˆ˜ = gridSize * gridSize
    public int   gridSize = 20;
    // spacing: ์ด์›ƒํ•œ ๊ตฌ์ฒด ์‚ฌ์ด์˜ ๊ฐ„๊ฒฉ(์›”๋“œ ์œ ๋‹›). ๊ฐ’์ด ํด์ˆ˜๋ก ๊ตฌ์ฒด๊ฐ€ ๋„“๊ฒŒ ํผ์ง
    public float spacing  = 1.2f;

    [Header("๋…ธ์ด์ฆˆ ์„ค์ •")]
    // noiseScale: PerlinNoise์— ๋„˜๊ธฐ๋Š” ์ขŒํ‘œ ๋ฐฐ์œจ. ์ž‘์„์ˆ˜๋ก ๋„“๊ณ  ์™„๋งŒํ•œ ํŒŒํ˜•, ํด์ˆ˜๋ก ์ข๊ณ  ๋น ๋ฅธ ํŒŒํ˜•
    public float noiseScale  = 0.15f;
    // heightScale: ๋…ธ์ด์ฆˆ ๊ฐ’(0~1)์— ๊ณฑํ•ด Y์ถ• ๋ณ€์œ„๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์Šค์ผ€์ผ. ๋†’์„์ˆ˜๋ก ํŒŒ๊ณ ๊ฐ€ ํผ
    public float heightScale = 3f;
    // noiseSpeed: ์‹œ๊ฐ„์ถ• ์˜คํ”„์…‹ ์†๋„. Update์—์„œ Time.time์— ๊ณฑํ•ด ๋…ธ์ด์ฆˆ๊ฐ€ ํ๋ฅด๋Š” ์†๋„๋ฅผ ๊ฒฐ์ •
    public float noiseSpeed  = 0.5f;

    [Header("ํฌ๊ธฐ ์„ค์ •")]
    // sphereSize: ๊ฐ ๊ตฌ์ฒด์˜ ๊ธฐ๋ณธ localScale ๊ฐ’. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ค‘ ๋งฅ๋™์˜ ๊ธฐ์ค€ ํฌ๊ธฐ๋กœ๋„ ์‚ฌ์šฉ๋จ
    public float sphereSize = 0.5f;

    [Header("์ƒ‰์ƒ ์„ค์ •")]
    // gradient: ๋…ธ์ด์ฆˆ ๊ฐ’(0~1)์„ ์ƒ‰์ƒ์œผ๋กœ ๋งคํ•‘ํ•˜๋Š” ๊ทธ๋ผ๋””์–ธํŠธ.
    //           Evaluate(n)์œผ๋กœ ๋‚ฎ์€ ์ง€์ ~๋†’์€ ์ง€์ ์˜ ์ƒ‰์„ ์—ฐ์†์ ์œผ๋กœ ๋ณด๊ฐ„ํ•จ
    public Gradient gradient;

    [Header("์žฌ์งˆ ์„ค์ •")]
    // baseMaterial: ๊ตฌ์ฒด ์ƒ์„ฑ ์‹œ Instantiate์˜ ์›๋ณธ์ด ๋˜๋Š” URP Lit ๋จธํ‹ฐ๋ฆฌ์–ผ.
    //              Inspector์—์„œ ๋ฐ˜๋“œ์‹œ URP/Lit ๊ธฐ๋ฐ˜ ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ์ง์ ‘ ํ• ๋‹นํ•ด์•ผ ํ•จ
    public Material baseMaterial;

    // -------------------------------------------------------
    // ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์บ์‹œ โ€” OnValidate ๋น„๊ต์šฉ
    // -------------------------------------------------------

    // _cachedGridSize / _cachedSpacing: ๋งˆ์ง€๋ง‰์œผ๋กœ Generate()๊ฐ€ ์‹คํ–‰๋์„ ๋•Œ์˜
    // gridSizeยทspacing ๊ฐ’์„ ์ €์žฅ. OnValidate์—์„œ ํ˜„์žฌ ๊ฐ’๊ณผ ๋น„๊ตํ•ด
    // ์‹ค์ œ๋กœ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ๋จ
    private int   _cachedGridSize;
    private float _cachedSpacing;

    // -------------------------------------------------------
    // ๊ตฌ์ฒด ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด
    // -------------------------------------------------------

    // SphereData: ๊ฐœ๋ณ„ ๊ตฌ์ฒด์˜ Transform๊ณผ ์ธ์Šคํ„ด์Šค Material์„ ๋ฌถ๋Š” ๊ฒฝ๋Ÿ‰ ๊ตฌ์กฐ์ฒด.
    //             ํด๋ž˜์Šค ๋Œ€์‹  struct๋ฅผ ์‚ฌ์šฉํ•ด ํž™ ํ• ๋‹น ์—†์ด List์— ๊ฐ’ ๋ณต์‚ฌ๋กœ ์ €์žฅ๋จ
    struct SphereData
    {
        // tr: ๊ตฌ์ฒด GameObject์˜ Transform. ์œ„์น˜(Y)์™€ ํฌ๊ธฐ(localScale)๋ฅผ ๋งค ํ”„๋ ˆ์ž„ ๊ฐฑ์‹ ํ•  ๋•Œ ์‚ฌ์šฉ
        public Transform tr;
        // mat: baseMaterial์„ Instantiateํ•œ ๊ฐœ๋ณ„ ์ธ์Šคํ„ด์Šค. ๊ตฌ์ฒด๋งˆ๋‹ค ๋…๋ฆฝ์ ์ธ ์ƒ‰์ƒ ์ ์šฉ์„ ์œ„ํ•ด ๋ณต์ œํ•จ
        public Material  mat;
    }

    // _spheres: ์ƒ์„ฑ๋œ ๋ชจ๋“  ๊ตฌ์ฒด์˜ SphereData๋ฅผ ๋‹ด๋Š” ๋ฆฌ์ŠคํŠธ.
    //           ApplyAnimation()์ด ์ด ๋ฆฌ์ŠคํŠธ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ๋งค ํ”„๋ ˆ์ž„ ์ „์ฒด ๊ตฌ์ฒด๋ฅผ ๊ฐฑ์‹ ํ•จ
    List<SphereData> _spheres = new List<SphereData>();

    // -------------------------------------------------------
    // ์—๋””ํ„ฐ ์ „์šฉ ์ƒํƒœ ํ”Œ๋ž˜๊ทธ
    // -------------------------------------------------------

#if UNITY_EDITOR
    // _generateQueued: Generate()์˜ ์ค‘๋ณต ์˜ˆ์•ฝ์„ ๋ง‰๋Š” ํ”Œ๋ž˜๊ทธ.
    //                  OnValidate๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ๋งˆ๋‹ค ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ
    //                  delayCall์ด ์ด๋ฏธ ๋“ฑ๋ก๋ผ ์žˆ์œผ๋ฉด ์žฌ๋“ฑ๋กํ•˜์ง€ ์•Š๋„๋ก ์ฐจ๋‹จํ•จ
    bool _generateQueued = false;
#endif

    // -------------------------------------------------------
    // Unity ์ด๋ฒคํŠธ ๋ฉ”์„œ๋“œ
    // -------------------------------------------------------

    // OnEnable: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋  ๋•Œ(์”ฌ ๋กœ๋“œ, ์ปดํฌ๋„ŒํŠธ ํ™œ์„ฑํ™”, ์—๋””ํ„ฐ ์žฌ์ปดํŒŒ์ผ ํ›„ ๋“ฑ) ํ˜ธ์ถœ.
    //           ๊ทธ๋ผ๋””์–ธํŠธ ์ดˆ๊ธฐํ™” โ†’ ๊ตฌ์ฒด ์ „์ฒด ์ƒ์„ฑ โ†’ ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์บ์‹œ ์ˆœ์œผ๋กœ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๊ตฌ์„ฑํ•จ
    void OnEnable()
    {
        // ๊ทธ๋ผ๋””์–ธํŠธ๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”
        if (gradient == null || gradient.colorKeys.Length == 0)
            gradient = CreateDefaultGradient();

        Generate();
        CacheStructureParams();
    }

    // OnValidate: Inspector์—์„œ ์–ด๋–ค public ํ•„๋“œ๋“  ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์—๋””ํ„ฐ์—์„œ ์ž๋™ ํ˜ธ์ถœ.
    //             ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ(gridSize, spacing)๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ตฌ์ฒด๋ฅผ ์žฌ์ƒ์„ฑํ•˜๊ณ ,
    //             ๊ทธ ์™ธ ํŒŒ๋ผ๋ฏธํ„ฐ(๋…ธ์ด์ฆˆยท์ƒ‰์ƒยทํฌ๊ธฐ)๋งŒ ๋ฐ”๋€Œ๋ฉด ์žฌ์ƒ์„ฑ ์—†์ด ์ฆ‰์‹œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋งŒ ๊ฐฑ์‹ ํ•จ
    void OnValidate()
    {
#if UNITY_EDITOR
        if (gradient == null || gradient.colorKeys.Length == 0)
            gradient = CreateDefaultGradient();

        if (StructureParamsChanged())
        {
            // ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์œผ๋ฉด ์บ์‹œ๋ฅผ ๋จผ์ € ๊ฐฑ์‹ ํ•˜๊ณ  ์žฌ์ƒ์„ฑ ์˜ˆ์•ฝ
            CacheStructureParams();
            QueueGenerate();
        }
        else
        {
            // ๋…ธ์ด์ฆˆยท์ƒ‰์ƒยทํฌ๊ธฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋งŒ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ โ†’ t=0 ๊ธฐ์ค€์œผ๋กœ ์ฆ‰์‹œ ์”ฌ ๋ทฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐฑ์‹ 
            ApplyAnimation(0f);
        }
#endif
    }

#if UNITY_EDITOR
    // QueueGenerate: Generate()๋ฅผ EditorApplication.delayCall์— ๋“ฑ๋กํ•ด ๋‹ค์Œ ์—๋””ํ„ฐ ํ”„๋ ˆ์ž„์— ์‹คํ–‰๋˜๊ฒŒ ์˜ˆ์•ฝ.
    //               OnValidate ๋„์ค‘์—๋Š” ์”ฌ ๊ทธ๋ž˜ํ”„ ์ˆ˜์ •์ด ์ œํ•œ๋˜๊ธฐ ๋•Œ๋ฌธ์—,
    //               ์ง์ ‘ ํ˜ธ์ถœ ๋Œ€์‹  delayCall๋กœ ํ•œ ํ”„๋ ˆ์ž„ ๋’ค์— ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํ–‰ํ•จ
    void QueueGenerate()
    {
        if (_generateQueued) return; // ์ด๋ฏธ ์˜ˆ์•ฝ๋ผ ์žˆ์œผ๋ฉด ์ค‘๋ณต ๋“ฑ๋ก ๋ฐฉ์ง€

        _generateQueued = true;
        EditorApplication.delayCall += () =>
        {
            _generateQueued = false;
            if (this == null) return; // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ญ์ œ๋œ ๊ฒฝ์šฐ ์กฐ๊ธฐ ํƒˆ์ถœ
            Generate();
        };
    }
#endif

    // Update: ๋งค ํ”„๋ ˆ์ž„ ํ˜ธ์ถœ. ํ”Œ๋ ˆ์ด ๋ชจ๋“œ์—์„œ๋งŒ ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌ๋™ํ•จ.
    //         ์—๋””ํ„ฐ ์”ฌ ๋ทฐ์—์„œ๋Š” ์ •์ง€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด isPlaying ์กฐ๊ฑด์œผ๋กœ ์ฐจ๋‹จ
    void Update()
    {
#if UNITY_EDITOR
        if (!Application.isPlaying) return;
#endif
        // Time.time * noiseSpeed: ๊ฒฝ๊ณผ ์‹œ๊ฐ„์— ์†๋„๋ฅผ ๊ณฑํ•ด ๋…ธ์ด์ฆˆ ์˜คํ”„์…‹์„ ์„ ํ˜•์œผ๋กœ ์ฆ๊ฐ€์‹œํ‚ด
        ApplyAnimation(Time.time * noiseSpeed);
    }

    // -------------------------------------------------------
    // ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ ๊ฐ์ง€
    // -------------------------------------------------------

    // StructureParamsChanged: ํ˜„์žฌ Inspector ๊ฐ’๊ณผ ์บ์‹œ๋œ ๊ฐ’์„ ๋น„๊ตํ•ด
    //                         ๊ตฌ์ฒด๋ฅผ ์žฌ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š” ๋ณ€๊ฒฝ์ธ์ง€ ํŒ๋‹จํ•จ.
    //                         true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด OnValidate๊ฐ€ QueueGenerate()๋ฅผ ํ˜ธ์ถœํ•จ
    bool StructureParamsChanged() =>
        gridSize != _cachedGridSize || spacing != _cachedSpacing;

    // CacheStructureParams: ํ˜„์žฌ gridSizeยทspacing์„ ์บ์‹œ์— ์ €์žฅ.
    //                       Generate() ์ง์ „ ๋˜๋Š” OnEnable์—์„œ ํ˜ธ์ถœํ•ด ๊ธฐ์ค€๊ฐ’์„ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•จ
    void CacheStructureParams()
    {
        _cachedGridSize = gridSize;
        _cachedSpacing  = spacing;
    }

    // -------------------------------------------------------
    // ๊ตฌ์ฒด ์ƒ์„ฑ
    // -------------------------------------------------------

    // Generate: ๊ธฐ์กด ๊ตฌ์ฒด๋ฅผ ๋ชจ๋‘ ์‚ญ์ œํ•˜๊ณ  gridSizeร—gridSize ๊ฐœ์˜ ๊ตฌ์ฒด๋ฅผ ์ƒˆ๋กœ ๋ฐฐ์น˜ํ•จ.
    //           ๊ทธ๋ฆฌ๋“œ๋ฅผ ๋กœ์ปฌ ์›์  ๊ธฐ์ค€์œผ๋กœ ์ค‘์•™ ์ •๋ ฌํ•œ ๋’ค ๊ฐ ์…€ ์œ„์น˜์— SpawnSphere๋ฅผ ํ˜ธ์ถœํ•จ
    void Generate()
    {
        Clear();

        // offset: ๊ทธ๋ฆฌ๋“œ ์ „์ฒด ํญ์˜ ์ ˆ๋ฐ˜. ๊ฐ ๊ตฌ์ฒด ์œ„์น˜์—์„œ ๋นผ๋ฉด XยทZ ์ถ•์ด ์›์  ๋Œ€์นญ์œผ๋กœ ์ •๋ ฌ๋จ
        // ์˜ˆ) gridSize=20, spacing=1.2 โ†’ offset=11.4 โ†’ X ๋ฒ”์œ„: -11.4 ~ +11.4
        float offset = (gridSize - 1) * spacing * 0.5f;

        for (int x = 0; x < gridSize; x++)
        for (int z = 0; z < gridSize; z++)
        {
            Vector3 pos = new Vector3(x * spacing - offset, 0f, z * spacing - offset);
            SpawnSphere(pos);
        }

        // ์ƒ์„ฑ ์งํ›„ t=0 ๊ธฐ์ค€์œผ๋กœ ๋…ธ์ด์ฆˆ ์ ์šฉ โ†’ ์—๋””ํ„ฐ์—์„œ๋„ ์ดˆ๊ธฐ ํ˜•ํƒœ๋ฅผ ์ฆ‰์‹œ ํ™•์ธ ๊ฐ€๋Šฅ
        ApplyAnimation(0f);
    }

    // SpawnSphere: ์ฃผ์–ด์ง„ ๋กœ์ปฌ ์œ„์น˜์— ๊ตฌ์ฒด ํ•˜๋‚˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ  _spheres ๋ฆฌ์ŠคํŠธ์— ๋“ฑ๋กํ•จ.
    //              baseMaterial์„ Instantiateํ•ด ๊ตฌ์ฒด๋ณ„ ๋…๋ฆฝ ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ๋งŒ๋“ค๊ณ ,
    //              _EMISSION ํ‚ค์›Œ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•ด ์‹ค์‹œ๊ฐ„ ์—๋ฏธ์…˜ ์ƒ‰์ƒ ์ ์šฉ์„ ์ค€๋น„ํ•จ
    void SpawnSphere(Vector3 pos)
    {
        if (baseMaterial == null)
        {
            Debug.LogWarning("[PerlinNoiseSurface] baseMaterial์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Inspector์—์„œ URP/Lit ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ํ• ๋‹นํ•ด์ฃผ์„ธ์š”.");
            return;
        }

        // CreatePrimitive: ๋ฉ”์‹œยท์ฝœ๋ผ์ด๋”๊ฐ€ ๋‚ด์žฅ๋œ ๊ธฐ๋ณธ ๊ตฌ์ฒด GameObject๋ฅผ ์ƒ์„ฑํ•จ
        GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        go.transform.SetParent(transform);        // ์ด ์ปดํฌ๋„ŒํŠธ์˜ GameObject๋ฅผ ๋ถ€๋ชจ๋กœ ์„ค์ •ํ•ด ๊ณ„์ธต ์ •๋ฆฌ
        go.transform.localPosition = pos;
        go.transform.localScale    = Vector3.one * sphereSize;

        // Instantiate(baseMaterial): ์›๋ณธ ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ๋ณต์ œํ•ด ๊ตฌ์ฒด๋งˆ๋‹ค ๊ณ ์œ ํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ฆ.
        //                            ๋ณต์ œํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๊ตฌ์ฒด๊ฐ€ ๊ฐ™์€ ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ๊ณต์œ ํ•ด ์ƒ‰์ƒ์„ ๊ฐœ๋ณ„ ์ œ์–ดํ•  ์ˆ˜ ์—†์Œ
        Material mat = Instantiate(baseMaterial);
        // _EMISSION ํ‚ค์›Œ๋“œ: URP Lit ์…ฐ์ด๋”์—์„œ ์—๋ฏธ์…˜ ๊ณ„์‚ฐ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ํ‚ค์›Œ๋“œ.
        //                   ์ด ํ‚ค์›Œ๋“œ ์—†์ด _EmissionColor๋ฅผ ์„ค์ •ํ•ด๋„ ๋ฐœ๊ด‘ ํšจ๊ณผ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์Œ
        mat.EnableKeyword("_EMISSION");
        // RealtimeEmissive: ์—๋ฏธ์…˜ ์ƒ‰์ƒ์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ GI์— ๊ธฐ์—ฌํ•˜๋„๋ก ํ”Œ๋ž˜๊ทธ๋ฅผ ์„ค์ •ํ•จ
        mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;

        go.GetComponent<Renderer>().material = mat;

        // SphereData ๊ตฌ์กฐ์ฒด๋กœ ๋ฌถ์–ด ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ โ†’ ApplyAnimation์ด ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•จ
        _spheres.Add(new SphereData { tr = go.transform, mat = mat });
    }

    // Clear: _spheres ๋ฆฌ์ŠคํŠธ๋ฅผ ๋น„์šฐ๊ณ  ์ž์‹ GameObject๋ฅผ ์ „๋ถ€ ์ฆ‰์‹œ ํŒŒ๊ดดํ•จ.
    //        DestroyImmediate: ์—๋””ํ„ฐ ๋ชจ๋“œ์—์„œ๋Š” Destroy๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฆ‰์‹œ ์‚ญ์ œ๊ฐ€ ํ•„์š”ํ•จ
    void Clear()
    {
        _spheres.Clear();
        while (transform.childCount > 0)
            DestroyImmediate(transform.GetChild(0).gameObject);
    }

    // -------------------------------------------------------
    // ์• ๋‹ˆ๋ฉ”์ด์…˜ (๋…ธ์ด์ฆˆ ๊ธฐ๋ฐ˜ ์œ„์น˜ยทํฌ๊ธฐยท์ƒ‰์ƒ ๊ฐฑ์‹ )
    // -------------------------------------------------------

    // ApplyAnimation: ํŒŒ๋ผ๋ฏธํ„ฐ t(์‹œ๊ฐ„ ์˜คํ”„์…‹)๋ฅผ ๋ฐ›์•„ ์ „์ฒด ๊ตฌ์ฒด์˜ Y ์œ„์น˜ยทํฌ๊ธฐยท์ƒ‰์ƒ์„ ๊ฐฑ์‹ ํ•จ.
    //                 ํ”Œ๋ ˆ์ด ๋ชจ๋“œ์—์„œ๋Š” Update๊ฐ€ Time.time ๊ธฐ๋ฐ˜ t๋ฅผ ์ „๋‹ฌํ•˜๊ณ ,
    //                 ์—๋””ํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์‹œ์—๋Š” t=0์œผ๋กœ ํ˜ธ์ถœํ•ด ์ •์  ์Šค๋ƒ…์ƒท์„ ํ‘œ์‹œํ•จ
    void ApplyAnimation(float t)
    {
        for (int i = 0; i < _spheres.Count; i++)
        {
            SphereData s = _spheres[i];
            if (s.tr == null || s.mat == null) continue; // ์™ธ๋ถ€์—์„œ ์‚ญ์ œ๋œ ๊ฒฝ์šฐ ๊ฑด๋„ˆ๋œ€

            Vector3 p = s.tr.localPosition;

            // PerlinNoise(x, y): ๋‘ ์ถ• ์ž…๋ ฅ์œผ๋กœ 0~1 ์‚ฌ์ด์˜ ์—ฐ์†์ ์ธ ๋…ธ์ด์ฆˆ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•จ.
            // p.xยทp.z์— noiseScale์„ ๊ณฑํ•ด ๊ณต๊ฐ„ ์ฃผํŒŒ์ˆ˜๋ฅผ ์กฐ์ ˆํ•˜๊ณ ,
            // t์™€ t*0.8f๋กœ XยทZ ์ถ• ์˜คํ”„์…‹์— ์„œ๋กœ ๋‹ค๋ฅธ ์†๋„๋ฅผ ์ฃผ์–ด ๋Œ€๊ฐ์„ ์ด ์•„๋‹Œ ์ž์—ฐ์Šค๋Ÿฌ์šด 2D ํ๋ฆ„์„ ๋งŒ๋“ฆ
            float n = Mathf.PerlinNoise(
                p.x * noiseScale + t,
                p.z * noiseScale + t * 0.8f
            );

            // Y ์œ„์น˜: ๋…ธ์ด์ฆˆ ๊ฐ’์— heightScale์„ ๊ณฑํ•ด ํŒŒ๋„์ฒ˜๋Ÿผ ์œ„์•„๋ž˜๋กœ ๋ณ€์œ„์‹œํ‚ด
            p.y = n * heightScale;
            s.tr.localPosition = p;

            // ํฌ๊ธฐ ๋งฅ๋™: ๋…ธ์ด์ฆˆ๊ฐ€ ๋‚ฎ์„ ๋•Œ 0.6ร—, ๋†’์„ ๋•Œ 1.4ร— ํฌ๊ธฐ๊ฐ€ ๋˜๋„๋ก ์„ ํ˜• ๋ณด๊ฐ„.
            //            ๊ตฌ์ฒด๊ฐ€ ๋†’์ด ์†Ÿ์„์ˆ˜๋ก ์ปค์ง€๋Š” ์‹œ๊ฐ์  ๊ฐ•์กฐ ํšจ๊ณผ๋ฅผ ๋งŒ๋“ฆ
            s.tr.localScale = Vector3.one * (sphereSize * (0.6f + n * 0.8f));

            // gradient.Evaluate(n): ๋…ธ์ด์ฆˆ ๊ฐ’์„ ๊ทธ๋ผ๋””์–ธํŠธ์˜ t ์œ„์น˜๋กœ ์‚ฌ์šฉํ•ด ์ƒ‰์ƒ์„ ์ƒ˜ํ”Œ๋งํ•จ
            Color albedo   = gradient.Evaluate(n);
            // emission: albedo์— ๋…ธ์ด์ฆˆ ๊ฐ•๋„๋ฅผ ๊ณฑํ•ด ๋†’์€ ์ง€์ ์ผ์ˆ˜๋ก ๋ฐ๊ฒŒ ๋ฐœ๊ด‘ํ•˜๋„๋ก ์Šค์ผ€์ผ๋งํ•จ
            Color emission = albedo * (n * 0.6f);

            // URP Lit ์…ฐ์ด๋”์˜ ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์œผ๋กœ ์ƒ‰์ƒ์„ ์ง์ ‘ ์ง€์ •
            // _BaseColor: ํ‘œ๋ฉด์˜ ์•Œ๋ฒ ๋„(๊ธฐ๋ณธ ๋ฐ˜์‚ฌ์ƒ‰)
            // _EmissionColor: ๋ฐœ๊ด‘ ์ƒ‰์ƒ. EnableKeyword("_EMISSION")์ด ์„ ํ–‰๋ผ์•ผ ์‹ค์ œ๋กœ ์ ์šฉ๋จ
            s.mat.SetColor("_BaseColor",     albedo);
            s.mat.SetColor("_EmissionColor", emission);
        }
    }

    // -------------------------------------------------------
    // ์œ ํ‹ธ๋ฆฌํ‹ฐ
    // -------------------------------------------------------

    // CreateDefaultGradient: gradient ํ•„๋“œ๊ฐ€ ๋น„์–ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ์ƒ‰์ƒ ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•จ.
    //                         ๋‚ฎ์€ ๋…ธ์ด์ฆˆ๊ฐ’(0) โ†’ ํŒŒ๋ž‘, ๋†’์€ ๋…ธ์ด์ฆˆ๊ฐ’(1) โ†’ ๋…ธ๋ž‘ ์ˆœ์œผ๋กœ
    //                         ์ˆ˜์‹ฌยท๊ณ ๋„๊ฐ์„ ์‹œ๊ฐ์ ์œผ๋กœ ์•”์‹œํ•˜๋Š” ํŒ”๋ ˆํŠธ๋ฅผ ์ œ๊ณตํ•จ
    Gradient CreateDefaultGradient()
    {
        Gradient g = new Gradient();
        g.SetKeys(
            new GradientColorKey[] {
                new GradientColorKey(Color.blue,   0.0f),
                new GradientColorKey(Color.cyan,   0.4f),
                new GradientColorKey(Color.green,  0.7f),
                new GradientColorKey(Color.yellow, 1.0f)
            },
            // AlphaKey: ์ „ ๊ตฌ๊ฐ„ alpha=1 (์™„์ „ ๋ถˆํˆฌ๋ช…) ๊ณ ์ •
            new GradientAlphaKey[] {
                new GradientAlphaKey(1f, 0f),
                new GradientAlphaKey(1f, 1f)
            }
        );
        return g;
    }
}
---
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€