๐ŸซงArt_008 Compute Shader

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

Unity GenArt

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

Compute Shader

Compute Shader๋กœ ๋งŒ๋“  ๊ฒฐ๊ณผ๋ฅผ ์˜ค๋ธŒ์ ํŠธ์˜ ๋จธํ‹ฐ๋ฆฌ์–ผ์— ๊ณต๊ธ‰ํ•ด ์‹œ๊ฐ์  ํšจ๊ณผ๋ฅผ ์ฃผ๋Š” ๋ฐฉ์‹

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฝ”๋“œ ๊ธฐ๋ฐ˜(HLSL, .compute ํŒŒ์ผ)์ž…๋‹ˆ๋‹ค.
  • ๋…ธ๋“œ GUI๊ฐ€ ์•„๋‹ˆ๋ผ ์Šค๋ ˆ๋“œ/์ปค๋„ ๋‹จ์œ„๋กœ GPU ์—ฐ์‚ฐ์„ ์ง์ ‘ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ํŒŒํ‹ฐํด ์‹œ๋ฎฌ๋ ˆ์ด์…˜, ๊ตฐ์ค‘ ๊ณ„์‚ฐ, ๋…ธ์ด์ฆˆ ํ•„๋“œ, ํ›„์ฒ˜๋ฆฌ์šฉ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๊ฐ™์€ ๋Œ€๋Ÿ‰ ๋ณ‘๋ ฌ ์—ฐ์‚ฐ์— ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค.
  • C# ์Šคํฌ๋ฆฝํŠธ์—์„œ ComputeBuffer, Dispatch()๋กœ ์‹คํ–‰์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

์ ์šฉ ๋ฐฉ๋ฒ•

  1. GameObject ์ƒ์„ฑ
  2. ComputePreview.cs (์ปจํŠธ๋กค๋Ÿฌ ์—ญํ• ) ์ž‘์„ฑํ•œ ๋‹ค์Œ์— GameObject์— ๋ถ€์ฐฉ
  3. GameObject์˜ Inspector์—์„œ ์ฐธ์กฐ ์„ค์ •
    • Compute : NoiseCompute.compute ์ฝ”๋“œ
    • Target Renderer : Scene์— ์กด์žฌํ•˜๋Š” ์˜ค๋ธŒ์ ํŠธ ์„ ํƒ

์ฝ”๋“œ ๊ฐœ์š”

  • NoiseCompute.compute์—์„œ GPU ์—ฐ์‚ฐ(๋…ธ์ด์ฆˆ ์ƒ์„ฑ)์„ ์ˆ˜ํ–‰
  • ComputePreview.cs๊ฐ€ ์ปดํ“จํŠธ ์…ฐ์ด๋”๋ฅผ ์‹คํ–‰(Dispatch)ํ•˜๊ณ  ๊ฒฐ๊ณผ RenderTexture๋ฅผ ๋ฐ›์Œ
  • ๊ทธ ํ…์Šค์ฒ˜๋ฅผ ๋Œ€์ƒ ์˜ค๋ธŒ์ ํŠธ(Renderer)์— ์—ฐ๊ฒฐํ•ด ํ™”๋ฉด์— ํ‘œ์‹œ
  • [ExecuteAlways]๋กœ ์—๋””ํ„ฐ์—์„œ๋„ ์ฆ‰์‹œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐ€๋Šฅ
  • MaterialPropertyBlock์œผ๋กœ ์ ์šฉํ•ด์„œ ๋จธํ‹ฐ๋ฆฌ์–ผ ์—์…‹ ์˜ค์—ผ/์ „ํŒŒ ๋ฌธ์ œ ๋ฐฉ์ง€

๐Ÿ“„NoiseCompute.compute

// ์ด ํŒŒ์ผ์—์„œ ์‚ฌ์šฉํ•  ์ปค๋„(์ง„์ž… ํ•จ์ˆ˜) ์ด๋ฆ„ ์„ ์–ธ
#pragma kernel CSMain

