2018년 2월 23일 금요일

obj viewer 구현 및 정리

obj viewer 구현


구현한 프로그램 동작 예

obj loader로 파일을 불러온 뒤 텍스쳐(마테리얼?) 여러장을 Modern OpenGL로 출력하는 깔끔한 예제가 없어서 만들어 봤습니다. 구현하면서 공부한 내용들을 정리합니다.

전체 소스 코드는 아래 GitHub저장소에서 받을 수 있습니다.
Win10, VS2017에서 따로 설치하지 않고 바로 돌려볼 수 있습니다.
https://github.com/hyunjun529/WIN_VS_GL_Setups/tree/master/6_obj_viewer

쉐이더(Shader)와 VAO, VBO를 효율적으로 사용하는 방법과 관심사의 분리(SoC)를 최대한 고려했습니다. 저장소에 올린 예제 코드 중 이벤트 처리와 파일 입출력 부분은 더 손댔다가 나중에 알아보기 힘들어질 것 같고 양적인 작업이라 미뤄뒀습니다.

MMD의 .pmx 파일에서 .obj 파일로 변경

MMD는 MikuMikuDance의 약어입니다. 유튜브에 찾아보면 많습니다.
http://www.geocities.jp/higuchuu4/index.htm

MMD에서 캐릭터 모델링에 .pmx 파일을 사용합니다.
나중에 다시 살펴보겠지만 pmx 형식은 캐릭터 모델과 강체, 관절을 포함하고 있습니다.
https://gist.github.com/felixjones/f8a06bd48f9da9a4539f

버츄얼 유튜버 키즈나 아이로 테스트해보고 싶습니다.
https://kizunaai.com/download-page/

Blender에 pmx 파일을 불러오기 위해선 아래 글을 참고합니다.
http://mmdguide.tistory.com/698

Blender mmd_tools 최신 버전은 여기서 받을 수 있습니다.
https://github.com/sugiany/blender_mmd_tools


위 과정을 통해서 블랜더에서 불러올 수 있게 됬습니다.
이걸 그대로 obj 파일로 추출(Import)해보면...


윈도우 10의 3D 그림판이나 뷰어가 있으니 그걸로 열어볼 수 있습니다.
이렇게 하면 위와 같이 좀 괴상하게 나옵니다.

.pmx 파일은 블랜더에서 위와 같이 보입니다,
joints는 관절 부분, rigidbodies는 강체를 나타냅니다.
obj로 바로 추출할 경우 강체(rigid body)가 시각화되어 같이 출력됩니다.

관절과 강체를 제거하고 다시 추출하면 위와 같이 깔끔한 obj 파일을 얻을 수 있습니다.

그리고 obj 파일을 생성하면 mtl 파일과 텍스쳐들이 같이 생성되는데 일단 텍스쳐를 obj로 생성된 물체에 입히기 위한 것이라고 생각하고 넘어갑니다.

윈도우10 그림판3D로 열어보면 위와 같이 obj / mtl 파일이 생성됬음을 확인할 수 있습니다.

이제 이 키즈나 아이의 obj 파일을 직접 코딩한 프로그램으로 출력해봅니다.

Wavefront .obj / .mtl 파일 분석

obj는 object, mtl은 material의 약어입니다.

.obj 파일의 포맷
http://paulbourke.net/dataformats/obj/

.mtl 파일의 포맷
http://paulbourke.net/dataformats/mtl/


위 두 포맷을 설명하는 간단한 캡슐 예제가 같이 있습니다.
http://paulbourke.net/dataformats/obj/minobj.html

이런 표준을 읽으려면 각 단어가 무엇을 의미하는지 정확히 파악하는게 중요합니다.
https://en.wikipedia.org/wiki/3D_computer_graphics

여기서 키즈나 아이 모델과 캡슐에서 사용되는 statement call 들을 위주로 살펴봅니다.

.obj 관련

mtllib (filename1 filename2 . . .)
mtl + lib의 약어
mtl 파일을 불러오는데 사용한다.
이후 usemtl (name) 형식으로 f와 함께 사용한다.
버텍스 표면의 마테리얼(material)과 텍스쳐(Texture)를 정의할 때 사용한다.
마테리얼은 버텍스로 표현된 물체 표면에 빛이 부딪힐 때 처리하는 방법을 표시한다.
텍스쳐는 마테리얼의 색상이나 반사율(albedo) 표시하거나 bump 또는 normal map의 표면을 제공하는데 사용한다.

