[DirectX] IMGUI


IMGUI를 이용해서 에디터를 만들어낼 것이다.

IMGUI란?


IMGUI는 게임을 만들 때 사용하는 에디터이다.

IMGUI


IMGUI 깃허브링크

상술한 링크에서 코드를 다운로드 받을 수 있다.

많은 버전 중에서 위의 버전은 도킹버전인데, 도킹버전을 선택한 이유는

  • UI가 게임 창 밖으로 나갈 수 있다.

UI

  • UI를 붙였다가 떼었다가 할 수 있다.


많은 게임회사들에서는 자체 엔진이나 언리얼, 유니티 엔진을 쓰지만,

간단한 툴을 만들 때에는 IMGUI를 사용한다.

IMGUI 띄우기


imgui_demo.cpp


imgui_demo.cpp 코드가 데모, 예시 코드이다. 이 코드를 통해 어떤 기능이 수행되는지 참고하면 된다.

자기 프로젝트에서 IMGUI 초기화 하기


main.cpp에서 참조할 헤더파일

  • imgui.h
  • imgui_impl_win32.h
  • imgui_impl_dx11.h
// Client 프로젝트의 main.cpp 중에서

// 전역 변수:
HINSTANCE   hInst;                                // 현재 인스턴스입니다.
HWND        g_hWnd;

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    // _CrtSetBreakAlloc(270);
   
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    // CEngine 초기화
    if (FAILED(CEngine::GetInst()->init(g_hWnd, 1280, 768)))
    {
        return 0;
    }

    // Editor 초기화
    CEditorObjMgr::GetInst()->init();


    // ImGui 초기화
     // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;       // Enable Keyboard Controls
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;           // Enable Docking
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;         // Enable Multi-Viewport / Platform Windows
    //io.ConfigViewportsNoAutoMerge = true;
    //io.ConfigViewportsNoTaskBarIcon = true;
    //io.ConfigViewportsNoDefaultParent = true;
    //io.ConfigDockingAlwaysTabBar = true;
    //io.ConfigDockingTransparentPayload = true;
    //io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleFonts;     // FIXME-DPI: Experimental. THIS CURRENTLY DOESN'T WORK AS EXPECTED. DON'T USE IN USER APP!
    //io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleViewports; // FIXME-DPI: Experimental.

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();
    //ImGui::StyleColorsLight();

    // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
    ImGuiStyle& style = ImGui::GetStyle();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        style.WindowRounding = 0.0f;
        style.Colors[ImGuiCol_WindowBg].w = 1.0f;
    }

    // Setup Platform/Renderer backends
    ImGui_ImplWin32_Init(g_hWnd);
    ImGui_ImplDX11_Init(DEVICE, CONTEXT);






    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CLIENT));
    MSG msg;
    
    bool show_demo_window = true;
    bool show_another_window = false;
    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    while (true)
    {
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            if (WM_QUIT == msg.message)
                break;

            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        else
        {
            CEngine::GetInst()->progress();
            CEditorObjMgr::GetInst()->progress();

            // ImGui Update               
            ImGui_ImplDX11_NewFrame();
            ImGui_ImplWin32_NewFrame();
            ImGui::NewFrame();

            // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).            
            if (show_demo_window)
                ImGui::ShowDemoWindow(&show_demo_window);

            // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
            {
                static float f = 0.0f;
                static int counter = 0;

                ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.

                ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)
                ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state
                ImGui::Checkbox("Another Window", &show_another_window);

                ImGui::SliderFloat("float", &f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
                ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color

                if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
                    counter++;
                ImGui::SameLine();
                ImGui::Text("counter = %d", counter);

                ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
                ImGui::End();
            }

            // 3. Show another simple window.
            if (show_another_window)
            {
                ImGui::Begin("Another Window", &show_another_window);   // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
                ImGui::Text("Hello from another window!");
                if (ImGui::Button("Close Me"))
                    show_another_window = false;
                ImGui::End();
            }

            // ImGui Rendering
            ImGui::Render();           
            ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

            // Update and Render additional Platform Windows
            if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
            {
                ImGui::UpdatePlatformWindows();
                ImGui::RenderPlatformWindowsDefault();
            }

            // 렌더 종료
            CDevice::GetInst()->Present();
        }       
    }

    // ImGui Release
    ImGui_ImplDX11_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();

    return (int) msg.wParam;
}

위와 같이 IMGUI 프로젝트의 main.cpp로부터 코드를 가져온다.

그리고 Client 프로젝트의 main.cpp에서 IMGUI의 이벤트, 메세지에 대한 처리 작업 또한 구현해주어야 한다.

// Client 프로젝트의 main.cpp 중에서


extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam))
        return true;

    switch (message)
    {
    case WM_DPICHANGED:
        if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
        {
            //const int dpi = HIWORD(wParam);
            //printf("WM_DPICHANGED to %d (%.0f%%)\n", dpi, (float)dpi / 96.0f * 100.0f);
            const RECT* suggested_rect = (RECT*)lParam;
            ::SetWindowPos(hWnd, NULL, suggested_rect->left, suggested_rect->top, suggested_rect->right - suggested_rect->left, suggested_rect->bottom - suggested_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
        }
        break;

    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석합니다:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;


    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;



    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}


버그 수정하기


IMGUI가 창 밖으로 나갔다가 다시 들어오면 UI가 사라지는 현상이 발생한다. 이를 어떻게 수정할까?

현재 렌더타겟을 스왑체인이 관리하고 있다. 이게 IMGUI가 사라지는 것이랑 무슨 상관이 있을까?

상술하였듯이, IMGUI Docking 버전의 장점은 UI가 창 밖으로 나갈 수 있다는 것이다.

이 때, 창 밖으로 나간 UI가 새로운 창이 되는 것이며, 새로운 스왚체인을 생성하게 된다.

이말인 즉슨, 출력하는 윈도우, 렌더 타겟이 달라지게 된다.

이런 현상이 왜 발생했냐면, 디바이스를 초기화 할때

// CDevice.cpp 에서
	
	// 출력 타겟 설정
    m_Context->OMSetRenderTargets(1, m_RTV.GetAddressOf(), m_DSV.Get());

타겟을 오직 메인 창만을 가리키도록 설정했기 때문이다.

그래서 출력 타겟 지정을 CRenderMgr.cpp에서 실시해야한다.

이를 위해서 각기 코드를 아래와 같이 작성한다.

// CDevice.h 중에서

public:
	void OMSet() { m_Context->OMSetRenderTargets(1, m_RTV.GetAddressOf(), m_DSV.Get());}
// CRenderMgr.cpp 파일 중에서 렌더링 함수


그래서 매번 렌더링 할때마다 렌더 타겟을 알맞게 바꿔주고, UI가 밖으로 나갔다가 들어왔다가 할 때마다 스왚체인이 새로 생성되어 안정적으로 구동이 된다.

IMGUI의 UI 별 구성 살펴보기


아래와 같이 데모코드가 구성되어 있다.

// ImGui Update               
	ImGui_ImplDX11_NewFrame();
	ImGui_ImplWin32_NewFrame();
	ImGui::NewFrame();

	// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).            
	if (show_demo_window)
		ImGui::ShowDemoWindow(&show_demo_window);

	// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
	{
		static float f = 0.0f;
		static int counter = 0;

		ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.

		ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)
		ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state
		ImGui::Checkbox("Another Window", &show_another_window);

		ImGui::SliderFloat("float", &f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
		ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color

		if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
			counter++;
		ImGui::SameLine();
		ImGui::Text("counter = %d", counter);

		ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
		ImGui::End();
	}

	// 3. Show another simple window.
	if (show_another_window)
	{
		ImGui::Begin("Another Window", &show_another_window);   // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
		ImGui::Text("Hello from another window!");
		if (ImGui::Button("Close Me"))
			show_another_window = false;
		ImGui::End();
	}


UI 띄우기


ImGui::Begin("TestUI");

UI의 이름을 부여할 경우 Begin 안에 String을 넣어준다.


UI에 내용 넣기


ImGui::Text("Test String")

UI 안에 내용을 넣고자 할 경우 위와 같이 함수에 String을 부여한다.

주의사항! 같은 String값이 있으면 안된다!

같은 String을 넣고 싶다면?

ImGui::Text("Test String##1")
ImGui::Text("Test String##2")

위와 같이 # 2개를 넣고 다른 번호를 부여하면 된다.
# 2개 뒤에 있는 문자는 노출이 되지 않는 점을 이용한다.

UI에 버튼 넣기


ImGui::Button("Test Button");

버튼을 만들고 버튼에 이름을 상기한 바와 같이 불일 수 있다.

UI 구성 완료하기


ImGui::End();

위 코드를 통해 UI 하나 구성을 완료한다.

창 디자인 해보기


bool bTestUI = true;

if(bTestUI)
{
	ImGui::Begin("TestWindow", &bTestUI)
	ImGui::Text("Test String");
	ImGui::Button("Test Button");

	if (ImGui::BeginChild("ChildUI"))
	{
		ImGui::Text("Child Part");
		ImGui::EndChild();
	}

	ImGui::End();
}

그리고 결과는 아래 그림과 같다.

Result


코드 별로 UI에 반영되는 것은 imgui_demo.cpp를 뜯어봄으로써 알 수 있다.


© 2022.07. by Wookey_Kim

Powered by Hydejack v7.5.2