Blender Python API와 AI로 3D 모델 LOD 0~4 자동 생성·검수하는 파이프라인
Blender Python API의 Decimate Modifier와 AI 기반 메타데이터 분석을 결합해 3D 모델 LOD 0~4를 자동 생성하고 삼각형 수, 실루엣, 노멀, UV를 검수하는 실전 파이프라인을 정리합니다.

핵심 요약
Blender 4.x의 bpy API와 Decimate Modifier를 사용하면 원본 메시에서 LOD 0~4를 일괄 생성할 수 있습니다. AI는 에셋 유형을 분류하고 LOD별 삼각형 예산과 검수 우선순위를 제안하는 역할을 맡기며 최종 승인은 실루엣·노멀·UV·머티리얼 검증을 통과했을 때만 자동 처리합니다.
핵심은 AI에게 메시를 무조건 수정하게 하는 것이 아니라 구조화된 파라미터와 측정값을 주고 결과를 다시 검증하는 폐쇄형 파이프라인입니다.
Blender LOD 0~4 자동화가 필요한 이유
게임에서 하나의 3D 모델을 모든 거리에서 같은 복잡도로 렌더링하면 멀리 있는 물체에도 불필요한 버텍스 처리 비용이 들 수 있습니다. LOD(Level of Detail)는 카메라와 에셋의 거리에 따라 다른 복잡도의 메시를 선택하는 방식입니다. LOD가 드로 콜을 줄이는지는 머티리얼 슬롯이나 메시 병합 방식에 달려 있고, 여러 LOD 메시를 저장하면 메모리가 더 필요할 수도 있습니다.
일반적으로 LOD 0은 원본에 가장 가까운 고품질 메시이고 LOD 4는 가장 단순한 원거리용 메시입니다. 다만 LOD 번호와 전환 거리는 엔진과 프로젝트 규칙에 따라 달라질 수 있으므로 아래 값은 시작점으로 사용해야 합니다.
| 단계 | 용도 | 시작 삼각형 비율 | 주요 검수 항목 |
|---|---|---|---|
| LOD 0 | 근거리·스크린샷·주요 상호작용 | 100% | 원본 대비 품질, UV, 노멀 |
| LOD 1 | 일반 플레이 거리 | 60~75% | 실루엣, 작은 디테일 |
| LOD 2 | 중거리 | 35~50% | 재질 경계, 하드서피스 윤곽 |
| LOD 3 | 원거리 | 15~25% | 전체 실루엣, 큰 색상 영역 |
| LOD 4 | 매우 먼 거리·배경 | 5~10% | 외곽선, 바운드, 피벗 |
비율만 낮추는 것은 충분한 최적화가 아닙니다. 캐릭터처럼 변형되는 메시에는 관절 주변의 엣지 루프가 필요하고 하드서피스 에셋에는 베벨과 노멀 정보가 실루엣만큼 중요합니다.
Blender Python API와 AI는 각각 무엇을 담당하는가?
자동화 품질을 높이려면 Blender와 AI의 책임을 분리해야 합니다. Blender는 결정적이고 재현 가능한 메시 생성과 측정을 담당하고 AI는 사람이 작성한 규칙을 보완하는 분석 계층으로 사용하는 편이 안전합니다.
| 구성 요소 | 담당 작업 | 결과 예시 |
|---|---|---|
Blender bpy | 오브젝트 복제, 모디파이어 적용, 컬렉션 구성 | LOD_0~LOD_4 오브젝트 |
Decimate Modifier | 메시 복잡도 감소 | ratio=0.35 |
bmesh | 메시 구조와 비정상 엣지 검사 | 비매니폴드 엣지 수 |
| AI 모델 | 에셋 분류, 파라미터 추천, 리포트 요약 | hard_surface, 우선 검수 항목 |
| 게임 엔진 | 거리 기반 LOD 전환과 실제 렌더링 검증 | 화면 품질, 팝핑 여부 |
AI에게는 다음과 같은 구조화된 입력을 제공하는 것이 좋습니다.
{
"asset_id": "crane_machine_001",
"asset_type": "hard_surface",
"source_triangles": 184000,
"has_uv": true,
"has_armature": false,
"lod_targets": {
"0": { "ratio": 1.0, "max_triangles": 184000 },
"1": { "ratio": 0.65, "max_triangles": 120000 },
"2": { "ratio": 0.38, "max_triangles": 70000 },
"3": { "ratio": 0.18, "max_triangles": 35000 },
"4": { "ratio": 0.08, "max_triangles": 16000 }
}
}
이렇게 하면 AI가 단순히 “폴리곤을 줄이세요”라고 답하는 대신 에셋 유형과 현재 상태를 기준으로 조정 가능한 파라미터를 반환할 수 있습니다. 회사 내부 에셋이나 미공개 프로젝트라면 먼저 회사 보안 정책과 AI 서비스의 데이터 처리 조건을 확인하고, 허용된 경우에만 원본 메시 대신 삼각형 수·바운드 크기·머티리얼 슬롯 수 같은 메타데이터를 전달하세요. 이런 메타데이터도 프로젝트 정보를 드러낼 수 있으므로 익명화만으로 안전하다고 단정할 수는 없습니다.
Blender에서 LOD 0~4를 어떻게 자동 생성할까?
다음 스크립트는 선택한 메시 오브젝트를 기준으로 LOD 컬렉션을 만들고 Decimate Modifier의 COLLAPSE 모드로 LOD 1~4를 생성합니다. ratio는 남길 메시의 비율이며 품질에 따라 에셋별로 조정해야 합니다.
import bpy
LOD_RATIOS = {
0: 1.00,
1: 0.65,
2: 0.38,
3: 0.18,
4: 0.08,
}
def get_or_create_collection(name):
collection = bpy.data.collections.get(name)
if collection is None:
collection = bpy.data.collections.new(name)
bpy.context.scene.collection.children.link(collection)
return collection
def move_to_collection(obj, collection):
for current in list(obj.users_collection):
current.objects.unlink(obj)
collection.objects.link(obj)
def triangle_count(obj):
mesh = obj.data
mesh.calc_loop_triangles()
return len(mesh.loop_triangles)
def create_lod(source, lod_level, ratio, collection):
lod_name = f'{source.name}_LOD_{lod_level}'
obj = source.copy()
obj.data = source.data.copy()
obj.name = lod_name
move_to_collection(obj, collection)
if lod_level > 0:
modifier = obj.modifiers.new(name='LOD_Decimate', type='DECIMATE')
modifier.decimate_type = 'COLLAPSE'
modifier.ratio = ratio
modifier.use_collapse_triangulate = True
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
with bpy.context.temp_override(
object=obj,
active_object=obj,
selected_objects=[obj],
):
bpy.ops.object.modifier_apply(modifier=modifier.name)
obj.select_set(False)
obj['lod_level'] = lod_level
obj['source_asset'] = source.name
obj['triangle_count'] = triangle_count(obj)
obj.hide_render = lod_level != 0
return obj
def build_lods(source):
collection = get_or_create_collection(f'{source.name}_LODs')
result = []
for lod_level, ratio in LOD_RATIOS.items():
result.append(create_lod(source, lod_level, ratio, collection))
# LOD_0 is a copy; hide the source to avoid rendering coincident meshes.
source.hide_render = True
return result
source = bpy.context.active_object
if source is None or source.type != 'MESH':
raise RuntimeError('활성 오브젝트로 메시를 선택해야 합니다.')
lod_objects = build_lods(source)
for obj in lod_objects:
print(obj.name, obj['triangle_count'])
이 코드에서 source.copy()는 오브젝트를 복제하고 source.data.copy()는 메시 데이터를 독립적으로 복사합니다. 메시 데이터를 공유하면 한 LOD에 적용한 변경이 다른 LOD에도 영향을 줄 수 있으므로 두 복사를 분리하는 것이 중요합니다.
hide_render는 Blender 내부 미리보기에서 모든 LOD가 동시에 렌더링되는 문제를 줄이기 위한 설정입니다. 이 예시에서는 LOD_0도 원본의 복사본이므로 원본 오브젝트의 hide_render를 켜 겹쳐 렌더링되지 않게 합니다. 실제 엔진으로 내보낼 때는 LOD별 파일을 따로 export하거나 엔진의 LOD 그룹 규칙에 맞춰 이름을 변환해야 합니다.