o (object_name)
object의 약어.
default도 아니고 자주 사용되는 문법도 아니지만 Blender로 obj파일을 생성하면 해당 mesh object 이름을 명명할 때 사용되기도 한다.

v (x y z w)
vertex의 약어
버텍스 각 정점의 좌표.
이 글의 예제에선 x y z 만 사용한다.
w를 지정하지 않은 경우 default 1.0으로 지정된다.

vt (u v w)
vertex texture의 약어
texture vertex의 좌표를 나타낸다.
u v w는 각각 다음과 같은 의미를 갖고 있다.
u 텍스쳐의 평행 방향(horizontal direction)
v 텍스쳐의 수직 방향(vertical direction), 기본 값은 0.
w 텍스쳐의 깊이(depth of the texture), 범위는 0-1.
깊이는 이 예제에서 사용되지 않았다.

vn (i , j, k)
vertex nomals의 약어
좌표가 아니라 3차원 벡터의 형식(i, j, k)으로 표현된다.
Vertex Normal에 대한 개념은 위키피디아에 잘 설명되어있다.
https://en.wikipedia.org/wiki/Normal_mapping

usemtl (material_name)
use + mtl의 약어
이제부터 나오는 f는 해당 mtl 객체를 사용한다고 명시하는 용도다.

f (v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 . . .)
face의 약어
v1/vt1/vn1에서 1은 각 버텍스의 레퍼런스(reference) 번호인데, 이 번호는 각 v, vt, vn이 선언된 순서에 따른다.
f는 표면에 텍스쳐를 어떻게 입혀야 하는가 좌표를 담고 있다. 자세한 원리는 .mtl에서 다룬다.
f의 arg1, 2, 3은 vertex indices를 의미한다.

그리고 counterclockwise라는 용어가 등장하는데 이는 역시계방향을 의미한다.

참고) 왼손/오른손 좌표계, 시계/반시계 방향, xyz/xzy 와 신난다!

capsule은 f의 v1/vt1/vn1이 모두 동일한 숫자를 사용한다.
하지만 키즈나아이의 경우 다른 숫자를 사용하기도 한다.
키즈나아이의 obj 파일에서 v, vt, vn의 reference의 수는 다음과 같다.
v 5 - 47555 = 47550
vt 47556 - 95106 = 47550
vn 95107 - 142349 = 47242
vn의 경우 obj 파일의 용량을 줄이기 위해서 중복되는 벡터를 생략한 결과 수가 줄어든 것이다.
v와 vt가 다른건 나와 비슷한 의문을 가진 사람이 StackOverflow에 있었다.
vertex와 vertex texture의 reference call 순서가 동일할 필요가 없기 때문이다.
https://stackoverflow.com/questions/29867926/why-does-the-number-of-vt-and-v-elements-in-a-blender-obj-file-differ

.mtl 관련

newmtl
new + mtl의 약어,
새 마테리얼을 정의한다.

Ns (exponent)
specular의 초점(exponent)를 나타낸다.

Ka (r g b)
ambient를 나타낸다.
여기서 접두어 K는 RGB로 이뤄진 값을 의미한다.
mtl 파일은 RGB를 0-1의 실수로 정의한다.

Kd (r g b)
diffuse를 나타낸다.

Ks (r g b)
specular를 나타낸다.

Ke (r g b)
coeficient를 나타낸다.
주의) 1995년 표준에는 적혀있지 않는 이후 추가된 feature다.
http://paulbourke.net/dataformats/mtl/

Ni (optical_density)
광학적 밀도, 굴절(refraction)을 의미하기도 한다.

d (factor)
해당 물체의 흩어짐 정도(dissolve)를 표현한다.

illum
illumination model, 마테리얼에 적용될 조명 모델을 결정한다.
 0 Color on and Ambient off
 1 Color on and Ambient on
 2 Highlight on
 3 Reflection on and Ray trace on
 4 Transparency: Glass on
  Reflection: Ray trace on
 5 Reflection: Fresnel on and Ray trace on
 6 Transparency: Refraction on
  Reflection: Fresnel off and Ray trace on
 7 Transparency: Refraction on
  Reflection: Fresnel on and Ray trace on
 8 Reflection on and Ray trace off
 9 Transparency: Glass on
  Reflection: Ray trace off
 10 Casts shadows onto invisible surfaces

map_kd (-options args filename)
color texture file이나 color procedural texture file을 나타낸다.
이미지 파일이라고 생각하면 편하다.
이 파일은 렌더링 될 때 kd 값을 곱해서 사용한다.

