๐ŸซงArt_019 Spiral Vortex [GPU Instancing]

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

Unity GenArt

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

์„ค๋ช…

  • ๋‚˜์„ ํ˜•์„ ๊ทธ๋ฆฌ๋ฉฐ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€๋Š” ํŒŒํ‹ฐํด
  • ์ดˆ๊ธฐ ์ƒ‰๊ณผ ๋งˆ์ง€๋ง‰ ์ƒ‰ ์‚ฌ์ด๋ฅผ Lerp๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ shader ํŒŒ์ผ ์ž‘์„ฑํ•ด ์—ฐ๊ฒฐ
    • Shader.Find("Custom/SpiralVortexLit") : ์กฐ๋ช… ์˜ํ–ฅ ๋ฐ›๋Š” ์…ฐ์ด๋”
    • Shader.Find("Custom/SpiralVortexUnlit") : ์กฐ๋ช… ์˜ํ–ฅ ๋ฐ›์ง€ ์•Š๋Š” ์…ฐ์ด๋”

์ƒ‰์ƒ Lerp๋กœ ์ „์ฒด ๊ทธ๋ผ๋ฐ์ด์…˜ ๋งŒ๋“ค ๋•Œ

GPU Instancing์œผ๋กœ ํŒŒํ‹ฐํด์„ ๋งŒ๋“ค ๋•Œ URP/Lit ์…ฐ์ด๋”๋Š” DrawMeshInstanced + MaterialPropertyBlock์˜ _BaseColor๋ฅผ per-instance๋กœ ์ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ์ƒํƒœ๋Š” ์ „์ฒด์— ๋™์ผํ•œ ์ƒ‰์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ผ๋ฐ์ด์…˜์œผ๋กœ ๋ฎ์œผ๋ ค๋ฉด UNITY_INSTANCING_BUFFER๋ฅผ ์ง์ ‘ ์„ ์–ธํ•œ ์ปค์Šคํ…€ ์…ฐ์ด๋”๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. csํŒŒ์ผ๊ณผ shader ๋‘ ํŒŒ์ผ์„ ๊ฐ™์€ ํด๋”์— ๋‘๊ณ  cs์—์„œ ์ปค์Šคํ…€ ์…ฐ์ด๋”๋ฅผ ์ฝ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๋ฐฉ์‹per-instance ์ƒ‰์ƒ ๋™์ž‘
URP/Lit + MaterialPropertyBlockโŒ ๋‚ด๋ถ€์ ์œผ๋กœ SRP Batcher๊ฐ€ ๊ฐœ์ž…ํ•ด instanced prop ๋ฌด์‹œ
์ปค์Šคํ…€ ์…ฐ์ด๋” + UNITY_INSTANCING_BUFFERโœ… #pragma multi_compile_instancing์œผ๋กœ ๊ฐ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๋ฒ„ํผ ์ ‘๊ทผ

Unlit vs Lit ์ฐจ์ด :

์ด๋ ‡๊ฒŒ ์ปค์Šคํ…€ ์…ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ Unlit์„ ์„ ํƒํ•˜๋ฉด ๋ผ์ดํŒ… ์—ฐ์‚ฐ์ด ์ „ํ˜€ ์—†์–ด์„œ ์กฐ๋ช…๊ณผ ์™„์ „ํžˆ ๋ฌด๊ด€ํ•œ flat color๋กœ๋งŒ ๋ Œ๋”๋ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉUnlitLit
์กฐ๋ช… ์˜ํ–ฅโŒโœ…
์Œ์˜ (Diffuse / Specular)โŒโœ…
๊ทธ๋ฆผ์ž ์ˆ˜์‹ โŒโœ… (ShadowCaster Pass ํ•„์š”)
Per-instance Colorโœ… (์ง์ ‘ ๊ตฌํ˜„)โŒ (SRP Batcher ๊ฐ„์„ญ)
ํผํฌ๋จผ์Šค๊ฐ€๋ฒผ์›€์ƒ๋Œ€์ ์œผ๋กœ ๋ฌด๊ฑฐ์›€

์กฐ๋ช…์„ ๋ฐ›์œผ๋ ค๋ฉด ์…ฐ์ด๋”์— ๋ผ์ดํŒ… ์—ฐ์‚ฐ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•ต์‹ฌ์€ ๋‘ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

  • ๋ฒ„ํ…์Šค์—์„œ ์›”๋“œ ๋…ธ๋ฉ€ ์ „๋‹ฌ
  • ํ”„๋ž˜๊ทธ๋จผํŠธ์—์„œ GetMainLight()๋กœ ๋ฉ”์ธ ๋ผ์ดํŠธ๋ฅผ ๋ฐ›์•„ LambertDiffuseLambert Diffuse ๊ณ„์‚ฐ

