1. ImGUI 코드 가져오기
ImGUI를 검색해서 GitHub에서 다운로드한 후 압축을 풀고 실행을 하면 된다.
examples폴더에 sln 파일이 있으니 실행을 해본다.
얘는 별도의 lib파일로 만드는 게 아니라 풀 소스코드를 제공하고 그걸 활용하라고 떠 넘겨주는 방식이다.
example_win32_directx11를 시작 프로젝트로 하고 실행을 해보면

이렇게 켜진다.
하고 싶은 건 다 할 수 있게 되어 있다. 온갖 기능들이 다 있다.
이 코드를 그대로 가져와서 사용을 하면 된다.
파일 탐색기를 보면

횡하다는 것을 알 수 있다.
즉

여기있는 소스코드들만 다 긁어오면 사용할 수 있다는 말이 된다.
이 소스코드들로 적정 라이브러리 프로젝트로 만들어서 하면 lib파일도 추출할 수 있겠지만
그냥 이 파일들을 프로젝트에 옮겨서 작업할 수 있다.
main.cpp 코드를 관찰해 본다.
// Main code
int main(int, char**)
{
// Create application window
//ImGui_ImplWin32_EnableDpiAwareness();
WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr };
::RegisterClassExW(&wc);
HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr);
// Initialize Direct3D
if (!CreateDeviceD3D(hwnd))
{
CleanupDeviceD3D();
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 1;
}
// Show the window
::ShowWindow(hwnd, SW_SHOWDEFAULT);
::UpdateWindow(hwnd);
여기까지는 기본적인 윈도우 코드이다.
이다음부터 재밌어진다.
// 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
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
ImGUI를 세팅하는 코드가 들어가 있다.
내려가서 main loop가 있다.
// Main loop
bool done = false;
while (!done)
{
// Poll and handle messages (inputs, window resize, etc.)
// See the WndProc() function below for our to dispatch events to the Win32 backend.
MSG msg;
while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_QUIT)
done = true;
}
if (done)
break;
WinMain에서 호출한 Game->Run에 들어 있는 그 mainloop다.
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
매 프레임마다 , 업데이트할 때마다 while문 안에서 이 부분을 해준 다는 것을 알 수 있다. 매 프레임마다 이것을 초기화하고
// 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 / io.Framerate, io.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();
}
1번 2번 3번 이 부분이 딱 봐도 봤던 화면들이랑 연관성이 있는 애라는 걸 알 수 있다.
특징은 항상 Begin으로 시작해서 End로 끝내서 그 사이에 뭔가가 들어간다.
창을 그려주는 역할을 하는 것이고, 여기다 세팅을 해놨으면
// Rendering
ImGui::Render();
const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
렌더링이라고 해서
사실상
// Start the Dear ImGui frame 이 부분이 렌더 비긴이고,
// Rendering 이 부분이 렌더 엔드라고 볼 수 있다.
즉 RenderBegin, RenderEnd를 한 다음에, 그 사이에 1,2,3 이런저런 필요한 UI들을 넣어주면 되는구나 하고 익히면 된다.
필요한 기능이 있으면 이 imgui_examples.sln을 켜 본 다음에 어떤 기능이 뭘 하는지를 살펴보고, 버튼을 눌렸을 때 카운트가 올라가는 거라든가가 이 코드 안 어딘가에 있으니까 찾아보면 된다.
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
모든 코드들이 직관적이다. 우리에게 중요한 건 우리 프로젝트에 어떻게 끌어올 것인가가 관건인데,
cpp와 h 파일들의 위치를 살펴보면
imgui-master폴더와 그 안의 backends폴더에 있다. imgui-master폴더에 핵심 코드들이 있고, backends에 샘플로 만들어서 DX11에 돌아가게 만들어 준 샘플코드들이 들어가 있다는 걸 알 수 있다.
ReadMe에 적힌 내용
Backends = 플랫폼/그래픽 API와의 통합을 촉진하기 위한 도우미 코드 ( Examples에 의해 사용되며, 여러분의 앱에서도 사용되어야 함).
Examples = 플랫폼/그래픽 API와의 통합을 보여주는 독립 실행형 응용 프로그램입니다.
필요한 걸 기존의 프로젝트에 이식을 하고 시작을 하면 된다.
Engine/99. Utils에 ImGUI라는 필터를 만든다.
코드들의 위치를 물리적으로 구분하면 Properties에서 설정해 줘야 하고 귀찮아지기 때문에 Engine 폴더에 코드들을 다 넣어준다.