map_d (-options args filename)
scalar texture file, scalar procedural texture file을 나타낸다.
이 파일은 렌더링 될 때 d 값을 곱해서 사용한다.

Tr
투명도(Transparency)이긴한데,
d의 역할과 중복된다.
해당 이슈는 다른 GitHub 저장소 이슈에서도 발생했었다.
https://github.com/syoyo/tinyobjloader/issues/43

obj loader 분석

obj, mtl 파일은 text/plain로 저장됩니다. 이런 파일을 읽기 위해서 loader는 사실상 parser와 비슷하게 구현됩니다. parser는 구문 분석인데.. PL이나 파싱을 검색해보는게 빠릅니다.

obj loader로는 tinyobjloader를 썼습니다. 이런 라이브러리를 선택할 때는 GtiHub에 star가 많은 순서로 정하는데, 일단 쓴 사람이 많으면 버그가 적고 예제가 많아서 좋습니다.

loader가 해야되는 가장 중요한 작업은 obj에서 f로 indicies를 재구성하는 작업입니다.
https://github.com/syoyo/tinyobjloader#usage

tinyobjloader는 vertex data를 attrib로, material을 material 클래스로 만들어줍니다.
그리고 각 obj의 o단위로 shape를 구성합니다. 사용 예제는 아래 코드를 참고합니다.
https://github.com/syoyo/tinyobjloader/blob/master/examples/viewer/viewer.cc

텍스쳐에 쓰이는 이미지 파일은 stb lib 중 stbi로 불러옵니다.
https://github.com/nothings/stb


DrawObject.h

DrawObjec를 위와 같이 구현합니다.
OpenGL Buffer에 넣기 위한 데이터들을 직접 들고 있습니다.
나중에 DrawObject는 VAO로 사용하며 VBO에 numTriangles를 통째로 넣으며
Texture, Material을 적용하는 단위를 SubMesh로 정의해서 순차적으로 glDrawArray합니다.
이에 대해서 자세한 설명은 뒤에서 다시하고 obj 파일을 tinyobjloader를 통해 DrawObject로 넣기 위한 방법을 살펴봅니다.


위는 tinyobjloader를 통해서 obj, mtl, texture image 파일을 읽고 저장하는 설명을 위한 이미지 입니다.
1. obj file을 tinyobjloader에 넣습니다. shape, attrib, material이 이에 맞춰 생성됩니다.
2. obj file에서 mtl 파일을 자동으로 읽습니다. mtl이 없을 경우 예외처리가 필요합니다.
3. shape 단위로 attrib에서 Vertex Position, UV, Normal을 Buffer 하나에 저장합니다.
4. Texture/Material 정보를 저장합니다. Vertex Buffer와는 별개로 저장합니다.
5. obj의 f가 어느 usemtl에 속했는지 체크한 후, 이 단위로 SubMesh를 저장합니다.
6. 이미지 파일은 파일 입출력 작업이니 경로만 갖고 몰아서 처리하는 편이 빠릅니다.

obj파일을 읽는 부분의 코드는 아래 링크에 있습니다.
https://github.com/hyunjun529/WIN_VS_GL_Setups/blob/master/6_obj_viewer/Engine/render/OBJLoader.h#L27

구현 개요

위와 같이 솔루션을 구성합니다.
Engine은 동적 라이브러리(lib)으로 만들어서 사용합니다.
실제 exe파일을 생성하는 프로젝트는 SingleWindowScene입니다.

Engine/component는 크게 input, physics, graphics로 나눠서 메인 루프를 순차적으로 처리하고 메시징할 때 각 컴포넌트가 서로의 객체를 참조하는 방식을 사용합니다. 컴포넌트 묶음의 포인터는 Scene이 관리합니다.

Engine/render는 OpenGL, GLFW 등과 관련있는 기능들 입니다.

SceneViewer는 예전 블로그 글에 설명했습니다.
https://3dshovel.blogspot.kr/2018/02/opengl.html

SingleWindowScene은 Scene 하나만 띄워서
이 프로그램에서 Scene은 여러 Componenet를 갖고 있는 클래스입니다.

입력과 렌더링을 분리하자

Input Component와 Graphics(Render) Component를 분리한 구조는 여러가지 이점을 가질 수 있습니다. 특히 기능을 조금씩 구현해나갈 때 유용합니다.

막상 적으려니 코딩하는 팁 밖에 없습니다.
1. 처음에 구조를 잘 잡으면 나중에 편합니다.
2. 여러 기능을 동시에 테스트 하지 맙시다.
2. Vertex Position이나 glEnable을 테스트할 때 color를 position으로 비례시키면 편합니다.