Code

๐Ÿ“’ SpiralVortexGPUInstancing.cs

using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class SpiralVortexGPUInstancing : MonoBehaviour
{
    enum RenderPipeline { BuiltIn, URP, HDRP }

    struct InstanceData
    {
        public float age;
        public float lifetime;
        public float baseAngle;
        public float radiusOffset;
        public float riseSpeed;
        public float size;
        public float noiseSeed;
    }

    // =========================================================
    [Header("Structure")]
    [SerializeField] int particleCount = 2000;
    [SerializeField] float spiralRadius = 2.0f;
    [SerializeField] float radiusJitter = 0.6f;
    [SerializeField] float maxHeight = 8.0f;
    [SerializeField] float riseSpeedMin = 1.0f;
    [SerializeField] float riseSpeedMax = 2.0f;
    [SerializeField] float twistSpeed = 3.0f;
    [SerializeField] int randomSeed = 1234;

    [Header("Radius Growth")]
    [SerializeField, Range(0.1f, 4f),
     Tooltip("๋ฐ˜๊ฒฝ ์„ฑ์žฅ ๊ณก์„ . 1=์„ ํ˜• / >1=์•„๋ž˜์ชฝ์—์„œ ์ข๊ฒŒ ์œ ์ง€, ์œ„๋กœ ๊ฐˆ์ˆ˜๋ก ๋ฒŒ์–ด์ง / <1=๋น ๋ฅด๊ฒŒ ํŽผ์ณ์ง")]
    float radiusGrowthCurve = 1.8f;

    [Header("Rise Speed Curve")]
    [SerializeField, Range(0.1f, 3f),
     Tooltip("์ƒ์Šน ์†๋„ ๊ณก์„ . <1=๋น ๋ฅด๊ฒŒ ์ถœ๋ฐœ ์ ์  ๊ฐ์† / 1=์„ ํ˜• / >1=์ฒœ์ฒœํžˆ ์ถœ๋ฐœ ์ ์  ๊ฐ€์†")]
    float riseCurve = 0.4f;

    [Header("Scale")]
    [SerializeField] float minSize = 0.05f;
    [SerializeField] float maxSize = 0.18f;

    [Header("Color")]
    [SerializeField, Tooltip("ํŒŒํ‹ฐํด ์ƒ์„ฑ ์งํ›„ ์ƒ‰์ƒ (ํ•˜๋‹จ)")]
    Color birthColor = new Color(0.2f, 0.8f, 1.0f, 1.0f);
    [SerializeField, Tooltip("ํŒŒํ‹ฐํด ์†Œ๋ฉธ ์ง์ „ ์ƒ‰์ƒ (์ƒ๋‹จ)")]
    Color deathColor = new Color(1.0f, 0.2f, 0.8f, 1.0f);
    [SerializeField] float emissionIntensity = 1.5f;

    [Header("Noise")]
    [SerializeField] float noiseStrength = 0.35f;
    [SerializeField] float noiseScale = 0.45f;
    [SerializeField] float noiseScrollSpeed = 0.7f;
    [SerializeField] float verticalNoiseAmount = 0.25f;

    [Header("Rendering")]
    [SerializeField] Mesh instanceMesh;
    [SerializeField] Material instanceMaterial;
    // =========================================================

    const int BATCH_SIZE = 1023;

    readonly List<InstanceData> _instances = new List<InstanceData>();
    Matrix4x4[] _matrices;
    Vector4[] _colors;
    Vector4[] _emissions;

    bool _generated;
    float _simTime;
    RenderPipeline _pipeline;

    // --- Structure cache ---
    int _cachedParticleCount;
    float _cachedSpiralRadius, _cachedRadiusJitter, _cachedMaxHeight;
    float _cachedRiseSpeedMin, _cachedRiseSpeedMax, _cachedTwistSpeed;
    float _cachedMinSize, _cachedMaxSize;
    int _cachedRandomSeed;

#if UNITY_EDITOR
    bool _generateQueued;
#endif

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

    void OnValidate()
    {
        particleCount = Mathf.Max(1, particleCount);
        spiralRadius = Mathf.Max(0f, spiralRadius);
        radiusJitter = Mathf.Max(0f, radiusJitter);
        maxHeight = Mathf.Max(0.01f, maxHeight);
        riseSpeedMin = Mathf.Max(0.01f, riseSpeedMin);
        riseSpeedMax = Mathf.Max(riseSpeedMin, riseSpeedMax);
        minSize = Mathf.Max(0.001f, minSize);
        maxSize = Mathf.Max(minSize, maxSize);
        noiseStrength = Mathf.Max(0f, noiseStrength);
        noiseScale = Mathf.Max(0.001f, noiseScale);
        noiseScrollSpeed = Mathf.Max(0f, noiseScrollSpeed);
        verticalNoiseAmount = Mathf.Max(0f, verticalNoiseAmount);

#if UNITY_EDITOR
        _pipeline = DetectPipeline();
        EnsureResources();

        if (StructureParamsChanged())
        {
            CacheStructureParams();
            QueueGenerate();
        }
        else
        {
            Animate(Application.isPlaying ? _simTime : 0f);
        }
#endif
    }

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

        if (!Application.isPlaying)
        {
            Animate(0f);
            RenderInstances();
            return;
        }

        float dt = Time.deltaTime;
        _simTime += dt;

        StepSimulation(dt);
        Animate(_simTime);
        RenderInstances();
    }

    // =========================================================
    // ๊ธฐ๋ณธ ๋ฉ”์‹œ๋ฅผ Cube๋กœ ์ƒ์„ฑ. ๋จธํ‹ฐ๋ฆฌ์–ผ์€ ํŒŒ์ดํ”„๋ผ์ธ์— ๋งž์ถฐ ์ž๋™ ์„ ํƒ.
    void EnsureResources()
    {
        if (instanceMesh == null)
        {
            GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Cube);
            instanceMesh = temp.GetComponent<MeshFilter>().sharedMesh;
            DestroyImmediate(temp);
        }

        if (instanceMaterial == null)
        {
            // ์ปค์Šคํ…€ ์…ฐ์ด๋” ์šฐ์„ , ์—†์œผ๋ฉด URP Unlit ํด๋ฐฑ
            Shader shader =
                Shader.Find("Custom/SpiralVortexLit") ??
                Shader.Find("Universal Render Pipeline/Unlit") ??
                Shader.Find("Standard");

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

    // ํŒŒํ‹ฐํด ๋ฐฐ์—ด ์ดˆ๊ธฐํ™” ๋ฐ ๋žœ๋ค ๋ฐฐ์น˜
    void Generate()
    {
        _instances.Clear();
        _generated = false;
        _simTime = 0f;

        Random.InitState(randomSeed);

        for (int i = 0; i < particleCount; i++)
            _instances.Add(CreateParticle(true));

        _matrices = new Matrix4x4[_instances.Count];
        _colors = new Vector4[_instances.Count];
        _emissions = new Vector4[_instances.Count];

        _generated = true;
        Animate(0f);
    }

    InstanceData CreateParticle(bool randomizeAge)
    {
        float riseSpeed = Random.Range(riseSpeedMin, riseSpeedMax);
        float lifetime = maxHeight / riseSpeed;
        float age = randomizeAge ? Random.Range(0f, lifetime) : 0f;

        return new InstanceData
        {
            age = age,
            lifetime = lifetime,
            baseAngle = Random.Range(0f, Mathf.PI * 2f),
            radiusOffset = Random.Range(-radiusJitter, radiusJitter),
            riseSpeed = riseSpeed,
            size = Random.Range(minSize, maxSize),
            noiseSeed = Random.Range(0f, 1000f)
        };
    }

    void StepSimulation(float dt)
    {
        for (int i = 0; i < _instances.Count; i++)
        {
            InstanceData inst = _instances[i];
            inst.age += dt;
            if (inst.age >= inst.lifetime)
                inst = CreateParticle(false);
            _instances[i] = inst;
        }
    }

    // =========================================================
    // ๋งค ํ”„๋ ˆ์ž„ ๋ชจ๋“  ํŒŒํ‹ฐํด์˜ ์œ„์น˜ / ํšŒ์ „ / ์ƒ‰์ƒ ๊ณ„์‚ฐ
    void Animate(float t)
    {
        if (_matrices == null || _colors == null || _emissions == null) return;

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

            float life01 = Mathf.Clamp01(inst.age / inst.lifetime);

            // riseCurve < 1 โ†’ ์•„๋ž˜์—์„œ ๋น ๋ฅด๊ฒŒ ์ถœ๋ฐœ, ์œ„๋กœ ๊ฐˆ์ˆ˜๋ก ๊ฐ์†
            float curvedLife = Mathf.Pow(life01, riseCurve);
            float yBase = curvedLife * maxHeight;

            // --- ๋…ธ์ด์ฆˆ ---
            float noiseTime = t * noiseScrollSpeed;
            float n1 = Mathf.PerlinNoise(inst.noiseSeed, yBase * noiseScale + noiseTime);
            float n2 = Mathf.PerlinNoise(inst.noiseSeed + 31.7f, yBase * noiseScale + noiseTime);
            float sn1 = (n1 - 0.5f) * 2f;
            float sn2 = (n2 - 0.5f) * 2f;

            // ๋ฐ˜๊ฒฝ: ํ•˜๋‹จ์—์„œ 0 โ†’ ์ƒ๋‹จ์—์„œ spiralRadius ๊นŒ์ง€ ์„ฑ์žฅ (radiusGrowthCurve๋กœ ๊ณก์„  ์ œ์–ด)
            float angle = inst.baseAngle + inst.age * twistSpeed + sn1 * noiseStrength;
            float radius = Mathf.Pow(life01, radiusGrowthCurve) * spiralRadius
                           + inst.radiusOffset + sn2 * noiseStrength;
            radius = Mathf.Max(0.01f, radius);

            float y = yBase + sn1 * verticalNoiseAmount * noiseStrength;

            Vector3 localPos = new Vector3(Mathf.Cos(angle) * radius, y, Mathf.Sin(angle) * radius);
            Vector3 worldPos = transform.TransformPoint(localPos);
            Quaternion worldRot = transform.rotation * Quaternion.Euler(0f, -Mathf.Rad2Deg * angle, 0f);

            _matrices[i] = Matrix4x4.TRS(worldPos, worldRot, Vector3.one * inst.size);

            // ์ƒ‰์ƒ์€ Y์ถ• ๋†’์ด์— ๋”ฐ๋ผ birthColor โ†’ deathColor ๊ทธ๋ผ๋ฐ์ด์…˜
            float heightT = Mathf.Clamp01(y / maxHeight);
            Color albedo = Color.Lerp(birthColor, deathColor, heightT);
            Color emission = albedo * emissionIntensity;

            _colors[i] = new Vector4(albedo.r, albedo.g, albedo.b, albedo.a);
            _emissions[i] = new Vector4(emission.r, emission.g, emission.b, 1f);
        }
    }

    // =========================================================
    // 1023๊ฐœ ๋‹จ์œ„ ๋ฐฐ์น˜๋กœ DrawMeshInstanced ํ˜ธ์ถœ
    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);

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

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

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

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

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

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

    string GetColorPropertyName() => _pipeline switch
    {
        RenderPipeline.URP => "_BaseColor",
        RenderPipeline.HDRP => "_BaseColor",
        _ => "_Color"
    };

    string GetEmissionPropertyName() => _pipeline switch
    {
        RenderPipeline.HDRP => "_EmissiveColor",
        _ => "_EmissionColor"
    };

    // =========================================================
    bool StructureParamsChanged() =>
        _cachedParticleCount != particleCount ||
        !Mathf.Approximately(_cachedSpiralRadius, spiralRadius) ||
        !Mathf.Approximately(_cachedRadiusJitter, radiusJitter) ||
        !Mathf.Approximately(_cachedMaxHeight, maxHeight) ||
        !Mathf.Approximately(_cachedRiseSpeedMin, riseSpeedMin) ||
        !Mathf.Approximately(_cachedRiseSpeedMax, riseSpeedMax) ||
        !Mathf.Approximately(_cachedTwistSpeed, twistSpeed) ||
        !Mathf.Approximately(_cachedMinSize, minSize) ||
        !Mathf.Approximately(_cachedMaxSize, maxSize) ||
        _cachedRandomSeed != randomSeed;

    void CacheStructureParams()
    {
        _cachedParticleCount = particleCount;
        _cachedSpiralRadius = spiralRadius;
        _cachedRadiusJitter = radiusJitter;
        _cachedMaxHeight = maxHeight;
        _cachedRiseSpeedMin = riseSpeedMin;
        _cachedRiseSpeedMax = riseSpeedMax;
        _cachedTwistSpeed = twistSpeed;
        _cachedMinSize = minSize;
        _cachedMaxSize = maxSize;
        _cachedRandomSeed = randomSeed;
    }

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

