๐Ÿ“”Boilerplate (GPU Instancing)

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

Unity GenArt

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

GPU Instancing ๊ธฐ๋ฐ˜ ์•„ํŠธ์›Œํฌ ์‹œ์Šคํ…œ

[์‚ฌ์šฉ ๋ฐฉ๋ฒ•]

  1. ์ด ํŒŒ์ผ์„ ๋ณต์‚ฌํ•ด์„œ ์ƒˆ ์ด๋ฆ„์œผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  2. ํด๋ž˜์Šค ์ด๋ฆ„์„ ํŒŒ์ผ๋ช…๊ณผ ๋™์ผํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
  3. [์•„ํŠธ์›Œํฌ ๋กœ์ง] ์„น์…˜๋งŒ ์ˆ˜์ •ํ•ด์„œ ์ƒˆ๋กœ์šด ์•„ํŠธ์›Œํฌ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  4. ๋‚˜๋จธ์ง€ ์„น์…˜([์ธํ”„๋ผ ์ฝ”๋“œ])์€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.

[์ฝ”๋“œ์˜ ๊ธฐ๋Šฅ]

  • Scene ์—๋””ํ„ฐ์—์„œ ์ฆ‰์‹œ ์•„ํŠธ์›Œํฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
  • ์ธ์ŠคํŽ™ํ„ฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜
  • Play ๋ชจ๋“œ์—์„œ๋งŒ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์žฌ์ƒ (Scene์€ ์ •์ง€)
  • GPU Instancing์œผ๋กœ ์ˆ˜์ฒœ ๊ฐœ ์˜ค๋ธŒ์ ํŠธ๋„ ๊ณ ์„ฑ๋Šฅ ๋ Œ๋”๋ง
  • ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ ๊ฐ์ง€ (URP / HDRP / Built-in)

[๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ๊ตฌ์กฐ]

GenArtBoilerplate
โ”‚
โ”œโ”€โ”€ [์ธํ”„๋ผ ์ฝ”๋“œ] ์ˆ˜์ • ๋ถˆํ•„์š”
โ”‚   โ”œโ”€โ”€ OnEnable โ†’ EnsureResources โ†’ Generate โ†’ CacheStructureParams
โ”‚   โ”œโ”€โ”€ OnValidate โ†’ QueueGenerate (๊ตฌ์กฐ ๋ณ€๊ฒฝ) / Animate(0f) (์‹œ๊ฐ ๋ณ€๊ฒฝ)
โ”‚   โ”œโ”€โ”€ QueueGenerate โ†’ EditorApplication.delayCall โ†’ Generate
โ”‚   โ”œโ”€โ”€ Update โ†’ Animate(t) + RenderInstances
โ”‚   โ”œโ”€โ”€ EnsureResources: ๋ฉ”์‹œยท๋จธํ‹ฐ๋ฆฌ์–ผ ์ž๋™ ์ƒ์„ฑ
โ”‚   โ”œโ”€โ”€ RenderInstances: DrawMeshInstanced 1023๊ฐœ ๋ฐฐ์น˜ ๋ถ„ํ• 
โ”‚   โ””โ”€โ”€ DetectPipeline: URP/HDRP/Built-in ์ž๋™ ๊ฐ์ง€
โ”‚
โ””โ”€โ”€ [์•„ํŠธ์›Œํฌ ๋กœ์ง] ์—ฌ๊ธฐ๋งŒ ์ˆ˜์ •
    โ”œโ”€โ”€ Generate() ๋‚ด๋ถ€: ์ธ์Šคํ„ด์Šค ๋ฐฐ์น˜ ๊ณต์‹
    โ””โ”€โ”€ Animate() ๋‚ด๋ถ€: ๋งค ํ”„๋ ˆ์ž„ ์œ„์น˜ยท์ƒ‰์ƒ ๊ณ„์‚ฐ ๊ณต์‹

๐Ÿ“„BoilerplateGPUInstancing.cs

using UnityEngine;
using System.Collections.Generic;

#if UNITY_EDITOR
using UnityEditor; // EditorApplication.delayCall ์‚ฌ์šฉ์„ ์œ„ํ•ด ์—๋””ํ„ฐ ์ „์šฉ์œผ๋กœ ์ž„ํฌํŠธ
#endif

[ExecuteAlways] // ์—๋””ํ„ฐ ๋ชจ๋“œ(Play ์ „)์—์„œ๋„ Update ๋“ฑ Unity ์ด๋ฒคํŠธ๊ฐ€ ํ˜ธ์ถœ๋˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค
public class BoilerplateGPUInstancing : MonoBehaviour
{
    // ============================================================
    // ์ธ์ŠคํŽ™ํ„ฐ ํŒŒ๋ผ๋ฏธํ„ฐ
    // Header ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋กœ ์ธ์ŠคํŽ™ํ„ฐ๋ฅผ ์„น์…˜๋ณ„๋กœ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.
    // ============================================================

    // ----- ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ----------------------------------------
    // ์ด ๊ฐ’๋“ค์ด ๋ฐ”๋€Œ๋ฉด ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ „๋ถ€ ์‚ญ์ œํ•˜๊ณ  ๋‹ค์‹œ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    // "๋ช‡ ๊ฐœ๋ฅผ ์–ด๋””์— ๋†“์„ ๊ฒƒ์ธ๊ฐ€"๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค.
    [Header("๊ตฌ์กฐ ์„ค์ • (๋ณ€๊ฒฝ ์‹œ ์˜ค๋ธŒ์ ํŠธ ์žฌ์ƒ์„ฑ)")]
    public int gridSize = 20;   // ๊ฐ€๋กœ/์„ธ๋กœ ํ๋ธŒ ๊ฐœ์ˆ˜
    public float spacing = 0.8f; // ํ๋ธŒ ๊ฐ„๊ฒฉ
    public float cubeSize = 0.3f; // ํ๋ธŒ ํฌ๊ธฐ
    public int colorSeed = 0;    // ์ƒ‰์ƒ ๋ฌด์ž‘์œ„ ์‹œ๋“œ (๊ฐ™์€ ์‹œ๋“œ = ๊ฐ™์€ ์ƒ‰์ƒ ๋ฐฐ์น˜)

    // ----- ์• ๋‹ˆ๋ฉ”์ด์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ ----------------------------------
    // ์ด ๊ฐ’๋“ค์ด ๋ฐ”๋€Œ๋ฉด ์žฌ์ƒ์„ฑ ์—†์ด ApplyAnimation(0f)๋งŒ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
    // "์–ด๋–ป๊ฒŒ ์›€์ง์ด๊ณ  ์–ด๋–ป๊ฒŒ ๋ณด์ผ ๊ฒƒ์ธ๊ฐ€"๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค.
    [Header("์• ๋‹ˆ๋ฉ”์ด์…˜ ์„ค์ • (์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜)")]
    public float waveHeight = 2.0f; // ํŒŒ๋™ ๋†’์ด
    public float waveSpeed = 1.2f; // ํŒŒ๋™ ์†๋„ (ํด์ˆ˜๋ก ๋น ๋ฆ„)
    public float waveFrequency = 0.8f; // ํŒŒ๋™ ์ฃผํŒŒ์ˆ˜ (ํด์ˆ˜๋ก ์ž˜๊ฒŒ ํŒŒ๋™)

