๐Ÿ“”C# Grammar Guide for Unity

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

Unity GenArt

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

Unity 6+ Generative Art / Procedural Art / Shader Art ๊ฐ€์ด๋“œ

์ด ๋ฌธ์„œ๋Š” Unity 6.0 ์ด์ƒ, URP ๊ธฐ์ค€, C# + HLSL/ShaderLab ์ฝ”๋“œ ์ค‘์‹ฌ์œผ๋กœ Generative Art, Procedural Art, Shader Art๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์ „ ์ง€์นจ์„œ์ž…๋‹ˆ๋‹ค. (AI๋กœ ์ž‘์„ฑ)


๋ชฉ์ฐจ


1. ์ด ๋ฌธ์„œ์˜ ๋ชฉ์ 

์ด ๋ฌธ์„œ๋Š” Generative Art๋ฅผ ์œ ๋‹ˆํ‹ฐ์—์„œ ์ฝ”๋“œ๋กœ ์ž‘์„ฑ ์‹œ ํ•ญ์ƒ ์—ผ๋‘์— ๋‘์–ด์•ผ ํ•˜๋Š” ๊ธฐ์ค€์„ ๋‹ค๋ฃฌ๋‹ค.

  • Unity์—์„œ ์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์•„ํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ์„ค๊ณ„ํ–ˆ๋Š”๊ฐ€
  • ์™œ ๊ทธ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๊ณจ๋ž๋Š”๊ฐ€
  • ์™œ Update ๋ฃจํ”„๋ฅผ ๊ทธ๋ ‡๊ฒŒ ์ž‘์„ฑํ–ˆ๋Š”๊ฐ€
  • ์™œ LineRenderer, Mesh, Texture2D, Shader ์ค‘ ํŠน์ • ๋ฐฉ์‹์„ ์„ ํƒํ–ˆ๋Š”๊ฐ€
  • ์™œ CPU๋กœ ์ฒ˜๋ฆฌํ–ˆ๊ณ , ์™œ ์–ด๋–ค ๋ถ€๋ถ„์€ GPU๋กœ ๋„˜๊ฒผ๋Š”๊ฐ€

2. Unity Generative Art๋ฅผ ๋ณด๋Š” ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ด€์ 

Unity์—์„œ Generative Art, Procedural Art, Shader Art๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ ๋‹ค๋Š” ๊ฒƒ์€ ๊ฒฐ๊ตญ ์‹ค์‹œ๊ฐ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•˜๋Š” ์ผ์ด๋‹ค.

๊ฒ‰์œผ๋กœ ๋ณด๊ธฐ์—๋Š” ์„ , ์ , ํ…์Šค์ฒ˜, ๋ฉ”์‹œ, ์ƒ‰ ๋ณ€ํ™”์ฒ˜๋Ÿผ ๋ณด์—ฌ๋„ ๋‚ด๋ถ€์—์„œ๋Š” ๊ฑฐ์˜ ํ•ญ์ƒ ๊ฐ™์€ ๊ตฌ์กฐ๊ฐ€ ๋ฐ˜๋ณต๋œ๋‹ค.

  1. ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ ๋‹ค.
  2. ์‹œ๊ฐ„๊ณผ ๊ทœ์น™์— ๋”ฐ๋ผ ์ƒํƒœ๋ฅผ ๋ฐ”๊พผ๋‹ค.
  3. ๊ทธ ์ƒํƒœ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์ถœ๋ ฅํ•œ๋‹ค.

์ฆ‰, ํ•ต์‹ฌ์€ ํ™”๋ คํ•œ ๋น„์ฃผ์–ผ๋ณด๋‹ค ๋จผ์ € ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์„ค๊ณ„๋‹ค.

์ด ๊ด€์ ์ด ์ค‘์š”ํ•œ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ ธ๋„ ๊ตฌ์กฐ๋Š” ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ Œ๋”๋ง ๋ฐฉ์‹์„ ๋ฐ”๊ฟ”๋„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ฝ”์–ด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • CPU ๊ธฐ๋ฐ˜ ์‹คํ—˜์—์„œ Compute Shader ๊ธฐ๋ฐ˜ ํ™•์žฅ์œผ๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์‰ฝ๋‹ค.

3. ์ „์ฒด ๊ตฌ์กฐ๋Š” ๋ฐ˜๋“œ์‹œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ โ†’ ์ƒํƒœ ๋ณ€ํ™” โ†’ ์‹œ๊ฐํ™”๋กœ ๋‚˜๋ˆˆ๋‹ค

Unity์—์„œ ์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์‹œ์Šคํ…œ์€ ์•„๋ž˜ ๊ตฌ์กฐ๋กœ ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์•ˆ์ „ํ•˜๋‹ค.

void Initialize()
{
    // ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
}

void Step(float dt)
{
    // ์ƒํƒœ ๋ณ€ํ™”
}

void Render()
{
    // ์‹œ๊ฐํ™” ๋ฐ˜์˜
}

Unity ๋ผ์ดํ”„์‚ฌ์ดํด๋กœ ์˜ฎ๊ธฐ๋ฉด ๋ณดํ†ต ์ด๋ ‡๊ฒŒ ๋œ๋‹ค.

  • Awake() ๋˜๋Š” Start() โ†’ ์ดˆ๊ธฐํ™”
  • Update() / FixedUpdate() / LateUpdate() โ†’ ์ƒํƒœ ๋ณ€ํ™”
  • Renderer, Mesh, Texture, Shader, Buffer โ†’ ์‹œ๊ฐํ™”

์ด ๊ตฌ๋ถ„์„ ํ•ด๋‘๋ฉด ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ ธ๋„ ์‹œ์Šคํ…œ์ด ๋ฌด๋„ˆ์ง€์ง€ ์•Š๋Š”๋‹ค.
๋ฐ˜๋Œ€๋กœ ์ด ๊ตฌ๋ถ„์ด ์—†์œผ๋ฉด ์ž‘์€ ์‹คํ—˜ ์ฝ”๋“œ๊ฐ€ ๊ธˆ๋ฐฉ ๋’ค์—‰ํ‚จ๋‹ค.


4. ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋‹จ๊ณ„์—์„œ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  C# ๋ฌธ๋ฒ•

4.1 Awake์™€ Start์˜ ์—ญํ•  ๋ถ„๋ฆฌ

์ดˆ๊ธฐํ™” ์‹œ์ ์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

  • Awake()๋Š” ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜์ž๋งˆ์ž ํ˜ธ์ถœ๋œ๋‹ค.
  • Start()๋Š” ์ฒซ ํ”„๋ ˆ์ž„ ์ง์ „์— ํ˜ธ์ถœ๋œ๋‹ค.

๋”ฐ๋ผ์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค์Œ ๊ธฐ์ค€์„ ์ถ”์ฒœํ•œ๋‹ค.

  • Awake() โ†’ ์ž๊ธฐ ์ž์‹ ์ด ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ƒ์„ฑ
  • Start() โ†’ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ ์ฐธ์กฐ ์ดํ›„์˜ ์‹ค์ œ ์ดˆ๊ธฐํ™”
using UnityEngine;
using System.Collections.Generic;

public class GeneratorBootstrap : MonoBehaviour
{
    private List<ParticleData> particles;
    private Camera cachedCamera;

    void Awake()
    {
        particles = new List<ParticleData>(1024);
    }

    void Start()
    {
        cachedCamera = Camera.main;
        SpawnParticles(512);
    }

    void SpawnParticles(int count)
    {
        for (int i = 0; i < count; i++)
        {
            particles.Add(new ParticleData
            {
                position = Random.insideUnitSphere * 5f,
                velocity = Random.onUnitSphere,
                life = Random.Range(1f, 3f),
                maxLife = 3f
            });
        }
    }
}

public struct ParticleData
{
    public Vector3 position;
    public Vector3 velocity;
    public float life;
    public float maxLife;
}

ํ•ต์‹ฌ์€ ์ดˆ๊ธฐํ™” ์ฝ”๋“œ๋ฅผ ํ•œ ๊ตฐ๋ฐ์— ๋ชฐ์•„๋„ฃ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์˜์กด์„ฑ ์œ ๋ฌด์— ๋”ฐ๋ผ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์ด๋‹ค.

4.2 struct์™€ class

์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์•„ํŠธ์—์„œ๋Š” ์•„๋ž˜์˜ ๊ตฌ๋ถ„์„ ๋”ฐ๋ผ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ๊ฒƒ์ด ํšจ์œจ์ ์ด๋‹ค.

  • ์ž‘๊ณ  ๋‹จ์ˆœํ•œ ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋Š” struct
  • ํ–‰๋™์„ ์กฐ์ •ํ•˜๋Š” ์‹œ์Šคํ…œ ๊ฐ์ฒด๋Š” class
public struct ParticleData
{
    public Vector3 position;
    public Vector3 velocity;
    public float life;
    public float maxLife;
}
public class ParticleController : MonoBehaviour
{
}

์™œ struct๊ฐ€ ์ค‘์š”ํ•œ๊ฐ€

ํŒŒํ‹ฐํด, ํฌ์ธํŠธ, ์…€, ์ƒ˜ํ”Œ์ฒ˜๋Ÿผ ์ˆ˜์ฒœ~์ˆ˜๋งŒ ๊ฐœ๊ฐ€ ๋ฐ˜๋ณต์ ์œผ๋กœ ๊ฐฑ์‹ ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์ฐธ์กฐ ๊ฐ์ฒด๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ๋ณด๋‹ค, ๊ฐ’ ํƒ€์ž… ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฌถ์–ด ๋‹ค๋ฃจ๋Š” ํŽธ์ด ๋” ์ ํ•ฉํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