이 파일들을 Engine 폴더에 복붙 해준다.
그리고 코드들을 솔루션 탐색기에서 Engine\99. Utils\ImGUI에 넣는다.
이렇게 ImGUI를 사용할 준비가 끝난다.
2. EnginePch.h에 헤더들을 추가하고, Precompiled Header 세팅을 해서 프로젝트에서 ImGUI 코드를 사용할 준비하기
EnginePch.h에
// ImGUI
#include "imgui.h"
#include "imgui_impl_dx11.h"
#include "imgui_impl_win32.h"
이렇게 추가한다.
Engine을 build 한다.
에러가 나는데 전에 모든 cpp에 pch.h를 사용하도록 설정을 했었다.
넣어준 ImGUI관련 cpp파일들을 선택한 뒤 속성에 가서

Precompiled Header를 사용 안 함으로 설정한다.
그 뒤에 build를 하면 에러 없이 잘 된다.
이제 ImGUI를 프로젝트에서 사용할 준비가 끝났다.
StaticMeshDemo에서 ImGUI를 연습하고 싶다면 그 부분들을 넣어서 테스트를 해보면 된다.
3. 가져온 ImGUI 코드를 이용해 창을 띄어보기
1) ImGUIDemo 클래스를 생성하고 Main에 세팅하기
여기서 example_win32_directx11 프로젝트에서 봤던 기능들을 하나씩 갖고 온 다음에 정상적으로 실행이 되는지 보는 식으로 작업을 할 것이다.
연습을 해보자.
AssimpTool 프로젝트 폴더에서
StaticMeshDemo클래스를 복붙 해서 이름을 ImGUIDemo로 한다.
그리고 AssimpTool\Game필터에 넣어준다.
코드를 ImGUIDemo에 맞게 수정을 한다. 필요 없는 부분도 다 삭제하고 시작한다.
#pragma once
#include "IExecute.h"
class ImGuiDemo : public IExecute
{
public:
void Init() override;
void Update() override;
void Render() override;
private:
};
#include "pch.h"
#include "ImGuiDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
void ImGuiDemo::Init()
{
}
void ImGuiDemo::Update()
{
}
void ImGuiDemo::Render()
{
}
그리고 Main.cpp에서
#include "ImGuiDemo.h"
desc.app = make_shared<ImGuiDemo>(); // 실행 단위
이렇게 세팅한다.
실행을 하면 아무것도 안 보이는 검은 화면으로 뜬다.
이제 하나씩 이식을 해 볼 것이다.
어지간하면 샘플이 있으니 샘플을 분석하는 게 빠르다.
2) 초기화하는 코드를 ImGuiDemo::Init()에 배치하기
main.cpp의 main함수에서 초기화하는 부분이 있었다.
// 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
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
초기화하는 부분을 ImGuiDemo::Init()에 배치한다.
3) 매 프레임마다 해야 되는 부분을 ImGuiDemo::Update()에 배치하기
그다음에 매 프레임마다 해야 되는 부분을 찾아보면
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
이 코드를 ImGuiDemo::Update()에 배치한다.
나중에 분석을 해서 매니저 쪽으로 옮기긴 해야 한다.
4) Rendering 하는 부분을 ImGuiDemo::Render()에 배치하기
그리고 Rendering하는 부분
// Rendering
ImGui::Render();
const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
ImGuiDemo::Render()에 배치한다.
이렇게 삼총사가 들어갈 거라 예상할 수 있다.
그리고 UI 관련된 부분을 그릴 때는
void ImGuiDemo::Update()
{
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
//UI
}
ImGuiDemo::Update() 안의 //UI 부분에 그려주면 되지 않을까 생각이 든다.
물론 나중에는 Manager 코드에서 끼워 넣어서 RenderBegin, RenderEnd 느낌으로 양쪽에 묶은 다음 중간에 Update코드가 실행이 되는 게 가장 이상적이긴 하다.
//UI 부분에 UI 관련 코드를 넣는 걸 일단 목표로 하고 있다.
5) ImGuiDemo::Render()에서 SetRenderTarget, ClearRenderTarget 부분 주석처리 하기
void ImGuiDemo::Render()
{
// Rendering
ImGui::Render();
//const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
//g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
//g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
SetRenderTarget, ClearRenderTarget은 화면 그릴 때 기본적인 것이었고 엔진에서 해주고 있으니 여기서 하면 위험할 거란 생각이 든다. 주석처리 해준다.
6) Test 함수를 만들어서 example_win32_directx11의 세 개의 창에 대한 코드를 넣어주고, Test함수를 ImGuiDemo::Update()의 //UI 부분에서 호출하기
나머지 샘플 코드 1,2,3도 긁어서
void Test();
이 함수를 만들어 주고,
void ImGuiDemo::Test()
{
// 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 / io.Framerate, io.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();
}
}
여기에 넣어준다.
이게 사실상 콘텐츠를 만들어 줄 때
RenderBegin(여기선 Update) 한 다음에
여기 안에서 Test()를 호출해 주고,
그다음에 RenderEnd(여기선 Render)가 이어서 호출이 된다.
void ImGuiDemo::Update()
{
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
//UI
Test();
}
1단계 : Update
2단계: Test
3단계: Render
Update, Test, Render지만 RenderBegin, Update, RenderEnd 이런 느낌으로 실행된다.
7) 코드에서 변수로 쓰고 있는 부분을 만들어주고, 기존에 있어 넣을 수 있는 건 넣어 에러를 해결하기
코드에서 빈 부분이라 에러가 나는 부분들은 ImGuiDemo.h에
bool show_demo_window = true;
bool show_another_window = false;
Vec4 clear_color = Vec4(0.f);
이렇게 변수를 추가해 줘서 에러를 해결한다.
ImGuiDemo::Init에서
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(GAME->GetGameDesc().hWnd);// 주소값을 받는걸로 되어 있지만 코드를 보면 bd->hWnd = (HWND)hwnd;이렇게 해주기 때문에 주소를 넘기면 안된다.
ImGui_ImplDX11_Init(DEVICE.Get(), DC.Get());
이렇게 기존에 있는 것으로 채워줘서 에러를 해결한다.
주의할 점은