    // ----- ์ƒ‰์ƒ ํŒŒ๋ผ๋ฏธํ„ฐ ----------------------------------------
    [Header("์ƒ‰์ƒ ์„ค์ • (์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜)")]
    [Range(0f, 1f)] public float hueBase = 0.55f; // ์ƒ‰์ƒ ์‹œ์ž‘ hue
    [Range(0f, 1f)] public float hueRange = 0.40f; // ํ๋ธŒ ๊ฐ„ hue ๋ถ„ํฌ ๋ฒ”์œ„
    [Range(0f, 1f)] public float saturation = 0.80f; // ์ฑ„๋„ (0 = ํ‘๋ฐฑ, 1 = ํ’€ ์ปฌ๋Ÿฌ)
    public float emissionIntensity = 1.0f;  // ๋ฐœ๊ด‘ ๊ฐ•๋„

    // ----- ์žฌ์งˆ ์„ค์ • --------------------------------------------
    [Header("์žฌ์งˆ ์„ค์ •")]
    public Mesh instanceMesh;     // GPU ๋“œ๋กœ์šฐ์— ์‚ฌ์šฉํ•  ๋ฉ”์‹œ (null์ด๋ฉด ์ž๋™ ์ƒ์„ฑ)
    public Material instanceMaterial; // GPU ๋“œ๋กœ์šฐ์— ์‚ฌ์šฉํ•  ๋จธํ‹ฐ๋ฆฌ์–ผ (null์ด๋ฉด ์ž๋™ ์ƒ์„ฑ)

    // ============================================================
    // ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ
    // ============================================================

    // ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜์˜ ์ •์  ๋ฐ์ดํ„ฐ (Generate ์‹œ ํ•œ ๋ฒˆ๋งŒ ๊ณ„์‚ฐ)
    // ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„์—์„œ ๋งค ํ”„๋ ˆ์ž„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.
    struct InstanceData
    {
        public Vector3 basePos;    // ์ƒ์„ฑ ์‹œ ๊ฒฐ์ •๋œ ๊ธฐ์ค€ ์œ„์น˜ (Y=0 ํ‰๋ฉด)
        public float hue;        // ์ƒ์„ฑ ์‹œ ๊ฒฐ์ •๋œ ๊ณ ์œ  ์ƒ‰์ƒ (์• ๋‹ˆ๋ฉ”์ด์…˜ ์ค‘ ๋ณ€ํ•˜์ง€ ์•Š์Œ)
        public float brightness; // ์ƒ์„ฑ ์‹œ ๊ฒฐ์ •๋œ ๊ธฐ๋ณธ ๋ฐ๊ธฐ
    }

    List<InstanceData> _instances = new List<InstanceData>(); // ์ „์ฒด ์ธ์Šคํ„ด์Šค ๋ฐ์ดํ„ฐ ๋ชฉ๋ก

    // GPU์— ๋„˜๊ธธ ๋ฐฐ์—ด (Animate์—์„œ ๋งค ํ”„๋ ˆ์ž„ ๊ฐฑ์‹ , RenderInstances์—์„œ GPU๋กœ ์ „์†ก)
    Matrix4x4[] _matrices;  // ๊ฐ ์ธ์Šคํ„ด์Šค์˜ ์œ„์น˜/ํšŒ์ „/ํฌ๊ธฐ ํ–‰๋ ฌ
    Vector4[] _colors;    // ๊ฐ ์ธ์Šคํ„ด์Šค์˜ albedo ์ƒ‰์ƒ
    Vector4[] _emissions; // ๊ฐ ์ธ์Šคํ„ด์Šค์˜ emission ์ƒ‰์ƒ

    // DrawMeshInstanced๋Š” ํ•œ ๋ฒˆ ํ˜ธ์ถœ์— ์ตœ๋Œ€ 1023๊ฐœ๊นŒ์ง€๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    // 1023๊ฐœ๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์—ฌ๋Ÿฌ ๋ฒˆ ๋‚˜๋ˆ ์„œ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    const int BATCH_SIZE = 1023;

    bool _generated = false; // Generate ์™„๋ฃŒ ์—ฌ๋ถ€. false๋ฉด Update์—์„œ ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋œ€

    // ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ ์ข…๋ฅ˜ (OnEnable์—์„œ ์ž๋™ ๊ฐ์ง€)
    enum RenderPipeline { BuiltIn, URP, HDRP }
    RenderPipeline _pipeline;

    // ============================================================
    // ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์บ์‹œ
    // OnValidate์—์„œ "๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€" ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•œ ์ด์ „ ๊ฐ’ ์ €์žฅ์†Œ.
    // ๋ฐ”๋€Œ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋ถˆํ•„์š”ํ•œ ์žฌ์ƒ์„ฑ์„ ๋ง‰์Šต๋‹ˆ๋‹ค.
    // ============================================================
    int _cachedGridSize;
    float _cachedSpacing;
    float _cachedCubeSize;
    int _cachedColorSeed;

#if UNITY_EDITOR
    // QueueGenerate์˜ ์ค‘๋ณต ํ˜ธ์ถœ ๋ฐฉ์ง€ ํ”Œ๋ž˜๊ทธ (์—๋””ํ„ฐ ์ „์šฉ)
    bool _generateQueued = false;
#endif

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

    // OnEnable: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋  ๋•Œ ํ˜ธ์ถœ (์”ฌ ๋กœ๋“œ, ์ปดํฌ๋„ŒํŠธ ์ฒดํฌ ON ๋“ฑ)
    void OnEnable()
    {
        _pipeline = DetectPipeline(); // ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์˜ ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค
        EnsureResources();            // ๋ฉ”์‹œ์™€ ๋จธํ‹ฐ๋ฆฌ์–ผ์ด ์—†์œผ๋ฉด ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
        Generate();                   // ์ธ์Šคํ„ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
        CacheStructureParams();       // ํ˜„์žฌ ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ์บ์‹œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค
    }

    // OnValidate: ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ
    // ์ฃผ์˜: ์ด ์ฝœ๋ฐฑ ์•ˆ์—์„œ SetParent(), CreatePrimitive() ๋“ฑ ์”ฌ ๊ทธ๋ž˜ํ”„๋ฅผ
    //       ์ง์ ‘ ์ˆ˜์ •ํ•˜๋ฉด Unity ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    //       โ†’ ์”ฌ ๊ทธ๋ž˜ํ”„ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฉด ๋ฐ˜๋“œ์‹œ QueueGenerate()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    void OnValidate()
    {
#if UNITY_EDITOR // OnValidate์˜ QueueGenerate ๋กœ์ง์€ ์—๋””ํ„ฐ์—์„œ๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
        _pipeline = DetectPipeline();
        EnsureResources();

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

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

        _generateQueued = true;

        EditorApplication.delayCall += () => // ๋‹ค์Œ ์—๋””ํ„ฐ ๋ฃจํ”„ ํƒ€์ด๋ฐ์— ์‹คํ–‰
        {
            _generateQueued = false;

            if (this == null) return; // ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์‚ญ์ œ๋œ ๊ฒฝ์šฐ ์•ˆ์ „ํ•˜๊ฒŒ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค

            Generate();
        };
    }
#endif