๋‹ค๋งŒ struct = ๋ฌด์กฐ๊ฑด ์Šคํƒ, class = ๋ฌด์กฐ๊ฑด ํž™์ด๋ผ๊ณ  ์ดํ•ดํ•˜๋ฉด ๊ณค๋ž€ํ•˜๋‹ค.
์‹ค์ „์—์„œ๋Š” ๊ทธ๋ณด๋‹ค ์•„๋ž˜ ํŠน์ง•์„ ๊ธฐ์–ตํ•˜๋Š” ํŽธ์ด ์ •ํ™•ํ•˜๋‹ค.

  • struct๋Š” ๊ฐ’ ํƒ€์ž…์ด๋‹ค.
  • ๊ฐ’ ๋ณต์‚ฌ๊ฐ€ ์ผ์–ด๋‚œ๋‹ค.
  • ์—ฐ์†๋œ ๋ฉ”๋ชจ๋ฆฌ ๊ตฌ์กฐ๋กœ ๋‹ค๋ฃจ๊ธฐ ์œ ๋ฆฌํ•˜๋‹ค.
  • ์ž‘์€ ๋ฐ์ดํ„ฐ ๋‹จ์œ„์— ์ ํ•ฉํ•˜๋‹ค.
  • GC ๋ถ€๋‹ด์„ ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋œ๋‹ค.

๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ฃผ์˜์ 

struct๋Š” ๊ฐ’์„ ๊บผ๋‚ด ์ˆ˜์ •ํ•˜๊ณ  ๋‹ค์‹œ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

for (int i = 0; i < particles.Count; i++)
{
    var p = particles[i];
    p.life -= Time.deltaTime;
    p.position += p.velocity * Time.deltaTime;
    particles[i] = p;
}

์ด ์žฌํ• ๋‹น ํŒจํ„ด์„ ๋†“์น˜๋ฉด ์ˆ˜์ •์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.
์‹ค์‹œ๊ฐ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ๋Š” ๋งค์šฐ ์ž์ฃผ ๋‚˜์˜ค๋Š” ํ•ต์‹ฌ ํŒจํ„ด์ด๋‹ค.

4.3 List์™€ Array ์„ ํƒ ๊ธฐ์ค€

์ž๋ฃŒ๊ตฌ์กฐ ์„ ํƒ์€ ์ฝ”๋“œ ์„ฑ๊ฒฉ์„ ๊ฒฐ์ •ํ•œ๋‹ค.

  • List<T>๋Š” ๊ฐœ์ˆ˜๊ฐ€ ์œ ๋™์ ์ธ ๋ฐ์ดํ„ฐ์— ์ ํ•ฉํ•˜๋‹ค.
  • T[] ๋ฐฐ์—ด์€ ํฌ๊ธฐ๊ฐ€ ๊ณ ์ •๋œ ๋ฐ์ดํ„ฐ์— ์ ํ•ฉํ•˜๋‹ค.
private List<ParticleData> particles = new List<ParticleData>(1024);
private Vector3[] gridPoints = new Vector3[128 * 128];

๋ณดํ†ต ์ด๋ ‡๊ฒŒ ์ •๋ฆฌํ•˜๋ฉด ๋œ๋‹ค

  • ํŒŒํ‹ฐํด ์ƒ์„ฑ/์‚ญ์ œ๊ฐ€ ์žฆ๋‹ค โ†’ List<T>
  • ํ•ด์ƒ๋„ ๊ณ ์ • ๊ทธ๋ฆฌ๋“œ๋‹ค โ†’ ๋ฐฐ์—ด
  • ๋ฒ„ํ…์Šค ๋ฒ„ํผ, ์ƒ‰์ƒ ๋ฒ„ํผ, ํ”ฝ์…€ ๋ฒ„ํผ์ฒ˜๋Ÿผ ํฌ๊ธฐ๊ฐ€ ์ •ํ•ด์ ธ ์žˆ๋‹ค โ†’ ๋ฐฐ์—ด

์ค‘์š”ํ•œ ์‹ค์ „ ์›์น™

๋Ÿฐํƒ€์ž„ ์ค‘ ๋งค ํ”„๋ ˆ์ž„ ์ƒˆ ๋ฐฐ์—ด์„ ๋งŒ๋“ค์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.
๊ฐ€๋Šฅํ•˜๋ฉด ํ•œ ๋ฒˆ ํ• ๋‹นํ•˜๊ณ  ๊ณ„์† ์žฌ์‚ฌ์šฉํ•œ๋‹ค.

4.4 SerializeField์™€ ์ธ์ŠคํŽ™ํ„ฐ ์„ค๊ณ„

์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์•„ํŠธ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ์‹คํ—˜์˜ ์—ฐ์†์ด๋‹ค.
๋”ฐ๋ผ์„œ Inspector ๋…ธ์ถœ ์ „๋žต์ด ์ค‘์š”ํ•˜๋‹ค.

[SerializeField] private int count = 500;
[SerializeField] private float radius = 5f;
[SerializeField] private float speed = 1f;
[SerializeField] private Color baseColor = Color.white;
[SerializeField, Range(0f, 1f)] private float noiseScale = 0.25f;

๊ถŒ์žฅ ๊ธฐ์ค€์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์™ธ๋ถ€ ์ฝ”๋“œ์—์„œ ์ง์ ‘ ๋งŒ์ง€์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด private ์œ ์ง€
  • ์‹คํ—˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” [SerializeField]๋กœ ๋…ธ์ถœ
  • ๋น ๋ฅธ ๋ฒ”์œ„ ์กฐ์ ˆ์ด ํ•„์š”ํ•˜๋ฉด [Range] ์‚ฌ์šฉ

์ฆ‰, ์บก์Аํ™”์™€ ์‹คํ—˜์„ฑ์„ ๋™์‹œ์— ํ™•๋ณดํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

4.5 Random๊ณผ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์‹œ๋“œ

๋žœ๋ค์€ ๋ณ€ํ˜•์„ ๋งŒ๋“ ๋‹ค. ํ•˜์ง€๋งŒ ์žฌํ˜„์„ฑ ์—†๋Š” ๋žœ๋ค์€ ๋””๋ฒ„๊น…์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ ๋‹ค.

using URandom = UnityEngine.Random;

float speed = URandom.Range(0.5f, 2f);
Vector2 p2 = URandom.insideUnitCircle * 3f;
Vector3 p3 = URandom.insideUnitSphere * 5f;

์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ๊ฒฐ๊ณผ๊ฐ€ ํ•„์š”ํ•  ๋•Œ

URandom.InitState(42);

์ด๋ ‡๊ฒŒ ์‹œ๋“œ๋ฅผ ๊ณ ์ •ํ•˜๋ฉด ์˜ˆ์ œ ์žฌํ˜„, ๋ธ”๋กœ๊ทธ ์„ค๋ช…, ๋””๋ฒ„๊น…์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ง„๋‹ค.

๋ณ„์นญ์„ ์ถ”์ฒœํ•˜๋Š” ์ด์œ 

Unity์˜ Random๊ณผ .NET์˜ System.Random์€ ์ด๋ฆ„์ด ๊ฒน์นœ๋‹ค.
ํ˜ผ๋™์„ ํ”ผํ•˜๋ ค๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋ณ„์นญ์„ ๋‘๋Š” ํŽธ์ด ์•ˆ์ •์ ์ด๋‹ค.

using URandom = UnityEngine.Random;

4.6 ScriptableObject๋กœ ์„ค์ • ๋ถ„๋ฆฌ

ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์ฝ”๋“œ์™€ ๋ฐ์ดํ„ฐ๊ฐ€ ์—‰ํ‚ค๊ธฐ ์‰ฝ๋‹ค.
์ด๋•Œ ScriptableObject๋Š” ๋งค์šฐ ์ข‹์€ ๋ถ„๋ฆฌ ์ˆ˜๋‹จ์ด๋‹ค.

using UnityEngine;

[CreateAssetMenu(menuName = "GenerativeArt/Noise Settings")]
public class NoiseSettings : ScriptableObject
{
    public float scale = 1f;
    public float speed = 1f;
    public float amplitude = 1f;
    public Color tint = Color.white;
}

์ด ๋ฐฉ์‹์€ ๋‹ค์Œ์— ํŠนํžˆ ์ข‹๋‹ค.

  • ์—ฌ๋Ÿฌ ์”ฌ์—์„œ ๊ฐ™์€ ์„ค์ •์„ ์žฌ์‚ฌ์šฉํ•  ๋•Œ
  • ์‹œ๊ฐ ์‹คํ—˜์šฉ ํ”„๋ฆฌ์…‹์„ ๊ด€๋ฆฌํ•  ๋•Œ
  • ๋Ÿฐํƒ€์ž„ ๋กœ์ง๊ณผ ํŠœ๋‹ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„๋ฆฌํ•  ๋•Œ

5. ์ƒํƒœ ๋ณ€ํ™” ๋‹จ๊ณ„์—์„œ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ๋ฌธ๋ฒ•

5.1 Update, FixedUpdate, LateUpdate

Unity ์‹ค์‹œ๊ฐ„ ์‹œ์Šคํ…œ์—์„œ ๋ฃจํ”„ ์„ ํƒ์€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

  • Update()๋Š” ์ผ๋ฐ˜์ ์ธ ์‹œ๊ฐ ์ƒํƒœ ๊ฐฑ์‹ ์— ์‚ฌ์šฉํ•œ๋‹ค.
  • FixedUpdate()๋Š” ๋ฌผ๋ฆฌ ์—”์ง„๊ณผ ๋™๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•œ๋‹ค.
  • LateUpdate()๋Š” ๋‹ค๋ฅธ ์—…๋ฐ์ดํŠธ ์ดํ›„ ๋ณด์ • ๋‹จ๊ณ„์— ์‚ฌ์šฉํ•œ๋‹ค.
void Update()
{
    StepParticles(Time.deltaTime);
}

void FixedUpdate()
{
    // Rigidbody ๊ธฐ๋ฐ˜ ๋ฌผ๋ฆฌ ์ „์šฉ
}

