imgui에서 Image를 통해 3D element를 렌더링 하는 방법
imgui UI 내에 scene을 그리는 예제를 만들어봤다.
이를 통해서 GLFW Window(Context)를 동시에 처리 하는 방법을 익힐 수 있었다.
또 그래픽스 라이브러리를 사용할 때 주의점이나 팁, 그리고 멀티스레드로 OpenGL을 렌더링하는 방법 등에 대해서 다룬다.
중간중간 이미지로 삽입된 코드는 GitHub 저장소에 올려놨다.
VS2017 환경에선 다운 받아서 바로 돌릴 수 있게 정리해뒀다.
https://github.com/hyunjun529/WIN_VS_GL_Setups/blob/master/4_imp_scene_in_imgui/main.cpp
imgui
Bloat-free Immediate Mode Graphical User interface for C++ with minimal dependencies
쉽게 설명하면 UI를 운영체제가 아니라 그래픽스 라이브러리를 통해서 그린다는 소리다.
아래는 imgui를 예시 목록이다.
그래픽스 에디터, 게임 툴, 엔진, 유체 시뮬레이션, Mod 제작 등에 사용되고 있다.
사전 조사
처음 이미지처럼 imgui window 내에 scene을 만들기 위한 예제를 찾아봤다.
사실 imgui window의 image를 통해서 렌더링하는 3D Element, widget 등에 정해진 명칭은 없다. 이 글에서는 해당 요소가 Unity 같은 게임 엔진에서 비슷한 역할을 한다는 의미로 scene이라 부른다.
근데 원리를 설명해둔 부분이나 깔끔한 예제 코드가 없어서 직접 공부해야 했다.
보통 내가 잘 모르겠으면 남들도 잘 모르는 거다. imgui 이슈트래커에 가장 비슷한 이슈로는 아래 두 가지가 있었다.
How to render 3D scene within UI Element ?
https://github.com/ocornut/imgui/issues/475
Rendering am image
https://github.com/ocornut/imgui/issues/561
기본 원리
핵심은 2가지다.1. GL Context를 별도로 생성하고 관리해야한다.
2. imgui Image()는 Image를 Texture로 그리는거다
Image()는 이름만 같고 운영체제에서 GUI 코딩이나 웹 프론트엔드의 개념과 전혀 다름을 명심하자.
scene을 생성하기 위해서는 다음과 같은 과정을 거친다.
1. scene을 렌더링할 OpenGL Context를 하나 더 생성한다.
2. 이를 위해서 GLFWwindow를 하나 더 생성한다(hidden).
3. hidden은 실제 화면에 그리지 않고 백그라운드에서 렌더링만 수행한다.
4. hidden에서 렌더링된 화면을 픽셀로 저장한다.
5. 4번에서 저장한 픽셀을 main에 전달한다.
6. main에서 imgui Image()로 hidden의 픽셀을 텍스쳐로 렌더링한다.
구현
이미지가 아닌 실제 코드는 아래 링크해둔 저장소에 있고 WIN10, VS2017로 바로 실행할 수 있도록 정리해놨다.https://github.com/hyunjun529/WIN_VS_GL_Setups/blob/master/4_imp_scene_in_imgui/main.cpp
위와 같이 GLFWwindow를 관리하는 클래스를 따로 만들어둔다.
70줄에 있는 *window는 저렇게 쓰면 좋지 않지만 이후 설명을 위해서 저렇게 해뒀다.setup()과 render()를 기본적으로 따로 관리해두자.
setup은 렌더링할 객체를 세팅하는 부분, render는 렌더링 루프에서 매번 실행할 함수다.
main 함수에서에서 GLWindow 클래스를 2개 생성한다.
main은 imgui가 있는 화면, hidden은 scene에 삽입할 화면이다.
window_main과 window_hidden을 각각 setup()해준다.
setup() 함수는 vertex binding, shader program을 세팅한다.
ImGui를 초기화 할 때 window_main를 사용하도록 해준다.
window_hidden은 glfwHideWindow를 설정해서 백그라운드에서 동작하도록 한다.
다른 옵션도 찾아보면 더 있는거 같지만 나중에 show()로 확인하기 위해서 사용.
그리고 glfwMakeContextCurrent가 핵심이다.
http://www.glfw.org/docs/latest/group__context.html#ga1c04dc242268f827290fe40aa1c91157
이 함수를 호출하는게 번거롭지만 멀티스레드 환경의 렌더링을 편하게 지원해준다.
가장큰 문제는 스코프를 명시하지 못하니 나중에 클래스로 묶을 때 주의해야한다.
glfwGetCurrentContext로 현재 Context를 확인하는 것도 방법이다.
http://www.glfw.org/docs/latest/group__context.html#gac84759b1f6c2d271a4fea8ae89ec980d
렌더링을 수행하는 루프.
보다시피 window_hidden과 window_main을 따로 렌더링한다.
window_hidden을 렌더링 한 후 glReadPixels로 렌더링된 화면을 저장한다.
하지만 이 함수로 얻은 이미지를 바로 사용하면 상하 반전(flip, flipped)되어 있다.
https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadPixels.xhtml
lower left corner에서 0.0으로 잡았기 때문인데 이는 아래 그림과 같다.
만약 영상처리나 파일 저장의 경우 일반적으로 역순으로 출력하면 된다.
하지만 우리는 이를 텍스쳐로 바로 옮겨야한다.
가장 쉬운 접근은 for문으로 역으로 뒤집는 방법을 떠올릴 수 있겠지만 우리는 이 픽셀 데이터를 텍스쳐로 다룬다는 사실을 명심하자.
http://dogfoottech.tistory.com/59
듀토리얼에도 따로 문서화도 없는 이유는 이 라이브러리를 쓰는 사람이 기본적으로 이런 지식을 알고 있으리라 기대하기 때문이다.
사전 조사에서 봤던 2가지 이슈도 이래서 해맸을 가능성이 높다.
ImGui::Image 함수를 살펴보자.
https://github.com/ocornut/imgui/blob/master/imgui.cpp#L6502
함수 인자에 uv0, uv1이 있다. 그리고 우리가 저장한 픽셀 데이터는 Texture2D로 처리된다.
즉 imgui image는 평면 GUI로 보이지만 Image를 삽입하기 위해서, 또 Plot, Font를 처리하기 위해서 Texture를 사용한다.
상하 반전을 하기 위해서 Texture를 거꾸로 씌우면 된다. 이에 관한 내용은 uv를 참조
http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-5-a-textured-cube/
위 과정을 통해서 scene 기능을 구현할 수 있었다.
---
여담
Bullet Physics 2.87 ExampleBrowser
이 블로그에서 계속 OpenGL이나 GLFW, imgui 등을 다루고 있는 이유는 최종적으로 Bullet Physics 같은 엔진(?)을 구현하기 위함이다.
18년 2월내로 기본적인 기능은 완성하는게 목표고 중간중간 구현했던 내용을 블로그에 남기는 중이다.