    // Update: ๋งค ํ”„๋ ˆ์ž„ ํ˜ธ์ถœ
    void Update()
    {
        if (!_generated) return; // ์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค

        if (!Application.isPlaying)
        {
            // ์—๋””ํ„ฐ Scene ๋ทฐ: ์• ๋‹ˆ๋ฉ”์ด์…˜ ์—†์ด t=0 ์ •์ง€ ์ƒํƒœ๋กœ๋งŒ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค
            Animate(0f);
            RenderInstances();
            return;
        }

        // Play ๋ชจ๋“œ: ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐฑ์‹  ํ›„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค
        Animate(Time.time * waveSpeed);
        RenderInstances();
    }

    // ============================================================
    // ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์บ์‹œ ์œ ํ‹ธ
    // ============================================================

    // ์ด์ „์— ์บ์‹œํ•ด๋‘” ๊ฐ’๊ณผ ํ˜„์žฌ ๊ฐ’์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
    // ํ•˜๋‚˜๋ผ๋„ ๋‹ค๋ฅด๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์žฌ์ƒ์„ฑ์„ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
    bool StructureParamsChanged()
    {
        return gridSize != _cachedGridSize
            || spacing != _cachedSpacing
            || cubeSize != _cachedCubeSize
            || colorSeed != _cachedColorSeed;
    }

    // ํ˜„์žฌ ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ์บ์‹œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    void CacheStructureParams()
    {
        _cachedGridSize = gridSize;
        _cachedSpacing = spacing;
        _cachedCubeSize = cubeSize;
        _cachedColorSeed = colorSeed;
    }

    // ============================================================
    // ๋ฆฌ์†Œ์Šค ์ดˆ๊ธฐํ™”
    // ๋ฉ”์‹œ์™€ ๋จธํ‹ฐ๋ฆฌ์–ผ์ด ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ํ• ๋‹น๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    // ============================================================
    void EnsureResources()
    {
        if (instanceMesh == null)
        {
            // ํ๋ธŒ ๋ฉ”์‹œ๋ฅผ ์ž„์‹œ ์˜ค๋ธŒ์ ํŠธ์—์„œ ์ถ”์ถœ ํ›„ ์ฆ‰์‹œ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค
            GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Cube);
            instanceMesh = temp.GetComponent<MeshFilter>().sharedMesh;
            DestroyImmediate(temp); // ์”ฌ์— ๋‚จ์ง€ ์•Š๋„๋ก ์ฆ‰์‹œ ์‚ญ์ œ
        }

