Unity에서 수많은 오브젝트를 효율적으로 렌더링하기 위해 Compute Shader를 활용하는 시스템, GPU 기반 잔디 렌더링 시스템을 구현해보겠다. 게임에서 맵 중 땅에 넓게 심는 풀들을 생각하면 될 것이다.
CPU 초기 전체 코드
using System.Runtime.InteropServices;
using Unity.Mathematics;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
namespace GPUGrass
{
public struct Grass
{
public static readonly int Size = Marshal.SizeOf<Grass>();
public float3 Position;
}
[ExecuteAlways]
public class GPUGrass : MonoBehaviour
{
[SerializeField] private ComputeShader compute;
[SerializeField] private int capacity;
private ComputeShader _compute;
private GraphicsBuffer _grassBuffer;
private void OnValidate() => Init();
private void OnEnable() => Init();
private void OnDisable() => Dispose();
private void Update()
{
if (Application.isPlaying) Tick();
}
#if UNITY_EDITOR
private void UpdateEditor()
{
if (!Application.isPlaying) Tick();
}
#endif
private void Init()
{
if (!compute) return;
_compute = Instantiate(compute);
if (null == _grassBuffer|| !_grassBuffer.IsValid()) _grassBuffer = NewGrassBuffer();
else if (_grassBuffer.count != capacity)
{
_grassBuffer?.Dispose();
_grassBuffer = NewGrassBuffer();
}
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= UpdateEditor;
UnityEditor.EditorApplication.update += UpdateEditor;
#endif
}
private GraphicsBuffer NewGrassBuffer() => new GraphicsBuffer(GraphicsBuffer.Target.Raw, capacity, Grass.Size);
private void Dispose()
{
if (Application.isPlaying) Destroy(_compute);
else DestroyImmediate(compute);
_grassBuffer?.Dispose();
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= UpdateEditor;
#endif
}
private void Tick()
{
if (!_compute) return;
}
}
}
Struct Grass
public struct Grass
{
// C# 코드에서 이 구조체의 메모리 크기를 바이트 단위로 계산 (GPU 버퍼 할당에 필요)
public static readonly int Size = Marshal.SizeOf<Grass>();
// 잔디의 3차원 위치. Unity.Mathematics의 float3 사용.
public float3 Position;
}
- float3 Position: 잔디가 월드 공간에서 어디에 위치하는지를 나타냅니다. 나중에 색상, 바람의 영향, 높이 등의 데이터가 추가될 수 있습니다.
- Marshal.SizeOf<Grass>(): GPU 메모리(버퍼)를 할당할 때, 구조체 하나의 정확한 크기가 필요합니다. 이 코드는 .NET의 Marshal 기능을 사용하여 이를 계산합니다.
리소스 초기화 및 정리 (Init / Dispose)
Init
Init은 OnValidate (에디터 값 변경 시), OnEnable (스크립트 활성화 시)에 호출됩니다.
private void Init()
{
if (!compute) return;
// Compute Shader를 인스턴스화하여 메모리에 로드 (Destroy를 위해 필요)
_compute = Instantiate(compute);
// 잔디 버퍼 생성 또는 갱신 로직
if (null == _grassBuffer || !_grassBuffer.IsValid())
_grassBuffer = NewGrassBuffer();
else if (_grassBuffer.count != capacity) // 용량(capacity)이 변경되면 버퍼를 재생성
{
_grassBuffer?.Dispose();
_grassBuffer = NewGrassBuffer();
}
// Unity Editor에서 Play 모드가 아닐 때도 업데이트를 실행하기 위한 로직
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= UpdateEditor;
UnityEditor.EditorApplication.update += UpdateEditor;
#endif
}
private GraphicsBuffer NewGrassBuffer()
=> new GraphicsBuffer(GraphicsBuffer.Target.Raw, capacity, Grass.Size);
- _compute = Instantiate(compute);: 스크립트 인스턴스마다 독립적인 Compute Shader 인스턴스를 확보하여 GPU 연산에 사용할 준비를 합니다.
- GraphicsBuffer: 잔디 데이터 배열(Grass[])을 CPU에서 GPU로 옮겨 담는 고속 메모리 영역입니다. capacity 변수만큼 Grass 구조체를 저장할 수 있습니다.
Dispose
OnDisable에서 호출되며, 리소스를 사용 후 반드시 해제하여 메모리 누수(Memory Leak)를 방지합니다.
private void Dispose()
{
// Compute Shader 인스턴스 해제
if (Application.isPlaying) Destroy(_compute);
else DestroyImmediate(compute); // 에디터 모드에서는 즉시 해제
// GPU 버퍼 해제 (가장 중요!)
_grassBuffer?.Dispose();
// 에디터 업데이트 이벤트 제거
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= UpdateEditor;
#endif
}
_grassBuffer?.Dispose(): GPU 버퍼는 관리되지 않는(Unmanaged) 리소스이므로, 반드시 명시적으로 Dispose()를 호출해 GPU 메모리에서 해제해야 합니다.
실행 루프 (Tick)
private void Tick()
{
if (!_compute) return;
// TODO: 1. 잔디 데이터 초기화 및 업데이트 (Compute Shader 실행)
// TODO: 2. DrawProceduralIndirect를 사용하여 잔디 그리기 (Graphics.DrawProcedural 등)
}
현재 코드는 Tick 함수 내부가 비어 있지만, 완성 단계에서는 다음과 같은 역할을 수행하게 됩니다.
- Compute Shader Dispatch: 바람이나 LOD(Level of Detail) 등 잔디의 위치/상태를 GPU에서 계산하도록 Compute Shader를 실행합니다.
- 렌더링 호출: Graphics.DrawProceduralIndirect 함수 등을 사용하여, CPU를 거치지 않고 GPU가 직접 버퍼의 잔디 데이터를 읽어와 수천 개의 메쉬를 한 번에 렌더링하도록 명령합니다.
'공부 일지 > 그래픽스' 카테고리의 다른 글
| GPU로 풀 그리기 #3 (0) | 2025.10.05 |
|---|---|
| GPU로 풀 그리기 #2 (0) | 2025.10.05 |
| 스토카스틱 타일링 쉐이더 노멀 구현 (0) | 2025.10.05 |
| 스토카스틱 타일링 쉐이더 구현 (0) | 2025.10.05 |
| OpenGL API [OpenGL로 배우는 3차원 컴퓨터 그래픽스] (0) | 2025.01.20 |