여기서 보면 ImGui_ImplWin32_Init이 포인터를 받아 주고 있는데 그렇다고 주소를 넣어주면 안 된다.
ImGui_ImplWin32_Init코드를 보면
bd->hWnd = (HWND)hwnd;
이렇게 다시 HWND로 바꿔주기 때문이다.
이렇게 Init, Update, Render까지 됐으면 일단 뭔가 실행이 되지 않을까 기대를 할 수 있다.
ImGuiDemo::Test에서
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
io가 없어서 에러가 나는데 중요한 부분이 아니니 주석처리를 한다.
LINK에러는 lib나 cpp가 없거나 하는 경우 발생한다. ImGUI에서 빼먹고 안 옮긴 코드가 있으면 옮기면 된다.
이제 AssimpTool 프로젝트 빌드가 통과한다.
실행을 하면

애매하게 실행이 된다. 먹통이 되어서 눌러서 반응이 없다.
8) 입력이 가능하게 수정하기
문제가 일어났을 때 구글링을 하기 전에 먼저 지금까지 알고 있는 지식을 동원해서 생각하며 헤딩을 해야 한다.
키보드 입력이랑 마우스 입력을 원래 어디서 해줬을까? 이전 코드에서 했던 기억이 있다.
Engine의 Game.cpp에서 해줬었는데,
example_win32_directx11 프로젝트에서 메시지를 받아서 조작해 주는 부분이 없는가 본다. main.cpp에
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
return true;
여기서 키보드나 마우스 입력이 오면 가로채서 처리해 주겠다는 게 들어가 있었다.
주석을 읽어 보면
// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
전방선언해서 extern으로 사용하라고 적혀있다.
이 코드를 Engine의 Game.cpp 위에 복붙 해준다.
인게임에다가 register 하는 순간에 넣어주면 되지 않을까 생각이 든다.
LRESULT CALLBACK Game::WndProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(handle, message, wParam, lParam))
return true;
이렇게 추가한다.
Engine 코드를 수정했으니 Engine을 build 하고
실행을 하면 이제 마우스와 키보드 입력이 잘 되는 것을 볼 수 있다.
이제 어딘가에 코드를 넣어준 다음에 이거를 똑같이 작업을 할 수 있게 준비를 해주면 된다.
여기까지 사용할 수 있다는 얘기는 이걸 이용해서 뭘 하더라도 다 할 수 있다고 할 수 있다.
ImGui는 사실 이게 끝이다.
4. 창을 하나 더 추가하고 옵션을 넣어가며 테스트하기
1) 창을 하나 추가하기
창을 하나 추가하며 테스트를 해보자.
ImGuiDemo::Test에서
// 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 / io.Framerate, io.Framerate);
ImGui::End();
}
코드의 이 부분을 복붙 해가지고
// TestCode
{
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("Nako Hello ImGui!");
//ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}
이렇게 해준다.
실행하면 어떻게 나올까?

Hello, world! 창이 하나 더 생기지 않고 Nako Hello ImGui! 가 같은 창에 뜨고 있다.
ImGui::Begin("Hello, world!");에서 "Hello, world!"가 키값처럼 맞춰져서 추가된 게 아닌가 싶다.
그래서 Begin에 들어가는 값을 바꿔 보면
ImGui::Begin("Hello, world! 22");
이렇게 수정해서 실행해 보면

이렇게 별도의 창으로 뜬다.
앞으로 하나의 창으로 할 거면 키값으로 동일한 이름을 Begin에 계속 넣어서 사용하면 되는 것이고,
별도로 할 것이면 Begin에 새로운 이름을 넣어서 창을 만들어 넣어서 하면 되는 것이다.
필요한 기능이 있으면

여기서 살펴보면서 체크 박스는 어떻게 만들었을까 코드를 보고 그대로 하면 된다.
이제부터는 문서를 찾아서 하는 것이지, 옆에서 가르쳐 주는 개념이 아니다.
UI는 대부분 이렇다.
UI가 일반적인 유니티처럼 드래그 앤 드롭으로 할 수 있는 게 아니라
언리얼의 slate 같은 느낌으로 코드로 UI인터페이스를 만들어서 하나씩 그려줘야 한다는 게 조금 귀찮긴 하지만 C++에서 이만한 게 없으니까 많은 사람들이 사용하는 것일 것이다.
엄청 어려운 부분은 일단 없다고 볼 수 있다.
앞으로 실습을 할 때 컬러를 바꿔주고 싶다면 변수를 세팅하는 식으로 동작을 시켜주면 툴을 이용해 고칠 수 있다. 얼마든지 사용할 준비가 끝났다.
이제부터는 문서를 찾아보는 게 좋을 것이다.
2) 창에 TitleBar를 없애 움직이지 않게 옵션을 줘보기
예를 들어서 Begin이 여러 가지 기능이 있을 수 있을 텐데