void LateUpdate()
{
    // ์นด๋ฉ”๋ผ ์ถ”์ , ํŠธ๋ ˆ์ผ ๋ณด์ • ๋“ฑ
}

์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์•„ํŠธ ๋Œ€๋ถ€๋ถ„์€ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด ์•„๋‹ˆ๋ผ ์‹œ๊ฐ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ์ƒํƒœ ๋ณ€ํ™”๋‹ค.
๋”ฐ๋ผ์„œ ๋Œ€๋ถ€๋ถ„์˜ ํ•ต์‹ฌ ๋กœ์ง์€ Update()์— ๋“ค์–ด๊ฐ„๋‹ค.

5.2 deltaTime๊ณผ time

ํ”„๋ ˆ์ž„๋ ˆ์ดํŠธ์— ์ƒ๊ด€์—†์ด ๋™์ผํ•œ ์›€์ง์ž„์„ ๋งŒ๋“ค๋ ค๋ฉด Time.deltaTime์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

position += velocity * Time.deltaTime;

์ „์ฒด ์‹œ๊ฐ„ ์ถ•์„ ํŒŒํ˜•์— ๋„ฃ๊ณ  ์‹ถ๋‹ค๋ฉด Time.time์„ ์‚ฌ์šฉํ•œ๋‹ค.

float t = Time.time;
float wave = Mathf.Sin(t * frequency + phase) * amplitude;
float ping = Mathf.PingPong(t * speed, 1f);

์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๋‹ค.

  • ์ด๋™๋Ÿ‰, ๊ฐ์‡ , ์ˆ˜๋ช… ๊ฐ์†Œ โ†’ deltaTime
  • ์ง„๋™, ๋ฃจํ”„, ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ํ•จ์ˆ˜ ์ž…๋ ฅ โ†’ time

5.3 Mathf์™€ ์ˆ˜ํ•™ ๊ธฐ๋ฐ˜ ๋ชจ์…˜

์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์•„ํŠธ๋Š” ์‚ฌ์‹ค์ƒ ์ˆ˜ํ•™์˜ ์‹œ๊ฐํ™”๋‹ค.
Unity์—์„œ๋Š” Mathf๊ฐ€ ๊ทธ ์ค‘์‹ฌ์ด๋‹ค.

float x = Mathf.Cos(angle) * radius;
float y = Mathf.Sin(angle) * radius;

float a = Mathf.Lerp(minValue, maxValue, t);
float b = Mathf.LerpUnclamped(minValue, maxValue, t);

float c = Mathf.Clamp(value, 0f, 1f);
float d = Mathf.Clamp01(value);

float loop = Mathf.Repeat(Time.time, 1f);
float ping = Mathf.PingPong(Time.time, 1f);

์ž์ฃผ ์“ฐ๋Š” ํ•จ์ˆ˜์™€ ์˜๋ฏธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Sin, Cos โ†’ ์ง„๋™, ์›์šด๋™, ์œ„์ƒ ๋ณ€ํ™”
  • Lerp โ†’ ๋ณด๊ฐ„
  • Clamp, Clamp01 โ†’ ๊ฐ’ ์•ˆ์ •ํ™”
  • Repeat, PingPong โ†’ ์ฃผ๊ธฐ์„ฑ ๋ถ€์—ฌ
  • SmoothDamp โ†’ ๋ถ€๋“œ๋Ÿฌ์šด ์ถ”์ข…
float velocityRef = 0f;
float smoothed = Mathf.SmoothDamp(current, target, ref velocityRef, 0.2f);

5.4 Vector ์—ฐ์‚ฐ

๋ฐฉํ–ฅ, ๊ฑฐ๋ฆฌ, ์˜ํ–ฅ๋ ฅ, ํšŒ์ „ ๊ธฐ์ค€ ๊ณ„์‚ฐ์— ๋ฒกํ„ฐ ์—ฐ์‚ฐ์ด ํ•„์ˆ˜๋‹ค.

Vector3 dir = (target - position).normalized;
float dist = Vector3.Distance(a, b);
float dot = Vector3.Dot(a.normalized, b.normalized);
Vector3 normal = Vector3.Cross(a, b);
Vector3 blended = Vector3.Lerp(from, to, t);

2D์—์„œ๋Š” ๊ฐ๋„๋ฅผ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ๋กœ ๋ฐ”๊พธ๋Š” ํŒจํ„ด๋„ ์ž์ฃผ ์‚ฌ์šฉํ•œ๋‹ค.

Vector2 dir2D = new Vector2(
    Mathf.Cos(angle * Mathf.Deg2Rad),
    Mathf.Sin(angle * Mathf.Deg2Rad)
);

์ด ์—ฐ์‚ฐ๋“ค์€ ์•„๋ž˜ ๋ฌธ์ œ๋ฅผ ํ‘ธ๋Š” ๋ฐ ์ž์ฃผ ์“ฐ์ธ๋‹ค.

  • ํƒ€๊นƒ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™ํ•˜๊ธฐ
  • ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ฐ์‡  ๋งŒ๋“ค๊ธฐ
  • ์ „๋ฐฉ/ํ›„๋ฐฉ ํŒ์ •ํ•˜๊ธฐ
  • ๋ฒ•์„  ๋ฐฉํ–ฅ ๋˜๋Š” ์ˆ˜์ง ๋ฐฉํ–ฅ ๋งŒ๋“ค๊ธฐ
  • ๋‘ ๋ฐฉํ–ฅ์„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์„ž๊ธฐ

5.5 ์‹ค์‹œ๊ฐ„ ์‹œ์Šคํ…œ์˜ ๊ธฐ๋ณธ์€ for ๋ฃจํ”„

์‹ค์‹œ๊ฐ„ ์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์‹œ์Šคํ…œ์—์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ํŒจํ„ด์€ ๋ช…์‹œ์  ๋ฐ˜๋ณต๋ฌธ์ด๋‹ค.

void UpdateParticles(float dt)
{
    for (int i = 0; i < particles.Count; i++)
    {
        var p = particles[i];
        p.position += p.velocity * dt;
        p.life -= dt;
        particles[i] = p;
    }
}

์ด ํŒจํ„ด์ด ์ค‘์š”ํ•œ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ฒ˜๋ฆฌ ์ˆœ์„œ๊ฐ€ ๋ช…ํ™•ํ•˜๋‹ค.
  • ๋””๋ฒ„๊น…์ด ์‰ฝ๋‹ค.
  • ๊ฐ’ ํƒ€์ž… ์žฌํ• ๋‹น์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.
  • GC๋ฅผ ํ†ต์ œํ•˜๊ธฐ ์‰ฝ๋‹ค.
  • ์„ฑ๋Šฅ ํŠน์„ฑ์„ ์ฝ๊ธฐ ์‰ฝ๋‹ค.

์‹คํ—˜ ๋‹จ๊ณ„์—์„œ ์ฝ”๋“œ๊ฐ€ ์งง์•„ ๋ณด์ธ๋‹ค๋Š” ์ด์œ ๋กœ ๋„ˆ๋ฌด ์ด๋ฅธ ์‹œ์ ์— ์ถ”์ƒํ™”๋ฅผ ๋Š˜๋ฆฌ๋ฉด, ์˜คํžˆ๋ ค ์ œ์–ด๋ ฅ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

5.6 Coroutine

Coroutine์€ ์‹œ๊ฐ„ ์ง€์—ฐ์ด๋‚˜ ์ˆœ์ฐจ ์ง„ํ–‰์„ ํ‘œํ˜„ํ•˜๊ธฐ ์ข‹๋‹ค.

using System.Collections;
using UnityEngine;

public class SpawnRoutine : MonoBehaviour
{
    [SerializeField] private int count = 20;
    [SerializeField] private float delay = 0.05f;

    void Start()
    {
        StartCoroutine(SpawnSequence());
    }

    IEnumerator SpawnSequence()
    {
        for (int i = 0; i < count; i++)
        {
            SpawnParticle(i);
            yield return new WaitForSeconds(delay);
        }
    }

    void SpawnParticle(int index)
    {
        Debug.Log(index);
    }
}

์ข‹์€ ์‚ฌ์šฉ์ฒ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ˆœ์ฐจ ์Šคํฐ
  • ํŽ˜์ด๋“œ ์ธ/์•„์›ƒ
  • ๋‹จ๊ณ„์  ์—ฐ์ถœ
  • ์ง€์—ฐ ํŠธ๋ฆฌ๊ฑฐ

๋‹ค๋งŒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ•ต์‹ฌ ๋ฃจํ”„ ์ „์ฒด๋ฅผ Coroutine์œผ๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์€ ์ถ”์ฒœํ•˜์ง€ ์•Š๋Š”๋‹ค.
ํ•ต์‹ฌ ์—…๋ฐ์ดํŠธ๋Š” Update(), ์—ฐ์ถœ ํ๋ฆ„์€ Coroutine์ด๋ผ๋Š” ๋ถ„๋ฆฌ๊ฐ€ ๋ณดํ†ต ๋” ์•ˆ์ •์ ์ด๋‹ค.

5.7 LINQ ์‚ฌ์šฉ ๊ธฐ์ค€

LINQ๋Š” ๊ฐ„๊ฒฐํ•˜์ง€๋งŒ ์‹ค์‹œ๊ฐ„ ๋ฃจํ”„์—์„œ๋Š” ์‹ ์ค‘ํžˆ ์จ์•ผ ํ•œ๋‹ค.

using System.Linq;

var alive = particles.Where(p => p.life > 0f).ToList();
float avgSpeed = particles.Average(p => p.velocity.magnitude);
ParticleData fastest = particles
    .OrderByDescending(p => p.velocity.magnitude)
    .First();

