🫧Art_024 Spiral Galaxy

BamgasiJMΒ·2026λ…„ 4μ›” 10일

Unity GenArt

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

Spiral Galaxy GPU Particle System

μ„€μΉ˜ 방법

1. 폴더 ꡬ쑰

Assets/
└── SpiralGalaxy/
    β”œβ”€β”€ SpiralGalaxySystem.cs          ← C# 슀크립트
    β”œβ”€β”€ Resources/
    β”‚   └── SpiralGalaxyCompute.compute  ← β˜… Resources 폴더 ν•„μˆ˜
    └── Shaders/
        └── SpiralGalaxy.shader

주의: SpiralGalaxyCompute.compute λŠ” λ°˜λ“œμ‹œ Resources/ 폴더 μ•ˆμ— μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
SpiralGalaxy.shader λŠ” μ–΄λŠ 폴더에 μžˆμ–΄λ„ λ¬΄λ°©ν•©λ‹ˆλ‹€ (Shader.Find 둜 이름 검색).


2. 씬 μ„ΈνŒ…

  1. 빈 GameObject 생성 (Create Empty)
  2. SpiralGalaxySystem μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
  3. 끝 β€” Inspector μ—μ„œ Compute/Shader/Material 을 λ“œλž˜κ·Έν•  ν•„μš” μ—†μŒ

3. μ—λ””ν„°μ—μ„œ λ°”λ‘œ 보기

  • [ExecuteAlways] κ°€ μ„ μ–Έλ˜μ–΄ μžˆμ–΄ Play 없이 Scene/Game λ·°μ—μ„œ μ¦‰μ‹œ λ Œλ”λ§λ©λ‹ˆλ‹€.
  • Inspector 의 νŒŒλΌλ―Έν„°λ₯Ό μ‘°μ •ν•˜λ©΄ OnValidate β†’ Cleanup β†’ Initialize μˆœμ„œλ‘œ μž¬μ΄ˆκΈ°ν™”λ©λ‹ˆλ‹€.

4. Inspector νŒŒλΌλ―Έν„°

νŒŒλΌλ―Έν„°κΈ°λ³Έκ°’μ„€λͺ…
Particle Count500,000νŒŒν‹°ν΄ 수 (GPU λ©”λͺ¨λ¦¬ 주의)
Galaxy Radius40μ€ν•˜ λ°˜μ§€λ¦„ (Unit)
Spiral Tightness0.35λ‚˜μ„  감기 정도
Arm Count4λ‚˜μ„ νŒ” 수
Arm Width0.25λ‚˜μ„ νŒ” λ‘κ»˜ (0~1)
Disk Thickness0.08μ›λ°˜ 수직 λ‘κ»˜ λΉ„μœ¨
Rotation Speed4νšŒμ „ 속도 (Play λͺ¨λ“œ)
Particle Size Min/Max0.04 / 0.18νŒŒν‹°ν΄ 크기 λ²”μœ„
Brightness Boost1.2전체 밝기 배율

5. 카메라 ꢌμž₯ μ„€μ •

  • Background: Solid Color β†’ μ™„μ „ν•œ κ²€μ • (0,0,0,0)
  • Clear Flags: Solid Color
  • μœ„μ—μ„œ μ•½ 45Β° λ‚΄λ €λ‹€λ³΄λŠ” 각도 (예: Position(0, 60, -60), Rotation(45, 0, 0))

6. λ Œλ” νŒŒμ΄ν”„λΌμΈ μ£Όμ˜μ‚¬ν•­

이 μ…°μ΄λ”λŠ” Built-in Render Pipeline κΈ°μ€€μž…λ‹ˆλ‹€.

URP μ‚¬μš© μ‹œ SpiralGalaxy.shader 의 Pass 블둝을 μ•„λž˜μ™€ 같이 μˆ˜μ •ν•˜μ„Έμš”:

Tags { "LightMode" = "UniversalForward" }

그리고 UnityCG.cginc β†’ Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl 둜 κ΅μ²΄ν•©λ‹ˆλ‹€.


7. νŠΈλŸ¬λΈ”μŠˆνŒ…