// Compute Shader๊ฐ€ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํ•  ์ถœ๋ ฅ ํ…์Šค์ฒ˜
// RWTexture2D: ์ฝ๊ธฐ/์“ฐ๊ธฐ ๊ฐ€๋Šฅ(์—ฌ๊ธฐ์„œ๋Š” ์“ฐ๊ธฐ ์ค‘์‹ฌ)
RWTexture2D<float4> Result;

// C#์—์„œ SetInt/SetFloat๋กœ ์ „๋‹ฌ๋ฐ›๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค
uint _Width;       // ์ถœ๋ ฅ ํ…์Šค์ฒ˜ ๊ฐ€๋กœ ํ•ด์ƒ๋„
uint _Height;      // ์ถœ๋ ฅ ํ…์Šค์ฒ˜ ์„ธ๋กœ ํ•ด์ƒ๋„
float _TimeValue;  // ์‹œ๊ฐ„ ๊ฐ’(ํ”Œ๋ ˆ์ด ๋ชจ๋“œ์—์„œ๋Š” Time.time, ์—๋””ํ„ฐ๋Š” ๊ณ ์ •๊ฐ’ ๊ฐ€๋Šฅ)
float _Scale;      // ๋…ธ์ด์ฆˆ ํ™•๋Œ€/์ถ•์†Œ ์ •๋„
float _Speed;      // ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ํ๋ฆ„ ์†๋„

float4 _ColorA;    // C#์—์„œ ์ „๋‹ฌ๋ฐ›๋Š” ๋‘ ์ƒ‰ (RGB๋งŒ ์‚ฌ์šฉ, A๋Š” ๋ฌด์‹œ ๊ฐ€๋Šฅ)
float4 _ColorB;

// 2D ์ขŒํ‘œ -> 0~1 ๋žœ๋ค๊ฐ’์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ํ•ด์‹œ ํ•จ์ˆ˜
// ์™„์ „ํ•œ ๋žœ๋ค์€ ์•„๋‹ˆ๊ณ , ์ขŒํ‘œ ๊ธฐ๋ฐ˜ ์˜์‚ฌ๋‚œ์ˆ˜ ์ƒ์„ฑ๊ธฐ
float hash21(float2 p)
{
    p = frac(p * float2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return frac(p.x * p.y);
}

// value noise ์Šคํƒ€์ผ์˜ 2D ๋…ธ์ด์ฆˆ ํ•จ์ˆ˜
// uv ์œ„์น˜์—์„œ ์ฃผ๋ณ€ 4๊ฐœ ๊ทธ๋ฆฌ๋“œ ๊ฐ’(a,b,c,d)์„ ๋ณด๊ฐ„ํ•ด ๋ถ€๋“œ๋Ÿฌ์šด ๊ฐ’ ์ƒ์„ฑ
float noise2(float2 uv)
{
    // ํ˜„์žฌ uv๊ฐ€ ์†ํ•œ ์ •์ˆ˜ ๊ฒฉ์ž ์ขŒํ‘œ
    float2 i = floor(uv);

    // ๊ฒฉ์ž ๋‚ด ๋กœ์ปฌ ์ขŒํ‘œ(0~1)
    float2 f = frac(uv);

    // ๊ฒฉ์ž 4๊ฐœ ๊ผญ์ง“์ ์—์„œ ํ•ด์‹œ๊ฐ’ ์ƒ˜ํ”Œ
    float a = hash21(i);
    float b = hash21(i + float2(1.0, 0.0));
    float c = hash21(i + float2(0.0, 1.0));
    float d = hash21(i + float2(1.0, 1.0));

    // ๋ณด๊ฐ„ ๊ณก์„ (๋ถ€๋“œ๋Ÿฌ์šด ์Šคํ…): f*f*(3-2f)
    float2 u = f * f * (3.0 - 2.0 * f);

    // x๋ฐฉํ–ฅ ๋ณด๊ฐ„ ํ›„ y๋ฐฉํ–ฅ ๋ณด๊ฐ„
    return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y);
}