LINQ๊ฐ€ ์ข‹์€ ๊ฒฝ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต
  • ํˆด ์Šคํฌ๋ฆฝํŠธ
  • ๋””๋ฒ„๊ทธ ํ†ต๊ณ„
  • ์—๋””ํ„ฐ ์œ ํ‹ธ๋ฆฌํ‹ฐ

๋ฐ˜๋ฉด ์•„๋ž˜ ์ƒํ™ฉ์—์„œ๋Š” ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

  • ๋งค ํ”„๋ ˆ์ž„ Where, Select, OrderBy, ToList, ToArray
  • ๋ฐ˜๋ณต์ ์œผ๋กœ ์ƒˆ๋กœ์šด ์—ด๊ฑฐ ๊ฐ์ฒด์™€ ๊ฒฐ๊ณผ ์ปฌ๋ ‰์…˜์„ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ

์ฆ‰, LINQ๋Š” ๊ธˆ์ง€ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ ํ•ซํŒจ์Šค์—์„œ ์ ˆ์ œํ•ด์•ผ ํ•˜๋Š” ๋„๊ตฌ๋‹ค.

5.8 delegate, Action, Func

๊ทœ์น™์„ ๊ฐˆ์•„๋ผ์šฐ๋Š” ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ๋•Œ ์œ ์šฉํ•˜๋‹ค.

using System;
using UnityEngine;

public class RuleSwitch : MonoBehaviour
{
    private Func<Vector3, Vector3> flowRule;

    void Awake()
    {
        flowRule = p => new Vector3(
            Mathf.Sin(p.y + Time.time),
            Mathf.Cos(p.x + Time.time),
            0f
        );
    }

    Vector3 Evaluate(Vector3 p)
    {
        return flowRule != null ? flowRule(p) : Vector3.zero;
    }
}

์ด๋Ÿฐ ํŒจํ„ด์€ ๋‹ค์Œ์— ์œ ์šฉํ•˜๋‹ค.

  • ์›€์ง์ž„ ๊ทœ์น™ ๊ต์ฒด
  • ๋…ธ์ด์ฆˆ ํ•จ์ˆ˜ ๋น„๊ต
  • ์ƒ์„ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ต์ฒด
  • ์‹คํ—˜์  ํŒจํ„ด A/B ํ…Œ์ŠคํŠธ

ํ•˜์ง€๋งŒ ์ตœ์ข… ์‹ค์‹œ๊ฐ„ ๋ฃจํ”„์—์„œ๋Š” ๊ณผ๋„ํ•œ ๋žŒ๋‹ค ์บก์ฒ˜๋‚˜ ๋ถˆํ•„์š”ํ•œ ๊ฐ„์ ‘ ํ˜ธ์ถœ์„ ์ค„์ด๋Š” ํŽธ์ด ๋‚ซ๋‹ค.


6. ์‹œ๊ฐํ™” ๋‹จ๊ณ„์—์„œ ์•Œ์•„์•ผ ํ•  Unity ๋ Œ๋”๋ง ์„ ํƒ์ง€

6.1 LineRenderer

์„ , ๊ถค์ , ๋ฆฌ๋ณธํ˜• ํ๋ฆ„์„ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด LineRenderer๊ฐ€ ๊ฐ€์žฅ ๋น ๋ฅด๋‹ค.

using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class LineExample : MonoBehaviour
{
    [SerializeField] private Vector3[] points;
    private LineRenderer lr;

    void Awake()
    {
        lr = GetComponent<LineRenderer>();
    }

    void Start()
    {
        lr.positionCount = points.Length;
        lr.startWidth = 0.02f;
        lr.endWidth = 0.005f;
        lr.useWorldSpace = true;
        lr.SetPositions(points);
    }
}

์ค‘์š”ํ•œ ํŒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ ์ด ๋งŽ์œผ๋ฉด SetPosition(i, ...) ๋ฐ˜๋ณต๋ณด๋‹ค SetPositions(array)๊ฐ€ ๋‚ซ๋‹ค.
  • ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฒ„ํผ์™€ ๋ Œ๋” ๋ฒ„ํผ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›Œ์ง„๋‹ค.
  • ๋น ๋ฅธ ์‹คํ—˜์— ํŠนํžˆ ์ข‹๋‹ค.

6.2 Mesh API

๋ณต์žกํ•œ ํ”„๋กœ์‹œ์ €๋Ÿด ํ˜•์ƒ์„ ์ง์ ‘ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด ๊ฒฐ๊ตญ ๋ฉ”์‹œ๋ฅผ ๋‹ค๋ฃจ๊ฒŒ ๋œ๋‹ค.

using UnityEngine;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class QuadMeshExample : MonoBehaviour
{
    private Mesh mesh;
    private Vector3[] verts;
    private int[] tris;
    private Color[] colors;

    void Awake()
    {
        mesh = new Mesh();
        mesh.name = "Procedural Quad";

        verts = new Vector3[4]
        {
            new Vector3(-1f, -1f, 0f),
            new Vector3( 1f, -1f, 0f),
            new Vector3(-1f,  1f, 0f),
            new Vector3( 1f,  1f, 0f)
        };

        tris = new int[6] { 0, 2, 1, 2, 3, 1 };
        colors = new Color[4] { Color.red, Color.yellow, Color.cyan, Color.magenta };

        mesh.vertices = verts;
        mesh.triangles = tris;
        mesh.colors = colors;
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();

        GetComponent<MeshFilter>().sharedMesh = mesh;
    }
}

์‹ค์ „์—์„œ๋Š” ์•„๋ž˜ ๊ธฐ์ค€์ด ์ค‘์š”ํ•˜๋‹ค.

  • ๋ฒ„ํ…์Šค ๋ฐฐ์—ด ์žฌ์‚ฌ์šฉ
  • ํ•„์š” ์—†๋Š” ์žฌ๊ณ„์‚ฐ ์ค„์ด๊ธฐ
  • ์ƒ‰์ƒ, UV, ๋…ธ๋ฉ€, ์ธ๋ฑ์Šค ๋ฒ„ํผ ์—ญํ•  ๋ถ„๋ฆฌ
  • CPU ๋ฉ”์‹œ ์ƒ์„ฑ๊ณผ ์…ฐ์ด๋” ๋ณ€ํ˜• ์—ญํ•  ๋ถ„๋ฆฌ

6.3 Graphics.DrawMeshInstanced

๊ฐ™์€ ๋ฉ”์‹œ๋ฅผ ๋งŽ์ด ์ฐ์–ด์•ผ ํ•  ๋•Œ๋Š” GameObject๋ฅผ ๊ฐœ๋ณ„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์ธ์Šคํ„ด์‹ฑ์ด ํšจ์œจ์ ์ด๋‹ค.

using UnityEngine;

public class InstancingExample : MonoBehaviour
{
    [SerializeField] private Mesh mesh;
    [SerializeField] private Material material;
    [SerializeField] private int instanceCount = 256;

    private Matrix4x4[] matrices;

    void Awake()
    {
        matrices = new Matrix4x4[instanceCount];

        for (int i = 0; i < instanceCount; i++)
        {
            Vector3 pos = Random.insideUnitSphere * 10f;
            Quaternion rot = Quaternion.identity;
            Vector3 scale = Vector3.one * Random.Range(0.2f, 1f);
            matrices[i] = Matrix4x4.TRS(pos, rot, scale);
        }
    }

    void Update()
    {
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices);
    }
}

์ฃผ์˜ํ•  ์ ๋„ ๋ช…ํ™•ํ•˜๋‹ค.

  • ํ•œ ๋ฒˆ์— ์ตœ๋Œ€ 1023๊ฐœ ์ œํ•œ์ด ์žˆ๋‹ค.
  • ์…ฐ์ด๋”์—์„œ GPU Instancing์ด ํ™œ์„ฑํ™”๋˜์–ด์•ผ ํ•œ๋‹ค.
  • ๋” ํฐ ๊ทœ๋ชจ๋กœ ๊ฐ€๋ฉด indirect ๋˜๋Š” compute ๊ธฐ๋ฐ˜ ๊ฒฝ๋กœ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค.

6.4 Texture2D

ํ”ฝ์…€ ๊ธฐ๋ฐ˜ ํŒจํ„ด, ํ”ผ๋“œ๋ฐฑ ์ด๋ฏธ์ง€, ์…€๋ฃฐ๋Ÿฌ ์‹œ์Šคํ…œ์—๋Š” Texture2D๊ฐ€ ์ง์ ‘์ ์ด๋‹ค.

using UnityEngine;

public class TextureExample : MonoBehaviour
{
    [SerializeField] private Renderer targetRenderer;
    [SerializeField] private int width = 256;
    [SerializeField] private int height = 256;

    private Texture2D tex;
    private Color32[] pixels;

    void Start()
    {
        tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
        tex.filterMode = FilterMode.Point;
        pixels = new Color32[width * height];

        for (int i = 0; i < pixels.Length; i++)
            pixels[i] = new Color32(255, 0, 128, 255);

        tex.SetPixels32(pixels);
        tex.Apply();
        targetRenderer.material.mainTexture = tex;
    }
}

ํ•ต์‹ฌ์€ ์•„๋ž˜ ๋‘ ๊ฐ€์ง€๋‹ค.

  • ๋Œ€๋Ÿ‰ ํ”ฝ์…€ ๊ฐฑ์‹ ์—๋Š” SetPixels32()๊ฐ€ ์œ ๋ฆฌํ•˜๋‹ค.
  • ๋ณ€๊ฒฝ ๋’ค Apply()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ GPU์— ๋ฐ˜์˜๋œ๋‹ค.

ํ•˜์ง€๋งŒ Apply() ์ž์ฒด๋„ ์—…๋กœ๋“œ ๋น„์šฉ์ด ์žˆ์œผ๋ฏ€๋กœ ํ˜ธ์ถœ ๋นˆ๋„๋ฅผ ์ƒ๊ฐํ•ด์•ผ ํ•œ๋‹ค.