화면과 수치는 설명을 위한 합성 예시입니다.
Decimate Modifier만으로 충분하지 않은 이유는 무엇인가?
Decimate Modifier는 빠르고 자동화하기 쉽지만 모든 에셋에 같은 결과를 보장하지 않습니다. 특히 COLLAPSE 모드는 삼각형을 줄이는 과정에서 작은 베벨, 구멍, 날카로운 실루엣을 손상시킬 수 있습니다.
에셋 유형에 따라 다음 전략을 조합해야 합니다.
| 에셋 유형 | 권장 전략 | 주의점 |
|---|---|---|
| 정적 하드서피스 | Decimate + Weighted Normal 또는 재생성된 노멀 | 베벨과 패널 경계 확인 |
| 유기적 소품 | Decimate + 실루엣 비교 | 얇은 돌출부가 사라질 수 있음 |
| 캐릭터 | 수동 토폴로지 또는 리토폴로지 도구 | 관절 변형과 스키닝 확인 |
| 나뭇잎·천 | 빌보드, 카드, 단순화된 쉘 | 알파 정렬과 양면 렌더링 확인 |
| 건물·배경 | 계층형 단순화와 머티리얼 병합 | 텍스처와 드로 콜 확인 |
LOD 생성 전에 모디파이어의 순서도 고정해야 합니다. 일반적인 정적 에셋은 기본 형태 수정, 미러 또는 배열, 베벨, 노멀 보정, Decimate 순서를 검토할 수 있지만 프로젝트의 제작 규칙에 맞춰 결과를 비교해야 합니다. 적용 순서가 달라지면 최종 실루엣과 노멀 결과도 달라집니다.
LOD 결과를 자동으로 어떻게 검수할까?
자동 검수는 “생성 성공”과 “게임에 사용할 수 있음”을 구분해야 합니다. 최소한 삼각형 수, 메시 유효성, 비매니폴드 엣지, UV 존재 여부, 머티리얼 슬롯, 바운드 크기, 피벗을 확인해야 합니다.
1단계: 삼각형 수와 단조 감소 확인
LOD 번호가 증가할수록 삼각형 수가 줄어드는지 확인합니다. LOD 3의 삼각형 수가 LOD 2보다 많다면 생성 순서나 원본 메시 참조에 문제가 있을 가능성이 큽니다.
import bmesh
def inspect_mesh(obj):
mesh = obj.data
mesh.calc_loop_triangles()
bm = bmesh.new()
bm.from_mesh(mesh)
bm.edges.ensure_lookup_table()
non_manifold = sum(
1 for edge in bm.edges
if not edge.is_manifold and not edge.is_boundary
)
uv_layer = mesh.uv_layers.active
report = {
'name': obj.name,
'triangles': len(mesh.loop_triangles),
'vertices': len(mesh.vertices),
'polygons': len(mesh.polygons),
'material_slots': len(obj.material_slots),
'has_uv': uv_layer is not None,
'non_manifold_edges': non_manifold,
'dimensions': tuple(round(value, 4) for value in obj.dimensions),
'location': tuple(round(value, 4) for value in obj.location),
}
bm.free()
return report
def validate_lod_group(objects):
# build_lods가 기록한 lod_level 기준으로 정렬해 입력 순서에 의존하지 않습니다.
objects = sorted(objects, key=lambda obj: obj.get('lod_level', 0))
reports = [inspect_mesh(obj) for obj in objects]
errors = []
triangle_values = [item['triangles'] for item in reports]
if any(current < following for current, following in zip(
triangle_values, triangle_values[1:]
)):
errors.append('LOD 단계가 증가해도 삼각형 수가 감소하지 않습니다.')
for report in reports:
if report['non_manifold_edges'] > 0:
errors.append(
f"{report['name']}: 비매니폴드 엣지 "
f"{report['non_manifold_edges']}개"
)
if not report['has_uv']:
errors.append(f"{report['name']}: 활성 UV 레이어가 없습니다.")
return reports, errors
경계 엣지는 열린 메시에서 정상일 수 있으므로 non_manifold_edges와 단순한 경계 엣지를 구분해야 합니다. 그러나 캐릭터나 밀폐형 하드서피스처럼 닫힌 메시를 요구하는 에셋은 프로젝트 규칙에 따라 경계 엣지도 오류로 처리할 수 있습니다.
2단계: 실루엣과 바운드 비교
삼각형 수가 목표 안에 들어와도 실루엣이 크게 바뀌면 LOD로 사용할 수 없습니다. 카메라 방향별 렌더 이미지를 비교하거나 원본과 LOD의 바운딩 박스 크기를 비교해 돌출부가 사라졌는지 확인합니다.
실루엣 오차를 수치화할 때는 다음과 같은 개념을 사용할 수 있습니다. 여기서 픽셀 차이는 두 마스크의 XOR(한쪽에만 포함된 픽셀) 수로 정의하고, 원본 마스크가 비어 있지 않다고 가정합니다. 그 값을 전체 원본 마스크 픽셀 수로 나누면 비교 가능한 비율을 얻을 수 있습니다.
이 값은 카메라 방향, 해상도, 알파 임계값에 따라 달라지므로 프로젝트에서 허용 기준을 고정해야 합니다. 예를 들어 정면과 측면의 오차를 각각 측정한 뒤 가장 큰 값을 최종 오차로 사용할 수 있습니다.
3단계: 노멀·UV·머티리얼을 확인
메시를 단순화하면 폴리곤 경계의 노멀 방향이 달라질 수 있습니다. 하드서피스에서는 다음 항목을 함께 확인해야 합니다.
- 하드 엣지가 의도치 않게 사라졌는가?
- Weighted Normal 또는 Custom Split Normal 결과가 유지되는가?
- UV 아일랜드가 찢어지거나 겹치지 않는가?
- 머티리얼 슬롯 수가 불필요하게 증가하지 않았는가?
- 노멀 맵의 디테일이 감소한 메시와 맞는가?
LOD마다 텍스처 해상도를 자동으로 낮추는 경우에는 메시 LOD와 텍스처 LOD를 별도 버전으로 관리해야 합니다. 메시만 줄이고 고해상도 텍스처를 계속 사용하면 메모리 절감 효과가 제한적일 수 있습니다.
AI를 LOD 검수에 어떻게 결합할까?
AI는 이미지 또는 메타데이터를 바탕으로 위험한 LOD를 우선 표시할 수 있습니다. 예를 들어 다음 입력을 사용하면 검수 우선순위를 정할 수 있습니다.
{
"asset_type": "hard_surface",
"lod_level": 3,
"triangle_reduction": 0.82,
"silhouette_error": 0.037,
"non_manifold_edges": 0,
"uv_overlap_count": 2,
"material_slot_count": 4,
"request": "검수 우선순위와 수정 파라미터를 JSON으로 반환"
}
AI 응답은 다음처럼 제한된 스키마로 받는 것이 좋습니다.
{
"status": "review_required",
"priority": "high",
"reason_codes": ["SILHOUETTE_ERROR", "UV_OVERLAP"],
"recommended_action": {
"decimate_ratio": 0.24,
"preserve_uv": true,
"manual_review_camera": ["front", "side"]
}
}
중요한 점은 AI의 자연어 답변을 바로 Blender 명령으로 실행하지 않는 것입니다. decimate_ratio가 0과 1 사이인지 허용된 필드만 포함하는지 기존 예산을 초과하지 않는지 검증한 뒤 Blender Python API에 전달해야 합니다.
AI를 활용한 자동화의 권장 순서는 다음과 같습니다.
- Blender가 메시와 렌더 결과에서 측정값을 생성합니다.
- AI가 에셋 유형과 오류 코드를 기준으로 LOD 파라미터를 제안합니다.
- 스키마 검증기가 제안값의 범위와 프로젝트 규칙을 확인합니다.
- Blender가 후보 LOD를 다시 생성합니다.
- 자동 검수와 사람이 보는 대표 카메라 검수를 모두 통과한 결과만 export합니다.
이 구조에서는 AI가 실패해도 마지막으로 통과한 LOD 결과를 유지할 수 있습니다. 또한 어떤 파라미터가 품질을 개선했는지 로그로 추적할 수 있습니다.
LOD 팝핑을 줄이려면 어떻게 해야 할까?
LOD 팝핑은 카메라가 전환 거리를 지날 때 메시의 형태가 갑자기 바뀌어 발생하는 시각적 현상입니다. 단순히 전환 거리를 멀리 배치하는 것보다 다음 방법을 함께 적용하는 편이 좋습니다.
- 인접 LOD의 실루엣 차이를 제한합니다.
- 엔진이 지원하면 디더 페이드 또는 크로스페이드를 사용합니다.
- 중요한 오브젝트는 LOD 단계 수를 줄이고 더 완만한 비율을 사용합니다.
- 애니메이션 오브젝트는 본 변형 후에도 전환이 안정적인지 확인합니다.
- 그림자 전용 메시와 렌더 메시의 LOD 규칙을 분리합니다.
Blender 내부에서 정상적으로 보이는 결과가 엔진에서 동일하게 보인다고 가정하면 안 됩니다. 엔진의 임포터가 노멀을 재계산하거나 머티리얼을 병합할 수 있으므로 최종 검수는 실제 엔진의 조명과 카메라 거리에서 진행해야 합니다.
자주 묻는 질문 (FAQ)
Blender의 Decimate Modifier만 사용해도 되나요?
정적이고 변형이 없는 소품에는 충분한 출발점이 될 수 있습니다. 캐릭터, 복잡한 하드서피스, 얇은 카드 메시처럼 품질 민감도가 높은 에셋은 수동 리토폴로지나 별도 규칙을 추가해야 합니다.
AI가 LOD 비율을 자동으로 결정하게 해도 되나요?
추천값을 생성하게 하는 것은 가능하지만 최종 실행 전에 범위 검증과 삼각형 예산 검사를 거쳐야 합니다. AI는 실루엣 오차와 메시 통계를 해석하는 보조 계층으로 두는 것이 안전합니다.
LOD 4에서 UV와 머티리얼을 생략해도 되나요?
배경 거리와 엔진의 렌더링 방식에 따라 다릅니다. 단색 재질이나 임포스터를 사용한다면 단순화할 수 있지만 그림자와 반사에 영향을 주는 에셋이라면 최소한의 UV와 머티리얼 검수는 유지해야 합니다.
정리
Blender Python API를 사용하면 LOD 0~4의 생성과 메시 통계 수집을 반복 가능한 작업으로 만들 수 있습니다. Decimate Modifier는 빠른 자동화에 적합하지만 에셋 유형별 예외 처리가 필요하며 AI는 에셋 분류·파라미터 추천·검수 우선순위 정리에 활용하는 것이 현실적입니다.
가장 안정적인 파이프라인은 생성 → 측정 → AI 추천 → 스키마 검증 → 재생성 → 자동·수동 검수 → 엔진 확인의 순서를 지킵니다. 이 과정을 CI나 아트 검수 도구에 연결하면 LOD 품질과 최적화 규칙을 동시에 관리할 수 있습니다.