// ์Šค๋ ˆ๋“œ ๊ทธ๋ฃน ํฌ๊ธฐ(ํ•œ ๊ทธ๋ฃน์— 8x8 ์Šค๋ ˆ๋“œ)
// C# Dispatch์—์„œ (ceil(width/8), ceil(height/8), 1)๋กœ ๋งž์ถฐ ์‹คํ–‰
[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
    // id.x, id.y๊ฐ€ ํ…์Šค์ฒ˜ ๋ฒ”์œ„๋ฅผ ๋„˜์œผ๋ฉด ์ฆ‰์‹œ ์ข…๋ฃŒ
    // (๊ฐ€์žฅ์ž๋ฆฌ ๊ทธ๋ฃน์—์„œ ์ดˆ๊ณผ ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์–ด์„œ ํ•„์š”)
    if (id.x >= _Width || id.y >= _Height) return;

    // ํ˜„์žฌ ํ”ฝ์…€ id๋ฅผ 0~1 UV๋กœ ์ •๊ทœํ™”
    // +0.5๋Š” ํ”ฝ์…€ ์ค‘์‹ฌ ์ƒ˜ํ”Œ๋ง ๋А๋‚Œ์„ ์ฃผ๊ธฐ ์œ„ํ•œ ์˜คํ”„์…‹
    float2 uv = float2((id.x + 0.5) / (float)_Width, (id.y + 0.5) / (float)_Height);

    // ์‹œ๊ฐ„์— ๋”ฐ๋ผ x์ถ• ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™ํ•˜๋Š” ๋…ธ์ด์ฆˆ
    // _Scale: ํŒจํ„ด ํฌ๊ธฐ, _Speed: ์›€์ง์ž„ ์†๋„
    float n = noise2(uv * _Scale + float2(_TimeValue * _Speed, 0.0));

    // ๋…ธ์ด์ฆˆ ํŒจํ„ด์— ์“ฐ์ด๋Š” ๋‘ ์ƒ‰์„ n์œผ๋กœ ๋ณด๊ฐ„
    float3 col = lerp(_ColorA.rgb, _ColorB.rgb, n);

    // ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅ ํ…์Šค์ฒ˜์˜ ํ˜„์žฌ ํ”ฝ์…€์— ๊ธฐ๋ก
    Result[int2(id.xy)] = float4(col, 1.0);
}

๐Ÿ“„ComputePreview.cs

using UnityEngine;

// ================================================================
// ComputePreview
// [ExecuteAlways] ๋กœ ์—๋””ํ„ฐ/ํ”Œ๋ ˆ์ด ๋ชจ๋“œ ๋ชจ๋‘์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
// ์ปดํ“จํŠธ ์…ฐ์ด๋”๋กœ ํ”„๋กœ์‹œ์ €๋Ÿด ๋…ธ์ด์ฆˆ ํ…์Šค์ฒ˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ ,
// MaterialPropertyBlock์„ ํ†ตํ•ด ํƒ€๊ฒŸ ๋ Œ๋”๋Ÿฌ์— ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
// ================================================================
[ExecuteAlways]  // ์—๋””ํ„ฐ์™€ ํ”Œ๋ ˆ์ด ๋ชจ๋“œ ๋ชจ๋‘์—์„œ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •
public class ComputePreview : MonoBehaviour
{
    // ----------------------------------------------------------------
    // ์ธ์ŠคํŽ™ํ„ฐ ๋…ธ์ถœ ํ•„๋“œ
    // ----------------------------------------------------------------

    [Header("References")]
    [SerializeField] ComputeShader compute;       // ๋…ธ์ด์ฆˆ ์ƒ์„ฑ์šฉ ์ปดํ“จํŠธ ์…ฐ์ด๋”
    [SerializeField] Renderer targetRenderer;     // ํ…์Šค์ฒ˜๋ฅผ ์ž…ํž ๋Œ€์ƒ ๋ Œ๋”๋Ÿฌ

    [Header("Texture")]
    [SerializeField, Range(32, 2048)] int width  = 512;  // ๋ Œ๋” ํ…์Šค์ฒ˜ ๊ฐ€๋กœ ํ•ด์ƒ๋„
    [SerializeField, Range(32, 2048)] int height = 512;  // ๋ Œ๋” ํ…์Šค์ฒ˜ ์„ธ๋กœ ํ•ด์ƒ๋„

