[UE5 TIL] Day 49 - Chpt 3. 게임기획 세션, Voxel Procedural Mesh

JungHoon Eum·2026년 5월 4일

키워드

ProceduralMeshComponent Voxel GenerateMesh CreateMeshSection Normal Tangent Unreal Insights OnConstruction Normal Tangent

[코드카타]

문제 링크

문제 요약

  • 문자열 t에서 길이가 p와 같은 부분 문자열을 모두 확인한다.
  • 각 부분 문자열이 숫자로 봤을 때 p보다 작거나 같으면 카운트한다.
  • 단, 부분 문자열과 p는 길이가 같으므로 문자열 비교(<=)를 그대로 사용해도 된다.

내가 제출한 코드

#include <string>

using namespace std;

int solution(string t, string p) {
    int answer = 0;

    for (size_t i = 0; i <= t.size() - p.size(); i++)
    {
        string part = t.substr(i, p.size());
        if (part <= p) answer++;
    }

    return answer;
}

배운 점

  • string::substr(pos, len)pos부터 len개만큼 잘라온다.
  • 이 문제에서는 partp의 길이가 항상 같기 때문에 문자열 비교가 숫자 비교처럼 동작한다.
  • 숫자 문자열의 길이가 다르면 "9" > "10"처럼 문자열 비교 결과가 숫자 비교와 달라질 수 있다.
  • stoll로 숫자 변환을 하지 않아도 되므로 오버플로우 걱정을 줄일 수 있다.

게임 기획: 언리얼 엔진 기반 개발 파이프라인

왜 엔진을 알아야 하는가

  • 구현 가능성 판단
  • 개발자와의 공통 언어 확보.
    • Timeline, Tick, Lerp...
  • 재작업 비용 미리 차단. Datatable, Nanite, NavMesh...
  • 엔진 제공 기능
기능설명기획 연관성
렌더링3D/2D 그래픽 출력그래픽 품질, 화면 연출, 분위기 기획에 영향
물리 엔진충돌, 중력, 시뮬레이션물리 기반 퍼즐, 낙하, 투사체, 탈것 시스템 기획과 연결
애니메이션캐릭터 동작 제어이동, 공격, 피격, 사망 등 캐릭터 상태 표현과 연결
AI경로 탐색, 행동 트리적 행동 패턴, NPC 반응, 전투 난이도 기획과 연결
UI / UMG화면 인터페이스HUD, 메뉴, 체력바, 점수판 등 사용자 정보 전달 담당
DataTable게임 데이터 에셋밸런스 수치, 아이템 정보, 웨이브 데이터 등을 쉽게 수정 가능
블루프린트비주얼 스크립팅기획자가 코드를 몰라도 간단한 로직 수정 가능

오늘 한 일

팀 프로젝트 One, Two... Shoot!에서 포탄에 맞은 지형이 파괴되는 기능을 만들기 위해 복셀 기반 지형 청크를 구현했다.

현재 구조는 AVoxelChunkActorTArray<bool>로 복셀 데이터를 들고 있고, ProceduralMeshComponent로 노출된 면만 메시로 만든다. 청크 크기는 현재 16 * 16 * 16이라 총 4096개의 복셀을 다룬다. 파괴 테스트는 우클릭/디버그 함수로 청크 안의 임의 지점을 잡고, 그 주변 복셀을 false로 바꾼 뒤 메시를 다시 생성하는 방식으로 진행했다.

구현한 핵심 흐름은 다음과 같다.

  1. 복셀 배열 초기화
  2. 각 복셀을 순회하면서 solid 복셀만 확인
  3. 인접 복셀이 비어 있는 방향의 면만 AddFace
  4. 정점, 삼각형, UV, 노멀, 탄젠트 배열 생성
  5. CreateMeshSection_LinearColor로 Procedural Mesh 생성
  6. 파괴 지점 주변 복셀 삭제
  7. 메시 재생성

기억해야 할 것

Procedural Mesh는 "만드는 것"보다 "다시 만드는 것"이 무겁다

처음에는 단순히 복셀 몇 개를 지우는 작업이라고 생각했지만, 실제 비용은 삭제 자체보다 GenerateMesh()CreateMeshSection()에서 발생한다. 복셀을 지울 때마다 전체 청크 메시를 다시 만들면, 청크 크기가 조금만 커져도 우클릭 파괴마다 끊김이 생길 수 있다.

Normal / Tangent 때문에 몇 시간 붙잡힌 이유

오늘 가장 오래 붙잡고 있었던 문제는 복셀 메시가 Lit 머티리얼에서 검게 보이는 문제였다. 처음에는 머티리얼이나 조명 문제라고 생각했는데, 실제 핵심은 Normal, Tangent, 그리고 삼각형 정점 순서였다.

Normal은 면이 어느 방향을 보고 있는지 알려주는 값이다. 언리얼의 Lit 머티리얼은 이 Normal을 기준으로 빛을 계산한다. 그래서 면은 실제로 위를 보고 있는데 Normal이 반대 방향을 가리키면, 빛을 제대로 받지 못해서 검게 보일 수 있다.

문제가 됐던 부분은 사각형 면 하나를 삼각형 두 개로 나누는 순서였다.

0, 1, 2 -> 0, 2, 1
0, 2, 3 -> 0, 3, 2

같은 네 점으로 같은 사각형을 만들더라도 엔진 입장에서는 서로 반대 방향을 보는 면이다. 이 순서를 바꾸면 메시의 앞면/뒷면 판정과 조명 계산 방향이 달라진다. 그래서 어떤 순서에서는 빛을 받는 것처럼 보이고, 어떤 순서에서는 Lit 머티리얼이 검게 보일 수 있다.

Tangent는 표면 위에서 UV의 U 방향이 증가하는 축이다. 처음에는 CalculateTangentsForMesh()를 쓰면 자동으로 해결될 줄 알았다. 하지만 이 함수는 내가 넘긴 Vertices, Triangles, UVs를 기준으로 Normal과 Tangent를 계산한다. 즉, 삼각형 정점 순서나 UV가 이상하면 자동 계산 결과도 같이 이상해진다.

결국 Emissive를 켜면 색이 보이고, Lit에서는 검게 보였던 이유는 메시가 엔진에게 “내 표면은 이 방향을 보고 있다”는 정보를 제대로 전달하지 못했기 때문이었다.

에디터에서 보이게 만들 때 OnConstruction은 조심해야 한다

BeginPlay()에서만 메시를 생성하면 에디터에서 플레이 전에는 복셀이 안 보이는 것이 정상이다. 에디터에서도 보이게 하려면 OnConstruction()에서 메시를 만들 수 있다.

하지만 OnConstruction()은 액터를 움직이거나 값이 바뀔 때도 호출될 수 있다. 여기에 무거운 메시 재생성을 넣으면 BP 액터를 조금 움직일 때마다 렉이 크게 발생한다.

지금 단계에서는 CallInEditor 버튼으로 필요할 때만 재생성하는 방식이 더 안전하다.

UFUNCTION(CallInEditor, Category = "Voxel")
void RebuildChunk();
profile
개발지망생

0개의 댓글