        if (instanceMaterial == null)
        {
            // ํ”„๋กœ์ ํŠธ์˜ ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ์— ๋งž๋Š” ์…ฐ์ด๋”๋ฅผ ์ž๋™์œผ๋กœ ์ฐพ์Šต๋‹ˆ๋‹ค
            Shader shader =
                Shader.Find("Universal Render Pipeline/Lit") ??
                Shader.Find("HDRP/Lit") ??
                Shader.Find("Standard");

            instanceMaterial = new Material(shader);
            instanceMaterial.enableInstancing = true; // GPU Instancing ๋ฐ˜๋“œ์‹œ ํ™œ์„ฑํ™”
            instanceMaterial.EnableKeyword("_EMISSION");
            instanceMaterial.globalIlluminationFlags =
                MaterialGlobalIlluminationFlags.RealtimeEmissive;

            Debug.Log("[GenArtBoilerplate] ๋จธํ‹ฐ๋ฆฌ์–ผ ์ž๋™ ์ƒ์„ฑ ์™„๋ฃŒ");
        }
    }

    // ============================================================
    // ์ƒ์„ฑ (GameObject๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด๋งŒ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค)
    // GPU Instancing์€ GameObject๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.
    // ์œ„์น˜, ์ƒ‰์ƒ ๋“ฑ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐฐ์—ด๋กœ ์ค€๋น„ํ•ด์„œ GPU์— ๋„˜๊น๋‹ˆ๋‹ค.
    // ============================================================
    void Generate()
    {
        _instances.Clear(); // ์ด์ „ ๋ฐ์ดํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค
        _generated = false; // ์ƒ์„ฑ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋ฆฌ์…‹ํ•ฉ๋‹ˆ๋‹ค

        // ---- [์•„ํŠธ์›Œํฌ ๋กœ์ง] ์—ฌ๊ธฐ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š” ----
        // ์˜ˆ์‹œ: ์ •์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๋“œ ๋ฐฐ์น˜

        Random.InitState(colorSeed); // ์‹œ๋“œ ๊ณ ์ • โ†’ ๊ฐ™์€ ์‹œ๋“œ๋ฉด ํ•ญ์ƒ ๋™์ผํ•œ ๋ฐฐ์น˜ ์žฌํ˜„

        // ๊ทธ๋ฆฌ๋“œ ์ „์ฒด๋ฅผ ๋กœ์ปฌ ์›์ (0,0,0) ๊ธฐ์ค€์œผ๋กœ ์ค‘์•™ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.
        // offset ์—†์ด ๋ฐฐ์น˜ํ•˜๋ฉด (0,0,0)~(max,0,max) ๋ฒ”์œ„๊ฐ€ ๋˜์–ด ํ•œ์ชฝ์œผ๋กœ ์น˜์šฐ์นฉ๋‹ˆ๋‹ค.
        float offset = (gridSize - 1) * spacing * 0.5f;

        for (int x = 0; x < gridSize; x++)
        {
            for (int z = 0; z < gridSize; z++)
            {
                _instances.Add(new InstanceData
                {
                    // offset์„ ๋นผ์„œ ๊ทธ๋ฆฌ๋“œ ์ค‘์‹ฌ์ด (0,0,0)์ด ๋˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค
                    basePos = new Vector3(x * spacing - offset, 0f, z * spacing - offset),
                    hue = (hueBase + Random.value * hueRange) % 1f, // ํ๋ธŒ๋งˆ๋‹ค ๊ณ ์œ  ์ƒ‰์ƒ
                    brightness = Random.Range(0.5f, 1.0f)                  // ํ๋ธŒ๋งˆ๋‹ค ๊ธฐ๋ณธ ๋ฐ๊ธฐ
                });
            }
        }
        // ---- [์•„ํŠธ์›Œํฌ ๋กœ์ง ๋] ----

        // GPU์— ๋„˜๊ธธ ๋ฐฐ์—ด์„ ์ธ์Šคํ„ด์Šค ์ˆ˜์— ๋งž๊ฒŒ ์‚ฌ์ „ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค
        _matrices = new Matrix4x4[_instances.Count];
        _colors = new Vector4[_instances.Count];
        _emissions = new Vector4[_instances.Count];

        _generated = true; // ์ƒ์„ฑ ์™„๋ฃŒ โ†’ Update์—์„œ ๋ Œ๋”๋ง์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค

        Animate(0f); // ์ƒ์„ฑ ์งํ›„ t=0 ๊ธฐ์ค€์œผ๋กœ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค
    }

    // ============================================================
    // ์• ๋‹ˆ๋ฉ”์ด์…˜ (๋งค ํ”„๋ ˆ์ž„ ๋ฐฐ์—ด ๊ฐ’์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค)
    // t = 0       โ†’ Scene ์—๋””ํ„ฐ ์ •์ง€ ์ƒํƒœ
    // t = Time.time * waveSpeed โ†’ Play ๋ชจ๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜
    // ============================================================
    void Animate(float t)
    {
        for (int i = 0; i < _instances.Count; i++)
        {
            InstanceData inst = _instances[i];

            // ---- [์•„ํŠธ์›Œํฌ ๋กœ์ง] ์—ฌ๊ธฐ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š” ----
            // ์˜ˆ์‹œ: X+Z ๋ฐฉํ–ฅ ์‚ฌ์ธ ํŒŒ๋™

            // x์™€ z ์ขŒํ‘œ์˜ ํ•ฉ์œผ๋กœ ์‚ฌ์ธํŒŒ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค โ†’ ๋Œ€๊ฐ์„  ๋ฐฉํ–ฅ ํŒŒ๋™
            float wave = Mathf.Sin(
                (inst.basePos.x + inst.basePos.z) * waveFrequency + t
            ) * waveHeight;

            // normalized: wave ๊ฐ’์„ 0~1 ๋ฒ”์œ„๋กœ ๋ณ€ํ™˜ (์ƒ‰์ƒ/ํฌ๊ธฐ ๊ณ„์‚ฐ์— ํ™œ์šฉ)
            float normalized = (wave / waveHeight + 1f) * 0.5f;

            // ์›”๋“œ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜: ๋ถ€๋ชจ Transform์˜ ์œ„์น˜/ํšŒ์ „์ด ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค
            Vector3 worldPos = transform.TransformPoint(
                new Vector3(inst.basePos.x, wave, inst.basePos.z)
            );

            // TRS ํ–‰๋ ฌ: Translation(์œ„์น˜) ร— Rotation(ํšŒ์ „) ร— Scale(ํฌ๊ธฐ)
            // GPU๋Š” ์ด ํ–‰๋ ฌ ํ•˜๋‚˜๋กœ ์ธ์Šคํ„ด์Šค์˜ ๋ชจ๋“  ๋ณ€ํ™˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
            _matrices[i] = Matrix4x4.TRS(
                worldPos,
                transform.rotation,
                Vector3.one * cubeSize
            );

            // ๊ณ ์œ  hue๋Š” ์œ ์ง€, ํŒŒ๋™ ๋†’์ด์— ๋”ฐ๋ผ ๋ฐ๊ธฐ๋งŒ ๋ณ€์กฐํ•ฉ๋‹ˆ๋‹ค
            float animBrightness = inst.brightness * Mathf.Lerp(0.2f, 1.0f, normalized);
            Color albedo = Color.HSVToRGB(inst.hue, saturation, animBrightness);
            Color emission = Color.HSVToRGB(inst.hue, saturation, normalized)
                                   * emissionIntensity;
            // ---- [์•„ํŠธ์›Œํฌ ๋กœ์ง ๋] ----

            // ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ GPU ์ „์†ก์šฉ ๋ฐฐ์—ด์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค
            _colors[i] = new Vector4(albedo.r, albedo.g, albedo.b, 1f);
            _emissions[i] = new Vector4(emission.r, emission.g, emission.b, 1f);
        }
    }

    // ============================================================
    // GPU ๋“œ๋กœ์šฐ
    // Graphics.DrawMeshInstanced๋กœ GameObject ์—†์ด ๋ฉ”์‹œ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
    // ํ•œ ๋ฒˆ์— ์ตœ๋Œ€ 1023๊ฐœ ์ œํ•œ์ด ์žˆ์œผ๋ฏ€๋กœ ๋ฐฐ์น˜๋กœ ๋‚˜๋ˆ ์„œ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
    // ============================================================
    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); // ์ด๋ฒˆ ๋ฐฐ์น˜ ํฌ๊ธฐ (์ตœ๋Œ€ 1023)

            // ๋ฐฐ์น˜ ํฌ๊ธฐ๋งŒํผ ์ž„์‹œ ๋ฐฐ์—ด์„ ๋งŒ๋“ค๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค
            Matrix4x4[] batchMatrices = new Matrix4x4[batch];
            Vector4[] batchColors = new Vector4[batch];
            Vector4[] batchEmissions = new Vector4[batch];

            System.Array.Copy(_matrices, index, batchMatrices, 0, batch);
            System.Array.Copy(_colors, index, batchColors, 0, batch);
            System.Array.Copy(_emissions, index, batchEmissions, 0, batch);

            // MaterialPropertyBlock: ์ธ์Šคํ„ด์Šค๋ณ„ ์ƒ‰์ƒ์„ GPU์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
            // ์ค‘์š”: ๋ฐฐ์น˜๋งˆ๋‹ค ๋ฐ˜๋“œ์‹œ new๋กœ ์ƒˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
            //       _block์„ ์žฌ์‚ฌ์šฉํ•˜๋ฉด ์ด์ „ ๋ฐฐ์น˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ„์„ญ์„ ์ผ์œผํ‚ต๋‹ˆ๋‹ค.
            MaterialPropertyBlock block = new MaterialPropertyBlock();
            block.SetVectorArray("_BaseColor", batchColors);
            block.SetVectorArray("_EmissionColor", batchEmissions);

            // GPU์— ๋“œ๋กœ์šฐ ๋ช…๋ น์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค
            // โ†’ GameObject ์—†์ด ๋ฉ”์‹œ๋ฅผ ์”ฌ์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค
            Graphics.DrawMeshInstanced(
                instanceMesh,   // ๋ Œ๋”๋งํ•  ๋ฉ”์‹œ
                0,              // ์„œ๋ธŒ๋ฉ”์‹œ ์ธ๋ฑ์Šค (๋‹จ์ผ ๋ฉ”์‹œ๋Š” ํ•ญ์ƒ 0)
                instanceMaterial,
                batchMatrices,  // ์ธ์Šคํ„ด์Šค๋ณ„ TRS ํ–‰๋ ฌ ๋ฐฐ์—ด
                batch,          // ์ด๋ฒˆ ๋ฐฐ์น˜์˜ ์ธ์Šคํ„ด์Šค ์ˆ˜
                block           // ์ธ์Šคํ„ด์Šค๋ณ„ ๋จธํ‹ฐ๋ฆฌ์–ผ ํ”„๋กœํผํ‹ฐ
            );

            index += batch; // ๋‹ค์Œ ๋ฐฐ์น˜ ์‹œ์ž‘ ์ธ๋ฑ์Šค๋กœ ์ด๋™
            total -= batch; // ๋‚จ์€ ์ธ์Šคํ„ด์Šค ์ˆ˜ ์ฐจ๊ฐ
        }
    }

    // ============================================================
    // ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ์ง€
    // ํ”„๋กœ์ ํŠธ ์„ค์ •์„ ์ฝ์–ด URP / HDRP / Built-in์„ ์ž๋™ ํŒ๋ณ„ํ•ฉ๋‹ˆ๋‹ค.
    // ์…ฐ์ด๋” ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์ด ํŒŒ์ดํ”„๋ผ์ธ๋งˆ๋‹ค ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    //   URP:     _BaseColor,    _EmissionColor
    //   HDRP:    _BaseColor,    _EmissiveColor
    //   Built-in: _Color,       _EmissionColor
    // ============================================================
    RenderPipeline DetectPipeline()
    {
        var pipeline = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;

        if (pipeline == null) return RenderPipeline.BuiltIn; // Built-in์€ ์—์…‹์ด null

        string name = pipeline.GetType().Name;

        if (name.Contains("Universal")) return RenderPipeline.URP;
        if (name.Contains("HighDefinition") || name.Contains("HDRP")) return RenderPipeline.HDRP;

        return RenderPipeline.BuiltIn;
    }
}