    [Header("Noise Params")]
    [SerializeField, Min(0.01f)] float scale = 8f;  // ๋…ธ์ด์ฆˆ ๊ณต๊ฐ„ ์Šค์ผ€์ผ (ํด์ˆ˜๋ก ๊ฑฐ์นœ ํŒจํ„ด)
    [SerializeField] float speed = 0.5f;            // ๋…ธ์ด์ฆˆ ์‹œ๊ฐ„ ์ง„ํ–‰ ์†๋„

    [Header("Gradient Colors")]
    [SerializeField] Color colorA = new Color(0.05f, 0.10f, 0.30f, 1f);  // ๋…ธ์ด์ฆˆ 0 ์ชฝ ์ƒ‰
    [SerializeField] Color colorB = new Color(0.90f, 0.40f, 0.20f, 1f);  // ๋…ธ์ด์ฆˆ 1 ์ชฝ ์ƒ‰

    [Header("Editor Preview")]
    [SerializeField] bool showInSceneEditor = true;   // ์”ฌ ๋ทฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™œ์„ฑ ์—ฌ๋ถ€
    [SerializeField] float editorPreviewTime = 0f;    // ์—๋””ํ„ฐ ๋ชจ๋“œ์—์„œ ์‚ฌ์šฉํ•  ๊ฐ€์ƒ ์‹œ๊ฐ„๊ฐ’

    // ----------------------------------------------------------------
    // ๋‚ด๋ถ€ ๋Ÿฐํƒ€์ž„ ์ƒํƒœ
    // ----------------------------------------------------------------

    RenderTexture rt;           // GPU์— ํ• ๋‹น๋œ ๊ฒฐ๊ณผ ํ…์Šค์ฒ˜
    MaterialPropertyBlock mpb;  // ๋จธํ‹ฐ๋ฆฌ์–ผ ์ธ์Šคํ„ด์Šค ์—†์ด ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ฎ์–ด์“ฐ๋Š” ๋ธ”๋ก
    int kernel = -1;            // CSMain ์ปค๋„ ์ธ๋ฑ์Šค (-1 = ์•„์ง ํƒ์ƒ‰ ์ „)
    int prevW  = -1;            // ์ด์ „ ๋„ˆ๋น„  โ€” ํ•ด์ƒ๋„ ๋ณ€๊ฒฝ ๊ฐ์ง€์šฉ
    int prevH  = -1;            // ์ด์ „ ๋†’์ด  โ€” ํ•ด์ƒ๋„ ๋ณ€๊ฒฝ ๊ฐ์ง€์šฉ
    bool ready;                 // ๋ Œ๋”๋ง ๊ฐ€๋Šฅ ์ƒํƒœ ํ”Œ๋ž˜๊ทธ (์ดˆ๊ธฐํ™” ์—ฌ๋ถ€)

    // ----------------------------------------------------------------
    // ์…ฐ์ด๋” ํ”„๋กœํผํ‹ฐ ID ์บ์‹ฑ
    // Shader.PropertyToID() ๋Š” ๋งค ํ”„๋ ˆ์ž„ ํ˜ธ์ถœ ์‹œ ๋น„์šฉ์ด ์žˆ์œผ๋ฏ€๋กœ
    // static readonly ๋กœ ํ•œ ๋ฒˆ๋งŒ ํ•ด์‹œํ™”ํ•ด ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    // ----------------------------------------------------------------

    static readonly int ID_Result  = Shader.PropertyToID("Result");
    static readonly int ID_Width   = Shader.PropertyToID("_Width");
    static readonly int ID_Height  = Shader.PropertyToID("_Height");
    static readonly int ID_Time    = Shader.PropertyToID("_TimeValue");
    static readonly int ID_Scale   = Shader.PropertyToID("_Scale");
    static readonly int ID_Speed   = Shader.PropertyToID("_Speed");
    static readonly int ID_ColorA  = Shader.PropertyToID("_ColorA");
    static readonly int ID_ColorB  = Shader.PropertyToID("_ColorB");
    static readonly int ID_BaseMap = Shader.PropertyToID("_BaseMap");  // URP / HDRP
    static readonly int ID_MainTex = Shader.PropertyToID("_MainTex");  // Built-in RP

