본문 바로가기

Unity 개발 공부

[내배캠] 본캠 69 렌더링공부, 페인트칠 공부

1. 멀티-렌더러 페인팅 이슈

  • 문제 발견
    프로젝트 초기엔 구조물 하나에 PaintableObject 컴포넌트를 달고, 자식에 달린 여러 MeshRenderer를 한 번에 순회하면서 그리도록 구현했다. 그런데 실험해 보니, 총알이 자식 A에만 충돌해 페인트가 찍혔음에도 자식 B, C에도 똑같이 칠해지는 현상이 발생했다.
  • 원인 분석
    1. 머티리얼 인스턴스 공유
      MeshRenderer.material 프로퍼티를 통해 접근하면, Unity는 내부적으로 shared material이 아닌 인스턴스를 만들어 모든 렌더러가 같은 인스턴스를 바라보게 된다.
    2. 커맨드 버퍼 DrawRenderer 순회
      CommandBuffer.DrawRenderer 호출 시도 모두 동일 머티리얼 인스턴스를 참조하므로, “A만 그릴” 페인트 셋업과 “B에도 그려” 셋업이 뒤섞인다.
  • 해결: 개별 인스턴스 분리
    • 이렇게 하면 A에만 속한 Renderer는 각자 고유한 머티리얼을 갖고, B/C와 충돌 없이 독립적으로 칠해진다.
// PaintableSetup.cs (Awake)
var renders = GetComponentsInChildren<Renderer>();
foreach(var r in renders) {
    // 각 렌더러마다 별도 복제(Material.Instantiate)한 머티리얼을 할당
    var mats = r.sharedMaterials;
    for(int i=0; i<mats.Length; i++){
        mats[i] = new Material(paintableMaterial);
        mats[i].SetTexture("_MainTex", originalTextures[i]);
    }
    r.materials = mats;
}

이렇게 하면 A에만 속한 Renderer는 각자 고유한 머티리얼을 갖고, B/C와 충돌 없이 독립적으로 칠해진다.

 

2. CommandBuffer 기반 GPU 페인팅

왜 CommandBuffer인가?
CPU에서 즉시 렌더링 API를 호출(Graphics.DrawMesh, Graphics.Blit 등)하면 GPU 동기화가 반복되어 프레임 드롭이 발생할 수 있다. CommandBuffer에 모든 그리기 명령을 모아뒀다가 한 번에 실행하면, 드라이버·GPU 왕복을 최소화하며 높은 퍼포먼스를 얻을 수 있다.

 

준비과정이다.

var cmd = new CommandBuffer { name = "PaintMask" };
cmd.SetRenderTarget(maskRT);

 

페인트 셋업은 아래와같이할수있따.

// Prepare UV (마스크 초기화)
paintMat.SetFloat(_PrepareUV, 1);
cmd.DrawRenderer(targetRenderer, paintMat, 0 /*패스 인덱스*/);

 

아래처럼

스탬프(페인트 하나)를 찍을수있다.

paintMat.SetFloat(_PrepareUV, 0);
paintMat.SetVector(_PainterPosition, worldPos);
paintMat.SetFloat(_Radius, radius);
cmd.SetRenderTarget(maskRT);
cmd.DrawRenderer(targetRenderer, paintMat, 0);

 

누적은 Blit을 이용해가능하다

// 방금 그린 마스크(maskRT)를 누적(maskTmp)→ 최종 텍스처(resultRT)
cmd.Blit(maskRT, maskTmp);
cmd.Blit(maskTmp, resultRT, extendMat);

 

실행및 클리어는

Graphics.ExecuteCommandBuffer(cmd);
cmd.Clear();

 

정리:

DrawRenderer로 GPU 내부에 물감 찍기 작업을 모아두고, Blit으로 누적·확장 셰이더를 한 번에 처리.

CPU<–>GPU 걸리는 오버헤드를 크게 줄여, 복잡한 씬에서도 매 초당 수백 번 그리기가 가능.

 

 

실제 페인트칠 예제

 

void Paint(Renderer target, Vector3 pos){
    cmd.SetRenderTarget(maskRT);
    paintMat.SetFloat(_PrepareUV, 0);
    paintMat.SetVector(_PainterPosition, pos);
    cmd.DrawRenderer(target, paintMat);
    cmd.Blit(maskRT, maskTmp);
    cmd.Blit(maskTmp, resultRT, extendMat);
    Graphics.ExecuteCommandBuffer(cmd);
    cmd.Clear();
}