지난번 포스팅에서 Compute Shader 기반 잔디 시스템의 기본 데이터 구조(Grass Struct)와 리소스 관리(Init, Dispose)를 살펴보았습니다. 이번 업데이트에서는 CPU 개입을 최소화하고 GPU가 직접 렌더링 명령을 처리하는 인다이렉트 드로우(Indirect Draw) 방식을 도입했습니다.
이 업데이트의 핵심은 다음과 같습니다.
- renderMaterial: 잔디를 실제로 그릴 때 사용할 머티리얼(Material) 추가.
- _renderParams: 렌더링에 필요한 모든 설정(머티리얼 등)을 담는 구조체 추가.
- _commandBuffer: 렌더링 명령어(몇 개의 잔디를 그릴지 등)를 GPU에 저장하는 커맨드 버퍼 추가.
- Tick(): Graphics.RenderPrimitivesIndirect를 사용하여 GPU 기반 렌더링 실행.
GPU 랜더링 기반 마련
GPU 렌더링 관련 변수 추가
// GPUGrass 클래스 내 변수 추가
[SerializeField] private Material renderMaterial; // 잔디 메쉬를 렌더링할 때 사용할 머티리얼
private RenderParams _renderParams;             // 렌더링에 필요한 모든 설정(머티리얼, 스케일 등)을 담는 구조체
private GraphicsBuffer _commandBuffer;          // 렌더링 명령 인자를 담는 GPU 버퍼 (Indirect Draw 핵심)
Init() 함수: 렌더링 파라미터 및 커맨드 버퍼 설정
private void Init()
{
    // ... (ComputeShader 및 GrassBuffer 초기화 로직 생략) ...
    
    // Command Buffer (인 다이렉트 드로우 명령을 담는 버퍼) 초기화
    if (null == _commandBuffer || !_commandBuffer.IsValid()) _commandBuffer = NewCommandBuffer();
    
    // 렌더링 파라미터 구조체 설정: 사용할 머티리얼 지정
    _renderParams = new RenderParams(renderMaterial) { };
    
    // ... (EditorApplication.update 로직 생략) ...
}
// Command Buffer를 생성하는 헬퍼 함수
private GraphicsBuffer NewCommandBuffer() 
    // IndirectArguments 타겟으로, 1개의 DrawArgs 구조체 크기만큼 GPU 버퍼 할당 (4*int 크기)
    => new(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawArgs.size);
_commandBuffer: 이 버퍼는 인스턴스 개수, 인덱스 개수, 시작 인덱스 등 렌더링에 필요한 4개의 int 인자를 담습니다. 이것이 바로 "Indirect (간접)"이라는 이름이 붙은 이유입니다. CPU가 아닌 GPU가 이 버퍼를 채우고, 렌더링 명령은 이 버퍼를 참조하여 실행됩니다.
Tick() 함수: 인다이렉트 렌더링 실행 (Draw Call)
private void Tick()
{
    if (!_compute) return;
    
    // Graphics.RenderPrimitivesIndirect 호출:
    // 1. _renderParams: 사용할 머티리얼 및 설정
    // 2. MeshTopology.Triangles: 그릴 도형의 형태 (삼각형)
    // 3. _commandBuffer: 렌더링 명령(몇 개를 그릴지)이 담긴 GPU 버퍼
    Graphics.RenderPrimitivesIndirect(_renderParams, MeshTopology.Triangles, _commandBuffer);
}
- Graphics.RenderPrimitivesIndirect: 이 함수는 단 하나의 드로우 콜(Draw Call)만 발생시킵니다. 이 드로우 콜은 _commandBuffer에 적힌 인스턴스 개수만큼 잔디를 한 번에 그리도록 GPU에 명령합니다.
- 이로써, 수십만 개의 잔디를 그릴 때 CPU가 수십만 번의 드로우 콜을 걸 필요 없이, 단 한 번의 명령으로 처리가 완료되어 성능이 극대화됩니다.
Dispose() 함수: 리소스 정리 업데이트
private void Dispose()
{
    // ... (ComputeShader 및 GrassBuffer 해제 생략) ...
    _commandBuffer?.Dispose(); // Command Buffer 해제 추가
    
    // ... (EditorApplication.update 로직 생략)
}
_commandBuffer도 GPU 메모리 리소스이므로, 누수를 방지하기 위해 Dispose() 함수에 해제 코드가 추가되었습니다.
다음 챕터
_commandBuffer 내부의 인스턴스 개수(Draw Arguments)는 아직 채워지지 않았습니다.
다음 단계는 Compute Shader를 사용하여 잔디 데이터를 월드에 배치하고, 잔디의 총 개수를 계산한 뒤, 그 결과를 _commandBuffer에 쓰는 로직이 Tick() 함수 내에 추가되어야 합니다. 그래야 Graphics.RenderPrimitivesIndirect가 실제로 몇 개의 잔디를 그려야 하는지 알 수 있습니다.
'공부 일지 > 그래픽스' 카테고리의 다른 글
| GPU로 풀 그리기 #4 (0) | 2025.10.11 | 
|---|---|
| GPU로 풀 그리기 #3 (0) | 2025.10.05 | 
| GPU로 풀 그리기 #1 (0) | 2025.10.05 | 
| 스토카스틱 타일링 쉐이더 노멀 구현 (0) | 2025.10.05 | 
| 스토카스틱 타일링 쉐이더 구현 (0) | 2025.10.05 |