Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

水の表現の挑戦 IMGUIの導入

youtu.be

砂浜のテクスチャを適当に貼り付けてそれっぽくしてみた。
光の反射の面が本来は水底に描いているはずなのに、動画にすると水面上に書かれているように見えるので、ここは改善点。

今回からデバッグ用に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);
}