자세한 작업 과정은 GitHub 저장소의 Commit Log로 확인할 수 있습니다.
Git을 사용할 때 Commit은 기능 단위로, 반드시 빌드가 가능하도록 하면 좋습니다.

ImGui로 디버깅


ImGui를 사용하면 console이나 GLFW callback 등을 사용하지 않고 더 손쉽게, 실시간으로 테스트할 수 있습니다. 그리고 사용 또한 직접 콜백을 구현하는 것 보다 더 간단합니다.
https://github.com/ocornut/imgui

이 프로젝트에선 Camera와 ImGui Component를 Input Component로 만들어서 사용합니다. 이 경우 각 컴포넌트가 서로에 대해서 의존성을 갖지만 이것까지 분리하려고 하면 ECS라는 Entity-Components-System을 구현해야합니다.
https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system

OpenGL 바인딩 순서(ShaderProgram, VAO/VBO Buffer)

이 부분의 코드는 아래 링크에 있습니다.
https://github.com/hyunjun529/WIN_VS_GL_Setups/blob/master/6_obj_viewer/Engine/component/RenderComponenet/OBJRenderComponent.h#L27

VAO와 VBO를 언제 어떻게 쓰는지에 대해서 간략히 설명합니다.
VBO(VertexBufferObject)는 직접 Buffer를 bind합니다.
그리고 VAO(VertexArrayObject)는 bind한 VBO들의 현재 상태를 저장합니다.
이로써 여러 객체를 그릴 때 VAO만 바꿔주면 VBO 집합을 다시 Bind할 필요가 없어집니다.

DrawObject는 obj 파일 하나당 하나씩 생성하며 VAO를 하나씩 할당합니다.
그리고 한 DrawObject에서 VBO에 Position, UV, Normal을 통째로 Buffer에 bind합니다.
glDrawArray를 할 때는 각 SubMesh의 시작 index와 size로 그립니다.
이 때 SubMesh 순서에 따라서 Texture Uniform을 바꿔서 bind합니다.

VAO와 VBO에 크로노스 그룹의 OpenGL의 Vertex 명세는 아래 링크에 있습니다.
https://www.khronos.org/opengl/wiki/Vertex_Specification

하지만 실제 사용법은 아래 스택오버플로 질답이 더 도움됬습니다.
https://stackoverflow.com/questions/23314787/use-of-vertex-array-objects-and-vertex-buffer-objects

Texture는 Fragment Shader의 Uniform으로 넘기는 부분이 Vertex와 다릅니다.
Uniform으로 넘기기 위한 TextureId를 SubMesh의 textureId와 별개로 관리합니다.

엄밀히 따지면 이 예제는 여러 텍스쳐를 사용할 뿐 다중 텍스쳐(Multi Texture)는 아닙니다.
다중 텍스쳐는 TextureUnit으로 glActiveTexture와 관련있습니다. 이 경우는 diffuse, ambient, specular, normal, alpha 등을 각각 따로 unit 별로 저장하고 호출하면 됩니다.

Setup 단계에서 DrawObject의 Texture와 Vertex 정보들을 Buffer에 등록합니다.

Render 단계에서는 각 DrawObjects의 SubMesh 순서에 따라서 glDrawArray를 하면 됩니다.

이를 통해서 VAO, VBO, Vertex Shader, Fragment Shader를 나쁘지 않게 사용하는 obj viewer 예제를 만들 수 있었습니다.

---

혹시 더 나은 방법이나 의견, 코드에 관해서 질문이 있으면 댓글 부탁드립니다.

2018년 2월 2일 금요일

Cpp11+에서 std::call_once를 활용한 Singleton logger 구현 (spdlog 사용)

Cpp11+에서 std::call_once를 활용한 Singleton logger 구현




Logger를 구현하다가 싱글턴으로 짜고 싶어져서 만든 코드 일부입니다.
이론적인 설명은 제가 하는 것보다 다른 블로그에 잘 설명된 글이 많으니 그 쪽을 참조해주시기 바랍니다.

이 코드에서 8줄부터 싱글턴 구현이 있고 37줄부터는 전역으로 선언된 싱글턴 클래스를 쉽게 쓰기 위해서 Cpp11+에 추가된 기능들을 이것 저것 사용해놨습니다. 최종적으로 55줄처럼 편하게 사용할 수 있는 형태입니다.