증상원인해결
SpiralGalaxyCompute.compute λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€Resources 폴더 미쑴재Resources/ 폴더 μ•ˆμ— .compute 파일 배치
Shader 'SpiralGalaxy/Particle' λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€Shader 이름 뢈일치.shader 파일 첫 쀄 Shader "SpiralGalaxy/Particle" 확인
μ—λ””ν„°μ—μ„œ 아무것도 μ•ˆ λ³΄μž„Scene view Camera not renderingScene λ·° 상단 Gizmos ν™œμ„±ν™”, Game λ·° 확인
νŒŒν‹°ν΄μ΄ 흰색 μ‚¬κ°ν˜•μœΌλ‘œ ν‘œμ‹œλ¨Shader compile 였λ₯˜Console μ°½ 였λ₯˜ 확인, #pragma target 4.5 지원 GPU μ—¬λΆ€ 확인

Code

SpiralGalaxySystem.cs

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

/// 폴더 ꡬ쑰:
///   Assets/SpiralGalaxy/
///     β”œβ”€β”€ SpiralGalaxySystem.cs
///     β”œβ”€β”€ Resources/
///     β”‚   └── SpiralGalaxyCompute.compute
///     └── Shaders/
///         └── SpiralGalaxy.shader

[ExecuteAlways]
public class SpiralGalaxySystem : MonoBehaviour
{
    [Header("Particle Settings")]
    public int particleCount = 500000;

    [Header("Galaxy Shape")]
    public float galaxyRadius    = 40f;
    public float spiralTightness = 0.35f;
    public int   armCount        = 4;
    public float armWidth        = 0.25f;
    public float diskThickness   = 0.08f;

    [Header("Animation")]
    public float rotationSpeed   = 4f;

    [Header("Visuals")]
    public float particleSizeMin = 0.04f;
    public float particleSizeMax = 0.18f;
    public float brightnessBoost = 1.2f;

    // ── λ‚΄λΆ€ μƒνƒœ ─────────────────────────────────────────────────
    ComputeShader _compute;
    Material      _material;
    Mesh          _mesh;
    ComputeBuffer _particleBuffer;

    int  _kernelInit;
    int  _kernelUpdate;
    int  _threadGroups;
    bool _initialized;

    // position(float3) + velocity(float3) + size(float) + color(float4) = 11 floats = 44 bytes
    const int PARTICLE_STRIDE = 11 * sizeof(float);

    // ─────────────────────────────────────────────────────────────
    void OnEnable()  => Initialize();
    void OnDisable() => Cleanup();

    // ─────────────────────────────────────────────────────────────
    void Initialize()
    {
        if (_initialized) return;

        // ── 1. ComputeShader (Resources 폴더 ν•„μˆ˜) ────────────────
        if (_compute == null)
        {
            _compute = Resources.Load<ComputeShader>("SpiralGalaxyCompute");
            if (_compute == null)
            {
                Debug.LogError("[SpiralGalaxy] Resources/SpiralGalaxyCompute.compute μ—†μŒ");
                return;
            }
        }

        // ── 2. Shader β†’ Material ──────────────────────────────────
        if (_material == null)
        {
            var shader = Shader.Find("SpiralGalaxy/Particle");
            if (shader == null)
            {
                Debug.LogError("[SpiralGalaxy] Shader 'SpiralGalaxy/Particle' μ—†μŒ");
                return;
            }
            _material = new Material(shader) { hideFlags = HideFlags.HideAndDontSave };
        }

        // ── 3. Quad Mesh 직접 생성 (CreatePrimitive μ—†μŒ) ─────────
        if (_mesh == null)
            _mesh = CreateQuadMesh();

        // ── 4. 컀널 인덱슀 ────────────────────────────────────────
        _kernelInit   = _compute.FindKernel("Initialize");
        _kernelUpdate = _compute.FindKernel("Update");

        // ── 5. ComputeBuffer ──────────────────────────────────────
        _particleBuffer = new ComputeBuffer(particleCount, PARTICLE_STRIDE);

        // ── 6. 버퍼 바인딩 ────────────────────────────────────────
        _compute.SetBuffer(_kernelInit,   "particles", _particleBuffer);
        _compute.SetBuffer(_kernelUpdate, "particles", _particleBuffer);
        _material.SetBuffer("particles",               _particleBuffer);

        // ── 7. νŒŒλΌλ―Έν„° β†’ Initialize 컀널 μ‹€ν–‰ ───────────────────
        UploadShapeParams();
        _threadGroups = Mathf.CeilToInt(particleCount / 256f);
        _compute.Dispatch(_kernelInit, _threadGroups, 1, 1);

        _initialized = true;
    }