๐Ÿ“’ SpiralVortexUnlit.shader

Shader "Custom/SpiralVortexUnlit"
{
    Properties
    {
        [PerRendererData] _BaseColor ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags
        {
            "RenderType"      = "Opaque"
            "Queue"           = "Geometry"
            "RenderPipeline"  = "UniversalPipeline"
        }
        Cull Back
        ZWrite On

        Pass
        {
            Name "UniversalForward"
            Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM
            #pragma vertex   vert
            #pragma fragment frag
            #pragma multi_compile_instancing

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            // per-instance color ๋ฒ„ํผ
            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
            UNITY_INSTANCING_BUFFER_END(Props)

            struct Attributes
            {
                float4 positionOS : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                UNITY_SETUP_INSTANCE_ID(IN);
                UNITY_TRANSFER_INSTANCE_ID(IN, OUT);
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(IN);
                return half4(UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor));
            }
            ENDHLSL
        }
    }
}

๐Ÿ“’ SpiralVortexLit.shader

Shader "Custom/SpiralVortexLit"
{
    Properties
    {
        [PerRendererData] _BaseColor ("Color", Color) = (1,1,1,1)
        _Smoothness ("Smoothness", Range(0,1)) = 0.3
    }
    SubShader
    {
        Tags
        {
            "RenderType"     = "Opaque"
            "Queue"          = "Geometry"
            "RenderPipeline" = "UniversalPipeline"
        }
        Cull Back
        ZWrite On

        // --- ๋ฉ”์ธ ์กฐ๋ช… + ์Œ์˜ ํŒจ์Šค ---
        Pass
        {
            Name "UniversalForward"
            Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM
            #pragma vertex   vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _SHADOWS_SOFT

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
            UNITY_INSTANCING_BUFFER_END(Props)

            CBUFFER_START(UnityPerMaterial)
                float _Smoothness;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS   : NORMAL;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4 positionCS  : SV_POSITION;
                float3 normalWS    : TEXCOORD0;
                float3 positionWS  : TEXCOORD1;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                UNITY_SETUP_INSTANCE_ID(IN);
                UNITY_TRANSFER_INSTANCE_ID(IN, OUT);

                VertexPositionInputs posInputs = GetVertexPositionInputs(IN.positionOS.xyz);
                VertexNormalInputs   nrmInputs = GetVertexNormalInputs(IN.normalOS);

                OUT.positionCS = posInputs.positionCS;
                OUT.positionWS = posInputs.positionWS;
                OUT.normalWS   = nrmInputs.normalWS;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(IN);

                float4 baseColor = UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor);

                // ๋ฉ”์ธ ๋ผ์ดํŠธ
                float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);
                Light  mainLight   = GetMainLight(shadowCoord);

                float3 normalWS  = normalize(IN.normalWS);
                float  NdotL     = saturate(dot(normalWS, mainLight.direction));

                // Lambert diffuse + ambient
                float3 ambient   = SampleSH(normalWS) * baseColor.rgb;
                float3 diffuse   = mainLight.color * mainLight.shadowAttenuation * NdotL * baseColor.rgb;

                return half4(ambient + diffuse, baseColor.a);
            }
            ENDHLSL
        }

        // --- ๊ทธ๋ฆผ์ž ์บ์Šคํ„ฐ ํŒจ์Šค ---
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On
            ZTest LEqual
            ColorMask 0

            HLSLPROGRAM
            #pragma vertex   vertShadow
            #pragma fragment fragShadow
            #pragma multi_compile_instancing

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

            struct AttributesShadow
            {
                float4 positionOS : POSITION;
                float3 normalOS   : NORMAL;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VaryingsShadow
            {
                float4 positionCS : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            VaryingsShadow vertShadow(AttributesShadow IN)
            {
                VaryingsShadow OUT;
                UNITY_SETUP_INSTANCE_ID(IN);
                UNITY_TRANSFER_INSTANCE_ID(IN, OUT);

                float3 posWS  = TransformObjectToWorld(IN.positionOS.xyz);
                float3 nrmWS  = TransformObjectToWorldNormal(IN.normalOS);
                float4 posCS  = TransformWorldToHClip(ApplyShadowBias(posWS, nrmWS, _LightDirection));

                // ๋’ค์ง‘ํž˜ ๋ฐฉ์ง€
                #if UNITY_REVERSED_Z
                    posCS.z = min(posCS.z, posCS.w * UNITY_NEAR_CLIP_VALUE);
                #else
                    posCS.z = max(posCS.z, posCS.w * UNITY_NEAR_CLIP_VALUE);
                #endif

                OUT.positionCS = posCS;
                return OUT;
            }

            half4 fragShadow(VaryingsShadow IN) : SV_Target { return 0; }
            ENDHLSL
        }
    }
}

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

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