이 코드는 성태의 닷넷 이야기 블로그에 올라온 std::call_once를 보고 cpp11+를 보고 꽂혀서 공부하면서 만들기 시작했습니다.
http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&detail=1&pageno=0&wid=11091&rssMode=1&wtype=0

가변 인자 매크로, Parameter pack 활용

Cpp11+에선 가변 인자 매크로를 통해서 여러 인자를 편하게 매크로로 사용할 수 있습니다.
가변 인자 매크로에 관해서는 mycoboco (Jun Woong)님 블로그에 잘 정리되어 있습니다.
http://blog.woong.org/v/59aa5b8cd845cbff6d76d418

Parameter pack은 Cpp11에서 추가된 템플릿 형식입니다.
http://en.cppreference.com/w/cpp/language/parameter_pack
typename... Args로 여러 인자를 편리하게 받을 수 있습니다.
가변 인자 매크로와 함께 사용하면 위 코드와 같이 사용할 수 있습니다.

31줄, 39줄을 쉽게 설명하면 Args&는 입력 받은 매개변수를 call by reference하는 겁니다.
디버그 모드로 찍어서 확인만 해봤지 자세한 동작 원리는 솔직히 잘 모르고 썼습니다.
이론적인 내용은 아래 StackOverFlow에서 읽어볼 수 있습니다.

가변 인자 매크로와 Parameter을 사용한 비슷한 구현 방식으로는 날쑤의 연습장 블로그에도 있습니다.
http://narss.tistory.com/entry/5%EC%9D%BC%EC%B0%A8-%EA%B0%80%EB%B3%80%EC%9D%B8%EC%9E%90-%ED%85%9C%ED%94%8C%EB%A6%BF-variadic-template

위 Gist를 구현할 때 Saba라는 프로젝트를 참조했습니다.
https://github.com/benikabocha/saba/blob/master/src/Saba/Base/Log.h

logger

위 코드의 Log클래스는 전역 싱글턴 객체입니다.
가변 인자 매크로와 템플릿으로 사용하기 편하게 만들어서 55줄처럼 사용합니다.

이 예제에서 사용한 spdlog는 C++ Logging library입니다.
https://github.com/gabime/spdlog

C++에서 Logger에 대한 종류나 개념은 김연우님 블로그에 잘 설명되어있습니다.
http://ozt88.tistory.com/49

2018년 1월 31일 수요일

imgui image 함수로 scene 만들기

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월내로 기본적인 기능은 완성하는게 목표고 중간중간 구현했던 내용을 블로그에 남기는 중이다.

2018년 1월 24일 수요일

GLFW 사용 방법 정리 (Windows 10, Visual Studio 2017)

외부 라이브러리(imgui)까지 세팅한 이 글의 최종 예제

Windows 10, Visual Studio 2017에서
GLFW를 사용해 OpenGL 프로젝트 세팅하기

윈도우 환경에서 C++로 OpenGL을 실습하려면 Visual Studio 2017 (이하 VS2017)를 설정해야한다. 하지만 이 세팅하고 설치하는 방법이 쉽지않다.
그래서 Vcpkg를 통해서 설치하는 방법과 CMake로 직접 빌드하는 방법을 소개하고 외부 라이브러리를 추가로 사용하는 방법을 소개하기 위해 dear imgui를 사용하는 예제까지 정리해둔다.
해당 블로그 글의 예제는 아래 GitHub 저장소에서 받아쓸 수 있다.

OpenGL은 삼각형만 그릴 수 있으면 깨달음을 얻은거다

0. GLFW

