Git LFS로 대용량 3D·VFX 에셋을 안전하게 버전 관리하기
Git LFS의 동작 방식부터 Unity·Unreal 프로젝트에 적용하는 추적 규칙, 협업 절차와 용량 관리까지 정리합니다. 대용량 바이너리 에셋을 일반 Git 히스토리에서 분리해 저장소를 건강하게 유지하는 방법을 다룹니다.

왜 일반 Git만으로는 부족할까
FBX, Blender 파일, 텍스처, EXR 시퀀스, Niagara 시스템처럼 게임 에셋의 상당수는 바이너리 파일이다. 바이너리 파일은 텍스트 소스처럼 줄 단위 변경 사항을 효율적으로 비교하거나 병합하기 어렵다. 파일이 조금만 바뀌어도 Git은 대체로 새 파일 전체에 가까운 데이터를 히스토리에 쌓는다.
그 결과 저장소를 오래 운영할수록 클론과 fetch 시간이 길어지고 개발자가 필요한 코드 변경을 받기 위해 과거의 대용량 에셋까지 내려받는 상황이 생긴다. Git LFS는 이런 파일의 실제 내용은 LFS 저장소에 보관하고 Git 저장소에는 해당 파일을 가리키는 작은 포인터 파일만 기록한다.
flowchart LR
A[아티스트가 대용량 에셋 수정] --> B[git add]
B --> C[Git에는 LFS 포인터 기록]
B --> D[실제 바이너리는 LFS 저장소에 업로드]
C --> E[커밋과 푸시]
D --> E
E --> F[동료가 체크아웃]
F --> G[필요한 LFS 객체 내려받기]
이 구조는 대용량 파일을 작게 만드는 압축 기술이 아니다. 바이너리 데이터의 저장 위치와 전송 방식을 Git의 일반 객체와 분리하는 방식이다. 따라서 LFS를 도입해도 팀의 저장 공간과 전송량 제한은 계속 관리해야 한다.
도입 전 결정할 기준
모든 파일을 LFS로 보내면 관리가 단순해 보이지만 작은 설정 파일까지 LFS에 넣을 이유는 없다. 다음처럼 사람이 검토하거나 병합해야 하는 파일은 일반 Git에 두는 편이 좋다.
- C#, C++, HLSL, 셰이더 그래프의 텍스트 표현 파일
.ini,.json,.yaml,.meta,.uproject,.uplugin같은 설정 파일- 빌드 스크립트와 문서
반대로 크기가 크고 바이너리 병합이 사실상 불가능한 파일은 LFS 후보가 된다.
| 분류 | 대표 확장자 | 권장 |
|---|---|---|
| 3D 모델·원본 씬 | .fbx, .blend, .max, .mb | LFS |
| 고해상도 텍스처·HDRI | .psd, .tif, .tga, .exr, .hdr | LFS |
| 영상·사운드 원본 | .mov, .mp4, .wav | LFS |
| 엔진이 생성한 대형 바이너리 | 프로젝트별 상이 | 필요성을 검토한 뒤 LFS |
| 코드·설정·메타데이터 | .cs, .cpp, .h, .json, .meta | 일반 Git |
엔진이 만드는 캐시나 중간 산출물은 LFS 대상이 아니라 대개 .gitignore 대상이다. Unity의 Library, Unreal Engine의 DerivedDataCache, Intermediate, Saved를 버전 관리에 넣으면 LFS를 사용해도 저장소가 불필요하게 비대해진다.