0. ์ฝ”๋“œ์˜ ์„ค๊ณ„ ๊ตฌ์กฐ

์ด ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋Š” "์•„ํŠธ์›Œํฌ ๋กœ์ง๋งŒ ๊ต์ฒดํ•˜๋ฉด ์ƒˆ๋กœ์šด ์ž‘ํ’ˆ์ด ์™„์„ฑ" ๋˜๋Š” ๊ตฌ์กฐ๋ฅผ ๋ชฉํ‘œ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ๋Š” ๋‘ ๋ ˆ์ด์–ด๋กœ ๋ช…ํ™•ํžˆ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค.

๋ ˆ์ด์–ด์—ญํ• ์ˆ˜์ • ์—ฌ๋ถ€
์ธํ”„๋ผ ์ฝ”๋“œUnity ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ, GPU ๋“œ๋กœ์šฐ, ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ์ง€์ˆ˜์ • ๋ถˆํ•„์š”
์•„ํŠธ์›Œํฌ ๋กœ์ง์˜ค๋ธŒ์ ํŠธ ๋ฐฐ์น˜ ๊ณต์‹, ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ณต์‹์ด ๋ถ€๋ถ„๋งŒ ์ˆ˜์ •

1. ์ „์ฒด ์‹คํ–‰ ํ๋ฆ„

[ ์ปดํฌ๋„ŒํŠธ ํ™œ์„ฑํ™” ]
        โ”‚
        โ–ผ
   OnEnable()
   โ”œโ”€ DetectPipeline()      โ† ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ์ง€
   โ”œโ”€ EnsureResources()     โ† ๋ฉ”์‹œ / ๋จธํ‹ฐ๋ฆฌ์–ผ ์ž๋™ ์ƒ์„ฑ
   โ”œโ”€ Generate()            โ† ์ธ์Šคํ„ด์Šค ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด ๊ตฌ์„ฑ
   โ”‚   โ””โ”€ Animate(0f)       โ† ์ดˆ๊ธฐ ์ •์ง€ ์ƒํƒœ ๊ณ„์‚ฐ
   โ””โ”€ CacheStructureParams() โ† ํ˜„์žฌ ๊ตฌ์กฐ ํŒŒ๋ผ๋ฏธํ„ฐ ์ €์žฅ

        โ”‚
        โ–ผ (์ธ์ŠคํŽ™ํ„ฐ ๊ฐ’ ๋ณ€๊ฒฝ)

   OnValidate()
   โ”œโ”€ DetectPipeline
   โ”œโ”€ EnsureResources
   โ”œโ”€ StructureParamsChanged ?
        โ”œโ”€ [ Yes ] โ†’ CacheStructureParams โ†’ QueueGenerate() โ†’ delayCall โ†’ Generate()
        โ””โ”€ [ No ] โ†’ Animate(0f) (๊ตฌ์กฐ๋ณ€๊ฒฝ์— ๋”ฐ๋ฅธ ์žฌ์ƒ์„ฑ ์—†์Œ)

        โ”‚
        โ–ผ (๋งค ํ”„๋ ˆ์ž„)

   Update()
   โ”œโ”€ generated ํ™•์ธ
   โ”œโ”€ [Editor ๋ชจ๋“œ] โ†’ Animate(0f) + RenderInstances()
   โ””โ”€ [Play ๋ชจ๋“œ]   โ†’ Animate(Time.time * waveSpeed) + RenderInstances()

ํ•ต์‹ฌ ํฌ์ธํŠธ: Generate()๋Š” ๋น„์šฉ์ด ํฐ ์—ฐ์‚ฐ์ž…๋‹ˆ๋‹ค. ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งŒ ํ˜ธ์ถœํ•˜๊ณ ,
๊ตฌ์กฐ๊ฐ€ ์•ˆ ๋ฐ”๋€Œ๋ฉด ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•œ ์ฑ„ ์‹œ๊ฐ์ ์ธ ๋ณ€ํ™”(waveHeight, hueBase ๋“ฑ)๋ฅผ Animate()๋งŒ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


2. ํ•จ์ˆ˜๋ณ„ ์ƒ์„ธ ๋ถ„์„

2-1. OnEnable() โ€” ์ปดํฌ๋„ŒํŠธ ์ง„์ž…์ 

void OnEnable()
{
    _pipeline = DetectPipeline();
    EnsureResources();
    Generate();
    CacheStructureParams();
}

์”ฌ ๋กœ๋“œ, ์ปดํฌ๋„ŒํŠธ ํ™œ์„ฑํ™”, ์—๋””ํ„ฐ์—์„œ ์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ ์žฌ์ ์šฉ ๋“ฑ
์ปดํฌ๋„ŒํŠธ๊ฐ€ "์ผœ์ง€๋Š”" ๋ชจ๋“  ์ƒํ™ฉ์—์„œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

ํ˜ธ์ถœ ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. Generate() ์ด์ „์— EnsureResources()๊ฐ€ ์‹คํ–‰๋˜์–ด์•ผ
๋ฉ”์‹œ์™€ ๋จธํ‹ฐ๋ฆฌ์–ผ์ด ์ค€๋น„๋œ ์ƒํƒœ์—์„œ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


2-2. DetectPipeline() โ€” ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ ๊ฐ์ง€

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

URP, HDRP, Built-in์€ ์…ฐ์ด๋” ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

ํŒŒ์ดํ”„๋ผ์ธAlbedo ํ”„๋กœํผํ‹ฐEmission ํ”„๋กœํผํ‹ฐ
URP_BaseColor_EmissionColor
HDRP_BaseColor_EmissiveColor
Built-in_Color_EmissionColor

GraphicsSettings.currentRenderPipeline์ด null์ด๋ฉด Built-in์ž…๋‹ˆ๋‹ค.
Built-in์€ ๋ณ„๋„์˜ Render Pipeline Asset์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