    // ์ปดํ“จํŠธ ์…ฐ์ด๋”์˜ [numthreads(8, 8, 1)] ๊ณผ ๋ฐ˜๋“œ์‹œ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    // ์…ฐ์ด๋” numthreads ๋ฅผ ๋ฐ”๊พธ๋ฉด ์ด ๊ฐ’๋„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•˜์„ธ์š”.
    const int THREAD_GROUP_SIZE = 8;


    // ================================================================
    // Unity ์ƒ๋ช…์ฃผ๊ธฐ
    // ================================================================

    void OnEnable()    // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋  ๋•Œ ํ˜ธ์ถœ
    {
				// ํ™œ์„ฑํ™” ์‹œ ์ดˆ๊ธฐํ™” ํ›„ ์ฆ‰์‹œ ํ•œ ๋ฒˆ ๋ Œ๋”๋งํ•ด ๋นˆ ํ™”๋ฉด์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
        EnsureInit();  // ์ดˆ๊ธฐํ™” ํ™•์ธ
        RenderNow();   // ์ฆ‰์‹œ ๋ Œ๋”๋งํ•ด์„œ ๋นˆ ํ™”๋ฉด ๋ฐฉ์ง€
    }

    void OnDisable()   // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋  ๋•Œ ํ˜ธ์ถœ
    {
        // ๋น„ํ™œ์„ฑํ™” ์‹œ ๋ Œ๋”๋Ÿฌ์—์„œ ํ…์Šค์ฒ˜ ์ฐธ์กฐ๋ฅผ ๋จผ์ € ๋Š์€ ๋’ค GPU ๋ฆฌ์†Œ์Šค๋ฅผ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.
        // ์ˆœ์„œ๊ฐ€ ๋ฐ˜๋Œ€๋ฉด ๋ Œ๋”๋Ÿฌ๊ฐ€ ์ด๋ฏธ ํ•ด์ œ๋œ ํ…์Šค์ฒ˜๋ฅผ ์ž ์‹œ ์ฐธ์กฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
        ClearPropertyBlockTexture(); // ํ”„๋กœํผํ‹ฐ ๋ธ”๋ก ํ…์Šค์ณ ์ดˆ๊ธฐํ™”
        ReleaseAll();  // ๋ชจ๋“  ๋ฆฌ์†Œ์Šค ํ•ด์ œ
    }

    void OnDestroy()
    {
        // OnDisable ์ด ํ•ญ์ƒ ๋จผ์ € ํ˜ธ์ถœ๋˜์ง€๋งŒ, ์„œ๋ธŒํด๋ž˜์‹ฑ ๋“ฑ ํ™•์žฅ์„ ๊ณ ๋ คํ•ด
        // ๋ช…์‹œ์ ์œผ๋กœ ํ•œ ๋ฒˆ ๋” ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ReleaseAll ์€ ์ค‘๋ณต ํ˜ธ์ถœ์— ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
        ReleaseAll();
    }

    void OnValidate()  // ์ธ์ŠคํŽ™ํ„ฐ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ ํ˜ธ์ถœ
    {
        // Range / Min ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๊ฐ€ ์ด๋ฏธ ํด๋žจํ•‘์„ ๋‹ด๋‹นํ•˜์ง€๋งŒ,
        // ์ฝ”๋“œ๋กœ ์ง์ ‘ ๊ฐ’์„ ๋„ฃ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•œ ๋ฐฉ์–ด ์ฒ˜๋ฆฌ์ž…๋‹ˆ๋‹ค.
        width  = Mathf.Max(32, width);     // ๋„ˆ๋น„ ์ตœ์†Œ๊ฐ’ ์„ค์ •
        height = Mathf.Max(32, height);    // ๋†’์ด ์ตœ์†Œ๊ฐ’ ์„ค์ •
        scale  = Mathf.Max(0.01f, scale);  // ์Šค์ผ€์ผ ์ตœ์†Œ๊ฐ’ ์„ค์ •

        // OnValidate ๋Š” ์—๋””ํ„ฐ ์ง๋ ฌํ™” ์ฝœ๋ฐฑ์œผ๋กœ ํƒ€์ด๋ฐ์ด ๋ถˆ์•ˆ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋น„ํ™œ์„ฑ ์ƒํƒœ๋ผ๋ฉด GPU ์ž‘์—…์„ ์‹œ๋„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
        if (!isActiveAndEnabled) return;   // ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ข…๋ฃŒ

        EnsureInit();  // ์ดˆ๊ธฐํ™” ํ™•์ธ
        RenderNow();   // ์ฆ‰์‹œ ๋ Œ๋”๋ง
    }