기본 설정과 추적 규칙
먼저 개발 환경에 Git LFS를 설치한 뒤 저장소에서 초기화한다.
git lfs install
이 명령은 LFS 파일을 체크아웃할 때 실제 바이너리를 받아오도록 Git 훅을 설정한다. 이어서 확장자별 추적 규칙을 등록한다.
git lfs track "*.fbx"
git lfs track "*.blend"
git lfs track "*.psd"
git lfs track "*.tga"
git lfs track "*.exr"
git lfs track "*.wav"
git lfs track은 저장소 루트의 .gitattributes를 수정한다. 이 파일 자체는 작은 텍스트 설정이므로 반드시 일반 Git으로 커밋해야 한다.
git add .gitattributes
git commit -m "chore: track large art assets with Git LFS"
생성된 규칙은 대략 다음과 같다.
*.fbx filter=lfs diff=lfs merge=lfs -text
*.blend filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
새 에셋을 추가하는 절차는 일반 Git과 같다. 단, 추적 규칙이 먼저 커밋되어 있어야 다른 팀원 환경에서도 일관되게 처리된다.
git add Assets/Art/Characters/hero.fbx
git commit -m "art: add hero base mesh"
git push
정상적으로 적용됐는지는 다음 명령으로 확인할 수 있다.
git lfs ls-files
파일이 목록에 나오지 않는다면 .gitattributes 규칙, 파일 확장자 대소문자, 파일을 추가한 시점을 확인한다. 이미 일반 Git으로 추적 중이던 파일에 나중에 규칙을 추가해도 과거 커밋의 파일이 자동으로 LFS로 바뀌지는 않는다.
기존 대용량 파일을 옮길 때의 주의점
이미 저장소 히스토리에 큰 바이너리가 쌓였다면 단순히 git lfs track만 실행해도 저장소의 과거 용량은 줄지 않는다. 과거 커밋을 LFS 포인터로 바꾸려면 히스토리를 재작성해야 한다.
git lfs migrate import --include="*.fbx,*.psd,*.exr" --everything
이 작업은 커밋 ID를 바꾸므로 공유 브랜치에서 무심코 실행하면 팀원의 로컬 브랜치와 원격 브랜치가 어긋난다. 다음 조건을 갖춘 유지보수 시간에만 진행하는 것이 안전하다.
- 대상 브랜치와 확장자 범위를 팀에 공지한다.
- 전체 저장소 백업과 복구 절차를 준비한다.
- 팀원이 진행 중인 작업을 푸시하도록 유도한다.
- 재작성 후 force push 정책과 로컬 저장소 재클론 또는 재동기화 방법을 안내한다.
히스토리 정리가 꼭 필요하지 않다면 새로 추가되는 파일부터 LFS로 관리하는 점진적 도입도 현실적인 선택이다.
Unity와 Unreal Engine에서의 운영 규칙
Unity에서는 Assets 아래의 원본 아트 파일을 LFS로 추적할 수 있다. 다만 .meta 파일은 GUID 참조를 유지하는 중요한 텍스트 파일이므로 LFS에 넣지 말고 일반 Git으로 함께 커밋한다. 에셋 파일과 .meta 파일을 분리해 커밋하면 참조가 깨진 상태를 동료가 받을 수 있으므로 관련 변경은 하나의 커밋에 묶는 것이 좋다.
Unreal Engine은 바이너리 형식의 .uasset, .umap을 많이 사용한다. Git LFS로 저장은 가능하지만 같은 에셋을 두 사람이 동시에 수정하면 자동 병합이 어렵다는 점은 그대로다. 팀 규모가 크고 레벨이나 블루프린트 충돌이 빈번하다면 LFS의 파일 잠금 또는 에셋 분할 규칙을 함께 검토해야 한다.
LFS 잠금은 편집 전에 파일의 소유권을 표시하고 다른 사용자의 수정 충돌을 예방하는 기능이다.
git lfs lock Content/Maps/PrototypeMap.umap
# 에셋 편집, 테스트, 커밋, 푸시
git lfs unlock Content/Maps/PrototypeMap.umap
잠금은 기술적인 충돌 방지 장치이면서 팀 규약이기도 하다. 잠금 대상은 공동 수정이 잦은 맵, 시퀀서, 대형 VFX 패키지처럼 병합 비용이 높은 파일로 제한하는 편이 운영 부담이 적다. 캐릭터별 폴더나 맵의 서브레벨처럼 작업 단위를 나누면 잠금에 의존하는 빈도도 낮출 수 있다.
부분 내려받기로 작업 환경을 가볍게 유지하기
프로그래머나 빌드 서버가 모든 고해상도 원본 에셋을 즉시 필요로 하지 않는 경우도 있다. 이때 LFS 자동 내려받기를 건너뛰고 필요한 경로만 가져올 수 있다.
GIT_LFS_SKIP_SMUDGE=1 git clone https://example.com/game.git
cd game
git lfs pull --include="Assets/Art/Characters/**"
Windows PowerShell에서는 환경 변수를 별도로 설정한다.
$env:GIT_LFS_SKIP_SMUDGE = "1"
git clone https://example.com/game.git
Remove-Item Env:GIT_LFS_SKIP_SMUDGE
이 방식은 디스크와 초기 다운로드 시간을 줄이지만 에디터를 열기 전에 필요한 에셋을 받지 않으면 누락된 파일처럼 보일 수 있다. 역할별로 받아야 하는 디렉터리를 문서화하고 CI 환경에서는 빌드에 필요한 LFS 파일을 명시적으로 내려받도록 구성해야 한다.
git lfs pull
git lfs checkout
자주 발생하는 문제와 점검 순서
포인터 파일이 그대로 보일 때
체크아웃한 파일을 열었는데 다음처럼 짧은 텍스트만 보인다면 LFS 객체가 내려받아지지 않은 상태일 수 있다.
version https://git-lfs.github.com/spec/v1
oid sha256:...
size 12345678
먼저 Git LFS가 설치됐는지 확인하고 해당 저장소에서 LFS 객체를 내려받는다.
git lfs install
git lfs pull
git lfs checkout
원격에는 커밋됐지만 다른 사람이 파일을 못 받을 때
일반 Git 원격과 LFS 저장소의 권한 또는 할당량이 분리되어 있을 수 있다. git push 출력에서 LFS 업로드가 성공했는지 확인하고 사용하는 Git 호스팅 서비스의 LFS 용량·대역폭 제한과 접근 권한을 점검한다. 코드 저장소 접근 권한이 있다고 해서 LFS 객체 접근도 항상 보장되는 것은 아니다.
대용량 파일이 여전히 일반 Git 히스토리에 남을 때
git lfs ls-files에 파일이 표시되더라도 과거 커밋에 큰 Git 객체가 남아 있을 수 있다. 새 파일의 추적 상태와 과거 히스토리 정리는 별개의 문제다. 저장소 용량 자체를 줄여야 한다면 앞서 설명한 git lfs migrate import를 팀 합의 아래 계획적으로 실행한다.
팀에서 지켜야 할 최소 규약
Git LFS는 도구일 뿐이며 실제 충돌 비용은 작업 방식이 결정한다. 다음 규약부터 정하면 대부분의 혼란을 줄일 수 있다.
.gitattributes와.gitignore는 프로젝트 초기에 합의하고 변경 이력을 남긴다.- 원본 파일과 엔진용 결과물 중 무엇을 저장할지 명확히 정한다.
- 맵, VFX, 애니메이션처럼 병합이 어려운 에셋은 담당 범위와 잠금 기준을 정한다.
- 생성 캐시와 개인별 에디터 설정은 커밋하지 않는다.
- CI가 사용하는 체크아웃 방식과 LFS 인증 정보를 문서화한다.
- 정기적으로 LFS 사용량과 오래된 대형 에셋을 점검한다.
핵심은 대용량 파일을 무조건 LFS로 보내는 데 있지 않다. 버전 관리가 필요한 원본만 남기고 생성 가능한 데이터는 제외하며 병합할 수 없는 에셋은 충돌하기 전에 작업 범위를 조정하는 데 있다. 이 원칙 위에서 Git LFS를 적용하면 코드와 아트 에셋을 하나의 변경 이력으로 연결하면서도 저장소의 성장 속도를 통제할 수 있다.