    // ─────────────────────────────────────────────────────────────
    void Update()
    {
        if (!_initialized) Initialize();
        if (!_initialized) return;

        if (Application.isPlaying)
        {
            _compute.SetFloat("deltaTime",     Time.deltaTime);
            _compute.SetFloat("time",          Time.time);
            _compute.SetFloat("rotationSpeed", rotationSpeed);
            _compute.Dispatch(_kernelUpdate, _threadGroups, 1, 1);
        }

        _material.SetFloat("_BrightnessBoost", brightnessBoost);
        Render();
    }

    // ─────────────────────────────────────────────────────────────
    void Render()
    {
        if (_mesh == null || _material == null || _particleBuffer == null) return;

        Graphics.DrawMeshInstancedProcedural(
            _mesh,
            0,
            _material,
            new Bounds(Vector3.zero, Vector3.one * galaxyRadius * 3f),
            particleCount
        );
    }

    // ─────────────────────────────────────────────────────────────
    void UploadShapeParams()
    {
        _compute.SetFloat("galaxyRadius",    galaxyRadius);
        _compute.SetFloat("spiralTightness", spiralTightness);
        _compute.SetFloat("armWidth",        armWidth);
        _compute.SetFloat("diskThickness",   diskThickness);
        _compute.SetFloat("particleSizeMin", particleSizeMin);
        _compute.SetFloat("particleSizeMax", particleSizeMax);
        _compute.SetInt("particleCount",     particleCount);
        _compute.SetInt("armCount",          armCount);
    }

    // ─────────────────────────────────────────────────────────────
    void Cleanup()
    {
        _particleBuffer?.Release();
        _particleBuffer = null;

        SafeDestroy(_material); _material = null;
        SafeDestroy(_mesh);     _mesh     = null;

        _initialized = false;
    }

    // OnValidate μ—μ„œ DestroyImmediate 직접 호좜 κΈˆμ§€
    // β†’ delayCall 둜 ν˜„μž¬ 콜백이 λλ‚œ λ’€ μž¬μ΄ˆκΈ°ν™”
    void OnValidate()
    {
#if UNITY_EDITOR
        EditorApplication.delayCall += () =>
        {
            if (this == null) return;  // μ»΄ν¬λ„ŒνŠΈκ°€ μ‚­μ œλœ 경우 λ°©μ–΄
            Cleanup();
            Initialize();
        };
#endif
    }

    // ─────────────────────────────────────────────────────────────
    // CreatePrimitive + Destroy λŒ€μ‹  Mesh 직접 생성
    // ─────────────────────────────────────────────────────────────
    static Mesh CreateQuadMesh()
    {
        var m = new Mesh
        {
            name      = "GalaxyQuad",
            hideFlags = HideFlags.HideAndDontSave
        };
        m.vertices = new[]
        {
            new Vector3(-0.5f, -0.5f, 0f),
            new Vector3( 0.5f, -0.5f, 0f),
            new Vector3( 0.5f,  0.5f, 0f),
            new Vector3(-0.5f,  0.5f, 0f),
        };
        m.uv = new[]
        {
            new Vector2(0f, 0f),
            new Vector2(1f, 0f),
            new Vector2(1f, 1f),
            new Vector2(0f, 1f),
        };
        m.triangles = new[] { 0, 2, 1,  0, 3, 2 };
        m.RecalculateNormals();
        return m;
    }