    void Update()  // ๋งค ํ”„๋ ˆ์ž„ ํ˜ธ์ถœ
    {
        if (!isActiveAndEnabled) return;   // ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ข…๋ฃŒ
        if (!EnsureInit()) return;    		 // ์ดˆ๊ธฐํ™” ์‹คํŒจ ์‹œ ์ข…๋ฃŒ

        if (Application.isPlaying)
        {
            RenderNow();  // ํ”Œ๋ ˆ์ด ๋ชจ๋“œ โ€” ๋งค ํ”„๋ ˆ์ž„ ๊ฐฑ์‹  (Time.time ๊ธฐ๋ฐ˜ ์• ๋‹ˆ๋ฉ”์ด์…˜)
            return;
        }

        // ์—๋””ํ„ฐ ๋ชจ๋“œ โ€” ์”ฌ ๋ทฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ์ผœ์ง„ ๊ฒฝ์šฐ์—๋งŒ ๋ Œ๋”๋ง
        if (showInSceneEditor) RenderNow();
    }


    // ================================================================
    // ์ดˆ๊ธฐํ™”
    // ================================================================

    // ์ปค๋„ ํƒ์ƒ‰ โ†’ RenderTexture ์ƒ์„ฑ โ†’ MPB ์ค€๋น„๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    // ์ด๋ฏธ ์ค€๋น„๋œ ์ƒํƒœ๋ฉด ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ์žฌ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    // ๋ฐ˜ํ™˜๊ฐ’: ๋ Œ๋”๋ง ๊ฐ€๋Šฅ ์—ฌ๋ถ€
    bool EnsureInit()
    {
        if (compute == null || targetRenderer == null)
        {
            ready = false;	// Compute Shader๋‚˜ ๋ Œ๋”๋Ÿฌ๊ฐ€ ์—†์œผ๋ฉด ์ค€๋น„ ์™„๋ฃŒ ์•„๋‹˜
            return false;
        }

        // ์ปค๋„ ์ธ๋ฑ์Šค๊ฐ€ ์•„์ง ์—†์œผ๋ฉด ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
        if (kernel < 0)
        {
            if (!compute.HasKernel("CSMain"))
            {
                Debug.LogError("[ComputePreview] ์ปดํ“จํŠธ ์…ฐ์ด๋”์— 'CSMain' ์ปค๋„์ด ์—†์Šต๋‹ˆ๋‹ค.", this);
                ready = false; // ์ปค๋„์ด ์—†์œผ๋ฉด ์ค€๋น„ ์™„๋ฃŒ ์•„๋‹˜
                return false;
            }
            kernel = compute.FindKernel("CSMain");	// ์ปค๋„ ID ๊ฐ€์ ธ์˜ค๊ธฐ
        }

        // ํ•ด์ƒ๋„๊ฐ€ ๋ฐ”๋€Œ์—ˆ๊ฑฐ๋‚˜ ํ…์Šค์ฒ˜๊ฐ€ ์—†์œผ๋ฉด ์žฌ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
        if (rt == null || prevW != width || prevH != height)
            CreateRT();  												// ๋ Œ๋” ํ…์Šค์ฒ˜ ์ƒ์„ฑ ๋˜๋Š” ๊ฐฑ์‹ 

        if (mpb == null)
            mpb = new MaterialPropertyBlock();  // ๋ Œ๋” ํ…์Šค์ฒ˜ ์ƒ์„ฑ ๋˜๋Š” ๊ฐฑ์‹ 

        // rt.IsCreated() ๋กœ GPU ์ƒ์˜ ์‹ค์ œ ์ƒ์„ฑ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
        // rt.Create() ๊ฐ€ ์‹คํŒจํ•ด๋„ rt != null ์ด๋ฏ€๋กœ ์ด ์ฒดํฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
        ready = rt != null && rt.IsCreated() && kernel >= 0;
        return ready;
    }