2-3. EnsureResources() โ€” ๋ฉ”์‹œยท๋จธํ‹ฐ๋ฆฌ์–ผ ์ž๋™ ์ƒ์„ฑ

void EnsureResources()
{
    if (instanceMesh == null)
    {
        GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Cube);
        instanceMesh = temp.GetComponent<MeshFilter>().sharedMesh;
        DestroyImmediate(temp);
    }
    if (instanceMaterial == null)
    {
        Shader shader =
            Shader.Find("Universal Render Pipeline/Lit") ??
            Shader.Find("HDRP/Lit") ??
            Shader.Find("Standard");
        instanceMaterial = new Material(shader);
        instanceMaterial.enableInstancing = true; // ๋ฐ˜๋“œ์‹œ true
        instanceMaterial.EnableKeyword("_EMISSION");
    }
}

์„ค๊ณ„ ์˜๋„: ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ์ง์ ‘ ํ• ๋‹นํ•˜์ง€ ์•Š์•„๋„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
GameObject.CreatePrimitive()๋กœ ์ž„์‹œ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  sharedMesh๋งŒ ์ถ”์ถœํ•œ ๋’ค
์ฆ‰์‹œ DestroyImmediate()๋กœ ์”ฌ์—์„œ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. ์”ฌ ๊ณ„์ธต์— ์ž”๋ฅ˜ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋‚จ๊ธฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

enableInstancing = true๋Š” GPU Instancing์˜ ํ•„์ˆ˜ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค.
์ด ํ”Œ๋ž˜๊ทธ๊ฐ€ ๊บผ์ ธ ์žˆ์œผ๋ฉด DrawMeshInstanced()๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์ธ์Šคํ„ด์‹ฑ์ด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


2-4. Generate() โ€” ์ธ์Šคํ„ด์Šค ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด ๊ตฌ์„ฑ

void Generate()
{
    _instances.Clear();
    _generated = false;

    // [์•„ํŠธ์›Œํฌ ๋กœ์ง] โ€” ๋ฐฐ์น˜ ๊ณต์‹
    Random.InitState(colorSeed);
    float offset = (gridSize - 1) * spacing * 0.5f;
    for (int x = 0; x < gridSize; x++)
        for (int z = 0; z < gridSize; z++)
            _instances.Add(new InstanceData { ... });

    // GPU ์ „์†ก ๋ฐฐ์—ด ์‚ฌ์ „ ํ• ๋‹น
    _matrices  = new Matrix4x4[_instances.Count];
    _colors    = new Vector4[_instances.Count];
    _emissions = new Vector4[_instances.Count];

    _generated = true;
    Animate(0f);
}

Generate()๋Š” GameObject๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

GPU Instancing์€ ์”ฌ ๊ณ„์ธต(Hierarchy)์— ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋“ฑ๋กํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋Œ€์‹  InstanceData ๊ตฌ์กฐ์ฒด ๋ฐฐ์—ด์„ ๋ฉ”๋ชจ๋ฆฌ์— ๊ตฌ์„ฑํ•˜๊ณ ,
์ดํ›„ Animate()๊ฐ€ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด GPU ์ „์†ก์šฉ ๋ฐฐ์—ด(_matrices, _colors)์„ ์ฑ„์›๋‹ˆ๋‹ค.

_generated ํ”Œ๋ž˜๊ทธ๋Š” ์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— Update()๊ฐ€ ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”
๊ฐ€๋“œ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์€ ์ดˆ๊ธฐํ™” ์ˆœ์„œ์— ์˜ํ•œ NullReferenceException์„ ์˜ˆ๋ฐฉํ•ฉ๋‹ˆ๋‹ค.

Random.InitState(colorSeed)๋กœ ์‹œ๋“œ๋ฅผ ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ์‹œ๋“œ ๊ฐ’์ด๋ฉด
์—๋””ํ„ฐ๋ฅผ ์žฌ์‹œ์ž‘ํ•ด๋„ ํ•ญ์ƒ ๋™์ผํ•œ ์ƒ‰์ƒ ๋ฐฐ์น˜๊ฐ€ ์žฌํ˜„๋ฉ๋‹ˆ๋‹ค.


2-5. OnValidate() + QueueGenerate() โ€” ์—๋””ํ„ฐ ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜

void OnValidate()
{
    if (StructureParamsChanged()) {
        CacheStructureParams();
        QueueGenerate();        // โ†’ ๋‹ค์Œ ํ”„๋ ˆ์ž„์— Generate() ์‹คํ–‰
    } else {
        Animate(0f);            // โ†’ ์ฆ‰์‹œ ์‹œ๊ฐ ๊ฐฑ์‹ 
    }
}

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

OnValidate()๋Š” ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
์ด ์ฝœ๋ฐฑ ์•ˆ์—์„œ ์ง์ ‘ ์”ฌ์„ ์ˆ˜์ •ํ•˜๋ฉด Unity ๋‚ด๋ถ€ ์ง๋ ฌํ™”์™€ ์ถฉ๋Œํ•ฉ๋‹ˆ๋‹ค.
EditorApplication.delayCall์€ ํ˜„์žฌ ํ”„๋ ˆ์ž„์˜ ์ง๋ ฌํ™” ์‚ฌ์ดํด์ด ๋๋‚œ ๋’ค
์•ˆ์ „ํ•œ ํƒ€์ด๋ฐ์— ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

QueueGenerate()๋Š” EditorApplication.delayCall์„ ์‚ฌ์šฉํ•ด Generate()๋ฅผ ํ•œ ํ”„๋ ˆ์ž„ ๋’ค๋กœ ๋ฏธ๋ฃน๋‹ˆ๋‹ค.

_generateQueued ํ”Œ๋ž˜๊ทธ๋Š” ์ธ์ŠคํŽ™ํ„ฐ ์Šฌ๋ผ์ด๋”๋ฅผ ๋“œ๋ž˜๊ทธํ•  ๋•Œ OnValidate๊ฐ€
์ˆ˜์‹ญ ๋ฒˆ ์—ฐ์† ํ˜ธ์ถœ๋˜์–ด๋„ Generate()๊ฐ€ ๋‹จ ํ•œ ๋ฒˆ๋งŒ ์˜ˆ์•ฝ๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์„ค๊ณ„ ํฌ์ธํŠธ๋Š” ๋‘ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

  • ์ค‘๋ณต ์˜ˆ์•ฝ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ _generateQueued ํ”Œ๋ž˜๊ทธ.
  • delay callback ์‹คํ–‰ ์‹œ this == null ์ฒดํฌ.

2-6. Animate(float t) โ€” ๋งค ํ”„๋ ˆ์ž„ ๋ฐฐ์—ด ๊ฐฑ์‹ 