6.5 MaterialPropertyBlock

๊ฐ™์€ ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ๊ณต์œ ํ•˜๋ฉด์„œ ๋ Œ๋”๋Ÿฌ๋ณ„ ๊ฐœ๋ณ„ ๊ฐ’์„ ๋„ฃ๊ณ  ์‹ถ๋‹ค๋ฉด MaterialPropertyBlock์ด ์ค‘์š”ํ•˜๋‹ค.

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class MPBExample : MonoBehaviour
{
    static readonly int ID_BaseColor = Shader.PropertyToID("_BaseColor");
    static readonly int ID_Alpha = Shader.PropertyToID("_Alpha");

    private Renderer cachedRenderer;
    private MaterialPropertyBlock mpb;

    void Awake()
    {
        cachedRenderer = GetComponent<Renderer>();
        mpb = new MaterialPropertyBlock();
    }

    void Update()
    {
        float alpha = Mathf.PingPong(Time.time, 1f);
        Color col = Color.Lerp(Color.cyan, Color.magenta, alpha);

        mpb.Clear();
        mpb.SetColor(ID_BaseColor, col);
        mpb.SetFloat(ID_Alpha, alpha);
        cachedRenderer.SetPropertyBlock(mpb);
    }
}

์ด ๋ฐฉ์‹์ด ์ค‘์š”ํ•œ ์ด์œ ๋Š” renderer.material ์ง์ ‘ ์ ‘๊ทผ์ด ๋จธํ‹ฐ๋ฆฌ์–ผ ์ธ์Šคํ„ด์Šค ๋ณต์ œ๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

6.6 GL

GL์€ ์ฆ‰์‹œ ๋ชจ๋“œ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ๋น ๋ฅธ ๋””๋ฒ„๊ทธ๋‚˜ ํ”„๋กœํ† ํƒ€์ดํ•‘์—๋Š” ์“ธ ์ˆ˜ ์žˆ์ง€๋งŒ, ์žฅ๊ธฐ์ ์œผ๋กœ ์ฃผ๋ ฅ ๋ Œ๋”๋ง ๊ฒฝ๋กœ๋กœ ์‚ผ๊ธฐ์—๋Š” ์ œํ•œ์ด ๋งŽ๋‹ค.

using UnityEngine;

public class GLExample : MonoBehaviour
{
    [SerializeField] private Material lineMaterial;

    void OnPostRender()
    {
        if (lineMaterial == null)
            return;

        lineMaterial.SetPass(0);

        GL.Begin(GL.LINES);
        GL.Color(Color.magenta);
        GL.Vertex3(0f, 0f, 0f);
        GL.Vertex3(1f, 1f, 0f);
        GL.End();
    }
}

์‹ค์ „ ๊ธฐ์ค€์œผ๋กœ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

  • ๋น ๋ฅธ ์‹œ๊ฐ ๊ฒ€์ฆ โ†’ ๊ฐ€๋Šฅ
  • ์ง€์†์ ์ธ ์ œ์ž‘ ํŒŒ์ดํ”„๋ผ์ธ โ†’ ๋น„์ถ”์ฒœ
  • ์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ ๊ฒฝ๋กœ โ†’ ๋ณดํ†ต ๋‹ค๋ฅธ ๋ Œ๋”๋ง ๋ฐฉ์‹์ด ๋‚ซ๋‹ค

7. Unity 6 URP ๊ธฐ์ค€์œผ๋กœ ๊ผญ ์ง€์ผœ์•ผ ํ•  ๋ Œ๋”๋ง ์›์น™

Unity 6 ์ด์ƒ์—์„œ URP๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ž‘์—…ํ•œ๋‹ค๋ฉด, ๋‹จ์ˆœํžˆ API๋ฅผ ์•„๋Š” ๊ฒƒ๋ณด๋‹ค ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ๊ฐ์ด ์ค‘์š”ํ•˜๋‹ค.

์ฒซ์งธ, URP์—์„œ๋Š” ์…ฐ์ด๋”๊ฐ€ Built-in ํŒŒ์ดํ”„๋ผ์ธ๊ณผ ๋‹ค๋ฅด๋ฏ€๋กœ, ์˜ˆ์ „ CGPROGRAM ์˜ˆ์ œ๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜ค๋ฉด ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.
๋‘˜์งธ, ์•„ํŠธ ์‹คํ—˜์šฉ ์ฝ”๋“œ๋ผ๋„ ๋จธํ‹ฐ๋ฆฌ์–ผ ์ธ์Šคํ„ด์Šค ๊ด€๋ฆฌ, ์…ฐ์ด๋” ํ”„๋กœํผํ‹ฐ ๊ด€๋ฆฌ, ๋ Œ๋” ํŒจ์Šค ํ˜ธํ™˜์„ฑ์„ ์—ผ๋‘์— ๋‘๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

ํŠนํžˆ ์•„๋ž˜ ์›์น™์„ ์ถ”์ฒœํ•œ๋‹ค.

  • URP์šฉ ์…ฐ์ด๋”๋Š” HLSLPROGRAM ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑํ•œ๋‹ค.
  • ์…ฐ์ด๋” ์†์„ฑ ์ œ์–ด๋Š” ๋ฌธ์ž์—ด๋ณด๋‹ค Shader.PropertyToID() ์บ์‹ฑ์„ ์šฐ์„ ํ•œ๋‹ค.
  • ์˜ค๋ธŒ์ ํŠธ๋ณ„ ๋จธํ‹ฐ๋ฆฌ์–ผ ์ฐจ๋“ฑ๊ฐ’์€ MaterialPropertyBlock์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  • ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ์ดํ„ฐ์™€ ์‹œ๊ฐ ํ‘œํ˜„ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•œ๋‹ค.
  • CPU์—์„œ ๊ณ„์‚ฐํ•  ๊ฒƒ๊ณผ GPU์—์„œ ๊ณ„์‚ฐํ•  ๊ฒƒ์„ ๋ฏธ๋ฆฌ ๋‚˜๋ˆˆ๋‹ค.

์ฆ‰, URP์—์„œ๋Š” ๋‹จ์ˆœํžˆ โ€œ๋ณด์ด๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒโ€๋ณด๋‹ค ์ง€์† ๊ฐ€๋Šฅํ•œ ๋ Œ๋”๋ง ๊ตฌ์กฐ๊ฐ€ ๋” ์ค‘์š”ํ•˜๋‹ค.


8. Shader Graph ์—†์ด HLSL/ShaderLab์œผ๋กœ ์—ฐ๋™ํ•˜๋Š” ๊ธฐ๋ณธ ๊ตฌ์กฐ

8.1 ์™œ Shader Graph ๋Œ€์‹  HLSL/ShaderLab์ธ๊ฐ€

Shader Graph๋Š” ๋น ๋ฅธ ์‹œ๊ฐ ์‹คํ—˜์—๋Š” ์ข‹๋‹ค.
ํ•˜์ง€๋งŒ Generative Art๋ฅผ ์ฝ”๋“œ ์ค‘์‹ฌ์œผ๋กœ ๋‹ค๋ฃจ๊ณ  ์‹ถ๋‹ค๋ฉด HLSL/ShaderLab ๋ฐฉ์‹์ด ๋‹ค์Œ ์žฅ์ ์„ ์ค€๋‹ค.

  • ์…ฐ์ด๋” ๋™์ž‘์„ ํ…์ŠคํŠธ๋กœ ์ •ํ™•ํžˆ ํ†ต์ œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ˆ˜ํ•™ ๊ตฌ์กฐ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ธ”๋กœ๊ทธ ๊ธ€์— ์ฝ”๋“œ์™€ ๋…ผ๋ฆฌ๋ฅผ ์ง์ ‘ ๊ธฐ๋กํ•˜๊ธฐ ์ข‹๋‹ค.
  • C#๊ณผ ์…ฐ์ด๋” ๊ฐ„์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํ๋ฆ„์„ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋…ธ๋“œ ์˜์กด ์—†์ด ์žฅ๊ธฐ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฆ‰, ๊ฒฐ๊ณผ๋งŒ์ด ์•„๋‹ˆ๋ผ ์›๋ฆฌ์™€ ๊ตฌ์กฐ๋ฅผ ํ•จ๊ป˜ ์„ค๋ช…ํ•˜๋ ค๋ฉด HLSL/ShaderLab์ด ๋” ์ ํ•ฉํ•˜๋‹ค.

8.2 URP Unlit ์…ฐ์ด๋” ๊ธฐ๋ณธ ์˜ˆ์ œ

์•„๋ž˜๋Š” Unity 6 URP์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ„๋‹จํ•œ Unlit ๊ธฐ๋ฐ˜ HLSL/ShaderLab ์˜ˆ์ œ๋‹ค.
์ƒ‰์ƒ๊ณผ ํŒŒํ˜• ์™œ๊ณก์„ ์ฝ”๋“œ๋กœ ์ง์ ‘ ์ œ์–ดํ•˜๋Š” ์ตœ์†Œ ๊ตฌ์กฐ๋‹ค.

Shader "Custom/URP/GenerativeUnlit"
{
    Properties
    {
        _BaseColor ("Base Color", Color) = (1,1,1,1)
        _Amplitude ("Amplitude", Float) = 0.1
        _Frequency ("Frequency", Float) = 2.0
        _Speed ("Speed", Float) = 1.0
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Geometry"
        }

        Pass
        {
            Name "ForwardUnlit"

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float wave : TEXCOORD1;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseColor;
                float _Amplitude;
                float _Frequency;
                float _Speed;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;

                float t = _Time.y * _Speed;
                float wave = sin(IN.positionOS.x * _Frequency + t);
                float3 displaced = IN.positionOS.xyz;
                displaced.y += wave * _Amplitude;

                OUT.positionHCS = TransformObjectToHClip(displaced);
                OUT.uv = IN.uv;
                OUT.wave = wave;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                float glow = IN.wave * 0.5 + 0.5;
                float3 color = lerp(_BaseColor.rgb * 0.3, _BaseColor.rgb, glow);
                return half4(color, _BaseColor.a);
            }
            ENDHLSL
        }
    }
}