    // ํ˜„์žฌ ํ•ด์ƒ๋„ ์„ค์ •์œผ๋กœ RenderTexture ๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    // enableRandomWrite = true ๋Š” ์ปดํ“จํŠธ ์…ฐ์ด๋”์˜ RWTexture2D ๋ฐ”์ธ๋”ฉ์— ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.
    void CreateRT()
    {
        ReleaseRT();  // ๊ธฐ์กด ํ…์Šค์ฒ˜๊ฐ€ ์žˆ์œผ๋ฉด ๋จผ์ € ํ•ด์ œ

        rt = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32); // ์ƒˆ๋กœ์šด ๋ Œ๋” ํ…์Šค์ฒ˜ ์ƒ์„ฑ
        rt.enableRandomWrite = true;							// ๋žœ๋ค ์“ฐ๊ธฐ ํ™œ์„ฑํ™”
        rt.wrapMode   = TextureWrapMode.Repeat;		// ํ…์Šค์ฒ˜ ๋ฐ˜๋ณต ๋ชจ๋“œ
        rt.filterMode = FilterMode.Bilinear;			// ํ•„ํ„ฐ ๋ชจ๋“œ ์„ค์ •

        if (!rt.Create())		
        {
            // GPU ํ…์Šค์ฒ˜ ์ƒ์„ฑ ์‹คํŒจ โ€” ํ•ด์ƒ๋„๊ฐ€ ๋„ˆ๋ฌด ํฌ๊ฑฐ๋‚˜ VRAM ๋ถ€์กฑ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
            Debug.LogError(
                $"[ComputePreview] RenderTexture ์ƒ์„ฑ ์‹คํŒจ ({width}x{height}). " +
                "ํ•ด์ƒ๋„๋ฅผ ๋‚ฎ์ถ”๊ฑฐ๋‚˜ GPU ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", this);
            DestroyImmediate(rt);
            rt = null;
            return;
        }

