- 3개월 동안 Vulkan을 학습하며 두 개의 데모 게임을 포함한 소형 게임 엔진을 직접 구현한 경험 정리
- 기존 OpenGL 경험을 바탕으로 Vulkan의 복잡성을 단계적으로 극복, glTF 로딩·스키닝·섀도 매핑 등 핵심 기능 구현
- 엔진은 EDBR(Elias Daler’s Bikeshed Engine) 으로, 약 1.9만 줄의 코드로 구성되어 있으며 바인드리스 디스크립터·PVP·BDA 등 현대적 그래픽스 기법 활용
- 글에서는 vk-bootstrap, VMA, volk 같은 필수 라이브러리와 파이프라인 패턴, 셰이더 빌드 자동화, 동기화 관리 등 실무적 구현 세부를 공유
- Vulkan 전환을 통해 글로벌 상태 제거, 명시적 제어, 향상된 디버깅 환경, GPU 간 일관성을 얻었으며, 향후 렌더 그래프·SDF 폰트·볼류메트릭 효과 추가 계획
Vulkan 학습과 엔진 개발 개요
- 저자는 그래픽스 프로그래밍을 독학으로 시작해 1년 반 전 OpenGL로 3D 엔진을 작성한 경험이 있음
- Vulkan 기반 엔진은 소규모 레벨 기반 게임에 적합하며, 효율성보다는 학습과 실험 목적 중심
- 초기에는 단순 3D 게임을 만들고, 재사용 가능한 부분을 분리해 엔진화하는 방식으로 진행
- 3개월 만에 완성할 수 있었던 이유는 범용 엔진이 아닌 특정 목적 엔진으로 한정했기 때문
그래픽스 프로그래밍 학습 경로
- 초보자는 OpenGL로 시작해 텍스처 모델 표시, Blinn-Phong 조명, 그림자 매핑 등을 익히는 것이 권장됨
- 추천 자료로 learnopengl.com, Anton’s OpenGL 4 Tutorials, Thorsten Thormählen 강의 등이 제시됨
- 최신 OpenGL 4.6 실습 자료와 함께 선형대수학(벡터, 행렬, 쿼터니언) 이해의 중요성 강조
Bike-shedding 방지 조언
- 불필요한 과도한 설계와 추상화를 피하고, “지금 필요한 것만 구현” 원칙 유지
- “일단 작동하게 만들고 나중에 개선” 접근 권장
- 일반 목적 엔진보다는 작은 게임을 먼저 완성하는 것이 효율적
- 다른 사람의 복잡한 코드나 구조를 그대로 모방하지 말고, 단순한 구조에서 출발
Vulkan 선택 이유
- AAA 게임에는 DirectX, macOS/iOS에는 Metal, 웹에는 WebGPU/WebGL이 주로 사용됨
- 저자는 오픈소스와 표준 기술 선호, 데스크톱용 소형 3D 게임 개발 목적에 맞춰 Vulkan 선택
- OpenGL은 더 이상 발전이 없고 macOS에서 폐기됨
- WebGPU는 간결하지만 안정성 부족, 기능 제약, 바인드리스·푸시 상수 미지원 등의 한계 존재
Vulkan 학습 과정
- 초기에는 “그래픽 드라이버를 직접 작성하는 수준”으로 느껴질 만큼 어려웠으나,
동적 렌더링, vk-bootstrap, vkguide 등의 등장으로 접근성이 향상됨 - 주요 학습 자료:
- vkguide.dev (기초부터 실습 중심)
- TU Wien Vulkan Lecture Series
- 3D Graphics Rendering Cookbook, Mastering Graphics Programming with Vulkan
- 첫 달에 glTF 로딩, 컴퓨트 스키닝, 프러스텀 컬링, 섀도 매핑 구현 완료
EDBR 엔진 구조와 프레임 처리
- 엔진 코드 약 19,000줄, 3D 게임 4,600줄, 2D 플랫폼 게임 1,200줄
- 주요 렌더링 단계:
- Compute 스키닝 → Cascaded Shadow Mapping(4096×4096) → PBR 기반 지오메트리 셰이딩
- Depth Resolve → Post FX(깊이 기반 안개) → UI 렌더링
- 모든 그래픽스 시스템은 Vulkan 전용으로 재작성, 이전 OpenGL 코드와 혼합하지 않음
Vulkan 개발 실무 팁
추천 라이브러리
- vk-bootstrap: 초기화 및 스왑체인 설정 단순화
- Vulkan Memory Allocator(VMA) : 메모리 관리 자동화
- volk: 확장 함수 로딩 단순화
GfxDevice 추상화
-
VkDevice,VkQueue,VmaAllocator등을 하나의 객체로 관리 - 프레임 시작/종료, 이미지·버퍼 생성, 바인드리스 디스크립터 관리 담당
셰이더 관리
-
GLSL 사용, 빌드 시점에
glslc로 SPIR-V 사전 컴파일 - CMake
DEPFILE을 이용해 셰이더 변경 시 자동 재빌드
파이프라인 패턴
- 각 렌더링 단계를 클래스 단위 파이프라인으로 분리 (
init,cleanup,draw) -
VK_KHR_dynamic_rendering사용으로 렌더패스·서브패스 생략, 단순화된 구조 유지
Programmable Vertex Pulling + Buffer Device Address
- 하나의 Vertex 구조체로 모든 메시 처리
- 셰이더에서 버퍼 참조(buffer_reference) 로 직접 접근, VAO 불필요
Bindless Descriptor
- 전역 텍스처 배열(
textures[],samplers[])을 사용해 텍스처 ID 기반 샘플링 - 머티리얼 구조체에 텍스처 ID 저장, 푸시 상수로 전달
동적 데이터 업로드
- 프레임별로 GPU 버퍼를 교체하거나 CPU 스테이징 버퍼를 이용해 데이터 전송
-
NBuffer클래스로 프레임 인플라이트 구조 관리
리소스 정리와 동기화
- 명시적 cleanup 함수 사용, 소멸자 자동 정리 대신 수동 관리
-
vkCmdPipelineBarrier2로 패스 간 메모리 동기화 수행 - Render Graph는 향후 구현 예정
구현 세부 사례
스프라이트 렌더링
- 바인드리스 텍스처와 인스턴싱으로 수천 개 스프라이트를 한 번에 렌더
-
SpriteDrawCommand구조체를 GPU 버퍼로 업로드,vkCmdDraw(6, N)호출 - 1만 개 스프라이트를 315μs에 렌더링
Compute 스키닝
- 컴퓨트 셰이더에서 본 행렬과 가중치 기반 버텍스 변형 수행
- 각 인스턴스별로 출력 버퍼를 별도 생성, 이후 렌더링 단계에서 동일 처리
게임/렌더러 분리
- 게임 로직은 entt ECS 사용, 렌더러는 DrawCommand 벡터만 처리
-
drawMesh,drawSkinnedMesh호출로 렌더 명령 생성
씬 로딩과 프리팹
- Blender에서 glTF로 레벨 구성, 노드 이름 규칙으로 프리팹 자동 스폰
- 프리팹은 JSON으로 정의, 외부 glTF 참조
MSAA, UI, ImGui
- Forward 렌더링 기반 MSAA x8 적용
- Roblox UI API에서 영감받은 자동 레이아웃 시스템 구현
- Dear ImGui sRGB 문제를 해결하기 위해 자체 Vulkan 백엔드 작성
기타 구성 요소
- Jolt Physics로 물리 처리, entt ECS, OpenAL-soft 오디오, Tracy 프로파일러 사용
Vulkan 전환의 이점
- 글로벌 상태 제거로 명시적·모듈형 코드 구조 확보
- 검증 계층과 RenderDoc 디버깅으로 문제 추적 용이
- GPU·OS 간 일관성 향상, OpenGL 대비 예측 가능한 동작
- 새 셰이딩 언어(slang, shady) 등 확장 가능성 확보
- 더 적은 추상화, 명확한 파이프라인 제어로 유지보수성 향상
향후 계획
- SDF 폰트, 병렬 이미지 로딩 및 밉맵 생성, Bloom, 볼류메트릭 포그, 애니메이션 블렌딩, 렌더 그래프, AO 추가 예정
- Vulkan 학습은 어렵지만, 현대 그래픽스 API 이해와 엔진 설계 역량 강화에 큰 도움이 되었음