水の表現の挑戦 IMGUIの導入
砂浜のテクスチャを適当に貼り付けてそれっぽくしてみた。
光の反射の面が本来は水底に描いているはずなのに、動画にすると水面上に書かれているように見えるので、ここは改善点。
今回からデバッグ用にIMGUIというツールを導入してみた。
インストール
IMGUI自体はオープンソースで開発されており、GitHubからインストール可能
github.com
環境にもよりますが、zipでダウンロードするなりgitコマンドでcloneするなりしてください。
git clone https://github.com/ocornut/imgui
IMGUIの基本
graphics(glfwなど)やrendering API(DirectX, Vulkanなど)は自分で選択し、IMGUIを利用するプログラム内で自分で準備する必要がある。インストールしたフォルダの中にexampleフォルダがあり、その中に組み合わせごとにexampleが用意されているので、自分の環境にあったものを選択し参照すると良いでしょう。私の環境ではglfw + vulkanなので、これを選択しています。
includeファイル
glfw + vulkanの場合、追加でincludeするファイルは次の通り
#include "imgui/imgui/backends/imgui_impl_glfw.h" #include "imgui/imgui/imgui.h" #include "imgui/imgui/backends/imgui_impl_vulkan.h"
基本的なIMGUIの書き方
基本的には公式に用意されているexampleフォルダ(この場合はexample_glfw_vulkan)配下にあるmain.cppを参照すれば良いでしょう。私は以下のような流れで自分のプログラム側でいろいろ準備し、上記YouTubeに埋め込んでいる波のwindowとはまた別のwindowでGUIだけを描画させています。
メインウィンドウ(RayTracingの波)で用いるのと共通のVulkanコンポーネント
別の方法もあるかもしれませんが、Vulkan Instance, Vulkan Logical Device, Graphics Queue, Present QueueはメインウィンドウとIMGUIウィンドウで同じものを使用しています。
メインウィンドウとは別に新たにIMGUI用に作成するコンポーネント
以下のものは同じinstance や logical deviceから新たに作成しています。
・window
・surface
・swapchain
・depth image
・render pass
・frame buffer
・descriptor pool
initialize
必要なコンポーネントを用意したのち、下記のコマンドで初期化します。
mContext = ImGui::CreateContext(); ImGui::SetCurrentContext(mContext); ImGui_ImplGlfw_InitForVulkan(mWindow->GetWindow(), true); ImGui::StyleColorsDark(); ImGui_ImplVulkan_InitInfo init_info = {}; init_info.Instance = instance->GetInstance(); init_info.PhysicalDevice = mDevice->GetPhysicalDevice(); init_info.Device = mDevice->GetDevice(); init_info.QueueFamily = mQueue->GetQueueFamilyIndex(); init_info.Queue = mQueue->GetQueue(); init_info.PipelineCache = VK_NULL_HANDLE; init_info.DescriptorPool = mPool->GetDescriptorPool(); init_info.Allocator = VK_NULL_HANDLE; init_info.MinImageCount = 2; init_info.ImageCount = mSwapchain->GetSize(); init_info.CheckVkResultFn = nullptr; ImGui_ImplVulkan_Init(&init_info, mRenderPass->GetRenderPass());
フォントのupload
どうやらフォントのuploadが必要なようです。
一部独自のコマンドが入っていますが、やっていることはbegin commandとend commandです。
VulkanCommand::BeginCommand(mCommandBuffer.get()); ImGui_ImplVulkan_CreateFontsTexture(*mCommandBuffer->GetCommandBuffer()); VulkanCommand::EndCommand(mCommandBuffer.get()); VkSubmitInfo end_info = {}; end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; end_info.commandBufferCount = 1; end_info.pCommandBuffers = mCommandBuffer->GetCommandBuffer(); vkEndCommandBuffer(mCommandBuffer->GetCommandBuffer()); vkQueueSubmit(mQueue->GetQueue(), 1, &end_info, VK_NULL_HANDLE); vkDeviceWaitIdle(mDevice->GetDevice()); ImGui_ImplVulkan_DestroyFontUploadObjects();
Render Pass
Render Passのコマンドを実行してrenderingする関数を準備します。
これはメインループ内で使用します。
void Render(uint32_t index) { ImGui::Render(); ImDrawData* drawData = ImGui::GetDrawData(); VulkanCommand::BeginCommand(mCommandBuffer.get()); VulkanCommand::BeginRenderPass(index, mCommandBuffer.get(),mFrameBuffers[index].get()); ImGui_ImplVulkan_RenderDrawData(drawData, mCommandBuffer->GetCommandBuffer()); VulkanCommand::EndRenderPass(mCommandBuffer.get()); VulkanCommand::EndCommand(mCommandBuffer.get()); }
Present
Queueにコマンドを送り、表示させる関数を準備します。
void Present(uint32_t index) { uint32_t imageIndex; VkResult result = vkAcquireNextImageKHR(mDevice->GetDevice(), mSwapchain->GetSwapchain(), std::numeric_limits<uint64_t>::max(), mImageSemaphores[index]- >GetSemaphore(), VK_NULL_HANDLE, &imageIndex); //submit info VkSemaphore waitSemaphores[] = { mImageSemaphores[index]->GetSemaphore() }; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSemaphore signalSemaphores[] = { mRenderSemaphores[index]->GetSemaphore() }; VkSubmitInfo submitInfo0 = {}; submitInfo0.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo0.waitSemaphoreCount = 1; submitInfo0.pWaitSemaphores = waitSemaphores; submitInfo0.pWaitDstStageMask = waitStages; submitInfo0.commandBufferCount = 1; submitInfo0.pCommandBuffers = mCommandBuffer->GetCommandBuffer(); submitInfo0.signalSemaphoreCount = 1; submitInfo0.pSignalSemaphores = signalSemaphores; VkSubmitInfo submitInfo[] = {submitInfo0}; vkResetFences(mDevice->GetDevice(), 1, mFences[index]->GetFence()); if (vkQueueSubmit(mQueue->GetQueue(), 1, submitInfo, *mFences[index]->GetFence()) != VK_SUCCESS) std::runtime_error("failed to submit draw command buffer"); //present info VkPresentInfoKHR info = {}; info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; info.waitSemaphoreCount = 1; info.pWaitSemaphores = signalSemaphores; info.swapchainCount = 1; info.pSwapchains = mSwapchain->GetSwapchain(); info.pImageIndices = &imageIndex; result = vkQueuePresentKHR(*mQueuePresent->GetQueue(), &info); }
メインループ内での処理
メインループ内で以下のように実行させます。
void loop(uint32_t index) { ImGui_ImplVulkan_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); static float f = 0.0f; static int counter = 0; ImGui::Begin("Hello, world!"); ImGui::SliderFloat("float", &f, 0.0f, 1.0f); ImGui::ColorEdit3("clear color", (float*)&imguiClearColor); ImGui::End(); Render(index); Present(index); }