메이즈라인은 고객사의 렌더링 파이프라인 제작 지원 과정에서 추가로 요청해 주신 아틀라스 빌더 툴을 약 15일간의 전담 기간을 통해 개발하였으며, 현재 1차 납품을 완료했습니다.
해당 툴은 이전 담당자분이 남기고 가신 리소스 자산을 복원하고, 사내 네이밍 컨벤션(name convention)에 맞춰 재정리할 수 있도록 구성되어 있습니다. 또한 이후 신규 아틀라스를 빌드하실 때, 사전에 정의된 인덱스 규칙(built-in index rule)에 따라 개별 소스 텍스처가 자동으로 지정된 그리드 좌표에 배치되도록 자동화하여 수동 편집 과정과 인적 오류 가능성을 최소화했습니다.
처리 과정 이야기를 간략히 기록 해 보려고 합니다.
SGE Voxel Block Texture Atlas Maker v1.0.1 개발 회고
복셀 게임용 텍스처 아틀라스 생성 툴을 개발하면서 겪은 기술적 도전과 해결 과정을 공유합니다.
프로젝트 개요
16x16 그리드로 8192x8192 크기의 아틀라스를 생성하는 도구입니다. Color, Mask, Normal 세 종류의 텍스처 그룹을 지원하며, Clean Architecture 패턴을 적용하여 설계했습니다. 처음에는 단순한 툴이라고 생각했지만, 예상보다 많은 고민이 필요했습니다.
사용자분들의 원활한 전환을 위해 마이그레이션 가이드를 작성하고 Old Atlas Import 기능을 구현했습니다. 이를 통해 v1.0.0에서 작업한 파일들을 자동으로 변환할 수 있도록 했습니다.
업계 표준을 따르는 것이 장기적으로 유리합니다. 직관적이라고 생각한 설계가 실제로는 불편함을 야기할 수 있으며, 특히 외부 도구와의 연동을 고려하면 더욱 그렇습니다.
성능 최적화
사실 개발 과정에서 잦은 수정과 이터레이션이 많았기 때문에 이것부터 처리 해야 했습니다. 일단 사용자에게 우리가 개발하고 있는 방향이 맞는지 보여줘야 하는 첫 번째 버전인 v1.0.0에서는 8192x8192 아틀라스 3개를 생성하는 데 15초 이상이 소요되었습니다 (i7 기준). 50개 정도의 512x512 텍스처를 배치하는 작업임을 감안하면 개선이 필요한 수준이었습니다.
프로파일링 결과, 픽셀 단위 루프가 주요 병목 구간으로 확인되었습니다.
벡터화 연산 도입
가장 큰 성능 향상은 픽셀 루프를 벡터화 연산으로 대체한 것입니다:
함수 batch_place_textures(canvas, textures, cell_size):
각 texture에 대해:
// 배치할 영역 계산
y_start = row * cell_size
y_end = y_start + cell_size
x_start = col * cell_size
x_end = x_start + cell_size
// 알파 블렌딩을 전체 영역에 한 번에 적용
alpha = texture의 알파 채널을 0-1 범위로 정규화
// 캔버스 영역 참조 (복사 없이 뷰만 가져옴)
canvas_region = canvas[y_start:y_end, x_start:x_end]
// 벡터화된 알파 블렌딩 (인플레이스 연산)
canvas_region = canvas_region * (1 - alpha) + texture * alpha
이 접근법의 핵심은 배열 슬라이싱을 통해 전체 영역을 일괄 처리하고, Broadcasting을 활용하여 알파 채널을 자동으로 확장하며, 인플레이스 연산으로 불필요한 메모리 할당을 최소화하는 것입니다.
픽셀 블렌딩 부분만 보면 약 10배 가까이 빨라졌습니다. I/O와 리샘플링 시간을 포함한 전체 파이프라인으로는 약 2배 정도 개선되었습니다.
멀티프로세싱 병렬화
Python의 GIL(Global Interpreter Lock) 제약을 고려하여 멀티쓰레딩 대신 멀티프로세싱을 적용했습니다:
프로세스_풀 생성 (워커 3개):
futures = 빈 딕셔너리
각 atlas_type (Color, Mask, Normal)에 대해:
output_path = 출력 디렉토리 / 파일명
// 각 아틀라스를 별도 프로세스에서 생성
future = 프로세스_풀.submit(
export_single_atlas,
output_path,
관련 설정들...
)
futures[future] = atlas_type
// 완료된 것부터 순차적으로 결과 수집
완료된 각 future에 대해:
atlas_type = futures[future]
result = future.result()
results[atlas_type] = result
Color, Mask, Normal 아틀라스를 각각 독립적인 프로세스에서 생성하여 CPU 코어를 효율적으로 활용할 수 있었습니다.
16-bit 변환 최적화
8-bit에서 16-bit로의 변환 과정도 비트 연산을 활용하여 최적화했습니다:
함수 load_texture_as_16bit(texture_path, target_size):
이미지 열기:
RGBA로 변환 (필요시)
크기가 다르면:
target_size로 리샘플링
8-bit 배열로 변환
// 비트 시프트를 활용한 16-bit 변환
16bit_array = 빈 16-bit 배열 생성
16bit_array = (8bit_array << 8) | 8bit_array
반환 16bit_array
최적화 결과
항목v1.0v1.1개선
| 생성 시간 (i7) | 15초+ | ~7초 | 3배 |
| 메모리 | 2.4GB | 1.7GB | 약 30% 감소 |
| CPU 활용 | 1코어 순차 | 3코어 병렬 | 병렬화 성공 |
TIFF 포맷 채택
출력 포맷을 PNG에서 TIFF로 변경했습니다. TIFF는 16-bit 데이터를 네이티브로 지원하여 안정성이 향상되었고, LZW 압축을 통해 효율적인 파일 크기 관리가 가능해졌습니다. 또한 읽기와 쓰기 속도도 PNG보다 빠른 것으로 확인되었습니다.
Clean Architecture의 이점
최적화 과정에서 Clean Architecture 패턴의 장점을 실감할 수 있었습니다:
domain/
├── grid_manager # 관리 로직
├── models # 도메인 모델
└── services/
├── export_service # 기존 버전
└── export_service_optimized # 최적화 버전
계층이 분리되어 있어 기존 코드를 유지하면서 새로운 최적화 버전을 추가할 수 있었고, 각각을 독립적으로 테스트할 수 있었습니다. 문제 발생 시 즉시 롤백할 수 있어 안정적으로 작업할 수 있었습니다.
정리 해 보면...
업계 표준 준수의 중요성
직관적이라고 판단한 설계가 실제로는 더 큰 불편함을 초래할 수 있습니다. 특히 다른 시스템과의 통합을 고려하면 표준을 따르는 것이 필수적입니다.
프로파일링 기반 최적화
막연한 추측보다는 정확한 측정이 중요합니다. 프로파일링을 통해 픽셀 루프가 전체 시간의 70%를 차지한다는 것을 확인하고 집중적으로 개선할 수 있었습니다.
벡터화 연산의 효과
반복문을 벡터화 연산으로 대체하는 것만으로도 상당한 성능 향상을 얻을 수 있습니다. 이미지 처리 라이브러리의 벡터화 기능을 적극적으로 활용하는 것이 좋습니다.
Python에서의 병렬 처리
CPU 집약적 작업의 경우 GIL의 영향으로 멀티쓰레딩이 효과적이지 않습니다. 이런 경우 멀티프로세싱이 더 적합합니다.
Breaking Change 관리
불가피한 Breaking Change의 경우 명확한 마이그레이션 경로를 제공하고, 가능하다면 호환성 레이어를 구현하는 것이 사용자 경험에 도움이 됩니다.
향후 계획
성능은 크게 개선되었지만 여전히 개선 여지가 있습니다. GPU 가속을 통해 CUDA를 활용한 이미지 처리를 구현하면 추가적인 성능 향상을 기대할 수 있습니다. 자주 사용되는 텍스처를 메모리에 캐싱하는 스마트 캐싱 시스템도 고려하고 있습니다. 또한 여러 프로젝트를 한 번에 처리할 수 있는 배치 익스포트 기능과 자주 사용하는 설정을 저장하고 관리할 수 있는 프리셋 시스템도 계획 중입니다. 특히 GPU 가속은 이미지 처리 작업의 특성상 큰 성능 향상을 기대할 수 있어 우선순위를 두고 있습니다.
아틀라스와 텍스처 어레이 모두 케시히트 보장은 비슷한 수준입니다.
혹 텍스처 메모리 케시 히트가 뭔가 하시는 아티스트분들은 이걸 잠깐 보시면 되고요.
[번역]Memory Statistics - Texture
역자의 말. 최근까지 CGI 회사의 언리얼 엔진 부분 컨설팅을 하다가 또 다른 게임회사의 신규 프로젝트 최적화 부분 카운셀링을 맡게 되었습니다. 단순히 시각화 처리 부분등에 관련 된 렌더링
techartnomad.tistory.com
추석 연휴에도 조금 씩 완성 해 나가고 있습니다.
원래 컨설팅사가 이렇습니다. 다 놀때 못놀고 다 일할 때도 일 해야 하는 거네요. ㅎ
2025.10.20
Release version 1.0.1 build5
2025.10.16
Validating texture list group within rull-base, Generating three texture at one time. Support efficiant preview teture your selected slot, Validating to texture channel bit depth and auto-convert 16-bit depth information texture and more.