void Animate(float t)
{
    for (int i = 0; i < _instances.Count; i++)
    {
        InstanceData inst = _instances[i];

        // [์•„ํŠธ์›Œํฌ ๋กœ์ง] โ€” ์œ„์น˜ยท์ƒ‰์ƒ ๊ณต์‹
        float wave = Mathf.Sin(
            (inst.basePos.x + inst.basePos.z) * waveFrequency + t
        ) * waveHeight;

        float normalized = (wave / waveHeight + 1f) * 0.5f;

        Vector3 worldPos = transform.TransformPoint(
            new Vector3(inst.basePos.x, wave, inst.basePos.z)
        );

        _matrices[i]  = Matrix4x4.TRS(worldPos, transform.rotation, Vector3.one * cubeSize);
        _colors[i]    = (Vector4)Color.HSVToRGB(inst.hue, saturation, animBrightness);
        _emissions[i] = (Vector4)(Color.HSVToRGB(inst.hue, saturation, normalized) * emissionIntensity);
    }
}

t = 0f์ด๋ฉด ์ •์ง€ ์ƒํƒœ(์—๋””ํ„ฐ ์”ฌ ๋ทฐ),
t = Time.time * waveSpeed์ด๋ฉด Play ๋ชจ๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ž…๋‹ˆ๋‹ค.

Matrix4x4.TRS() ๋Š” Translation ร— Rotation ร— Scale ํ–‰๋ ฌ์„ ํ•œ ๋ฒˆ์— ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
GPU๋Š” ์ด ํ–‰๋ ฌ ํ•˜๋‚˜๋กœ ์ธ์Šคํ„ด์Šค์˜ ๋ชจ๋“  ๊ณต๊ฐ„ ๋ณ€ํ™˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

transform.TransformPoint() ๋Š” ๋กœ์ปฌ ์ขŒํ‘œ๋ฅผ ์›”๋“œ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ถ™์–ด ์žˆ๋Š” GameObject์˜ ์œ„์น˜/ํšŒ์ „์ด ์ž๋™์œผ๋กœ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.

normalized ๋ณ€์ˆ˜๋Š” ์‚ฌ์ธํŒŒ์˜ -1 ~ +1 ๋ฒ”์œ„๋ฅผ 0 ~ 1๋กœ ์ •๊ทœํ™”ํ•œ ๊ฐ’์ž…๋‹ˆ๋‹ค.
์ด ๊ฐ’์„ ์ƒ‰์ƒ์˜ ๋ฐ๊ธฐ(Value)์— ์—ฐ๊ฒฐํ•˜๋ฉด ํŒŒ๋™ ๋†’๋‚ฎ์ด์™€ ์ƒ‰์ƒ ๋ฐ๊ธฐ๊ฐ€ ์—ฐ๋™๋ฉ๋‹ˆ๋‹ค.


2-7. RenderInstances() โ€” GPU ๋“œ๋กœ์šฐ ๋ช…๋ น ๋ฐœํ–‰