GLFW(http://www.glfw.org/)는 OpenGL을 데스크탑에서 쉽게 사용할 수 있도록 해주며 멀티 플랫폼을 지원하는 라이브러리이다. 주로 창(Window)과 입력(I/O) 구현을 보조해준다.
비슷한 역할을 하는 라이브러리로 GLUT(https://www.opengl.org/resources/libraries/glut/)이 있다. 사족이지만 GLUT을 사용할 경우도 이 글에서 설명한 것과 동일한 방법으로 설치할 수 있다.

본 글에서는 GLFW를 세팅한 후 quick이라고 불리는 코드가 실행될 수 있도록 확인 과정까지 거친다. quick을 실행해봄으로써 설치가 제대로 이뤄졌는지 확인할 수 있다.
quick은 2가지 버전이 있는데 3.0과 latest이다.

두 버전의 차이는 OpenGL의 old vs modern으로 요약할 수 있는데 간단히 정리하면 OpenGL 버전이 올라가면서 오래된 함수를 더이상 지원하지 않는다 정도로 이해해두자.

1. Vcpkg를 사용하는 방법

Vcpkg만 설치하면 복잡한 프로젝트 설정, 라이브러리 설치과정 없이 #include <stdio.h>하듯 바로 GLFW를 사용할 수 있다. 과정이 복잡해보이지만 2018년 1월 기준으로 처음 세팅하기에 이보다 편리하고 의존성 낮은 방법은 없다고 단언한다.
Vcpkg(https://github.com/Microsoft/vcpkg)는 마이크로소프트가 공식으로 지원하는 C/C++ 라이브러리의 패키지매니저이다. 이름부터 VC + pkg(Package)이다.
공식 문서에 설치 방법과 설명서가 있고 공식 블로그에 Vcpkg로 설치할 수 있는 라이브러리의 목록이 있다.
이 글에서는 GLFW를 설치하면서 Vcpkg를 사용할 때 주의해야하는 점과 Vcpkg의 동작 원리를 주로 살펴본다.

Vcpkg를 설치하는 방법

1. https://github.com/Microsoft/vcpkg에서 Vcpkg 다운로드
2. C:\ 경로에 압축을 풀거나 Git을 저장한다.
3. 이후 아래 과정을 따라간다. 윈도우 콘솔 창을 사용한다.

bootstrap-vcpkg.bat을 실행한다. 여기서 부트스트랩의 뜻은 설치한다 정도의 의미를 갖는다. 이 과정에서 Vcpkg는 주로 환경변수에 관한 작업을 수행한다.
CMake가 환경변수에 등록되어 있지 않다면 Vcpkg는 CMake를 설치한다.
그리고 설치된 VS를 찾고 기본 참조 경로들에 Vcpkg를 등록한다.

위와 같은 창이 뜨면 된다.
Vcpkg는 반드시 C:\ 나 C:\src 같은 곳에 설치해주고, 한 번 설치된 Vcpkg는 경로를 옳기면 안된다.
특히 VS 프로젝트 폴더에 설치하는 경우도 있었는데 Vcpkg는 윈도우와 VS에 설치하는 개념임을 명심하자.
또 통상적으로 사용하는 다운로드 폴더나 경로가 30글자 정도 넘어가면 나중에 빌드할 때 경로 문제가 발생할 수 있다(경험).

설치 후 VS2017의 프로젝트 속성에서 참조 경로에 대한 매크로를 살펴보면 VcpkgRoot가 위와 같이 존재함을 확인할 수 있다.

이제 vcpkg 폴더에서 vcpkg 명령어를 사용할 수 있다.
./vcpkg integrate install 를 실행하면 위와 같은 창을 볼 수 있다.
integrate install시 VS가 참조하는 디렉터리 기본 경로에 Vcpkg가 추가되는 구조다.
차후 Vcpkg를 사용하지 않을 경우 vcpkg integrate remove를 하면 Vcpkg가 기본 VS 참조 목록에서 사용하지 않도록 설정할 수 있다.

vcpkg install glfw3만 수행해도 큰 문제는 없지만 일단 라이브러리 설치 방법이다.
위 명령어만 실행하면 glfw3 설치가 끝난다.
여기서 주의할 점은 x86-windows와 x64-windows가 별도로 되어 있다는 점이다.


x64-windows를 설치하기 위해서는 --triplet x64-windows 옵션을 위와 같이 추가해주면 된다.

sdl2, curl은 각각 호환성 라이브러리, http 클라이언트인데 GLFW와는 관련 없으니 빼고 설치해도 된다. 포함해서 설치할 경우 약 16~20분 소요된다.

vcpkg install을 수행할 경우 2가지 작업으로 나눠진다.
먼저 /vcpkg/downloads 경로에 설치할 라이브러리를 다운받는다.

그리고 앞서 설명했던 Vcpkg가 CMake를 설치/사용한다고 했던 내용이다.
C/C++ 라이브러리는 Windows에서 사용할 때 컴파일/빌드해서 lib이나 dll 형식으로 만들 필요가 있다. 이 역할을 수행하는게 CMake다.
Vcpkg는 /vcpkg/buildtrees에 라이브러리를 자동으로 설치하고 세팅해준다.
정리하자면 Vcpkg가 다운로드 받아서 CMake로 컴파일/빌드하고 Vcpkg는 CMake가 만든 파일을 쉽게 사용할 수 있도록 등록해주는 것이다.

이제 old한 GLFW 예제를 돌릴 수 있다.
따로 프로젝트 속성을 만지지 않고도 #include <iostream> 쓰듯 #include <GL/GLFW>를 사용할 수 있게 됬다.

사실 컴파일은 되지만 링킹에러가 발생할 것이다.
LNK2019 __imp_glBegin, LNK2019 __imp_glClear, LNK2019 __imp_glColor3f
이런 에러는 OpenGL 라이브러리를 참조하지 않기 때문에 발생한 문제이다.

위 에러는 opengl32.lib을 추가하면 해결된다.
솔루션 탐색기 > 프로젝트 속성 > 링커 > 입력 > 참조 라이브러리 > opengl32.lib을 추가하면 되는데, 위의 구성에서 모든 구성, 모든 플랫폼으로 두고 설정하자.
위 설정은 아까도 언급했던 x86, x64냐 debug냐 release냐에 따라서 다르게 설정되기 때문이다. 이에 대해서는 C/C++의 컴파일/링킹/빌드 동작 원리를 따로 찾아보자. 여기선 쉽게 할 수 있는 팁 위주로 공유한다.

하지만 latest를 돌리기 위해서는 좀 더 할 일이 있다. GLAD와 linmath.h를 설정해야하는 것이다. 두 라이브러리의 코드는 GLFW 공식 코드의 /deps에 제공된다. https://github.com/glfw/glfw/tree/master/deps에서 다운받자.

GLAD는 OpenGL Loading Library로 유사품은 GLEW, gl3w가 있다. 이건 OpenGL의 버전이나 구현체가 조금씩 다른 환경에서 동일한 함수를 사용할 수 있도록 만들어둔 라이브러리다. GLAD를 공식사이트에서 설치하려다가 http://glad.dav1d.de/ 에서 더 해맬 수 있으니 GLFW에 있는 코드를 일단 쓰는게 좋다.

linmath.h(https://github.com/datenwolf/linmath.h)는 vec3, vec4, mat4x4 등 수학 모듈을 모아둔 라이브러리이다. 헤더 하나가 뭔가 싶지만 C/C++에서 외부 라이브러리를 서너개씩 쓰다보면 수학 모듈을 통일할 필요가 생긴다. 그 때 참조하면 좋은 구현체다.

2. CMake를 사용하는 방법

만약 1번을 한 상황이라면 vcpkg integrate remove를 해두자.
CMake로 설치한 라이브러리와 경로가 꼬이는 상황이 발생할 수 있다.

GLFW는 GLFW 공식 GitHub 저장소의 릴리즈 탭에서 받을 수 있다.
https://github.com/glfw/glfw/releases
Git master 브랜치를 받는 경우가 있는데, 이건 개발 중인 버전으로 안정성이 보장되지 않고 문제가 발생해도 해결하기 어려운 상황이 발생한다. 조심하자.

GLFW를 CMake로 직접 빌드해서 사용하는 이유는 크게 2가지가 있다.
glfw-3.2.1.bin.WIN32나 glfw-3.2.1.bin.WIN64를 받아보면 vc2017이 없다.
즉 VS2017, VC2017, MSVC15에서 GLFW를 제대로 쓰고 싶다면 직접 빌드해야한다.
애초에 GLFW의 최신버전 3.2.1은 16년에 나왔다는 점을 명심하자.

2번째 이유는 앞으로도 다른 라이브러리를 계속 사용하게 될텐데, 대부분의 C/C++ 라이브러리나 프로젝트는 CMake를 사용한다. 사용법을 익혀둬서 나쁠 것도 없고 또 윈도우가 아닌 환경에서 C/C++을 할 때 도움이 된다. 그리고 Vcpkg도 결국 CMake를 사용하기 쉽게 묶어둔 것에 불과하며 Vcpkg에서 자동으로 설치된 라이브러리를 수정해서 써야하는 상황이 발생하면 수동으로 빌드해야한다.

CMake-gui를 받아서 위와 같이 실행해보자.
CMake를 사용하는 방법은 따로 링크해두고, 위와 같은 화면이 뜨면 정상 진행된 것이다.
다운로드 링크 : https://cmake.org/download/ 
CMake에 대한 설명 : https://tuwlab.com/27193

Generate 버튼을 누르면 위와 같은 디렉터리가 생성된다.

GLFW.sln 솔루션 파일을 실행하고 위와 같이 ALL_BUILD나 INSTALL을 빌드한다.

정상적으로 빌드되면 lib 디렉터리가 생성되고 위와 같이 glfw3.lib이 생성된다.
vc2017에서 빌드한 파일은 이 글을 작성하면서 만든 예제 코드에 같이 올려놨다.
https://github.com/hyunjun529/WIN_VS_GL_Setups

만약 위와 같이 MSB3073 setlocal cmake_install.cmake 에러가 발생한다면 권한 문제이다.
GLFW CMake-gui에서 자세히보면 C:\Program Files에 무언가 하려고하는데, 여기에 설치하는건 관리자권한을 필요로한다. 그런데 VS2017은 기본적으로 관리자권한이 없다. 

위 에러는 CMake Generate를 하기 전에 CMAKE_INSTALL_PREFIX의 위치를 위와 같이 빌드하고자 하는 디렉터리 경로로 변경해주면 해결된다. 여기서 설정한 디렉터리에 lib 파일이 생성된다.

위와 같이 솔루션/프로젝트 속성에서 3곳을 수정해준다.
1. C/C++ > 일반 > 추가 포함 디렉터리 : include 할 glfw3.h가 있는 경로를 추가한다.
2. 링커 > 일반 > 추가 라이브러리 디렉터리 : glfw3.lib이 있는 파일의 경로를 추가한다.
3. 링커 > 입력 > 추가 종속성 : opengl32.lib과 그림에선 빠졌는데 glfw3.lib도 추가해야한다.

여기서 본인은 ./external로 상대경로를 사용했는데 왠만하면 절대경로보다 상대경로로 세팅하는게 좋다. 마찬가지로 이 예제 프로젝트는 GitHub 저장소에 바로 실행할 수 있는 버전으로 올려놨다.
https://github.com/hyunjun529/WIN_VS_GL_Setups/blob/master/2_using_CMake/2_using_CMake.vcxproj#L96

그 후 추가 포함 디렉터리 등을 좀 더 설정해서 ./GLFW/deps도 추가해주면 위와 같이 GLFW 예제 2개를 모두 실행할 수 있는 프로젝트가 설정/세팅 완료된다.

3. 외부 라이브러리(dear imgui)를 사용해보자

dear imgui는 OpenGL을 사용하는 환경에서 쉽게 GUI를 구성할 수있게 해주는 라이브러리다.
위와 같은 GUI를 코드 몇 줄로 쉽게 사용할 수 있다. WinAPI나 WinForm, QT, nana, GLFW GUI등을 고려하고 있다면 imgui 또는 nanogui로 갈아타자.

이 예제는 imgui를 GLFW에 연동하고 modern OpenGL로 그린 깨달음!삼각형을 제어하는 것 까지 진행한다.

마찬가지로 imgui 라이브러리도 GitHub 저장소에서 받을 수 있다.

지금까지 언급된 설치 방법을 참조해서 위와 같이 설정하면 된다.
일단 imgui를 포함해서 빌드할 수 있는 곳까지 진행해보자.

imgui의 공식 예제는 GLFW + gl3w를 사용한다.
GLFW quick 예제는 GLFW + GLAD를 사용한 것과 다른 양상이다.
본 예제는 GLFW quick에 있는 렌더링 코드를 GLAD에서 gl3w로 옮겼다.

앞서 짧게 소개한대로 GLFW는 창과 입력에 대한 지원을 제공하는 라이브러리라고 소개했다. imgui에서 GLFW를 통해 이벤트를 받을 수 있도록 연결해주는 작업이 필요하다.
위 링크가 해당 코드인데, gl3w를 사용하고 있다. 헤더파일을 살펴보면 이 파일이 하는 역할은 키보드, 마우스 등의 입력을 imgui가 사용할 수 있도록 추상화해둔 것을 알 수 있다.

위에서 언급한 2가지만 주의하면 쉽게 imgui를 붙일 수 있다.
위 링크는 CMake로 직접 빌드한 버전에 imgui를 적용한 예제이다.

---

늦었지만 이 글은 처음 OpenGL을 접하고 프로젝트 설정하는 사람들을 대상으로 적었습니다. 또 Vcpkg와 CMake 사용법과 원리에 대한 내용, imgui 소개가 섞여있습니다.
읽다가 어려운 부분이나 잘못 설명된 부분이 있으면 댓글로 지적해주시면 감사하겠습니다.

OpenGL로 FPS 카메라 구현하기

https://youtu.be/ikhlwGKKqZk 대학교 그래픽스 강의에서 OpenGL로 간단한 FPS를 구현했었습니다. 세가 신입사원 책이랑 게임 프로그래밍 패턴 책을 참고했었습니다. 핵심 아이디어만 간단히 요약해서 정리해둡니다. ...