์ด ์…ฐ์ด๋”๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜์ง€๋งŒ, ๋‹ค์Œ ๊ฐœ๋…์„ ๋ชจ๋‘ ๋ณด์—ฌ์ค€๋‹ค.

  • URP์šฉ ShaderLab ๊ตฌ์กฐ
  • HLSLPROGRAM ์‚ฌ์šฉ
  • Core.hlsl ํฌํ•จ
  • ๋ฒ„ํ…์Šค ๋‹จ๊ณ„์—์„œ ์œ„์น˜ ๋ณ€ํ˜•
  • ํ”„๋ž˜๊ทธ๋จผํŠธ ๋‹จ๊ณ„์—์„œ ์ƒ‰์ƒ ๊ณ„์‚ฐ
  • ๋จธํ‹ฐ๋ฆฌ์–ผ ํ”„๋กœํผํ‹ฐ๋ฅผ CBUFFER๋กœ ๋ฐ›๋Š” ๊ตฌ์กฐ

8.3 C#์—์„œ ์…ฐ์ด๋” ํ”„๋กœํผํ‹ฐ ์ œ์–ดํ•˜๊ธฐ

URP ์…ฐ์ด๋”๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด, ์ด์ œ C#์—์„œ ์•ˆ์ •์ ์œผ๋กœ ๊ฐ’์„ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class URPShaderDriver : MonoBehaviour
{
    static readonly int ID_BaseColor = Shader.PropertyToID("_BaseColor");
    static readonly int ID_Amplitude = Shader.PropertyToID("_Amplitude");
    static readonly int ID_Frequency = Shader.PropertyToID("_Frequency");
    static readonly int ID_Speed = Shader.PropertyToID("_Speed");

    [SerializeField] private Color baseColor = Color.cyan;
    [SerializeField] private float amplitude = 0.25f;
    [SerializeField] private float frequency = 4f;
    [SerializeField] private float speed = 1f;

    private Renderer cachedRenderer;
    private MaterialPropertyBlock mpb;

    void Awake()
    {
        cachedRenderer = GetComponent<Renderer>();
        mpb = new MaterialPropertyBlock();
    }

    void Update()
    {
        float animatedAmplitude = amplitude + Mathf.Sin(Time.time) * 0.05f;

        mpb.Clear();
        mpb.SetColor(ID_BaseColor, baseColor);
        mpb.SetFloat(ID_Amplitude, animatedAmplitude);
        mpb.SetFloat(ID_Frequency, frequency);
        mpb.SetFloat(ID_Speed, speed);
        cachedRenderer.SetPropertyBlock(mpb);
    }
}

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋‘ ๊ฐ€์ง€๋‹ค.

  • ๋ฌธ์ž์—ด ๋Œ€์‹  ํ”„๋กœํผํ‹ฐ ID๋ฅผ ์บ์‹ฑํ•œ๋‹ค.
  • MaterialPropertyBlock์œผ๋กœ ์˜ค๋ธŒ์ ํŠธ๋ณ„ ๊ฐ’์„ ์ „๋‹ฌํ•œ๋‹ค.

์ด ๊ตฌ์กฐ๋ฅผ ์ตํžˆ๋ฉด Shader Graph ์—†์ด๋„ ๋Œ€๋ถ€๋ถ„์˜ Shader Art ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.


9. Compute Shader๋กœ ํ™•์žฅํ•  ๋•Œ ์•Œ์•„์•ผ ํ•  ๊ธฐ์ค€

9.1 ์–ธ์ œ Compute Shader๋กœ ๋„˜์–ด๊ฐ€์•ผ ํ•˜๋Š”๊ฐ€

์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋“  ๊ฒƒ์„ Compute Shader๋กœ ์‹œ์ž‘ํ•  ํ•„์š”๋Š” ์—†๋‹ค.
์˜คํžˆ๋ ค ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ๋Š” CPU ๋ฒ„์ „์œผ๋กœ ๊ตฌ์กฐ๋ฅผ ๋จผ์ € ๊ฒ€์ฆํ•˜๋Š” ํŽธ์ด ๋‚ซ๋‹ค.

๊ทธ๋Ÿผ์—๋„ ์•„๋ž˜ ์ƒํ™ฉ์—์„œ๋Š” Compute Shader ํ™•์žฅ์„ ์ง„์ง€ํ•˜๊ฒŒ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค.

  • ํŒŒํ‹ฐํด ์ˆ˜๊ฐ€ ํฌ๊ฒŒ ๋Š˜์–ด๋‚œ๋‹ค.
  • CPU ๋ฃจํ”„๊ฐ€ ๋ณ‘๋ชฉ์ด ๋œ๋‹ค.
  • ๋Œ€๊ทœ๋ชจ ๊ทธ๋ฆฌ๋“œ ์—ฐ์‚ฐ์ด ํ•„์š”ํ•˜๋‹ค.
  • ํ”ฝ์…€ ๋˜๋Š” ํ•„๋“œ ๊ณ„์‚ฐ์„ GPU ๋ณ‘๋ ฌํ™”ํ•˜๋Š” ํŽธ์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.
  • ๋ Œ๋”๋ง ์ด์ „์˜ ์ƒํƒœ ๊ณ„์‚ฐ ์ž์ฒด๊ฐ€ ๋„ˆ๋ฌด ๋ฌด๊ฒ๋‹ค.

์ฆ‰, Compute Shader๋Š” โ€œ๋ฉ‹์žˆ์–ด์„œโ€ ์“ฐ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ฐ์ดํ„ฐ ๊ทœ๋ชจ์™€ ๊ณ„์‚ฐ ํŒจํ„ด์ด GPU ๋ณ‘๋ ฌํ™”์— ์–ด์šธ๋ฆด ๋•Œ ์“ฐ๋Š” ๊ฒƒ์ด๋‹ค.

9.2 CPU ์‹œ๋ฎฌ๋ ˆ์ด์…˜๊ณผ Compute ์‹œ๋ฎฌ๋ ˆ์ด์…˜์˜ ์—ญํ•  ๋ถ„๋ฆฌ

Compute๋กœ ๋„˜์–ด๊ฐ€๋”๋ผ๋„ ๊ตฌ์กฐ ์›์น™์€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค.

  • CPU๋Š” ์‹œ์Šคํ…œ ์ œ์–ด, ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •, ๋ฒ„ํผ ๋ฐ”์ธ๋”ฉ์„ ๋‹ด๋‹นํ•œ๋‹ค.
  • GPU Compute๋Š” ๋Œ€๋Ÿ‰ ์ƒํƒœ ๊ณ„์‚ฐ์„ ๋‹ด๋‹นํ•œ๋‹ค.
  • ๋ Œ๋” ์…ฐ์ด๋”๋Š” ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ํ™”๋ฉด์— ๋ณด์—ฌ์ค€๋‹ค.

์ฆ‰, ๊ตฌ์กฐ๋Š” ์—ฌ์ „ํžˆ ์ด๋ ‡๋‹ค.

  1. CPU๊ฐ€ ์ดˆ๊ธฐ ๋ฒ„ํผ๋ฅผ ์ค€๋น„ํ•œ๋‹ค.
  2. Compute Shader๊ฐ€ ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•œ๋‹ค.
  3. ๋ Œ๋” ๋‹จ๊ณ„๊ฐ€ ๊ทธ ๋ฒ„ํผ๋ฅผ ์ฝ๋Š”๋‹ค.

ํ•ต์‹ฌ์€ CPU์™€ GPU์˜ ์—ญํ• ์„ ์„ž์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.

9.3 Compute Shader ๊ธฐ๋ณธ ์˜ˆ์ œ

์•„๋ž˜๋Š” ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ํŒŒํ‹ฐํด ์ด๋™์šฉ Compute Shader ์˜ˆ์ œ๋‹ค.

#pragma kernel CSMain

struct Particle
{
    float3 position;
    float3 velocity;
    float life;
    float maxLife;
};

RWStructuredBuffer<Particle> particles;

float deltaTime;
float radius;
float timeValue;