void RenderInstances()
{
    int total = _instances.Count;
    int index = 0;
    while (total > 0)
    {
        int batch = Mathf.Min(BATCH_SIZE, total); // BATCH_SIZE = 1023

        Matrix4x4[] batchMatrices  = new Matrix4x4[batch];
        Vector4[]   batchColors    = new Vector4[batch];
        Vector4[]   batchEmissions = new Vector4[batch];

        System.Array.Copy(_matrices,  index, batchMatrices,  0, batch);
        System.Array.Copy(_colors,    index, batchColors,    0, batch);
        System.Array.Copy(_emissions, index, batchEmissions, 0, batch);

        MaterialPropertyBlock block = new MaterialPropertyBlock();
        block.SetVectorArray("_BaseColor",     batchColors);
        block.SetVectorArray("_EmissionColor", batchEmissions);

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

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

Graphics.DrawMeshInstanced()๋Š” GameObject ์—†์ด ๋ฉ”์‹œ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ํ•ต์‹ฌ API์ž…๋‹ˆ๋‹ค.
์ด ํ•จ์ˆ˜ ํ•˜๋‚˜๋กœ batch๊ฐœ์˜ ๋ฉ”์‹œ๋ฅผ GPU์— ๋‹จ ํ•œ ๋ฒˆ์˜ ๋“œ๋กœ์šฐ ์ฝœ๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

1023 ์ œํ•œ์˜ ์ด์œ :
DrawMeshInstanced()๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ GPU ์ƒ์ˆ˜ ๋ฒ„ํผ(Constant Buffer)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
ํ”Œ๋žซํผ ๊ณตํ†ต ์ตœ์†Œ ๋ณด์žฅ ์ƒ์ˆ˜ ๋ฒ„ํผ ํฌ๊ธฐ(D3D11 ๊ธฐ์ค€ 4096 float4 ์Šฌ๋กฏ)์™€
Unity ๋‚ด๋ถ€ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ๊ณ ๋ คํ•œ ์‹ค์งˆ์  ์ƒํ•œ์ด 1023์ž…๋‹ˆ๋‹ค.

MaterialPropertyBlock์„ ๋ฐฐ์น˜๋งˆ๋‹ค new๋กœ ์ƒ์„ฑํ•˜๋Š” ์ด์œ :
๊ฐ™์€ block ์ธ์Šคํ„ด์Šค๋ฅผ SetVectorArray()๋กœ ๋ฎ์–ด์“ฐ๋ฉด
์ด์ „ ๋ฐฐ์น˜์— ๋Œ€ํ•œ ๋“œ๋กœ์šฐ ์ฝœ์ด ์•„์ง GPU ํ์— ์žˆ๋Š” ์‹œ์ ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฐฐ์น˜๋งˆ๋‹ค ์ƒˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.


3. ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์š”์•ฝ

Generate()
โ”‚
โ”œโ”€โ”€ InstanceData[] _instances          (์ •์  ๋ฐ์ดํ„ฐ โ€” ๋ฐฐ์น˜ ํ›„ ๋ณ€ํ•˜์ง€ ์•Š์Œ)
โ”‚     โ”œโ”€โ”€ basePos  : Vector3           ๊ธฐ์ค€ ์œ„์น˜ (Y=0 ํ‰๋ฉด)
โ”‚     โ”œโ”€โ”€ hue      : float             ๊ณ ์œ  ์ƒ‰์ƒ
โ”‚     โ””โ”€โ”€ brightness : float           ๊ธฐ๋ณธ ๋ฐ๊ธฐ
โ”‚
Animate(t)
โ”‚
โ”œโ”€โ”€ _matrices[]   : Matrix4x4[]        โ† TRS ํ–‰๋ ฌ (์œ„์น˜ + ํšŒ์ „ + ํฌ๊ธฐ)
โ”œโ”€โ”€ _colors[]     : Vector4[]          โ† Albedo ์ƒ‰์ƒ
โ””โ”€โ”€ _emissions[]  : Vector4[]          โ† Emission ์ƒ‰์ƒ
โ”‚
RenderInstances()
โ”‚
โ””โ”€โ”€ Graphics.DrawMeshInstanced()       GPU โ†’ ํ™”๋ฉด
      โ””โ”€โ”€ MaterialPropertyBlock        ์ธ์Šคํ„ด์Šค๋ณ„ ์ƒ‰์ƒ ์ฃผ์ž…

_instances๋Š” Generate()์—์„œ ํ•œ ๋ฒˆ๋งŒ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.
_matrices, _colors, _emissions๋Š” Animate()๊ฐ€ ๋งค ํ”„๋ ˆ์ž„ ๋ฎ์–ด์”๋‹ˆ๋‹ค.
RenderInstances()๋Š” ์™„์„ฑ๋œ ๋ฐฐ์—ด์„ ๊ทธ๋Œ€๋กœ GPU์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.


4. [ExecuteAlways]์˜ ์˜๋ฏธ

[ExecuteAlways]
public class BoilerplateGPUInstancing : MonoBehaviour

๊ธฐ๋ณธ์ ์œผ๋กœ Update(), OnEnable() ๋“ฑ Unity ์ด๋ฒคํŠธ๋Š” Play ๋ชจ๋“œ์—์„œ๋งŒ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
[ExecuteAlways]๋ฅผ ๋ถ™์ด๋ฉด ์—๋””ํ„ฐ ์”ฌ ๋ทฐ์—์„œ๋„ ์ด๋ฒคํŠธ๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์ด ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์—์„œ๋Š” ์ด ์†์„ฑ ๋•๋ถ„์—:

  • ์”ฌ ๋ทฐ๋ฅผ ์—ด์ž๋งˆ์ž ์•„ํŠธ์›Œํฌ๊ฐ€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
  • ์ธ์ŠคํŽ™ํ„ฐ ๊ฐ’์„ ๋ฐ”๊พธ๋ฉด Play ๋ฒ„ํŠผ ์—†์ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.

Update() ์•ˆ์—์„œ Application.isPlaying์œผ๋กœ ๋ถ„๊ธฐํ•˜์—ฌ
์—๋””ํ„ฐ์—์„œ๋Š” Animate(0f) (์ •์ง€),
Play ๋ชจ๋“œ์—์„œ๋Š” Animate(Time.time * waveSpeed) (์• ๋‹ˆ๋ฉ”์ด์…˜)์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.


5. ์ตœ์†Œ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๋‚˜๋งŒ์˜ GPU Instancing ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์ž‘์„ฑํ•  ๋•Œ ๋ฐ˜๋“œ์‹œ ํฌํ•จํ•ด์•ผ ํ•  ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.

ํ•„์ˆ˜ ๊ตฌ์กฐ

  • [ExecuteAlways] ์–ดํŠธ๋ฆฌ๋ทฐํŠธ
  • struct InstanceData โ€” ์ •์  ๋ฐ์ดํ„ฐ ์ €์žฅ ๊ตฌ์กฐ์ฒด
  • List<InstanceData> _instances โ€” ์ธ์Šคํ„ด์Šค ๋ชฉ๋ก
  • Matrix4x4[] _matrices, Vector4[] _colors โ€” GPU ์ „์†ก ๋ฐฐ์—ด
  • const int BATCH_SIZE = 1023 โ€” ๋ฐฐ์น˜ ์ƒํ•œ
  • bool _generated โ€” ์ƒ์„ฑ ์™„๋ฃŒ ๊ฐ€๋“œ ํ”Œ๋ž˜๊ทธ

ํ•„์ˆ˜ ํ•จ์ˆ˜

  • OnEnable() โ†’ EnsureResources() โ†’ Generate() โ†’ CacheStructureParams()
  • OnValidate() โ†’ ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์—ฌ๋ถ€ ๋ถ„๊ธฐ โ†’ QueueGenerate() or Animate(0f)
  • QueueGenerate() โ†’ EditorApplication.delayCall ํŒจํ„ด
  • Update() โ†’ isPlaying ๋ถ„๊ธฐ โ†’ Animate(t) + RenderInstances()
  • EnsureResources() โ†’ ๋ฉ”์‹œยท๋จธํ‹ฐ๋ฆฌ์–ผ null ์ฒดํฌ + ์ž๋™ ์ƒ์„ฑ
  • Generate() โ†’ _instances ๊ตฌ์„ฑ + ๋ฐฐ์—ด ํ• ๋‹น + Animate(0f) ํ˜ธ์ถœ
  • Animate(float t) โ†’ _matrices, _colors ๊ฐฑ์‹  ๋ฃจํ”„
  • RenderInstances() โ†’ 1023 ๋ฐฐ์น˜ ๋ถ„ํ•  while ๋ฃจํ”„ + DrawMeshInstanced()

์•„ํŠธ์›Œํฌ ๋กœ์ง ๊ต์ฒด ํฌ์ธํŠธ

ํ•จ์ˆ˜๊ต์ฒด ๋‚ด์šฉ
Generate()์ธ์Šคํ„ด์Šค๋ฅผ ๋ช‡ ๊ฐœ, ์–ด๋–ค ํŒจํ„ด์œผ๋กœ ๋ฐฐ์น˜ํ•  ๊ฒƒ์ธ๊ฐ€
Animate(t)๋งค ํ”„๋ ˆ์ž„ ์œ„์น˜ยท์ƒ‰์ƒ์„ ์–ด๋–ค ์ˆ˜์‹์œผ๋กœ ๊ณ„์‚ฐํ•  ๊ฒƒ์ธ๊ฐ€

์ด ๋‘ ํ•จ์ˆ˜์˜ ์ˆ˜์‹๋งŒ ๋ฐ”๊พธ๋ฉด ํŒŒ๋™, ๋‚˜์„ , ๋…ธ์ด์ฆˆ ํ•„๋“œ, ์–ดํŠธ๋ž™ํ„ฐ ๋“ฑ
์–ด๋–ค ์•„ํŠธ์›Œํฌ ํŒจํ„ด์œผ๋กœ๋„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


6. ์ฐธ๊ณ  โ€” ์ฃผ์š” Unity API

API์„ค๋ช…
Graphics.DrawMeshInstanced()GameObject ์—†์ด GPU ์ธ์Šคํ„ด์‹ฑ์œผ๋กœ ๋ฉ”์‹œ ๋ Œ๋”๋ง
MaterialPropertyBlock๋จธํ‹ฐ๋ฆฌ์–ผ ๋ณต์‚ฌ ์—†์ด ์ธ์Šคํ„ด์Šค๋ณ„ ํ”„๋กœํผํ‹ฐ ์„ค์ •
Matrix4x4.TRS()TranslationยทRotationยทScale ๋ณตํ•ฉ ํ–‰๋ ฌ ์ƒ์„ฑ
transform.TransformPoint()๋กœ์ปฌ ์ขŒํ‘œ โ†’ ์›”๋“œ ์ขŒํ‘œ ๋ณ€ํ™˜
EditorApplication.delayCall์—๋””ํ„ฐ ๋ฃจํ”„ ๋‹ค์Œ ํ‹ฑ์— ์ฝœ๋ฐฑ ์˜ˆ์•ฝ
GraphicsSettings.currentRenderPipelineํ˜„์žฌ ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ ์—์…‹ ๋ฐ˜ํ™˜
Random.InitState(seed)Unity ๋‚ด์žฅ ๋‚œ์ˆ˜ ์‹œ๋“œ ์ดˆ๊ธฐํ™”
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

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