        prevW = width;   // ์ด์ „ ๋„ˆ๋น„ ์ €์žฅ
        prevH = height;  // ์ด์ „ ๋†’์ด ์ €์žฅ
    }


    // ================================================================
    // ๋ Œ๋”๋ง
    // ================================================================

    // ํ”Œ๋ ˆ์ด ๋ชจ๋“œ โ†’ Time.time (์‹ค์ œ ๊ฒฝ๊ณผ ์‹œ๊ฐ„)
    // ์—๋””ํ„ฐ ๋ชจ๋“œ โ†’ editorPreviewTime (์ธ์ŠคํŽ™ํ„ฐ์—์„œ ์ˆ˜๋™ ์„ค์ •)
    float GetTimeValue()  // ํ˜„์žฌ ์‹œ๊ฐ„ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
    {
        return Application.isPlaying ? Time.time : editorPreviewTime;
    }

    // ์ปดํ“จํŠธ ์…ฐ์ด๋”์— ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  Dispatch ํ•œ ๋’ค,
    // ๊ฒฐ๊ณผ ํ…์Šค์ฒ˜๋ฅผ MPB ๋ฅผ ํ†ตํ•ด ๋ Œ๋”๋Ÿฌ์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
    void RenderNow()
    {
        if (!ready || compute == null || rt == null || kernel < 0) return;

        compute.SetTexture(kernel, ID_Result, rt);
        compute.SetInt   (ID_Width,  width);
        compute.SetInt   (ID_Height, height);
        compute.SetFloat (ID_Time,   GetTimeValue());
        compute.SetFloat (ID_Scale,  scale);
        compute.SetFloat (ID_Speed,  speed);
        compute.SetVector(ID_ColorA, colorA);
        compute.SetVector(ID_ColorB, colorB);

        // ์Šค๋ ˆ๋“œ ๊ทธ๋ฃน ์ˆ˜ = ํ…์Šค์ฒ˜ ํฌ๊ธฐ / numthreads ํฌ๊ธฐ (์˜ฌ๋ฆผ)
        // ์˜ˆ: 512 / 8 = 64 ๊ทธ๋ฃน โ†’ 64 * 8 = 512 ์Šค๋ ˆ๋“œ๋กœ ์ „์ฒด ํ”ฝ์…€์„ ์ปค๋ฒ„
        int gx = Mathf.CeilToInt(width  / (float)THREAD_GROUP_SIZE);
        int gy = Mathf.CeilToInt(height / (float)THREAD_GROUP_SIZE);
        compute.Dispatch(kernel, gx, gy, 1);

        ApplyPropertyBlockTexture();
    }


    // ================================================================
    // MaterialPropertyBlock ๊ด€๋ฆฌ
    // ================================================================

    // ๋ Œ๋”๋Ÿฌ์˜ MPB ์— ์ƒ์„ฑ๋œ ํ…์Šค์ฒ˜๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    // _BaseMap(URP/HDRP) ๊ณผ _MainTex(Built-in RP) ๋ฅผ ๋ชจ๋‘ ์„ค์ •ํ•ด
    // ๋ Œ๋” ํŒŒ์ดํ”„๋ผ์ธ์— ๊ด€๊ณ„์—†์ด ๋™์ž‘ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    void ApplyPropertyBlockTexture()
    {
        if (targetRenderer == null || rt == null) return;
        if (mpb == null) mpb = new MaterialPropertyBlock();

        targetRenderer.GetPropertyBlock(mpb);  // ๊ธฐ์กด MPB ๊ฐ’์„ ์œ ์ง€ํ•œ ์ฑ„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        mpb.SetTexture(ID_BaseMap, rt);
        mpb.SetTexture(ID_MainTex, rt);
        targetRenderer.SetPropertyBlock(mpb);
    }

    // ๋ Œ๋”๋Ÿฌ์—์„œ ํ…์Šค์ฒ˜ ์ฐธ์กฐ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
    // SetTexture(..., null) ์€ Unity ๋ฒ„์ „์— ๋”ฐ๋ผ ๊ฒฝ๊ณ /์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ
    // mpb.Clear() ๋กœ ๋ธ”๋ก ์ „์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    void ClearPropertyBlockTexture()
    {
        if (targetRenderer == null) return;
        if (mpb == null) mpb = new MaterialPropertyBlock();

        mpb.Clear();
        targetRenderer.SetPropertyBlock(mpb);
    }


    // ================================================================
    // GPU ๋ฆฌ์†Œ์Šค ํ•ด์ œ
    // ================================================================

    // [ExecuteAlways] ํ™˜๊ฒฝ์—์„œ๋Š” Destroy() ๋Œ€์‹  DestroyImmediate() ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ
    // ์—๋””ํ„ฐ ๋ชจ๋“œ์—์„œ ์ฆ‰์‹œ ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค. (Destroy ๋Š” ํ”Œ๋ ˆ์ด ๋ชจ๋“œ์—์„œ๋งŒ ๋™์ž‘)
    void ReleaseRT()
    {
        if (rt == null) return;
        rt.Release();          // GPU ๋ฉ”๋ชจ๋ฆฌ ๋ฐ˜ํ™˜ (ํ…์Šค์ณ ํ•ด์ œ)
        DestroyImmediate(rt);  // ๋ Œ๋” ํ…์Šค์ณ ์ฆ‰์‹œ ์‚ญ์ œ
        rt = null;						 // ์ฐธ์กฐ ์ดˆ๊ธฐํ™”	
    }

    // ๋ชจ๋“  GPU ๋ฆฌ์†Œ์Šค์™€ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
    // kernel ์„ -1 ๋กœ ๋ฆฌ์…‹ํ•ด ๋‹ค์Œ EnsureInit() ํ˜ธ์ถœ ์‹œ ์žฌํƒ์ƒ‰ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    void ReleaseAll()
    {
        ReleaseRT();					 // ๋ Œ๋” ํ…์Šค์ณ ํ•ด์ œ
        kernel = -1;				   // ์ปค๋„ ID ์ดˆ๊ธฐํ™”
        ready  = false;			   // ์ค€๋น„ ์™„๋ฃŒ ์ƒํƒœ ์ดˆ๊ธฐํ™”
    }
}

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

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