이런 식으로 옵션을 가진 것을 볼 수 있다.
ImGui::Begin("Nako", nullptr,
ImGuiWindowFlags_NoTitleBar);
이렇게 해주고 ImGuiWindowFlags_NoTitleBar에서 f12를 눌러보면 옵션이 굉장히 많다는 것을 볼 수 있다. 주석을 안 읽어 봐도 보면 어떤 기능인지 알 수 있기 때문에 골라서 옵션을 사용하면 된다.
예를 들어 여기서 움직이면 안 되게 한다면
ImGui::Begin("Nako", nullptr,
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoMove);
ImGuiWindowFlags_NoTitleBar = 1 << 0,
ImGuiWindowFlags_NoResize = 1 << 1,
ImGuiWindowFlags_NoMove = 1 << 2,
이렇게 비트플래그로 되어 있기 때문에 섞어서 옵션을 줄 수 있다.

실행을 해보면 이렇게 TitleBar가 사라지고 이동이 안되게 만들어진 것을 볼 수 있다.
이런 식으로 감각을 갖고 해 보면 된다.
여기까지 하면 대부분의 중요한 내용은 다 다룬 것이다.
5. ImGuiManager를 만들어 Init, Update, Render에 ImGuiDemo의 코드를 옮겨 범용적으로 사용하게 하고, ImGuiDemo에는 Test코드만 남겨 같은 결과가 나오게 하기
1) ImGuiManager클래스를 생성해 코드를 옮기기
Engine/02. Managers 필터에
ImGuiManager클래스를 생성한다.
여기에 핵심적인 오늘 연구한 코드들을 넣어두고 범용적으로 사용할 수 있게 만들어준 다음에 여기다가 새로 추가된 내용 중에서 resize 등 뭐가 필요할지 스타일을 정해서 그 스타일대로 일관성 있게 만들기를 원한다거나 하면 그거를 얼마든지 추가해서 업데이트하며 만들 수 있을 것이다. 이런 식으로 작업을 하면 된다.
일단 매니저니 Declare 하고 Init을 하면 된다.
#pragma once
class ImGuiManager
{
DECLARE_SINGLE(ImGuiManager);
public:
void Init();
void Update();
void Render();
};
#include "pch.h"
#include "ImGuiManager.h"
void ImGuiManager::Init()
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(GAME->GetGameDesc().hWnd);
ImGui_ImplDX11_Init(DEVICE.Get(), DC.Get());
}
void ImGuiManager::Update()
{
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
}
void ImGuiManager::Render()
{
// Rendering
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
연구했던 기능들을 다 옮겨 놨다.
Init에서 초기화하는 코드를 넣어주고,
Update는 RenderBegin 같은 거였고,
Render는 실질적으로 다 그렸으면 하는 RenderEnd 같은 부분이었다.
이렇게 하고 그때 그때마다 뭔가를 할 때 코드를 하나씩 추가를 하도록 한다.
2) ImGuiManager를 간단히 사용할 수 있게 매크로를 추가하고, Game::Run에서 ImGuiManager의 Init을 호출하고, EnginPch에 #include로 추가해 세팅하기
Engine/99. Headers/Define.h에다가
#define GUI GET_SINGLE(ImGuiManager)
이렇게 넣어주고
최종적으로 초기화, 업데이트, 렌더 하는 부분 3가지 아이를 어딘가에다가 끼워 넣어줘야겠다 생각이 든다.
지금 작업하고 있는 프로젝트에서는 main 프레임워크를 Game.cpp에서 관리하고 있기 때문에
Game.cpp에 가서
Game::Run에
GUI->Init();
이렇게 추가해 GUI를 사용할 준비를 해주고,
EnginePch.h에
#include "ImGuiManager.h"
이렇게 추가한다.
Manager는 괜찮은데 일반 클래스를 여기에 너무 많이 넣으면 헤더가 꼬일 수 있으니 주의한다.
Engine프로젝트를 리빌드 한다.
3) Game::Update에서 ImGuiManager의 Update, Render를 호출해 ImGuiManager를 만들기 이전과 같은 결과가 나오게 하기
Game::Update에서 뭔가를 넣어주면 된다.
이건 테스트를 해봐야 한다. GUI의 Update, Render가 RenderBegin, RenderEnd 같은 느낌이니까
여러 가지 테스트해보다가 이렇게 하는 게 좋은 거 같았다.
void Game::Update()
{
TIME->Update();
INPUT->Update();
GRAPHICS->RenderBegin();
GUI->Update();
_desc.app->Update();
_desc.app->Render();
GUI->Render();
GRAPHICS->RenderEnd();
}
app에 대한 코드가 Contents단에서 ImGuiDemo 같은 데서 실행되는 코드를 말한다.
여기서 ImGuiDemo::Test 하는 거처럼 이런저런 내용들을 넣어주면
최종적으로
GUI->Update();에는 ImGuiDemo::Update()에 있던 코드가,
_desc.app->Update();에는 ImGuiDemo::Update()에서 호출되는 ImGuiDemo::Test() 코드가,
_desc.app->Render();에는 다 옮겨 줬기 때문에 ImGuiDemo::Render()가 비어 있고,
GUI->Render();에는 ImGuiDemo::Render()에 있던 코드가 실행되므로
ImGuiManager를 만들어 주기 전과 똑같은 결과가 나오는 것을 예상할 수 있다.
void ImGuiManager::Render()
{
// Rendering
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
ImGui::Render();나 ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());를 지워도 되는지 실험해 봤을 때 지우면 안 되는 필수적인 코드라는 걸 알 수 있었다.
ImGui_ImplDX11_RenderDrawData가 궁금하면 f12로 들어가서 하나씩 살펴보면 렌더링 파이프라인 스테이트를 다 바꿔버리고 있다. 렌더를 해주고, 근데 끝나는 부분에서 다시 // Restore 복원해주고 있다. 잠시 건드렸다가 렌더 했다가 복원하는 식으로 동작하고 있는 것이다.
살펴보면 지금 까지 배운 내용들이 많이 때문에 적응을 잘할 수 있다.
4) ImGuiDemo에서 ImGuiManager로 옮겨준 내용들을 주석처리 하기
여기까지 했다면 ImGuiDemo에서 ImGuiManager로 옮겨준 내용들을 날려주면 된다.
void ImGuiDemo::Init()
{
// 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
// Setup Dear ImGui style
//ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
//ImGui_ImplWin32_Init(GAME->GetGameDesc().hWnd);// 주소값을 받는걸로 되어 있지만 코드를 보면 bd->hWnd = (HWND)hwnd;이렇게 해주기 때문에 주소를 넘기면 안된다.
//ImGui_ImplDX11_Init(DEVICE.Get(), DC.Get());
}
void ImGuiDemo::Update()
{
// Start the Dear ImGui frame
//ImGui_ImplDX11_NewFrame();
//ImGui_ImplWin32_NewFrame();
//ImGui::NewFrame();
//UI
Test();
}
void ImGuiDemo::Render()
{
// Rendering
//ImGui::Render();
//const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
//g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
//g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
//ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
다 정상적으로 옮겨놓았다고 하면 다 주석처리를 해주고
Manager를 통해가지고도 정상적으로 되는지 확인하면 된다.
5) 실행하고 잘 되는지 확인하기
Engine을 빌드하고 실행을 하면

