๐ŸซงArt_015 Noise Field Sphere

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

Unity GenArt

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

0. ํด๋” ๊ตฌ์กฐ

ํŽธ์˜๋ฅผ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด Assets ์•„๋ž˜์— Scripts ํด๋”์™€ Shaders ํด๋”๋ฅผ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
Scirpts ํด๋”๋Š” ํ•„์š”์— ๋”ฐ๋ผ ํ•˜์œ„ ํด๋”๋ฅผ ๋งŒ๋“ค์–ด์„œ C# ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋„ฃ์–ด ๋†“์Šต๋‹ˆ๋‹ค.

Assets
 โ”œโ”€ Scripts
 โ”‚   โ””โ”€ Artworks
 โ”‚       โ””โ”€ Art_015_NoiseFieldSphere.cs
 โ”‚
 โ”œโ”€ Shaders
 โ”‚   โ””โ”€ GPUInstancedParticles.shader

1. GameObject ์ƒ์„ฑ

Hierarchy > Create Empty (Ctrl + Shift + N)

์ด๋ ‡๊ฒŒ ๋งŒ๋“  GameObject์˜ ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ด๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์— C# ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ€์ฐฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์˜ ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด

GameObject ์ˆ˜ = 1
โ†“
Sphere Mesh ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉ
โ†“
Graphics.DrawMeshInstanced()
โ†“
GPU์—์„œ ์ˆ˜์ฒœ ๊ฐœ ์ธ์Šคํ„ด์Šค ๋ Œ๋”๋ง

์ด๋ ‡๊ฒŒ ์•„ํŠธ์›Œํฌ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹๋‹ค.

2. Shader ํŒŒ์ผ ์ƒ์„ฑ

์œ„์—์„œ ๋งŒ๋“  Shaders ํด๋”๋กœ ์ด๋™
Project Window > Create > Shader > URP Unlit Shader

์ƒˆ๋กœ์šด ์…ฐ์ด๋” ํŒŒ์ผ์ด NewUnlitUniversalRenderPipelineShader๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.
์ด๋ฆ„์€ ์•„ํŠธ์›Œํฌ์— ์–ด์šธ๋ฆฌ๊ฒŒ GPUInstancedParticles๋กœ ๋ฐ”๊พธ์–ด ์ค๋‹ˆ๋‹ค. ์ดํ›„ ์ด ํŒŒ์ผ์€ C# ์Šคํฌ๋ฆฝํŠธ์—์„œ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์œผ๋กœ ์ž‘์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ์ง€์šฐ๊ณ  ์•„๋ž˜์˜ ์…ฐ์ด๋” ์Šคํฌ๋ฆฝํŠธ๋กœ ๊ต์ฒดํ•ฉ๋‹ˆ๋‹ค.

3. Script ์ž‘์„ฑ

๐Ÿ“„Art_015_NoiseFieldSphere.cs

using UnityEngine;

[ExecuteAlways]
public class Art_015_NoiseFieldSphere : MonoBehaviour
{
    [Header("Particle Count")]
    public int particleCount = 100000;

    [Header("Field Radius")]
    public float radius = 15f;

    [Header("Motion")]
    public float flowScale = 0.35f;
    public float flowStrength = 6f;
    public float curlStrength = 2f;
    public float motionSpeed = 0.3f;

    [Header("Particle Size")]
    public float sizeMin = 0.05f;
    public float sizeMax = 0.25f;

    [Header("Color Field")]
    public Gradient gradient;

    public Mesh mesh;
    public Material material;

    ComputeBuffer particleBuffer;
    ComputeBuffer argsBuffer;

    struct Particle
    {
        public Vector3 pos;
        public float size;
        public Vector4 color;
    }

    Particle[] particles;

    uint[] args = new uint[5];

    bool initialized = false;

    void OnEnable()
    {
        Initialize();
    }

    void OnDisable()
    {
        Release();
    }

    void Initialize()
    {
        if (initialized) return;

        CreateGradient();
        CreateMesh();
        CreateMaterial();

        particles = new Particle[particleCount];

        for (int i = 0; i < particleCount; i++)
        {
            Vector3 p = Random.insideUnitSphere * radius;

            float size = Random.Range(sizeMin, sizeMax);

            float t = p.magnitude / radius;
            Color col = gradient.Evaluate(t);

            particles[i].pos = p;
            particles[i].size = size;
            particles[i].color = col;
        }

        particleBuffer = new ComputeBuffer(
            particleCount,
            sizeof(float) * 8
        );

        particleBuffer.SetData(particles);

        args[0] = mesh.GetIndexCount(0);
        args[1] = (uint)particleCount;
        args[2] = mesh.GetIndexStart(0);
        args[3] = mesh.GetBaseVertex(0);

        argsBuffer = new ComputeBuffer(
            1,
            args.Length * sizeof(uint),
            ComputeBufferType.IndirectArguments
        );

        argsBuffer.SetData(args);

        material.SetBuffer("_Particles", particleBuffer);

        initialized = true;
    }