[numthreads(256, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
    uint i = id.x;
    Particle p = particles[i];

    float3 flow = float3(
        sin(p.position.y + timeValue),
        cos(p.position.x + timeValue),
        sin(p.position.z + timeValue * 0.5)
    );

    p.velocity += flow * deltaTime;
    p.position += p.velocity * deltaTime;
    p.life -= deltaTime;

    if (p.life <= 0.0)
    {
        float a = frac(sin((i + 1) * 12.9898) * 43758.5453);
        float b = frac(sin((i + 7) * 78.233) * 12345.6789);
        float c = frac(sin((i + 13) * 39.425) * 24680.1357);

        p.position = (float3(a, b, c) * 2.0 - 1.0) * radius;
        p.velocity = 0;
        p.life = p.maxLife;
    }

    particles[i] = p;
}

์ด ์˜ˆ์ œ๋Š” ๋‹จ์ˆœํ•˜์ง€๋งŒ ์•„๋ž˜ ๊ฐœ๋…์„ ๋ณด์—ฌ์ค€๋‹ค.

  • RWStructuredBuffer๋ฅผ ํ†ตํ•œ ์ฝ๊ธฐ/์“ฐ๊ธฐ
  • numthreads ๊ธฐ๋ฐ˜ ๋ณ‘๋ ฌ ์‹คํ–‰
  • ํŒŒํ‹ฐํด ๊ตฌ์กฐ์ฒด ๊ธฐ๋ฐ˜ ์ƒํƒœ ๊ฐฑ์‹ 
  • ์ˆ˜๋ช… ๋ฆฌ์…‹ ๋กœ์ง

9.4 C#์—์„œ ComputeBuffer ์—ฐ๊ฒฐํ•˜๊ธฐ

Compute Shader๋ฅผ C#์—์„œ ์—ฐ๊ฒฐํ•˜๋Š” ๊ธฐ๋ณธ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

using UnityEngine;
using System.Runtime.InteropServices;

public class ComputeParticleController : MonoBehaviour
{
    [StructLayout(LayoutKind.Sequential)]
    public struct ParticleData
    {
        public Vector3 position;
        public Vector3 velocity;
        public float life;
        public float maxLife;
    }

    [SerializeField] private ComputeShader computeShader;
    [SerializeField] private int count = 4096;
    [SerializeField] private float radius = 5f;

    private ComputeBuffer particleBuffer;
    private int kernel;

    void Start()
    {
        kernel = computeShader.FindKernel("CSMain");

        ParticleData[] data = new ParticleData[count];
        for (int i = 0; i < count; i++)
        {
            Vector3 pos = Random.insideUnitSphere * radius;
            data[i] = new ParticleData
            {
                position = pos,
                velocity = Random.onUnitSphere,
                life = Random.Range(1f, 3f),
                maxLife = 3f
            };
        }

        int stride = Marshal.SizeOf<ParticleData>();
        particleBuffer = new ComputeBuffer(count, stride);
        particleBuffer.SetData(data);

        computeShader.SetBuffer(kernel, "particles", particleBuffer);
    }

    void Update()
    {
        computeShader.SetFloat("deltaTime", Time.deltaTime);
        computeShader.SetFloat("radius", radius);
        computeShader.SetFloat("timeValue", Time.time);

        int groups = Mathf.CeilToInt(count / 256f);
        computeShader.Dispatch(kernel, groups, 1, 1);
    }

    void OnDestroy()
    {
        if (particleBuffer != null)
        {
            particleBuffer.Release();
            particleBuffer = null;
        }
    }
}

์—ฌ๊ธฐ์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ CPU ๊ตฌ์กฐ์ฒด์™€ HLSL ๊ตฌ์กฐ์ฒด์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ ˆ์ด์•„์›ƒ์„ ์ผ์น˜์‹œํ‚ค๋Š” ๊ฒƒ์ด๋‹ค.
Compute Shader ์ž‘์—…์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ๊ฐ€์žฅ ๋จผ์ € ๊ตฌ์กฐ์ฒด ์ •๋ ฌ๊ณผ stride๋ฅผ ์˜์‹ฌํ•ด์•ผ ํ•œ๋‹ค.

9.5 Compute ํ™•์žฅ ์‹œ ์ฃผ์˜ํ•  ์ 

Compute Shader๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ์•„๋ž˜ ํ•จ์ •์„ ํ•ญ์ƒ ์˜์‹ํ•ด์•ผ ํ•œ๋‹ค.

  • CPU์™€ GPU ๊ตฌ์กฐ์ฒด ์ •๋ ฌ ๋ถˆ์ผ์น˜
  • ๋ฒ„ํผ Release ๋ˆ„๋ฝ
  • ์Šค๋ ˆ๋“œ ๊ทธ๋ฃน ํฌ๊ธฐ ๊ณ„์‚ฐ ์˜ค๋ฅ˜
  • ๋ Œ๋” ์…ฐ์ด๋”์™€ Compute ๋ฒ„ํผ ๋ ˆ์ด์•„์›ƒ ๋ถˆ์ผ์น˜
  • ๋””๋ฒ„๊น… ๋‚œ์ด๋„ ์ƒ์Šน

๋”ฐ๋ผ์„œ ๊ถŒ์žฅ ์ˆœ์„œ๋Š” ์ด๋ ‡๋‹ค.

  1. ๋จผ์ € CPU ๋ฒ„์ „์œผ๋กœ ๋™์ž‘์„ ๊ฒ€์ฆํ•œ๋‹ค.
  2. ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ํ™•์ •ํ•œ๋‹ค.
  3. ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„๋งŒ Compute๋กœ ์˜ฎ๊ธด๋‹ค.
  4. ๋ Œ๋” ๋‹จ๊ณ„๋Š” ๊ฐ€๋Šฅํ•œ ํ•œ ๊ธฐ์กด ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค.

์ฆ‰, Compute๋Š” ์‹œ์ž‘์ ์ด ์•„๋‹ˆ๋ผ ํ™•์žฅ ๋‹จ๊ณ„๋กœ ๋ณด๋Š” ํŽธ์ด ๋งž๋‹ค.


10. Unity 6 URP ๊ธฐ์ค€ ์ตœ์†Œ ์‹ค์ „ ์˜ˆ์ œ

10.1 C# ํŒŒํ‹ฐํด ์‹œ๋ฎฌ๋ ˆ์ด์…˜ + LineRenderer

์•„๋ž˜ ์˜ˆ์ œ๋Š” Unity 6 URP ํ”„๋กœ์ ํŠธ์—์„œ๋„ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”, ๊ฐ€์žฅ ์ž‘์€ ํ˜•ํƒœ์˜ ์‹ค์ „ ์˜ˆ์ œ๋‹ค.
ํ•ต์‹ฌ์€ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ โ†’ ์ƒํƒœ ๋ณ€ํ™” โ†’ ๋ Œ๋” ๋ฐ˜์˜ ๊ตฌ์กฐ๋ฅผ ๋ถ„๋ช…ํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(LineRenderer))]
public class GenerativeTrailURP : MonoBehaviour
{
    private struct ParticleData
    {
        public Vector3 position;
        public Vector3 velocity;
        public float life;
        public float maxLife;
    }

    [Header("Simulation")]
    [SerializeField] private int count = 128;
    [SerializeField] private float radius = 5f;
    [SerializeField] private float speed = 1f;
    [SerializeField] private float noiseScale = 0.5f;
    [SerializeField] private float noiseSpeed = 1f;
    [SerializeField] private int randomSeed = 42;

    [Header("Line")]
    [SerializeField] private float startWidth = 0.06f;
    [SerializeField] private float endWidth = 0.01f;
    [SerializeField] private Gradient lineGradient;

    private readonly List<ParticleData> particles = new List<ParticleData>();
    private Vector3[] positions;
    private LineRenderer lineRenderer;

    void Awake()
    {
        lineRenderer = GetComponent<LineRenderer>();
        positions = new Vector3[count];
        particles.Capacity = count;
    }

    void Start()
    {
        Random.InitState(randomSeed);
        InitializeParticles();
        SetupLineRenderer();
    }

    void Update()
    {
        Step(Time.deltaTime);
        ApplyLine();
    }

    void InitializeParticles()
    {
        particles.Clear();

        for (int i = 0; i < count; i++)
        {
            Vector3 pos = Random.insideUnitSphere * radius;
            particles.Add(new ParticleData
            {
                position = pos,
                velocity = Random.onUnitSphere * speed,
                life = Random.Range(2f, 5f),
                maxLife = 5f
            });
            positions[i] = pos;
        }
    }

    void SetupLineRenderer()
    {
        lineRenderer.positionCount = count;
        lineRenderer.useWorldSpace = true;
        lineRenderer.startWidth = startWidth;
        lineRenderer.endWidth = endWidth;
        lineRenderer.textureMode = LineTextureMode.Stretch;
        lineRenderer.alignment = LineAlignment.View;

        if (lineGradient != null)
            lineRenderer.colorGradient = lineGradient;
    }

    void Step(float dt)
    {
        float t = Time.time * noiseSpeed;

        for (int i = 0; i < particles.Count; i++)
        {
            var p = particles[i];

            Vector3 flow = new Vector3(
                Mathf.PerlinNoise(p.position.y * noiseScale + t, p.position.z * noiseScale) - 0.5f,
                Mathf.PerlinNoise(p.position.z * noiseScale + t, p.position.x * noiseScale) - 0.5f,
                Mathf.PerlinNoise(p.position.x * noiseScale + t, p.position.y * noiseScale) - 0.5f
            );

            p.velocity += flow * dt;
            p.position += p.velocity * dt;
            p.life -= dt;

            if (p.life <= 0f)
            {
                p.position = Random.insideUnitSphere * radius;
                p.velocity = Random.onUnitSphere * speed;
                p.life = p.maxLife;
            }

            particles[i] = p;
            positions[i] = p.position;
        }
    }

    void ApplyLine()
    {
        lineRenderer.SetPositions(positions);
    }
}

์ด ์˜ˆ์ œ๊ฐ€ ๋ณด์—ฌ์ฃผ๋Š” ํ•ต์‹ฌ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • struct ๊ธฐ๋ฐ˜ ์ƒํƒœ ์ €์žฅ
  • List<T>๋ฅผ ํ†ตํ•œ ๋™์  ๊ด€๋ฆฌ
  • Update() ๊ธฐ๋ฐ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฃจํ”„
  • deltaTime ์‚ฌ์šฉ
  • PerlinNoise ๊ธฐ๋ฐ˜ ํ๋ฆ„ ๋ณ€ํ™”
  • LineRenderer๋ฅผ ํ†ตํ•œ ์ฆ‰์‹œ ์‹œ๊ฐํ™”
  • ๋ฐฐ์—ด ์žฌ์‚ฌ์šฉ์„ ํ†ตํ•œ GC ๊ฐ์†Œ

10.2 URP์šฉ HLSL ์…ฐ์ด๋”์™€ ์—ฐ๊ฒฐํ•˜๋Š” ์˜ˆ์ œ

