TECHARTNOMAD | MAZELINE.TECH

TECH.ART.FLOW.IO

[번역] UE5 코드 한 줄도 안 고치고 카툰 렌더링 외곽선 구현하기

jplee 2026. 5. 4. 16:19

역자의 말: UE 5.1에서 추가된 Overlay Material 은 표면적으로는 "메쉬를 한 번 더 그려주는" 가벼운 기능으로 보이지만, 이 글을 끝까지 따라가다 보면 그 사소한 장치가 사실 MeshBatch 복제 + MeshIdInPrimitive 오프셋이라는 아주 단순한 패턴이고, 바로 그 단순함 덕분에 배열로 확장해 외곽선·빙결·석화·황금 같은 이펙트를 셰이더 베리언트 폭발 없이 그대로 올릴 수 있다는 점을 깨달게 된다. 제목 그대로 "코드 한 줄 안 고치고" 끝낼 수도 있고, 필요하면 엔진 멤버를 직접 고치거나 Scene Proxy를 서브클래싱해서 더 확장 할 수도 있다.


저자: litmin
카툰 렌더링을 쓰는 게임에서는 보통 캐릭터를 한 번 더 그린 다음, 정면(front face)을 컬링하고 정점을 법선 방향으로 밀어내는 방식으로 외곽선(描边 / outline)을 구현한다. 근데 언리얼 엔진의 이전 버전에서는 MeshPass를 직접 추가해야만 이 기능을 만들 수 있었다. 그러다가 공식적으로 5.1에서 드디어 Overlay Material 기능을 추가해주어서, 이런 식의 요구사항을 손쉽게 처리할 수 있게 됐다:

구현 원리는 MeshBatch를 하나 더 생성해내고, 그 MeshBatch를 OverlayMaterial로 렌더링하는 것이다:
코드(StaticMeshRender.cpp)를 코드 블록으로 옮기면 다음과 같다:

// StaticMeshRender.cpp
if (OverlayMaterial != nullptr)
{
	FMeshBatch OverlayMeshBatch(BaseMeshBatch);
	OverlayMeshBatch.bOverlayMaterial = true;
	OverlayMeshBatch.CastShadow = false;
	OverlayMeshBatch.bSelectable = false;
	OverlayMeshBatch.MaterialRenderProxy = OverlayMaterial->GetRenderProxy();
	// make sure overlay is always rendered on top of base mesh
	OverlayMeshBatch.MeshIdInPrimitive += LODModel.Sections.Num();
	PDI->DrawMesh(OverlayMeshBatch, FLT_MAX);
}

 

더보기

BaseMeshBatch는 이미 GetMeshElement로 채워진 해당 LOD·섹션의 베이스 메시 드로우 정보(지오메트리, 버텍스 팩토리, 인덱스, 깊이 그룹, 기존 MeshIdInPrimitive 등)를 담고 있다.

FMeshBatch OverlayMeshBatch(BaseMeshBatch)로 복사한 뒤, 오버레이 전용으로 몇 가지만 바꾼다.

  • bOverlayMaterial = true
  • 이 패스가 “위에 덧씌우는” 오버레이 머티리얼용이라는 뜻으로 셰이딩/정렬 쪽에서 베이스와 구분하는 데 쓰인다.
  • CastShadow = false
  • 오버레이는 보통 추가 시각 효과용이라 그림자를 또 드리우지 않게 한다.
  • bSelectable = false
  • 에디터에서 이 패스가 별도로 피킹/선택 대상이 되지 않게 한다 (베이스 메시 선택과 섞이지 않도록).
  • MaterialRenderProxy = OverlayMaterial->GetRenderProxy()
  • 그리는 머티리얼만 OverlayMaterial로 바꾼다. 지오메트리는 베이스와 동일하다.
  • MeshIdInPrimitive += LODModel.Sections.Num()
  • 주석대로, 같은 프리미티브 안에서 메시 ID 구간을 한 블록(섹션 개수만큼) 밀어서, 베이스 섹션들의 ID와 겹치지 않게 한다. 정렬·선택 등에서 “이건 오버레이 패스”라고 구분하기 위한 오프셋이다.
  • PDI->DrawMesh(OverlayMeshBatch, FLT_MAX)
  • 정적 경로에서 컬 거리를 사실상 무한대로 두어, 이 분기에서는 거리로 오버레이를 끄지 않고 항상 같은 컬 정책으로 그린다 (FLT_MAX = 멀리 가도 잘리지 않게).

눈여겨볼 만한 점은 이 MeshBatch에 몇 가지 파라미터가 고정으로 설정된다는 것이다. 예를 들어 그림자를 투사하지 않는다. 이건 꽤 합리적이다 — 같은 메쉬가 그림자를 두 번 드리워야 할 이유는 없으니까. 또 MeshIdInPrimitive 값이 원본 MeshBatch보다 크게 잡혀 있어서, 먼저 Base Material로 그린 다음에 Overlay Material로 다시 그리는 순서가 된다.
이제 외곽선용 머티리얼을 하나 만들어서, WorldPositionOffset으로 정점 오프셋을 주고, 이렇게 정면을 컬링해주면 끝이다 (모바일이라면 차라리 엔진을 고쳐서 Culling Front을 구현하는 편이 낫다. 그전에는 성능 영향이 꽤 크다):

메쉬의 OverlayMaterial 슬롯에 드래그해서 끼워주기만 하면 끝:

MeshBatch 레이어에서 추가된 것이기 때문에, 이 외곽선 Draw Call들도 렌더 상태(render state) 순서대로 정렬되어 한 묶음으로 렌더링된다:

UE4로 포팅하는 것도 별로 어렵지 않다. 고쳐야 할 코드가 정말 별로 없다.

확장

공식 구현은 딱 한 번만 더 그릴 수 있는 구조지만, OverlayMaterial을 아예 배열로 확장해 버리면 외곽선을 그리는 동시에 캐릭터에 여러 머티리얼 이펙트를 같이 올릴 수 있다. 예를 들어 빙결(冰冻), 석화(石化) 같은 이펙트, 또는 아래처럼 파티클을 뿌린 듯 번짝거리는 황금 이펙트까지 만들 수 있다:

이런 이펙트들을 캐릭터 머티리얼 안에서 직접 구현하는 것에 비하면, 셰이더 베리언트(变体)가 잔뜩 늘어나지 않는다는 게 큰 장점이다.


원문
(70 封私信 / 30 条消息) UE5 不改一行代码实现卡通渲染描边 - 知乎