    // ─────────────────────────────────────────────────────────────
    // 에디터/λŸ°νƒ€μž„ 곡톡 μ•ˆμ „ μ‚­μ œ
    // ─────────────────────────────────────────────────────────────
    static void SafeDestroy(Object obj)
    {
        if (obj == null) return;
#if UNITY_EDITOR
        if (!Application.isPlaying)
            DestroyImmedi...

<...etc...>

SpiralGalaxyCompute.compute

// SpiralGalaxyCompute.compute
// μœ„μΉ˜: Assets/SpiralGalaxy/Resources/SpiralGalaxyCompute.compute
//
// Particle stride = 11 floats = 44 bytes
//   float3 position (0~11)
//   float3 velocity (12~23)
//   float  size     (24~27)
//   float4 color    (28~43)

#pragma kernel Initialize
#pragma kernel Update

struct Particle
{
    float3 position;
    float3 velocity;
    float  size;
    float4 color;
};

RWStructuredBuffer<Particle> particles;

// ── Galaxy shape params ──────────────────────────────────────────
float galaxyRadius;
float spiralTightness;
float armWidth;
float diskThickness;
float particleSizeMin;
float particleSizeMax;
int   particleCount;
int   armCount;

// ── Animation params ─────────────────────────────────────────────
float rotationSpeed;
float deltaTime;
float time;

// ────────────────────────────────────────────────────────────────
// Hash functions (integer-based, no stdlib 의쑴 μ—†μŒ)
// ────────────────────────────────────────────────────────────────
uint hash1(uint n)
{
    n = (n ^ 61u) ^ (n >> 16u);
    n *= 9u;
    n = n ^ (n >> 4u);
    n *= 0x27d4eb2du;
    n = n ^ (n >> 15u);
    return n;
}

float rand(uint seed)
{
    return float(hash1(seed) & 0x00FFFFFFu) / float(0x01000000u);
}

// Box-Muller: κ°€μš°μ‹œμ•ˆ 뢄포 (평균 0, ν‘œμ€€νŽΈμ°¨ 1)
float2 randGaussian(uint seed)
{
    float u1 = max(rand(seed * 1664525u + 1013904223u), 1e-6);
    float u2 = rand(seed * 22695477u + 1u);
    float mag = sqrt(-2.0 * log(u1));
    float phi = 6.28318530718 * u2;
    return float2(mag * cos(phi), mag * sin(phi));
}

// ────────────────────────────────────────────────────────────────
// Initialize 컀널
// ────────────────────────────────────────────────────────────────
[numthreads(256, 1, 1)]
void Initialize(uint3 id : SV_DispatchThreadID)
{
    uint i = id.x;
    if ((int)i >= particleCount) return;

    // ── λ‚˜μ„ νŒ” 선택 ──────────────────────────────────────────────
    int   arm       = (int)(i % (uint)armCount);
    float armAngle  = (float)arm / (float)armCount * 6.28318530718;

    // ── λ°˜μ§€λ¦„: μ§€μˆ˜ 뢄포 (쀑심에 집쀑, μ™Έκ³½μœΌλ‘œ 희미해짐) ────────
    float r_raw = rand(hash1(i * 3u + 0u));
    // pow(x, 0.6)으둜 쀑간 λ°˜μ§€λ¦„λŒ€ 밀도 κ°•ν™”
    float r     = pow(r_raw, 0.6) * galaxyRadius;

    // ── λ‚˜μ„  각도 = λ‚˜μ„ νŒ” μœ„μƒ + λ‚˜μ„  감기 + νŒ” λ‚΄ λΆ„μ‚° ─────────
    float spiralAngle = r * spiralTightness * 6.28318530718;

    // νŒ” λ‚΄ 각도 λΆ„μ‚° (κ°€μš°μ‹œμ•ˆ)
    float2 g     = randGaussian(hash1(i * 7u + 1u));
    float scatter = g.x * armWidth * (1.0 + r / galaxyRadius); // μ™Έκ³½μΌμˆ˜λ‘ 퍼짐

    float theta  = armAngle + spiralAngle + scatter;

    // ── μœ„μΉ˜ 계산 ─────────────────────────────────────────────────
    float x = r * cos(theta);
    float z = r * sin(theta);

    // 수직 λ°©ν–₯: 쀑심은 두껍고 외곽은 얇은 μ›λ°˜
    float yScale = galaxyRadius * diskThickness * (1.0 - r / galaxyRadius * 0.7);
    float y      = g.y * yScale;

    particles[i].position = float3(x, y, z);
    particles[i].velocity = float3(0, 0, 0);

    // ── νŒŒν‹°ν΄ 크기 ───────────────────────────────────────────────
    float sizeFactor = rand(hash1(i * 11u + 3u));
    // 쀑심뢀에 μ•½κ°„ 더 큰 μž…μž 배치
    float coreBoost  = exp(-r / (galaxyRadius * 0.3)) * 0.5;
    particles[i].size = lerp(particleSizeMin, particleSizeMax, sizeFactor + coreBoost);

    // ── 색상: 쀑심(μ²­λ°±) β†’ 쀑간(λ”°λœ»ν•œ 흰색) β†’ μ™Έκ³½(μ κ°ˆμƒ‰/희미) ──
    float t = r / galaxyRadius; // 0 = 쀑심, 1 = μ™Έκ³½

    // λ‚˜μ„ νŒ” μœ„μ— μžˆλŠ”μ§€ νŒλ³„ (νŒ” κ°λ„μ™€μ˜ 거리)
    float onArm = saturate(1.0 - abs(scatter) / (armWidth * 2.0));

    float4 coreColor   = float4(0.55, 0.80, 1.00, 1.0);   // μ°¨κ°€μš΄ μ²­λ°±
    float4 midColor    = float4(1.00, 0.95, 0.80, 1.0);   // λ”°λœ»ν•œ 흰색
    float4 outerColor  = float4(0.90, 0.45, 0.20, 0.5);   // 뢉은빛, 희미

    float4 col;
    if (t < 0.4)
        col = lerp(coreColor, midColor, t / 0.4);
    else
        col = lerp(midColor,  outerColor, (t - 0.4) / 0.6);

    // λ‚˜μ„ νŒ” λ°”κΉ₯ μž…μžλŠ” 더 ν¬λ―Έν•˜κ²Œ
    col.a *= lerp(0.3, 1.0, onArm);
    // μ€‘μ‹¬μœΌλ‘œ 갈수둝 밝게
    col.a *= lerp(0.5, 1.0, 1.0 - t * 0.8);
    // μ™Έκ³½ νŽ˜μ΄λ“œμ•„μ›ƒ
    col.a *= saturate(1.0 - t * t);

    particles[i].color = col;
}

// ────────────────────────────────────────────────────────────────
// Update 컀널 (Play λͺ¨λ“œμ—μ„œλ§Œ 호좜됨)
// μ°¨λ“± νšŒμ „ (Differential rotation): λ‚΄λΆ€κ°€ 더 λΉ λ₯΄κ²Œ νšŒμ „
// ────────────────────────────────────────────────────────────────
[numthreads(256, 1, 1)]
void Update(uint3 id : SV_DispatchThreadID)
{
    uint i = id.x;
    if ((int)i >= particleCount) return;

    float3 pos = particles[i].position;

    // XZ 평면 λ°˜μ§€λ¦„
    float r = length(float2(pos.x, pos.z));

    // μΌ€ν”ŒλŸ¬μ‹ μ°¨λ“± νšŒμ „: 쀑심 κ·Όμ²˜λŠ” λΉ λ₯΄κ³ , 외곽은 느림
    // v ∝ 1 / sqrt(r + epsilon) λ₯Ό 근사
    float keplerian = rotationSpeed / sqrt(max(r * 0.4 + 0.5, 0.01));

    float angle  = keplerian * deltaTime;
    float cosA   = cos(angle);
    float sinA   = sin(angle);

    float newX = pos.x * cosA - pos.z * sinA;
    float newZ = pos.x * sinA + pos.z * cosA;

    particles[i].position = float3(newX, pos.y, newZ);
}

SpiralGalaxy.shader

// μœ„μΉ˜: Assets/SpiralGalaxy/Shaders/SpiralGalaxy.shader
Shader "SpiralGalaxy/Particle"
{
    Properties
    {
        _BrightnessBoost ("Brightness Boost", Float) = 1.2
    }

    SubShader
    {
        Tags
        {
            "Queue"           = "Transparent"
            "RenderType"      = "Transparent"
            "IgnoreProjector" = "True"
        }

        Pass
        {
            Blend  One One      // Additive: νŒŒν‹°ν΄ λˆ„μ  = μ„±μš΄ 효과
            ZWrite Off
            ZTest  LEqual
            Cull   Off
            Lighting Off

            CGPROGRAM
            #pragma vertex   vert
            #pragma fragment frag
            #pragma target   4.5
            // DrawMeshInstancedProcedural 에 ν•„μˆ˜
            #pragma multi_compile_instancing
            #pragma instancing_options procedural:setup

            #include "UnityCG.cginc"

            // ── Particle ꡬ쑰체 (C# PARTICLE_STRIDE = 44 bytes 와 동일) ──
            struct Particle
            {
                float3 position;   // 12 bytes
                float3 velocity;   // 12 bytes
                float  size;       //  4 bytes
                float4 color;      // 16 bytes
            };

            StructuredBuffer<Particle> particles;
            float _BrightnessBoost;

            // procedural instancing setup ν•¨μˆ˜ (빈 ν•¨μˆ˜μ—¬λ„ μ„ μ–Έ ν•„μš”)
            void setup() {}

            struct appdata
            {
                float4 vertex   : POSITION;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 pos   : SV_POSITION;
                float2 uv    : TEXCOORD0;
                float4 color : COLOR;
            };

            v2f vert(appdata v)
            {
                UNITY_SETUP_INSTANCE_ID(v);

                // unity_InstanceID: DrawMeshInstancedProcedural μ—μ„œ
                // SV_InstanceID κ°€ μžλ™μœΌλ‘œ 이 λ³€μˆ˜μ— μ €μž₯됨
                Particle p = particles[unity_InstanceID];

                // ── λΉŒλ³΄λ“œ: View Matrix 의 각 ν–‰ = 카메라 둜컬 μΆ•(μ›”λ“œ κΈ°μ€€)
                //
                // UNITY_MATRIX_V (View Matrix) ν–‰ ꡬ쑰:
                //   [0].xyz = 카메라 Right  (μ›”λ“œ 곡간)
                //   [1].xyz = 카메라 Up     (μ›”λ“œ 곡간)
                //   [2].xyz = 카메라 Back   (μ›”λ“œ 곡간, -Forward)
                //
                // 주의: UNITY_MATRIX_V[row][col] μ—μ„œ
                //   UNITY_MATRIX_V[0].xyz κ°€ μ˜¬λ°”λ₯Έ ν–‰ 접근법
                //   UNITY_MATRIX_V[0][0], [1][0], [2][0] 은 μ—΄ 0의 각 μ„±λΆ„μœΌλ‘œ
                //   Right/Up/Back 이 μ•„λ‹ˆλΌ 잘λͺ»λœ 벑터가 됨 ← 이전 λ²„μ „μ˜ 버그
                float3 camRight = UNITY_MATRIX_V[0].xyz;
                float3 camUp    = UNITY_MATRIX_V[1].xyz;

                // Quad UV λŠ” 0~1 λ²”μœ„, 쀑심 κΈ°μ€€ -0.5~0.5 둜 μ˜€ν”„μ…‹ λ³€ν™˜
                float2 offset   = v.texcoord.xy - 0.5;
                float3 worldPos = p.position
                                + camRight * (offset.x * p.size)
                                + camUp    * (offset.y * p.size);

                v2f o;
                o.pos   = UnityWorldToClipPos(worldPos);
                o.uv    = v.texcoord.xy;
                o.color = p.color * _BrightnessBoost;
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                // Gaussian falloff: 쀑심이 밝고 κ°€μž₯μžλ¦¬λŠ” λΆ€λ“œλŸ½κ²Œ 0으둜
                float2 uv    = i.uv * 2.0 - 1.0;    // -1 ~ 1
                float  dist2 = dot(uv, uv);           // |uv|^2

                float alpha = exp(-dist2 * 3.5);

                // 거의 투λͺ…ν•œ νŒŒν‹°ν΄ μ‘°κΈ° 폐기 (fillrate μ ˆμ•½)
                clip(alpha * i.color.a - 0.002);

                float4 col = i.color;
                col.rgb *= alpha;
                col.a    = alpha * i.color.a;
                return col;
            }
            ENDCG
        }
    }

    FallBack Off
}

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

0개의 λŒ“κΈ€