

GameObject์ BoilerplateGPUInstancing.cs ํ๋๋ง ๋ถ์ ๋๋ค.
Hierarchy โโโ GameObject (๋น ์ค๋ธ์ ํธ) โโโ BoilerplateGPUInstancing โ ์ด๊ฒ๋ง Add Component
GPUInstanceRenderer์ GenArtResources๋ MonoBehaviour๊ฐ ์๋ ์ผ๋ฐ C# ํด๋์ค์ ๋๋ค. Unity ์ปดํฌ๋ํธ ์์คํ ๋ฐ๊นฅ์ ์์ผ๋ฏ๋ก Inspector์ ๋ถ์ผ ์ ์๊ณ , ๋ถ์ผ ํ์๋ ์์ต๋๋ค.
BoilerplateGPUInstancing โ MonoBehaviour โ GameObject์ ๋ถ๋ ๊ฒ โ ์์ GenArtBase โ MonoBehaviour โ ์ง์ ๋ถ์ด์ง ์์ (์ถ์ ํด๋์ค) โ ์์ (new) GPUInstanceRenderer โ ์ผ๋ฐ ํด๋์ค โ ๋ถ์ด๋ ๊ฐ๋ ์์ฒด๊ฐ ์์ โ ํธ์ถ (static) GenArtResources โ static ํด๋์ค โ ๋ถ์ด๋ ๊ฐ๋ ์์ฒด๊ฐ ์์
GenArtBase๋ MonoBehaviour์ด๊ธด ํ์ง๋ง abstract์ด๊ธฐ ๋๋ฌธ์ Inspector์ Add Component ๋ชฉ๋ก์ ์์ ๋ํ๋์ง ์์ต๋๋ค. BoilerplateGPUInstancing์ ๋ถ์ด๋ ์๊ฐ GenArtBase์ OnEnable, Update ๋ฑ์ด ์๋์ผ๋ก ํจ๊ป ๋์ํฉ๋๋ค.
Assets/ โโโ BoilerplateGPUInstancing.cs โโโ GenArtBase.cs โโโ GenArtResources.cs โโโ GPUInstanceRenderer.cs โโโ GPUInstancingLit.shader
C#์ ๊ฐ์ ์ด์
๋ธ๋ฆฌ(Assembly) ์์ ์์ผ๋ฉด ์๋์ผ๋ก ์๋ก๋ฅผ ์ธ์ํฉ๋๋ค.
Unity ํ๋ก์ ํธ์์ Assets/ ํด๋ ์์ ๋ชจ๋ .cs ํ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก Assembly-CSharp ๋ผ๋ ํ๋์ ์ด์
๋ธ๋ฆฌ๋ก ์ปดํ์ผ๋ฉ๋๋ค. ๊ฐ์ ์ด์
๋ธ๋ฆฌ ์์ด๋ฉด ๋ณ๋์ import ๊ฐ๋
์์ด ํด๋์ค ์ด๋ฆ๋ง์ผ๋ก ๋ฐ๋ก ์ฐธ์กฐํ ์ ์์ต๋๋ค.
๋๋ก์ฐ ์ฝ ์ ๋ด ๊ฐ์ฒด๋ Unity ์ด๋ฒคํธ๊ฐ ํ์ ์์ต๋๋ค. GenArtBase๊ฐ new GPUInstanceRenderer(mesh, mat)๋ก ์์ ํ๋ ํํ๊ฐ ์ธ์คํํฐ ๋ ธ์ถ ์์ด ๊น๋ํฉ๋๋ค. Inspector์์ mesh/material์ด ๊ต์ฒด๋ ๋ EnsureResources()์์ ์๋์ผ๋ก ์ฌ๊ตฌ์ฑ๋ฉ๋๋ค.
waveSpeed ๊ฐ์ ์ค์ผ์ผ ํ๋ผ๋ฏธํฐ๋ ์๋ธํด๋์ค ๊ณ ์ ๊ฐ์ ๋๋ค. ๋ฒ ์ด์ค๊ฐ ์ ์ ์์ผ๋ฏ๋ก Time.time์ ๋ ๊ฒ์ผ๋ก ์ ๋ฌํ๊ณ Animate() ๋ด๋ถ์์ t * waveSpeed๋ก ์ฒ๋ฆฌํฉ๋๋ค.
BoilerplateGPUInstancing.cs๋ฅผ ๋ณต์ฌ โ ํด๋์ค๋ช ๋ณ๊ฒฝ โ Generate() ๋ฐฐ์น ๊ณต์, Animate() ์์, ์ธ์คํํฐ ํ๋ผ๋ฏธํฐ, ์บ์ 4๊ณณ๋ง ์์ ํ๋ฉด ๋ฉ๋๋ค. GenArtBase, GPUInstanceRenderer, GenArtResources๋ ์๋ ํ์๊ฐ ์์ต๋๋ค.
using UnityEngine;
using System.Collections.Generic;
// ============================================================
// ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ํธ์ํฌ โ GenArtBase ์๋ธํด๋์ค
//
// ์์ ํด์ผ ํ ๊ณณ์ ๋ ๊ตฐ๋ฐ๋ฟ์
๋๋ค.
// Generate() : ์ธ์คํด์ค๋ฅผ ๋ช ๊ฐ, ์ด๋ค ํจํด์ผ๋ก ๋ฐฐ์นํ ๊ฒ์ธ๊ฐ
// Animate(t) : ๋งค ํ๋ ์ ์์นยท์์์ ์ด๋ค ์์์ผ๋ก ๊ณ์ฐํ ๊ฒ์ธ๊ฐ
//
// ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ(์ฌ์์ฑ์ด ํ์ํ ๊ฒ) vs ์๊ฐ ํ๋ผ๋ฏธํฐ(์ค์๊ฐ ๋ฐ์)๋ฅผ
// ๊ตฌ๋ถํด์ ์ธ์คํํฐ ํค๋๋ฅผ ๋๋๊ณ ,
// StructureParamsChanged() / CacheStructureParams()๋ฅผ ํจ๊ป ๊ด๋ฆฌํฉ๋๋ค.
// ============================================================
public class BoilerplateGPUInstancing : GenArtBase
{
// ============================================================
// ์ธ์คํํฐ ํ๋ผ๋ฏธํฐ
// ============================================================
// ----- ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ (๋ณ๊ฒฝ ์ Generate ์ฌ์คํ) ---------------
[Header("๊ตฌ์กฐ ์ค์ (๋ณ๊ฒฝ ์ ์ค๋ธ์ ํธ ์ฌ์์ฑ)")]
public int gridSize = 100;
public float spacing = 0.38f;
public float cubeSize = 0.30f;
public int colorSeed = 0;
// ----- ์๊ฐ ํ๋ผ๋ฏธํฐ (๋ณ๊ฒฝ ์ Animate(0f)๋ง ์ฌ์คํ) ----------
[Header("์ ๋๋ฉ์ด์
์ค์ (์ค์๊ฐ ๋ฐ์)")]
public float waveHeight = 0.60f;
public float waveSpeed = 1.2f;
public float waveFrequency = 0.6f;
[Header("์์ ์ค์ (์ค์๊ฐ ๋ฐ์)")]
[Range(0f, 1f)] public float hueBase = 0.58f;
[Range(0f, 1f)] public float hueRange = 0.04f;
[Range(0f, 1f)] public float saturation = 0.80f;
public float emissionIntensity = 1.0f;
// ============================================================
// ์ธ์คํด์ค ๋ฐ์ดํฐ
// Generate์์ ํ ๋ฒ๋ง ๊ณ์ฐ๋๋ ์ ์ ๋ฐ์ดํฐ์
๋๋ค.
// Animate์์ ๋งค ํ๋ ์ ์ฐธ์กฐํ์ง๋ง ๊ฐ์ ๋ณ๊ฒฝํ์ง ์์ต๋๋ค.
// ============================================================
struct InstanceData
{
public Vector3 basePos; // ๊ธฐ์ค ์์น (Y = 0 ํ๋ฉด)
public float hue; // ๊ณ ์ ์์
public float brightness; // ๊ธฐ๋ณธ ๋ฐ๊ธฐ
}
readonly List<InstanceData> _instances = new List<InstanceData>();
// ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ์บ์ โ OnValidate์์ ๋ณ๊ฒฝ ๊ฐ์ง์ ์ฌ์ฉํฉ๋๋ค.
int _cachedGridSize;
float _cachedSpacing;
float _cachedCubeSize;
int _cachedColorSeed;
// ============================================================
// Generate โ [์ํธ์ํฌ ๋ก์ง] ๋ฐฐ์น ๊ณต์์ ์ฌ๊ธฐ์ ์์ ํฉ๋๋ค.
// ============================================================
protected override void Generate()
{
_instances.Clear();
_generated = false;
Random.InitState(colorSeed); // ๊ฐ์ ์๋ = ํญ์ ๋์ผํ ์์ ๋ฐฐ์น
// ๊ทธ๋ฆฌ๋ ์ค์ฌ์ ๋ก์ปฌ ์์ (0,0,0)์ ๋ง์ถฅ๋๋ค.
float offset = (gridSize - 1) * spacing * 0.5f;
for (int x = 0; x < gridSize; x++)
{
for (int z = 0; z < gridSize; z++)
{
_instances.Add(new InstanceData
{
basePos = new Vector3(x * spacing - offset, 0f, z * spacing - offset),
hue = (hueBase + Random.value * hueRange) % 1f,
brightness = Random.Range(0.5f, 1.0f)
});
}
}
// GPU ์ ์ก ๋ฐฐ์ด์ ์ธ์คํด์ค ์์ ๋ง๊ฒ ์ฌ์ ํ ๋นํฉ๋๋ค.
int count = _instances.Count;
_matrices = new Matrix4x4[count];
_colors = new Vector4[count];
_emissions = new Vector4[count];
_generated = true;
Animate(0f); // ์์ฑ ์งํ ์ด๊ธฐ ์ํ ๊ณ์ฐ
}
// ============================================================
// Animate โ [์ํธ์ํฌ ๋ก์ง] ์ ๋๋ฉ์ด์
์์์ ์ฌ๊ธฐ์ ์์ ํฉ๋๋ค.
// t : GenArtBase.Update()์์ Time.time์ ์ ๋ฌํฉ๋๋ค.
// ์๋ํฐ ์ฌ ๋ทฐ / ์ด๊ธฐํ ์์๋ 0f๊ฐ ์ ๋ฌ๋ฉ๋๋ค.
// ============================================================
protected override void Animate(float t)
{
if (!_generated) return;
float animT = t * waveSpeed; // ์๊ฐ ์ค์ผ์ผ ์ ์ฉ
for (int i = 0; i < _instances.Count; i++)
{
InstanceData inst = _instances[i];
// X + Z ์ขํ ํฉ์ผ๋ก ๋๊ฐ์ ๋ฐฉํฅ ์ฌ์ธํ๋ฅผ ๋ง๋ญ๋๋ค.
float wave = Mathf.Sin(
(inst.basePos.x + inst.basePos.z) * waveFrequency + animT
) * waveHeight;
// -1~+1 ๋ฒ์๋ฅผ 0~1๋ก ์ ๊ทํํฉ๋๋ค. ์์ยท๋ฐ๊ธฐ ๋ณ์กฐ์ ์ฌ์ฉํฉ๋๋ค.
float normalized = (wave / waveHeight + 1f) * 0.5f;
// ๋ก์ปฌ โ ์๋ ์ขํ ๋ณํ (๋ถ๋ชจ GameObject์ ์์นยทํ์ ๋ฐ์)
Vector3 worldPos = transform.TransformPoint(
new Vector3(inst.basePos.x, wave, inst.basePos.z)
);
_matrices[i] = Matrix4x4.TRS(
worldPos,
transform.rotation,
Vector3.one * cubeSize
);
float animBrightness = inst.brightness * Mathf.Lerp(0.2f, 1.0f, normalized);
Color albedo = Color.HSVToRGB(inst.hue, saturation, animBrightness);
Color emission = Color.HSVToRGB(inst.hue, saturation, normalized) * emissionIntensity;
_colors[i] = new Vector4(albedo.r, albedo.g, albedo.b, 1f);
_emissions[i] = new Vector4(emission.r, emission.g, emission.b, 1f);
}
}
// ============================================================
// ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ์บ์ โ ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐ/์ ๊ฑฐํ๋ฉด ์ฌ๊ธฐ๋ ํจ๊ป ์์ ํฉ๋๋ค.
// ============================================================
protected override bool StructureParamsChanged()
{
return gridSize != _cachedGridSize
|| spacing != _cachedSpacing
|| cubeSize != _cachedCubeSize
|| colorSeed != _cachedColorSeed;
}
protected override void CacheStructureParams()
{
_cachedGridSize = gridSize;
_cachedSpacing = spacing;
_cachedCubeSize = cubeSize;
_cachedColorSeed = colorSeed;
}
}
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
// ============================================================
// ์์ฑ ์ํธ ๋ฒ ์ด์ค ํด๋์ค
// Unity ๋ผ์ดํ์ฌ์ดํด(OnEnable / OnValidate / Update)๊ณผ
// ์๋ํฐ ์์ ํจํด(QueueGenerate)์ ์ ๋ดํฉ๋๋ค.
//
// ์๋ธํด๋์ค๋ 4๊ฐ์ abstract ๋ฉ์๋๋ง ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
// Generate() โ ๋ฐฐ์น ๊ณต์
// Animate(float t) โ ์ ๋๋ฉ์ด์
๊ณต์
// StructureParamsChanged() โ ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ ๊ฐ์ง
// CacheStructureParams() โ ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ์บ์ ์ ์ฅ
// ============================================================
[ExecuteAlways]
public abstract class GenArtBase : MonoBehaviour
{
[Header("๋ ๋ ๋ฆฌ์์ค (๋น์๋๋ฉด ์๋ ์์ฑ)")]
public Mesh instanceMesh;
public Material instanceMaterial;
// ์๋ธํด๋์ค์์ Animate()๊ฐ ์ฑ์ฐ๊ณ RenderInstances()๊ฐ ์ฝ์ต๋๋ค.
protected Matrix4x4[] _matrices;
protected Vector4[] _colors;
protected Vector4[] _emissions;
// false์ด๋ฉด Update์์ ๋ ๋๋ง์ ๊ฑด๋๋๋๋ค.
protected bool _generated;
GPUInstanceRenderer _renderer;
#if UNITY_EDITOR
bool _generateQueued;
#endif
// ============================================================
// Unity ์ด๋ฒคํธ โ ์๋ธํด๋์ค์์ override ๋ถํ์
// ============================================================
void OnEnable()
{
EnsureResources();
Generate();
CacheStructureParams();
}
void OnValidate()
{
#if UNITY_EDITOR
EnsureResources();
if (StructureParamsChanged())
{
// ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ โ ์ฌ์์ฑ ์์ฝ
CacheStructureParams();
QueueGenerate();
}
else
{
// ์๊ฐ ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ โ ์ฌ์์ฑ ์์ด ์ฆ์ ๊ฐฑ์
Animate(0f);
}
#endif
}
void Update()
{
if (!_generated || _renderer == null) return;
if (!Application.isPlaying)
{
// ์ฌ ๋ทฐ: ์ ์ง ์ํ๋ก ๋ ๋๋งํฉ๋๋ค.
Animate(0f);
_renderer.Render(_matrices, _colors, _emissions, _matrices.Length);
return;
}
// Play ๋ชจ๋: ์๊ฐ ๊ธฐ๋ฐ ์ ๋๋ฉ์ด์
Animate(Time.time);
_renderer.Render(_matrices, _colors, _emissions, _matrices.Length);
}
// ============================================================
// ๋ฆฌ์์ค ์ด๊ธฐํ
// ============================================================
// ๋ฉ์ยท๋จธํฐ๋ฆฌ์ผ์ด ์์ผ๋ฉด ์๋ ์์ฑํ๊ณ renderer๋ฅผ (์ฌ)๊ตฌ์ฑํฉ๋๋ค.
void EnsureResources()
{
if (instanceMesh == null) instanceMesh = GenArtResources.CreateDefaultMesh();
if (instanceMaterial == null) instanceMaterial = GenArtResources.CreateDefaultMaterial();
// ์ธ์คํํฐ์์ mesh/material์ด ๊ต์ฒด๋ ๊ฒฝ์ฐ๋ฅผ ์ํด ํญ์ ์ฌ๊ตฌ์ฑํฉ๋๋ค.
_renderer = new GPUInstanceRenderer(instanceMesh, instanceMaterial);
}
// ============================================================
// ์๋ํฐ ์์ Generate ์์ฝ
// ============================================================
// OnValidate ์ปจํ
์คํธ์์ ์ง์ ์ฌ์ ์์ ํ๋ฉด Unity ์ง๋ ฌํ์ ์ถฉ๋ํฉ๋๋ค.
// delayCall๋ก ์๋ํฐ ๋ฃจํ ๋ค์ ํฑ์ ์์ ํ๊ฒ ์คํํฉ๋๋ค.
#if UNITY_EDITOR
void QueueGenerate()
{
if (_generateQueued) return;
_generateQueued = true;
EditorApplication.delayCall += () =>
{
_generateQueued = false;
if (this == null) return; // ์ค๋ธ์ ํธ๊ฐ ์ญ์ ๋ ๊ฒฝ์ฐ ๋ฐฉ์ด
Generate();
};
}
#endif
// ============================================================
// ์๋ธํด๋์ค ๊ตฌํ ์๊ตฌ โ ์ํธ์ํฌ ๋ก์ง
// ============================================================
// ์ธ์คํด์ค ๋ฐฐ์น๋ฅผ ๊ณ์ฐํ๊ณ _matrices, _colors, _emissions ๋ฐฐ์ด์ ๊ตฌ์ฑํฉ๋๋ค.
// ๋ง์ง๋ง์ _generated = true ๋ฅผ ์ธํ
ํ๊ณ Animate(0f)๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค.
protected abstract void Generate();
// ๋งค ํ๋ ์(๋๋ t=0 ์ ์ง ์ํ) ๊ธฐ์ค์ผ๋ก _matrices, _colors, _emissions๋ฅผ ๊ฐฑ์ ํฉ๋๋ค.
// t ๊ฐ: Update์์๋ Time.time, ์๋ํฐ/์ด๊ธฐํ์์๋ 0f
protected abstract void Animate(float t);
// ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ(์ค๋ธ์ ํธ ์ยท๋ฐฐ์น ํํ๋ฅผ ๊ฒฐ์ ํ๋ ํ๋ผ๋ฏธํฐ)๊ฐ ๋ฐ๋์์ผ๋ฉด true๋ฅผ ๋ฐํํฉ๋๋ค.
protected abstract bool StructureParamsChanged();
// ํ์ฌ ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ๊ฐ์ ์บ์์ ์ ์ฅํฉ๋๋ค.
protected abstract void CacheStructureParams();
}
using UnityEngine;
using UnityEngine.Rendering;
// ============================================================
// ๋ฉ์ยท๋จธํฐ๋ฆฌ์ผ ์๋ ์์ฑ ์ ํธ๋ฆฌํฐ
// Shader.Find() ๋์ ์ฌ์ ๊ธฐ์กด Renderer ๋๋ GraphicsSettings์์
// ์
ฐ์ด๋๋ฅผ ์ถ์ถํฉ๋๋ค. Shader.Find()๋ Always Included Shaders์
// ๋ฑ๋ก๋์ง ์์ ์
ฐ์ด๋๋ฅผ ๋ฐํ์์ ์ฐพ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
// ============================================================
public static class GenArtResources
{
// ์์ Primitive์์ ํ๋ธ ๋ฉ์๋ฅผ ์ถ์ถ ํ ์ฆ์ ์ญ์ ํฉ๋๋ค.
// ์ฌ ๊ณ์ธต(Hierarchy)์ ์๋ฅ ์ค๋ธ์ ํธ๋ฅผ ๋จ๊ธฐ์ง ์์ต๋๋ค.
public static Mesh CreateDefaultMesh()
{
GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Cube);
Mesh mesh = temp.GetComponent<MeshFilter>().sharedMesh;
Object.DestroyImmediate(temp);
return mesh;
}
public static Material CreateDefaultMaterial()
{
Shader shader = ResolveShader();
if (shader == null)
{
Debug.LogError("[GenArtResources] ์ ํจํ ์
ฐ์ด๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. " +
"instanceMaterial์ ์ธ์คํํฐ์์ ์ง์ ํ ๋นํ์ธ์.");
return null;
}
Debug.Log($"[GenArtResources] ์ฌ์ฉ ์
ฐ์ด๋: {shader.name}");
Material mat = new Material(shader);
mat.enableInstancing = true;
mat.EnableKeyword("_EMISSION");
mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
return mat;
}
// ============================================================
// ์
ฐ์ด๋ ํด๊ฒฐ ์์
// 1์์: ์ปค์คํ
์ธ์คํด์ฑ ์
ฐ์ด๋ (SRP Batcher ๊ฐ์ญ ์์)
// 2์์: Shader.Find() ํด๋ฐฑ
// ============================================================
static Shader ResolveShader()
{
// 1์์: UNITY_INSTANCING_BUFFER ๊ธฐ๋ฐ ์ปค์คํ
์
ฐ์ด๋
// SRP Batcher๊ฐ ํ์ฑํ๋ URP์์ per-instance ์์์ ์ ํํ ์ ๋ฌํฉ๋๋ค.
Shader custom = Shader.Find("Custom/GPUInstancingLit");
if (custom != null) return custom;
// 2์์: ํด๋ฐฑ (์ปค์คํ
์
ฐ์ด๋ ํ์ผ์ด ์๋ ๊ฒฝ์ฐ)
Debug.LogWarning("[GenArtResources] Custom/GPUInstancingLit ์
ฐ์ด๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. " +
"GPUInstancingLit.shader๋ฅผ Assets ํด๋์ ์ถ๊ฐํ์ธ์.");
return Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard");
}
}
using UnityEngine;
// ============================================================
// GPU Instancing ๋๋ก์ฐ ์ฝ ์ ๋ด ํด๋์ค
// MonoBehaviour๊ฐ ์๋ ์์ C# ํด๋์ค์
๋๋ค.
// GenArtBase๊ฐ ์์ ํ๋ฉฐ, ์ํธ์ํฌ ํด๋์ค์์ ์ง์ ๊ฑด๋๋ฆฌ์ง ์์ต๋๋ค.
// ============================================================
public class GPUInstanceRenderer
{
const int BATCH_SIZE = 1023;
readonly Mesh _mesh;
readonly Material _material;
public GPUInstanceRenderer(Mesh mesh, Material material)
{
_mesh = mesh;
_material = material;
}
// matrices / colors / emissions ๋ฐฐ์ด์ 1023๊ฐ ๋จ์ ๋ฐฐ์น๋ก ๋๋ GPU์ ์ ์กํฉ๋๋ค.
// count๋ ๋ฐฐ์ด์ ์ค์ ์ฌ์ฉ ๊ธธ์ด์
๋๋ค (๋ฐฐ์ด ํฌ๊ธฐ์ ๋ค๋ฅผ ์ ์์).
public void Render(Matrix4x4[] matrices, Vector4[] colors, Vector4[] emissions, int count)
{
if (_mesh == null || _material == null || count == 0) return;
int index = 0;
int remaining = count;
while (remaining > 0)
{
int batch = Mathf.Min(BATCH_SIZE, remaining);
// ๋ฐฐ์น ํฌ๊ธฐ๋งํผ ์ฌ๋ผ์ด์ค ๋ณต์ฌํฉ๋๋ค.
Matrix4x4[] batchMatrices = new Matrix4x4[batch];
Vector4[] batchColors = new Vector4[batch];
Vector4[] batchEmissions = new Vector4[batch];
System.Array.Copy(matrices, index, batchMatrices, 0, batch);
System.Array.Copy(colors, index, batchColors, 0, batch);
System.Array.Copy(emissions, index, batchEmissions, 0, batch);
// ๋ฐฐ์น๋ง๋ค new๋ก ์์ฑํด์ผ ์ด์ ๋ฐฐ์น ๋ฐ์ดํฐ์ ๊ฐ์ญ์ด ์์ต๋๋ค.
MaterialPropertyBlock block = new MaterialPropertyBlock();
block.SetVectorArray("_BaseColor", batchColors);
block.SetVectorArray("_EmissionColor", batchEmissions);
Graphics.DrawMeshInstanced(
_mesh, 0, _material,
batchMatrices, batch, block
);
index += batch;
remaining -= batch;
}
}
}
Shader "Custom/GPUInstancingLit"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1)
_EmissionColor ("Emission Color", Color) = (0,0,0,1)
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Geometry"
}
Pass
{
Name "ForwardLit"
Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// GPU Instancing ํ์ฑํ
#pragma multi_compile_instancing
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// --------------------------------------------------------
// ์ธ์คํด์ค๋ณ ํ๋กํผํฐ ๋ฒํผ
// UNITY_INSTANCING_BUFFER ์์ ์ ์ธ๋ ๋ณ์๋
// ์ธ์คํด์ค๋ง๋ค ๋ค๋ฅธ ๊ฐ์ GPU์์ ์ง์ ์ฝ์ต๋๋ค.
// MaterialPropertyBlock.SetVectorArray()๋ก ์ฑ์ด ๊ฐ์ด ์ฌ๊ธฐ๋ก ๋ค์ด์ต๋๋ค.
// --------------------------------------------------------
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_DEFINE_INSTANCED_PROP(float4, _EmissionColor)
UNITY_INSTANCING_BUFFER_END(Props)
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID // ์ธ์คํด์ค ID ์
๋ ฅ
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float3 normalWS : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID // fragment๋ก ์ ๋ฌ์ฉ
};
Varyings vert(Attributes IN)
{
Varyings OUT;
// ์ธ์คํด์ค ID๋ฅผ ํ์ฌ ์ปจํ
์คํธ์ ์ธํ
ํฉ๋๋ค.
// UNITY_ACCESS_INSTANCED_PROP ์ฌ์ฉ ์ ๋ฐ๋์ ํ์ํฉ๋๋ค.
UNITY_SETUP_INSTANCE_ID(IN);
UNITY_TRANSFER_INSTANCE_ID(IN, OUT);
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS);
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(IN);
// ์ธ์คํด์ค๋ณ ๋ฒํผ์์ ์ด ์ธ์คํด์ค์ ์์์ ์ฝ์ต๋๋ค.
float4 baseColor = UNITY_ACCESS_INSTANCED_PROP(Props, _BaseColor);
float4 emissionColor = UNITY_ACCESS_INSTANCED_PROP(Props, _EmissionColor);
// ๊ฐ๋จํ Lambert diffuse ์กฐ๋ช
Light mainLight = GetMainLight();
float3 normalWS = normalize(IN.normalWS);
float NdotL = saturate(dot(normalWS, mainLight.direction));
float3 diffuse = baseColor.rgb * mainLight.color * NdotL;
// ambient + diffuse + emission
float3 ambient = baseColor.rgb * 0.2;
float3 color = ambient + diffuse + emissionColor.rgb;
return half4(color, baseColor.a);
}
ENDHLSL
}
// ShadowCaster Pass โ ๊ทธ๋ฆผ์ ํฌ์์ ์ํด ํ์ํฉ๋๋ค.
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"
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_DEFINE_INSTANCED_PROP(float4, _EmissionColor)
UNITY_INSTANCING_BUFFER_END(Props)
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings vertShadow(Attributes IN)
{
Varyings OUT;
UNITY_SETUP_INSTANCE_ID(IN);
UNITY_TRANSFER_INSTANCE_ID(IN, OUT);
float3 posWS = TransformObjectToWorld(IN.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(IN.normalOS);
float4 posHCS = TransformWorldToHClip(
ApplyShadowBias(posWS, normalWS, _MainLightPosition.xyz)
);
OUT.positionHCS = posHCS;
return OUT;
}
half4 fragShadow(Varyings IN) : SV_Target
{
return 0;
}
ENDHLSL
}
}
}