์•„๋ž˜๋Š” ์œ„ LineRenderer ์˜ˆ์ œ์™€๋Š” ๋ณ„๊ฐœ์ด๋ฉฐ, URP ์…ฐ์ด๋”๋ฅผ ์ฝ”๋“œ๋กœ ์ œ์–ดํ•˜๋Š” ์ตœ์†Œ ๋“œ๋ผ์ด๋ฒ„ ์˜ˆ์ œ์ด๋‹ค.

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class GenerativeMaterialDriverURP : MonoBehaviour
{
    static readonly int ID_BaseColor = Shader.PropertyToID("_BaseColor");
    static readonly int ID_Amplitude = Shader.PropertyToID("_Amplitude");
    static readonly int ID_Frequency = Shader.PropertyToID("_Frequency");
    static readonly int ID_Speed = Shader.PropertyToID("_Speed");

    [SerializeField] private Color baseColor = new Color(0.2f, 0.9f, 1f, 1f);
    [SerializeField] private float amplitude = 0.12f;
    [SerializeField] private float frequency = 3f;
    [SerializeField] private float speed = 1f;
    [SerializeField] private bool animateColor = true;

    private Renderer cachedRenderer;
    private MaterialPropertyBlock mpb;

    void Awake()
    {
        cachedRenderer = GetComponent<Renderer>();
        mpb = new MaterialPropertyBlock();
    }

    void Update()
    {
        Color finalColor = baseColor;
        if (animateColor)
        {
            float t = Mathf.PingPong(Time.time * 0.5f, 1f);
            finalColor = Color.Lerp(new Color(0.2f, 0.9f, 1f, 1f), new Color(1f, 0.2f, 0.8f, 1f), t);
        }

        mpb.Clear();
        mpb.SetColor(ID_BaseColor, finalColor);
        mpb.SetFloat(ID_Amplitude, amplitude);
        mpb.SetFloat(ID_Frequency, frequency);
        mpb.SetFloat(ID_Speed, speed);
        cachedRenderer.SetPropertyBlock(mpb);
    }
}

์ด ์ฝ”๋“œ๋Š” Shader Graph ์—†์ด๋„ C#์—์„œ URP ์…ฐ์ด๋”๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์ œ์–ดํ•˜๋Š” ๊ธฐ๋ณธ ํŒจํ„ด์„ ๋ณด์—ฌ์ค€๋‹ค.


11. ์„ฑ๋Šฅ๊ณผ GC ์ฒดํฌ๋ฆฌ์ŠคํŠธ

Generative Art ์‹œ์Šคํ…œ์€ ๋ณด๊ธฐ๋ณด๋‹ค ๋นจ๋ฆฌ ๋ณ‘๋ชฉ์— ๋‹ฟ๋Š”๋‹ค.
๋”ฐ๋ผ์„œ ์•„๋ž˜ ํ•ญ๋ชฉ์€ ๊ฑฐ์˜ ํ•ญ์ƒ ์ ๊ฒ€ํ•ด์•ผ ํ•œ๋‹ค.

  • ๋งค ํ”„๋ ˆ์ž„ new๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์ง€ ์•Š์€๊ฐ€
  • ToList(), ToArray()๋ฅผ ํ•ซํŒจ์Šค์—์„œ ๋‚จ๋ฐœํ•˜๊ณ  ์žˆ์ง€ ์•Š์€๊ฐ€
  • ๋ฒ„ํผ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๊ฐ€
  • renderer.material์„ ๋ฌด์‹ฌ์ฝ” ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์ง€ ์•Š์€๊ฐ€
  • ํ…์Šค์ฒ˜ Apply() ํ˜ธ์ถœ ๋นˆ๋„๊ฐ€ ๊ณผ๋„ํ•˜์ง€ ์•Š์€๊ฐ€
  • ๋ฉ”์‹œ ๋ฒ„ํ…์Šค ๋ฐฐ์—ด์„ ๊ณ„์† ์ƒˆ๋กœ ๋งŒ๋“ค๊ณ  ์žˆ์ง€ ์•Š์€๊ฐ€
  • GPU Instancing์„ ์“ธ ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์—์„œ GameObject๋ฅผ ๊ณผ๋„ํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์žˆ์ง€ ์•Š์€๊ฐ€
  • ComputeBuffer Release()๋ฅผ ๋†“์น˜์ง€ ์•Š์•˜๋Š”๊ฐ€
  • ๊ตฌ์กฐ์ฒด ๋ ˆ์ด์•„์›ƒ์ด C#๊ณผ HLSL ์‚ฌ์ด์—์„œ ์ผ์น˜ํ•˜๋Š”๊ฐ€

๊ฒฐ๊ตญ ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ๋Œ€๊ฐœ ํŠน๋ณ„ํ•œ ๋น„๋ฐ€์ด ์•„๋‹ˆ๋ผ, ๋ฐ์ดํ„ฐ ์žฌ์‚ฌ์šฉ๊ณผ ํ• ๋‹น ์ œ์–ด ๋ฌธ์ œ๋กœ ๋Œ์•„์˜จ๋‹ค.


12. ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์“ฐ๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ ์ ๊ฒ€ํ•  ๊ฒƒ

์ด ๋ฌธ์„œ์˜ ์ตœ์ข… ๋ชฉ์ ์€ ๊ตฌํ˜„๋ฟ ์•„๋‹ˆ๋ผ ์„ค๋ช… ๊ฐ€๋Šฅํ•œ ๊ตฌํ˜„์ด๋‹ค.
๋”ฐ๋ผ์„œ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์“ฐ๊ธฐ ์ „์—๋Š” ์•„๋ž˜ ์งˆ๋ฌธ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

  • ๋‚ด ์ž‘์—…์€ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ, ์ƒํƒœ ๋ณ€ํ™”, ์‹œ๊ฐํ™” ์ค‘ ์–ด๋””์— ์ดˆ์ ์ด ์žˆ๋Š”๊ฐ€
  • ํ•ต์‹ฌ ์ƒํƒœ๋Š” ์–ด๋–ค ์ž๋ฃŒ๊ตฌ์กฐ๋กœ ์ €์žฅํ–ˆ๋Š”๊ฐ€
  • ์™œ struct ๋˜๋Š” class๋ฅผ ์„ ํƒํ–ˆ๋Š”๊ฐ€
  • ์™œ Update()๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ , ์™œ ๋‹ค๋ฅธ ๋ฃจํ”„๊ฐ€ ์•„๋‹Œ๊ฐ€
  • ์‹œ๊ฐํ™” ๋ฐฉ์‹์€ ์™œ LineRenderer, Mesh, Texture2D, Shader, Compute ์ค‘ ํ•˜๋‚˜์˜€๋Š”๊ฐ€
  • ๋žœ๋ค์€ ๋‹จ์ˆœ ์žฅ์‹์ธ๊ฐ€, ๊ตฌ์กฐ์  ๊ทœ์น™์˜ ์ผ๋ถ€์ธ๊ฐ€
  • CPU ๊ณ„์‚ฐ๊ณผ GPU ๊ณ„์‚ฐ์€ ์–ด๋””์„œ ๋‚˜๋‰˜๋Š”๊ฐ€
  • ์„ฑ๋Šฅ์ƒ ๊ฐ€์žฅ ์œ„ํ—˜ํ•œ ์ง€์ ์€ ๋ฌด์—‡์ธ๊ฐ€

์ด ์งˆ๋ฌธ๋“ค์— ๋‹ตํ•  ์ˆ˜ ์žˆ์œผ๋ฉด, ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ ๋‚˜์—ดํ•˜๋Š” ์ˆ˜์ค€์„ ๋„˜์–ด์„œ ๊ตฌ์กฐ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๊ธ€์„ ์“ธ ์ˆ˜ ์žˆ๋‹ค.


13. ๊ฒฐ๋ก 

Unity 6 URP์—์„œ Generative Art, Procedural Art, Shader Art๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ ๋‹ค๋Š” ๊ฒƒ์€ ๊ฒฐ๊ตญ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ , ๊ทธ๊ฒƒ์„ ์ ์ ˆํ•œ ๋ Œ๋”๋ง ๊ฒฝ๋กœ์— ์—ฐ๊ฒฐํ•˜๋Š” ์ผ์ด๋‹ค.

์ข‹์€ ์ถœ๋ฐœ์ ์€ ํ•ญ์ƒ ๊ฐ™๋‹ค.

  • ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ์„ค๊ณ„ํ•œ๋‹ค.
  • ์ƒํƒœ ๋ณ€ํ™” ๊ทœ์น™์„ ๋ช…ํ™•ํžˆ ๋งŒ๋“ ๋‹ค.
  • ์‹œ๊ฐํ™”๋Š” ๋ชฉ์ ์— ๋งž๋Š” API๋ฅผ ๊ณ ๋ฅธ๋‹ค.
  • Inspector ์‹คํ—˜์„ฑ๊ณผ ๋‚ด๋ถ€ ์ œ์–ด๋ฅผ ๋™์‹œ์— ํ™•๋ณดํ•œ๋‹ค.
  • ๋งค ํ”„๋ ˆ์ž„ ํ• ๋‹น์„ ์ค„์ธ๋‹ค.
  • ์…ฐ์ด๋”์™€ C#์˜ ์—ญํ• ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.
  • ํ•„์š”ํ•  ๋•Œ๋งŒ Compute Shader๋กœ ํ™•์žฅํ•œ๋‹ค.

์ฆ‰, ์ข‹์€ ์ œ๋„ˆ๋ ˆ์ดํ‹ฐ๋ธŒ ์•„ํŠธ ์ฝ”๋“œ๋Š” ์šฐ์—ฐํžˆ ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค.
๋Œ€๋ถ€๋ถ„์€ ์ž‘์€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ, ๋ช…์‹œ์ ์ธ ์—…๋ฐ์ดํŠธ ๋ฃจํ”„, ์•ˆ์ •์ ์ธ ๋ Œ๋”๋ง ๊ฒฝ๋กœ, ๋ถ„๋ช…ํ•œ ์—ญํ•  ๋ถ„๋ฆฌ์—์„œ ๋‚˜์˜จ๋‹ค.


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

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