2025.10.14
Add service class of texture import with save workspace as .atlas


2025.10.7
Improvement of Selection High -light visualization

2025.10.6
Add Selection High-light
Fixed bugs Multiple Selection Manager function ( Re-order to upate to clear method for remove previous selection history )

2025.10.4.
Default Interface release.

10년도 전에는 QT 를 종종 써왔었지만 최근 10년은 거의 렌더링만 해 왔기 때문에 다시 손에 익숙해지려면 시간이 필요 해 보였습니다.
서브스턴스 페인터 역시 QT5 로 개발이 되었어요. 처음엔 QT4 였다가 인터페이스를 좀 더 유연하게 만들기 위해서 QT5 이상으로 변경되었습니다. 뭐 지금은 어떤 버전인지 모르지만 제가 알레고리드믹 어도비에 근무 할 당시에는 그랬습니다. ㅎ 서브스턴스 페인터 1.0 이 막 나왔을때죠.
오랫만에 QT 크리에이터와 디자이너를 설치 하고 pyCharm 을 사용해서 작업을 하고 있어요.
이제 기본 인페이스를 만들었고 몇 가지 UX 고도화를 해야 하고 실제 코어 로직을 추석 연휴에 완성 해야 합니다.
완성이 되면 이게 뭐 하는 툴인지 또 프리뷰를 올려보도록 하겠습니다.
'MAZELINE TOPIC' 카테고리의 다른 글
| GPU-Driven Renderer에서의 Heterogeneous AoS Instance Encoding (0) | 2025.10.23 |
|---|---|
| Unity URP Decal과 After Transparent Depth 충돌 해결 가이드 (0) | 2025.10.22 |
| Unity HDRP 커스텀 Depth 셰이더 가이드 (0) | 2025.10.21 |
| URP에서 HDRP로 셰이더 포팅 가이드 (0) | 2025.10.21 |
| GPU 병렬 연산: Warp Divergence 이해하고 해결하기 (2) | 2025.10.20 |