Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions guide/build.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/theme" DESTINATION "${CMAKE_CURRENT_SOURC

BuildBook("en" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/book")
BuildBook("zh-TW" "${CMAKE_CURRENT_SOURCE_DIR}/translations/zh-TW" "${CMAKE_CURRENT_SOURCE_DIR}/book/zh-TW")
BuildBook("ko-KR" "${CMAKE_CURRENT_SOURCE_DIR}/translations/ko-KR" "${CMAKE_CURRENT_SOURCE_DIR}/book/ko-KR")
1 change: 1 addition & 0 deletions guide/theme/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
<ul id="lang-list" class="theme-popup" aria-label="Languages" role="menu">
<li role="none"><button role="menuitem" class="theme" data-lang="en">English</button></li>
<li role="none"><button role="menuitem" class="theme" data-lang="zh-TW">中文</button></li>
<li role="none"><button role="menuitem" class="theme" data-lang="ko-KR">한글</button></li>
</ul>
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
Expand Down
2 changes: 1 addition & 1 deletion guide/theme/lang_toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
document.addEventListener('click', function (e) {
if (e.target && e.target.matches('button[data-lang]')) {
const chosenLang = e.target.getAttribute('data-lang');
const supportedLangs = ['en', 'zh-TW']; // Add translated languages here
const supportedLangs = ['en', 'zh-TW', 'ko-KR']; // Add translated languages here

let currentPath = window.location.pathname;

Expand Down
10 changes: 10 additions & 0 deletions guide/translations/ko-KR/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[book]
authors = ["Karnage", "DDing"]
language = "ko-KR"
src = "src"
title = "Learn Vulkan"

[output.html]
theme = "../theme"
additional-js = ["../theme/lang_toggle.js"]
additional-css = ["../theme/lang_toggle.css"]
34 changes: 34 additions & 0 deletions guide/translations/ko-KR/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 소개

Vulkan은 매우 명시적이고 어려운 API로 알려져 있습니다. 하지만 버전이 거듭될수록 새로운 기능이 추가되고 기존 확장 기능들이 핵심 API에 통합되면서, 필수적인 어려움은 점차 줄어들고 있습니다. RAII는 C++의 핵심 개념 중 하나이지만, 대부분의 Vulkan 가이드에서는 이를 제대로 활용하지 않고, 자원을 수동으로 해제하는 방식으로 오히려 명시성을 강조하는 경우가 많습니다.

이러한 격차를 메우기 위해 이 가이드는 다음과 같은 목표를 가지고 있습니다.

- 모던 C++, VulkanHPP, Vulkan 1.3 기능을 적극 활용합니다.
- 성능이 아닌, 단순하고 직관적인 접근에 초점을 맞춥니다.
- 기본적인 렌더링 기능을 갖춘 동적 렌더링 기반을 구축합니다.

다시 한번 말하자면, 이 가이드의 목적은 성능이 아닙니다. 이 가이드는 현대 패러다임과 도구를을 활용해 현재 표준으로 자리잡은 멀티 플랫폼 그래픽스 API를 빠르게 소개하는 데 중점을 둡니다. 성능을 고려하지 않더라도 Vulkan은 OpenGL보다 현대적이고 우수한 설계를 갖추고 있습니다. 예를 들어, Vulkan에는 전역 상태 기계가 없고, 파라미터는 의미있는 멤버로 구성된 구조체를 통해 전달되며, 멀티쓰레딩 역시 상당히 간단하게 구현할 수 있습니다(실제로 OpenGL보다 Vulkan에서 멀티쓰레딩이 더 쉽습니다). 또한, 애플리케이션 코드를 변경하지 않고도 오용을 가밎할 수 있는 강력한 검증 레이어를 활성화할 수 있습니다.

더 깊이 Vulkan에 대해 학습하고 싶다면 [공식 튜토리얼](https://docs.vulkan.org/tutorial/latest/00_Introduction.html)이 권장됩니다. [vkguide](https://vkguide.dev/)와 [Vulkan Tutorial](https://vulkan-tutorial.com/) 또한 많이 참고되는 자료로, 내용이 매우 자세하게 정리되어 있습니다.

## 대상 독자

이 가이드는 이런 분들께 추천합니다.

- 모던 C++의 원리와 사용법을 이해하시는 분
- 써드파티 라이브러리를 사용해 C++ 프로젝트를 진행해보신 분
- 그래픽스에 어느 정도 익숙하신 분
- OpenGL 튜토리얼을 따라 해본 경험이 있다면 이상적입니다
- SFML / SDL과 같은 프레임워크를 사용해본 경험도 도움이 됩니다
- 필요한 모든 정보가 이 가이드 하나에 전부 담겨 있지 않아도 괜찮으신 분

이 책은 다음 내용을 다루지 않습니다.

- GPU 기반 렌더링 기법
- 그래픽스 시스템의 근본적인 구조부터 시작하는 실시간 렌더링
- 타일 기반 GPU(예: 모바일 기기나 Android)를 위한 고려 사항

## 소스코드

프로젝트의 소스코드와 본 가이드는 여기에서 확인할 수 있습니다. `section/*` 브랜치는 각 섹션의 끝에서의 코드 상태를 반영하는 것을 목표로 합니다. 버그 수정이나 일부 변경사항은 가능한 한 반영하지만, `main`브랜치의 최신 상태와는 일부 차이가 있을 수 있습니다. 가이드 자체의 소스는 오직 `main` 브랜치에만 최신 상태로 유지되며, 변경사항은 다른 브랜치로 반영되지 않습니다.
51 changes: 51 additions & 0 deletions guide/translations/ko-KR/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Summary

[소개](README.md)

# 기초

- [시작하기](getting_started/README.md)
- [프로젝트 레이아웃](getting_started/project_layout.md)
- [검증 레이어](getting_started/validation_layers.md)
- [class App](getting_started/class_app.md)
- [초기화](initialization/README.md)
- [GLFW Window](initialization/glfw_window.md)
- [Vulkan 인스턴스](initialization/instance.md)
- [Vulkan Surface](initialization/surface.md)
- [Vulkan 물리 디바이스](initialization/gpu.md)
- [Vulkan 디바이스](initialization/device.md)
- [Scoped Waiter](initialization/scoped_waiter.md)
- [스왑체인](initialization/swapchain.md)

# Hello Triangle

- [렌더링](rendering/README.md)
- [스왑체인 루프](rendering/swapchain_loop.md)
- [렌더 싱크](rendering/render_sync.md)
- [스왑체인 업데이트](rendering/swapchain_update.md)
- [동적 렌더링](rendering/dynamic_rendering.md)
- [Dear ImGui](dear_imgui/README.md)
- [class DearImGui](dear_imgui/dear_imgui.md)
- [ImGui 통합](dear_imgui/imgui_integration.md)
- [셰이더 오브젝트](shader_objects/README.md)
- [에셋 위치](shader_objects/locating_assets.md)
- [셰이더 프로그램](shader_objects/shader_program.md)
- [GLSL 에서 SPIR-V](shader_objects/glsl_to_spir_v.md)
- [삼각형 그리기](shader_objects/drawing_triangle.md)
- [그래픽스 파이프라인](shader_objects/pipelines.md)

# 셰이더 자원

- [메모리 할당](memory/README.md)
- [Vulkan Memory Allocator](memory/vma.md)
- [버퍼](memory/buffers.md)
- [정점 버퍼](memory/vertex_buffer.md)
- [Command Block](memory/command_block.md)
- [디바이스 버퍼](memory/device_buffers.md)
- [이미지](memory/images.md)
- [디스크립터 셋](descriptor_sets/README.md)
- [파이프라인 레이아웃](descriptor_sets/pipeline_layout.md)
- [Descriptor Buffer](descriptor_sets/descriptor_buffer.md)
- [텍스쳐](descriptor_sets/texture.md)
- [뷰 행렬](descriptor_sets/view_matrix.md)
- [인스턴스 렌더링](descriptor_sets/instanced_rendering.md)
3 changes: 3 additions & 0 deletions guide/translations/ko-KR/src/dear_imgui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Dear ImGui

Dear ImGui는 네이티브 CMake를 지원하지 않기 때문에, 소스를 실행 파일에 직접 추가하는 방법도 있지만, 컴파일 경고 등에서 우리 코드와 분리하기 위해 외부 라이브러리 타겟인 `imgui`로 추가할 예정입니다. 이를 위해 `imgui`는 GLFW 및 Vulkan-Headers에 연결되어야 하고, `VK_NO_PROTOTYPES`도 정의되어야 하므로 `ext` 타겟 구조에 약간의 변경이 필요합니다. 이후 `learn-vk-ext`는 `imgui` 및 기타 라이브러리들(현재는 `glm`만 있음)과 연결됩니다. 우리는 동적 렌더링을 지원하는 Dear ImGui v1.91.9 버전을 사용할 예정입니다.
137 changes: 137 additions & 0 deletions guide/translations/ko-KR/src/dear_imgui/dear_imgui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# class DearImGui

Dear ImGui는 자체적인 초기화 과정과 렌더링 루프를 가지고 있으며, 이를 `class DearImGui`로 캡슐화하겠습니다.

```cpp
struct DearImGuiCreateInfo {
GLFWwindow* window{};
std::uint32_t api_version{};
vk::Instance instance{};
vk::PhysicalDevice physical_device{};
std::uint32_t queue_family{};
vk::Device device{};
vk::Queue queue{};
vk::Format color_format{}; // single color attachment.
vk::SampleCountFlagBits samples{};
};

class DearImGui {
public:
using CreateInfo = DearImGuiCreateInfo;

explicit DearImGui(CreateInfo const& create_info);

void new_frame();
void end_frame();
void render(vk::CommandBuffer command_buffer) const;

private:
enum class State : std::int8_t { Ended, Begun };

struct Deleter {
void operator()(vk::Device device) const;
};

State m_state{};

Scoped<vk::Device, Deleter> m_device{};
};
```

생성자에서는 ImGui 컨텍스트를 생성하고, Vulkan 함수를 불러와 Vulkan을 위한 GLFW 초기화를 진행합니다

```cpp
IMGUI_CHECKVERSION();
ImGui::CreateContext();

static auto const load_vk_func = +[](char const* name, void* user_data) {
return VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr(
*static_cast<vk::Instance*>(user_data), name);
};
auto instance = create_info.instance;
ImGui_ImplVulkan_LoadFunctions(create_info.api_version, load_vk_func,
&instance);

if (!ImGui_ImplGlfw_InitForVulkan(create_info.window, true)) {
throw std::runtime_error{"Failed to initialize Dear ImGui"};
}
```

그 후 Vulkan용 Dear ImGui를 초기화합니다.

```cpp
auto init_info = ImGui_ImplVulkan_InitInfo{};
init_info.ApiVersion = create_info.api_version;
init_info.Instance = create_info.instance;
init_info.PhysicalDevice = create_info.physical_device;
init_info.Device = create_info.device;
init_info.QueueFamily = create_info.queue_family;
init_info.Queue = create_info.queue;
init_info.MinImageCount = 2;
init_info.ImageCount = static_cast<std::uint32_t>(resource_buffering_v);
init_info.MSAASamples =
static_cast<VkSampleCountFlagBits>(create_info.samples);
init_info.DescriptorPoolSize = 2;
auto pipline_rendering_ci = vk::PipelineRenderingCreateInfo{};
pipline_rendering_ci.setColorAttachmentCount(1).setColorAttachmentFormats(
create_info.color_format);
init_info.PipelineRenderingCreateInfo = pipline_rendering_ci;
init_info.UseDynamicRendering = true;
if (!ImGui_ImplVulkan_Init(&init_info)) {
throw std::runtime_error{"Failed to initialize Dear ImGui"};
}
ImGui_ImplVulkan_CreateFontsTexture();
```

sRGB 포맷을 사용하고 있지만 Dear ImGui는 색상 공간에 대한 인식이 없기 때문에, 스타일 색상들을 선형 공간으로 변환해주어야 합니다. 이렇게 하면 감마 보정 과정을 통해 의도한 색상이 출력됩니다.

```cpp
ImGui::StyleColorsDark();
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
for (auto& colour : ImGui::GetStyle().Colors) {
auto const linear = glm::convertSRGBToLinear(
glm::vec4{colour.x, colour.y, colour.z, colour.w});
colour = ImVec4{linear.x, linear.y, linear.z, linear.w};
}
ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.99f; // more opaque
```

마지막으로 삭제자(Deleter)를 생성하고 구현합니다.

```cpp
m_device = Scoped<vk::Device, Deleter>{create_info.device};

// ...
void DearImGui::Deleter::operator()(vk::Device const device) const {
device.waitIdle();
ImGui_ImplVulkan_DestroyFontsTexture();
ImGui_ImplVulkan_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
```

이 외의 나머지 함수들은 비교적 단순합니다.

```cpp
void DearImGui::new_frame() {
if (m_state == State::Begun) { end_frame(); }
ImGui_ImplGlfw_NewFrame();
ImGui_ImplVulkan_NewFrame();
ImGui::NewFrame();
m_state = State::Begun;
}

void DearImGui::end_frame() {
if (m_state == State::Ended) { return; }
ImGui::Render();
m_state = State::Ended;
}

// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
void DearImGui::render(vk::CommandBuffer const command_buffer) const {
auto* data = ImGui::GetDrawData();
if (data == nullptr) { return; }
ImGui_ImplVulkan_RenderDrawData(data, command_buffer);
}
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions guide/translations/ko-KR/src/dear_imgui/imgui_integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ImGui 통합

`Swapchain`이 이미지 포맷을 외부에 노출하도록 수정하겠습니다.

```cpp
[[nodiscard]] auto get_format() const -> vk::Format {
return m_ci.imageFormat;
}
```

`class App`은 이제 `std::optional<DearImGui>` 멤버를 담을 수 있으며, 이를 생성하는 함수를 추가하고 호출할 수 있습니다.

```cpp
void App::create_imgui() {
auto const imgui_ci = DearImGui::CreateInfo{
.window = m_window.get(),
.api_version = vk_version_v,
.instance = *m_instance,
.physical_device = m_gpu.device,
.queue_family = m_gpu.queue_family,
.device = *m_device,
.queue = m_queue,
.color_format = m_swapchain->get_format(),
.samples = vk::SampleCountFlagBits::e1,
};
m_imgui.emplace(imgui_ci);
}
```

렌더 패스를 리셋한 이후에 새로운 ImGui 프레임을 시작하고, 데모 창을 띄워봅시다.

```cpp
m_device->resetFences(*render_sync.drawn);
m_imgui->new_frame();

// ...
command_buffer.beginRendering(rendering_info);
ImGui::ShowDemoWindow();
// draw stuff here.
command_buffer.endRendering();
```

ImGui는 이 시점에서는 아무것도 그리지 않습니다(실제 그리기 명령은 커맨드 버퍼가 필요합니다). 이 부분은 상위 로직을 구성하기 위한 커스터마이징 지점입니다.

우리는 Dear ImGui를 위한 별도의 렌더 패스를 사용합니다. 이는 코드의 분리를 위한 목적도 있고, 메인 렌더 패스를 나중에 깊이 버퍼를 추가하는 것과 같은 상황에 변경할 수 있도록 하기 위함입니다 `DearImGui`는 하나의 색상 어태치먼트만 사용하는 전용 렌더 패스를 설정한다고 간주합니다.

```cpp
m_imgui->end_frame();
// we don't want to clear the image again, instead load it intact after the
// previous pass.
color_attachment.setLoadOp(vk::AttachmentLoadOp::eLoad);
rendering_info.setColorAttachments(color_attachment)
.setPDepthAttachment(nullptr);
command_buffer.beginRendering(rendering_info);
m_imgui->render(command_buffer);
command_buffer.endRendering();
```

![ImGui Demo](./imgui_demo.png)
5 changes: 5 additions & 0 deletions guide/translations/ko-KR/src/descriptor_sets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 디스크립터 셋

[Vulkan 디스크립터](https://docs.vulkan.org/guide/latest/mapping_data_to_shaders.html#descriptors)는 기본적으로 셰이더가 접근할 수 있는 자원(예 : 유니폼 버퍼, 스토리지 버퍼, 샘플러가 결합된 텍스쳐 등)에 대한 타입이 지정된 포인터입니다. 디스크립터 셋은 이러한 디스크립터들을 다양한 **바인딩**에 모아 하나의 단위로 결합한 집합이며, 셰이더는 특정 셋 번호와 바인딩 번호를 기준으로 입력을 선언합니다. 셰이더에서 사용하는 모든 디스크립터 셋은 드로우 콜 전에 반드시 업데이트, 바인딩되어야 합니다. 디스크립터 셋 레이아웃은 특정 셋 번호에 해당하는 디스크립터 셋의 구성 방식을 나타내며, 일반적으로 셰이더에서 사용하는 모든 디스크립터 셋을 나타냅니다. 디스크립터 셋은 디스크립터 풀과 원하는 디스크립터 셋 레이아웃을 이용해 할당됩니다.

디스크립터 셋 레이아웃을 구성하고 디스크립터 셋을 관리하는 것은 다양한 접근 방법이 가능하며, 각각 장단점이 있어 꽤 복잡한 주제입니다. [이 페이지](https://docs.vulkan.org/samples/latest/samples/performance/descriptor_management/README.html)에서는 그중에서도 신뢰할 수 있는 몇 가지 방법들을 설명합니다. 2D 프레임워크와 간단한 3D 프레임워크의 경우 문서에서 가장 단순한 접근 방식이라 설명하는 것처럼, 디스크립터 셋을 매 프레임마다 할당하고 업데이트하는 방식으로 처리할 수 있습니다. [이 글은](https://zeux.io/2020/02/27/writing-an-efficient-vulkan-renderer/) 매우 상세하지만 현재는 다소 오래된 자료입니다. 더 현대적인 접근법으로는 바인드리스 혹은 디스크립터 인덱싱이라 불리는 방식이 있으며,이에 대해서는 [공식 문서](https://docs.vulkan.org/samples/latest/samples/extensions/descriptor_indexing/README.html)에서 다루고 있습니다.
Loading