    void CreateGradient()
    {
        if (gradient != null) return;

        gradient = new Gradient();

        GradientColorKey[] ck = new GradientColorKey[3];

        ck[0] = new GradientColorKey(
            new Color(0.2f,0.3f,1f),
            0f
        );

        ck[1] = new GradientColorKey(
            new Color(0f,1f,0.8f),
            0.5f
        );

        ck[2] = new GradientColorKey(
            new Color(1f,0.2f,1f),
            1f
        );

        GradientAlphaKey[] ak = new GradientAlphaKey[2];

        ak[0] = new GradientAlphaKey(1,0);
        ak[1] = new GradientAlphaKey(1,1);

        gradient.SetKeys(ck,ak);
    }

    void CreateMesh()
    {
        if (mesh != null) return;

        GameObject g =
            GameObject.CreatePrimitive(
                PrimitiveType.Sphere
            );

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

        DestroyImmediate(g);
    }

    void CreateMaterial()
    {
        if (material != null) return;

        Shader shader =
            Shader.Find("Custom/GPUInstancedParticles");

        material = new Material(shader);
        material.enableInstancing = true;
    }

    void Update()
    {
        if (!initialized) return;

        if (!Application.isPlaying)
        {
            Render();
            return;
        }

        AnimateParticles();

        particleBuffer.SetData(particles);

        Render();
    }

    void AnimateParticles()
    {
        float t = Time.time * motionSpeed;

        for (int i = 0; i < particleCount; i++)
        {
            Vector3 p = particles[i].pos;

            Vector3 flow =
                FlowNoise(
                    p * flowScale +
                    Vector3.one * t
                );

            Vector3 curl =
                CurlNoise(
                    p * flowScale +
                    Vector3.one * t
                );

            p += flow * flowStrength * Time.deltaTime;
            p += curl * curlStrength * Time.deltaTime;

            if (p.magnitude > radius)
                p = p.normalized * radius;

            particles[i].pos = p;

            float d = p.magnitude / radius;

            particles[i].color =
                gradient.Evaluate(d);
        }
    }

    Vector3 FlowNoise(Vector3 p)
    {
        float nx = Mathf.PerlinNoise(p.y,p.z);
        float ny = Mathf.PerlinNoise(p.z,p.x);
        float nz = Mathf.PerlinNoise(p.x,p.y);

        return new Vector3(nx,ny,nz) - Vector3.one*0.5f;
    }

    Vector3 CurlNoise(Vector3 p)
    {
        float e = 0.1f;

        float nx1 = Mathf.PerlinNoise(p.y,p.z+e);
        float nx2 = Mathf.PerlinNoise(p.y,p.z-e);

        float ny1 = Mathf.PerlinNoise(p.z,p.x+e);
        float ny2 = Mathf.PerlinNoise(p.z,p.x-e);

        float nz1 = Mathf.PerlinNoise(p.x,p.y+e);
        float nz2 = Mathf.PerlinNoise(p.x,p.y-e);

        float x = ny1-ny2 - (nz1-nz2);
        float y = nz1-nz2 - (nx1-nx2);
        float z = nx1-nx2 - (ny1-ny2);

        return new Vector3(x,y,z);
    }

    void Render()
    {
        Graphics.DrawMeshInstancedIndirect(
            mesh,
            0,
            material,
            new Bounds(
                Vector3.zero,
                Vector3.one * radius * 4
            ),
            argsBuffer
        );
    }

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

        if (argsBuffer != null)
            argsBuffer.Release();
    }
}

๐Ÿ“„GPUInstancedParticles.shader

Shader "Custom/GPUInstancedParticles"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing

            #include "UnityCG.cginc"

            struct Particle
            {
                float3 pos;
                float size;
                float4 color;
            };

            StructuredBuffer<Particle> _Particles;

            struct appdata
            {
                float3 vertex : POSITION;
                uint instanceID : SV_InstanceID;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 col : COLOR;
            };

            v2f vert(appdata v)
            {
                v2f o;

                Particle p =
                    _Particles[v.instanceID];

                float3 world =
                    v.vertex * p.size + p.pos;

                o.pos =
                    UnityObjectToClipPos(
                        float4(world,1)
                    );

                o.col = p.color;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return i.col;
            }

            ENDCG
        }
    }
}

4. Post Processing

ํŒŒํ‹ฐํด์ด ์ƒ์„ฑ๋œ Scene์— Bloom, Color Adjustment, Depth of Field ๋“ฑ์„ ์ ์šฉํ•˜์—ฌ ๊พธ๋ฐ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ Œ๋”๋ง์ด ๋๋‚œ ํ›„ ํ™”๋ฉด์— ํ›„์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด๊ณ  ์ฝ”๋“œ๋กœ๋„ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅํ•˜๋‚˜ GUI์—์„œ ์กฐ์ •ํ•˜๋ฉฐ ๋ฐ”๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋Š” ํŽธ์ด ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.
๋จผ์ € Global Volume์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Hierarchy > ์šฐํด๋ฆญ > Volume > Global Volume

Inspector ์ฐฝ์—์„œ ๊ฐ์ข… ์ˆ˜์น˜ ์กฐ์ •์„ ํ•˜๋ฉฐ, ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉด Add Override์„ ๋ˆŒ๋Ÿฌ Post-processing ํ•ญ๋ชฉ ์ค‘์—์„œ ๊ณ ๋ฅด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ ์ด Volume ์„ค์ •์€ Camera์˜ Inspector์—์„œ Rendering > Post Processing์ด ์ฒดํฌ๋˜์–ด์•ผ์ง€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.


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

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