이렇게 잘 나오면 ImGui를 사용할 준비가 끝났다.
MFC에 비해 훨씬 좋다.
필요한 기능이 있을 때마다 연구해서 사용하는 것을 권장한다.
ImGui를 사용할 때 분석해서 사용하기보다 샘플을 보고 조립해서 사용하는 것이기 때문에 뚝딱뚝딱 만들면 된다.
헤딩하는 작업을 하다 보면 본인만의 루틴이 생긴다.
라이브러리 다운하고, 설치하고, 샘플 보고, 분석하고, 작업하고
엔진뿐만 아니라 콘텐츠부도 마찬가지다.
6. 후기
디버그를 할 때 아무리 프로젝트의 properies를 확인해도 계속 엉뚱한 위치에 있는 다른 버전의 ImGUI 코드를 사용하고 에러를 일으키길래 그 코드를 삭제했더니 이제 디버깅도 안되고 계속 에러를 일으켜 원인을 찾는데 시간이 많이 걸렸다. 그러다가 예전에 vcpkg를 이용해 ImGUI를 설치한적이 있었다는 것을 기억났고, 그냥 탐색기에서 코드를 삭제하면 안되고 vcpkg를 이용해 삭제를 해야 한다는걸 알 수 있었다. 정말 별거 아닌 거였는데도 많은 가설을 세우고 디버깅을 하고 chat gpt에게 물어보고 하며 겨우 도달할 수 있었다. vcpkg를 이용해 라이브러리를 설치하면 VisualStudio 전체에 영향을 줘서 다른 프로젝트에도 전부 영향을 주기 때문에 사용 시 주의해야 하고 사용 안할 라이브러리는 잊지말고 삭제해야 겠다.
'DirectX' 카테고리의 다른 글
59. 애니메이션_스키닝 (0) | 2024.03.03 |
---|---|
58. 애니메이션_애니메이션 이론 (0) | 2024.03.02 |
56. 모델_계층 구조 (0) | 2024.02.29 |
55. 모델_모델 띄우기 (0) | 2024.02.28 |
54. 모델_Bone, Mesh 로딩 (0) | 2024.02.23 |
댓글