Text Rendering
너의 그래픽스 어드벤쳐의 어떤 단계에서, 너는 OpenGL에서 text를 그리길 원할 것이다.너가 기대하는 것과 대조적으로, 스크린에 간단한 string을 렌더링하는 것은 OpenL 처럼 low-level library로 꽤 어렵다. 만약 너가 128개의 다른 문자들 보다 더만이 렌더링 하는 것에 신경쓰지 안는다면, 그러면 그것은 아마도 그러게 어렵지 않다. 각 문자가 다른 width, height and margin을 가지자마자 상황은 어려워질 것이다. 너가 사는 곳을 기반으로, 너는 또는 128개 이상의 문자들을 필요할지도 모른다. 그리고 너가 수학 표현 또는 음악 기호 같은 특수 기호를 표현하고 싶으면 어떨까? 텍스트를 꼭대기에서 바닥으로 렌더링하는 건 어떨까? 너가 text의 모든 이러한 복잡한 문제들에 대해 생각한다면, 이것이 OpenGL 같은 low-level API에 소가지 않는다는 것이 너를 놀라게 하지 않을 것이다.
OpenGL 내에 어떤 text capabilities에 대한 지원이 없기 때문에, 스크린에 텍스트를 렌더리아는 것에 대한 시스템을 정의하는 것은 너에게 달려있다. text characters에 대한 어떠한 그래픽적 primitives가 없기 때문에,우리는 창의적이여야만 한다. 어떤 예제기법들은: GL_LINES을통해 문자 모양을 그려서, letters의 3D meshes를 생성하거나 또는 3D 환경에서, 2Dquads에 문자 텍스쳐들을 렌더링한다.
대부분 종종 개발자들은 quads에 textures를 렌더링하는 것을 선택한다. 이러한 textured quads 자체를 렌더링하는 것은 너무 어렵지 안을 것이지만, 관련된 문자를 한 텍스쳐에 보내는 것은 어려울 수 있다. 이 튜토리얼에서, 우리는 몇 가지 방법들을 조사할 것이고, 좀 더 고급이지만 FreeType library를 사용하여 텍스트를 렌더링하는 유연한 기법을 구현 할 것이다.
Classical text rendering : bitmap fonts
이전 시절에, text를 렌더링하는 것은 너의 어플리케이션을 위해 원하는 한 font를 선택하는 것(또는 스스로 만들거나)과 그것들을 단일의 큰 텍스쳐에 복사하기 위해 이 font에서 모든 관련된 문자들을 추출하는 것을 포함했다. 이제부터 우리가 bitmap font라고 부르는 그러한 텍스쳐는 그 텍스쳐의 predefined regions에서 우리가 사용하길 원하는 모든 문자 기호들을 포함한다. 그 폰트의 이러한 문자 기호들은 glyphs라고 알려져있다. 각 glyph는 그것들과 관련된 텍스쳐좌표의 특정한 영역을 가진다. 너가한 문자를 렌더링하길 원할 때 마다, 너는 그 bitmap font의 이 섹션을 2D quad에 렌더링하여 해당 glyph를 선택한다.
여기에서, 너는 bitmap font를 가져와서 해당 glyphs를 텍스쳐로부터 샘플리아여 'OpenGL' text를 어떻게 렌더링하는지 알 수 있다. blending을 활성화하고, background를 투명하게 하여, 우리는 스크린에 렌더링된 문자열을 얻게 될 것이다. 이 특정한 bitmap font는 Codehead의 Bitmap Font Generator를 사용하여 생성되었다.
이 접근법은 몇 가지 장단점을 가진다. 첫 째로, 구현하기에 상대적으로 쉽다. 왜냐하면 bitmap fonts는 pre-rasterized 되어있기 때문에, 그것들은 꽤 효율적이다. 그러나, 그것은 유연하지 않다. 너가 다른 font를 렌더링하길 원할 때, 너는 한 완전한 새로운 bitmap font를 재컴파일 할 필요가 있다. 그리고 그 시스템은 단일 해상도로 제한된다; zooming 하는 것은 빠르게 pixelated edges를 보여줄 것이다. 게다가, 작은 문자 셋에만 제한된다. 그래서 확장된 또는 Unicode characters들은 종종 불가능하다.
이 접근법은 옛날 시절에 꽤 인기있었다 (그리고 여전히 그렇다), 왜냐하면 그것은 빠르고 어떤 플랫폼에서든지 작동하기 때문이다. 그러나 오늘날에서, 좀 더 유연한 접근법이 존재한다. 이러한 접근법들 중의 하나는 FreeType library를 사용하여 TrueType fonts를 불러오는 것이다.
Mordern text rendering : FreeType
FreeType은 fonts를 불러올 수 있고, 그것들을 bitmap에 렌더링하고, 몇 가지 font와 관련된 연산을 지원하는 소프트웨어 개발 라이브러리이다. 그것은 Mac OS X, Java, PlayStation Consoles, Linux, 그리고 Android에 의해 사용되는 인기있는 라이브러리이다. FreeType을 특히 매력적이게 만드는 것은 그것이 TrueType fonts를 불러올 수 있다는 것이다.
TrueType font는 pixels 또는 어떤 다른 scalable할 수 없는 솔루션이 아닌 수학적 공식(splines의 조합)으로 정의된 character glyphs의 집합이다. vector images와 유사ㅏ게, 그 rasterized font images는 절차적으로 너가 얻고싶어하는 선호되는 font height를 기반으로 생성될 수 있다. TrueType fonts를 사용하여, 우리는 쉽게 어떠한 퀄리티의 손실 없이, 다양한 사이즈를 가진 character glphys를 렌더링할 수 있다.
FreeType은 그것들의 웹사이트에서 다운로드 되어질 수 있다. 너는 그것들의 소스코드로부터라이브러리를 컴파일하는 것을 선택할 수 있고, 또는 만약 너의 타겟 플랫폼이 열거된다면 precompiled libraries중의 하나를 사용하는 것을 선태갈 수 있다. freetype.lib에 link하는것을 확실히 해라.그리고 너의 컴파일러가 그 헤더 파일이 어디에 있는지를 알게 하도록 ㅐ라.
그러고나서 그 적절한 헤더를 include하도록 해라.
FreeType이 하는 것은 이러한 TrueType fonts를 불러오고, 각 glyph에 대해 bitmap image를 생성하고, 몇 가지 metrics를 계산한다.우리는 텍스쳐를 생성하기 위해 이러한 bitmap imaes를 추출할 수 있고, 각 character glyph를 loaded metrics를 사용하여 적저리 위치시킬 수 있다.
한 font를 불러오기 위해, 우리가 해야하는 것은 FreeType library를 초기화하고, 그 font를 FreeType이 부르듯이 face로서 부르는 것이다. 여기에서 우리는 arial.ttf 라는 Windows/Fonts directory에서 복사된 TrueType font를 불러온다.
이러한 FreeType 함수들 각각은 에러가 발생할 때 마다 0이 아닌 정수를 반환하다.
우리가 그 face를 불러왔다면, 우리는 이 face로부터 우리가 추추라고 싶은 font size를 정의해야만 한다:
그 함수는 font의 width and height parameters를 설정한다. 그 width를 0으로 설정하는 것은 그 face가 주어진 height에 따라 width를 동적으로 계산하는 것을 허용한다.
FreeType face는 glyphs의 한 모음을 불러온다. 우리는 FT_Load_Char를 호출하여 active glyph로서 그러한 glyphs들 중 하나를 설정할 수 있다. 여기에서 우리는 그 문자 glyph 'X'를 부르는 것을 선택했다
FT_LOAD_RENDERER를 loadin flags중의 하나로 설정하여, 우리는 FreeType에게 우리가 face->flyph->bitmap을 통해 접근할 수 있는 우리를 위한 8-bit grayscale bitmap imae를 생성하라고 말한다.
그러나, 우리가 FreeType으로 불러오는 lyphs의 각각은 같은 size를 가지고 있지 안는다 (우리가 bitmap fonts로 가지고 있기 때문에). FreeType으로 생성된 bitmap imae는 한 문자의 visible part를 퐘하기에 충분히 크다. 예를들어, dot character '.'의 bitmap image는 문자 'X'의 bitmap image보다 더 작다. 이 이유 때문에, FreeType은 또한 각 문자가 얼마나 커야만 하는지와, 그것들을 적절히 어떻게 배치할지를 명시하는 몇 가지 측정치를 불러온다. 아래에 각 character glyph를 위해 계산하는 모든 측정치를 보여주는 이미지가 있다.
glyphs의 각각은 수평의 baseline에 있다 (horizontal arrow로 그려지는), 거기에서 어떤 lyphs는 정ㅎ확히 이 baseline의 위에 있다 ('X' 처럼) 또는 어떤 것은 baseline 아래에 있다 ('g' or 'p'처럼). 이러한 측정치들은 그 baseline에서 각 glyph를 적절히 배치하는 offsets을 정의한다. 그리고 각 glyph가 얼마나 커야하는지와 우리가 그 다음 glyph로 진해아기 위해 얼마나 많은 픽셀이 필요한지를 정의한다. 아래는 우리가 필요한 이러한 properties의 작은 목록이다.
- width : face->glyph->bitmap.width로 접근되는 bitmap의 width (in pixels).
- height : face->glyph->bitmap.rows로 접근되는 bitmap의 height (in pixels).
- bearingX : horizontal bearing. face->glyph->bitmap_left로 접근되는데, 원점을 기준으로 bitmap의 horizontal position(in pixels).
- bearingY : vertical bearing. face->glyph->bitmap_top으로 접근되는데, baseline을 기준으로 bitmap의 vertical position (in pixels).
- advance : horizontal advance. face->glyph->advance.x를 통해 접근되는데, 현재 origin에서 다음 glyph의 origin으로 가는 horizontal distance (1 / 64th pixels).
우리는 character glyph를 가져오고, 그것의 metrics를 가져오고, 우리가 스크린에 한 문자를 렌더링하고 싶을 때 마다 한 텍스쳐를 생성할 수 있다. 그러나, 매 프레임마다 이것을 하는 것은 비효율적이다. 우리는 오히려 어플리케이션 어딘가에 생성된 데이터를 저장하고, 우리가 한 문자를 렌더링하고 싶을 때 마다 그것을 query하고 싶을 것이다. 우리는 우리가 map에 저장할 편리한 구조체를 정의할 것이다.
이 튜토리얼에 대해, 우리는 ASCII 문자 set의 처음부터 128개의 문자에 제한을 하여 상황을 간단하게 할 것이다. 각 문자에 대해, 우리는 텍스쳐를 생성하고, 그것의 관련된 데이터를 Character 구조체에 저장한다. 우리는 그 Character struct를 Characters map에 추가한다. 이 방식으로, 각 문자를 렌더링하는 요구되는 모든데이터는 나중을 위해 저장된다.
for loop내에서, 우리는 ASCII set의 128개의 문자를 열거하고, 그것들의 대응되는 character glyphs를 가져온다. 각 문자에 대해, 우리는 한 텍스쳐를 생성하고, 그것의 옵션들을 설정하고, 그것의 metrics를 저장한다. 여기에서 주목해야할 흥미로운 것은, 우리가 texture의 internalFormat과 foramt arguments로서 GL_RED를 사용한다는 것이다. 그 glyph으로부터 생성되는 bitmap은 grayscale의 8-bit image인데, 거기에서 각 color는 single byte로 나타내어진다. 이 이유 때문에, 우리는 bitmap buffer의 각 byte를 텍스쳐의 color value로서 저장하고 싶다. 우리는 각 byte가 그 텍스쳐 color의 red component로 대응되는 한 텍스쳐를 생성하여 이것을 한다. 만약 우리가 신경쓸 필요가 있는 한 텍스쳐의 컬러들을 나타내기 위히 한 single byte를 사용한다면, OpenGL의 제한은:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
OpenGL은 텍스쳐들이 모두 4-byte alignemtn를 가질 것을 요구한다. 예를들어, 그것들의 size는 항상 4 bytes의 배수이다. 보통, 이것은 대부분의 텍스쳐가 pixel당 4bytes 또는 4의 배수인 width를 가지고 있기 때문에 문제가 되지 않을 것이지만, 우리가 지금은 오직 pixel당 a single byte를 사용하기 때문에, 그것들은 어떤 width든 가질 수 있다. 그것의 unpack alignment를 1로 설정하여, 우리는 alignment issue가 없게 보장한다 (이것은 segmentation faults를 발생시킬 수 있다).
이 튜토리얼에 대해, 우리는 ASCII 문자 set의 처음부터 128개의 문자에 제한을 하여 상황을 간단하게 할 것이다. 각 문자에 대해, 우리는 텍스쳐를 생성하고, 그것의 관련된 데이터를 Character 구조체에 저장한다. 우리는 그 Character struct를 Characters map에 추가한다. 이 방식으로, 각 문자를 렌더링하는 요구되는 모든데이터는 나중을 위해 저장된다.
for loop내에서, 우리는 ASCII set의 128개의 문자를 열거하고, 그것들의 대응되는 character glyphs를 가져온다. 각 문자에 대해, 우리는 한 텍스쳐를 생성하고, 그것의 옵션들을 설정하고, 그것의 metrics를 저장한다. 여기에서 주목해야할 흥미로운 것은, 우리가 texture의 internalFormat과 foramt arguments로서 GL_RED를 사용한다는 것이다. 그 glyph으로부터 생성되는 bitmap은 grayscale의 8-bit image인데, 거기에서 각 color는 single byte로 나타내어진다. 이 이유 때문에, 우리는 bitmap buffer의 각 byte를 텍스쳐의 color value로서 저장하고 싶다. 우리는 각 byte가 그 텍스쳐 color의 red component로 대응되는 한 텍스쳐를 생성하여 이것을 한다. 만약 우리가 신경쓸 필요가 있는 한 텍스쳐의 컬러들을 나타내기 위히 한 single byte를 사용한다면, OpenGL의 제한은:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
OpenGL은 텍스쳐들이 모두 4-byte alignemtn를 가질 것을 요구한다. 예를들어, 그것들의 size는 항상 4 bytes의 배수이다. 보통, 이것은 대부분의 텍스쳐가 pixel당 4bytes 또는 4의 배수인 width를 가지고 있기 때문에 문제가 되지 않을 것이지만, 우리가 지금은 오직 pixel당 a single byte를 사용하기 때문에, 그것들은 어떤 width든 가질 수 있다. 그것의 unpack alignment를 1로 설정하여, 우리는 alignment issue가 없게 보장한다 (이것은 segmentation faults를 발생시킬 수 있다).
너가 glyphs를 처리하는 것을 마쳤다면, FreeType의 resources를 클리어하도록 해라.
Shaders
실제 glyphs를 렌더링하기 위해, 우리는 다음의 vertex shader를 사용할 것이다.
그 fragment shader는 두 개의 uniforms를 취한다. 하나는 glyph의 mono-colored bitmap image이고, 나머지 하나는 그 text의 final color를 조정하기 위한 color uniform이다. 우리는 처음에 bitmap texture의 color value를 샘플한다. 그 texture의 data는 red component에 저장되었기 때문에, 우리는 그 texture의 r 컴포넌트를 sampled alpha value로 샘플한다. 그 컬러의 alpha값을 다르게 하여, 최종 컬러는 모든 glyph의 배경 컬러에 대해 투명할 것이고, 실제 문자 픽셀에 대해 투며하지 않을 것이다. 우리는 또한 그 text color를 다르게 하기 위해 textColor uniform으로 RGB colors를 곱한다.
우리는 이것을 작동시킥 위해 blending을 활성화 시킬 필요가 있다.
projection matrix에 대해, 우리는 orthographic projection marix를 사용할 것이다. 텍스트를 렌더링하기 위해, 우리는 (보통) perspective가 필요하지 않고, orthographic projection matrix를 사용하는 것은 또한 우리가 스크린 좌표에 있는 모든 vertex 좌표를 명시하게 해준다, 만약 우리가 그것을 다음과 같이 설정한다면.
우리는 projection matrix의 bottom parameter를 0.0으로, 그것의 top parameter를 window height와 동일하게 설정한다. 그 결과는 우리가 스크린의 bottom part (0.0f)에서, 스크린의 top part (600.0f)의 범위의 가진 y값의 좌표를 명시하는 것이다. 이것은 (0.0, 0.0) 점이 bottom-left corner와 대응되는 것을 의미한다.
마지막은 quads를 렌더링하기 위한 VBO와 VAO를 생성하는 것이다. 이제, 우리는 VBO를 초기화 할 때 충분한 메모리를 보유하는데, 우리가 나중에 문자들을 렌더링할 때, VBO의 메모리를 업데이트할 수 있게 하기 위해서이다.
2D quad는 4개의 floats 중 6개의 정점을 요구한다. 그래서 우리는 6 * 4 floats의 메모리를 보유한다. 우리는 VBO의 메모리의 내용을 꽤 종종 업데이트할 것이기 때문에, 우리는 GL_DYNAMIC_DRAW로 메모리를 할당할 것이다.
Render line of text
한 문자를 렌더링하기 위해, 우리는 Characters map의 대응되는 Character struct를 추출하고, character의 metrics를 사용하여 quad의 dimension을 계산한다. quad의 계산된 수치들로, 우리는 동적으로 6개의 정점의 한 set을 생성한다. 우리는 glBufferSubData를 사용하여 VBO에 의해 관리되는 메모리 내용을 업데이트하기 위해 그것을 사용한다.
우리는 문자들의 한 string을 렌더링하는 RenderText라고 불리는 함수를 생성한다.
그 함수의 내용은 상대적으로 자기-설명적이다: 우리는 처음에 quad의 origin position을 계산하고 (xpos와 ypos로), 그리고 그 quad의 사이즈를 계산하고 (w와 h), 그리고 2D quad를 만들기 위해 6개의 정점을 생성한다. 우리가 scale로 각 metric를 scale 한다는것에 유의해라. 우리는 그러과서 VBO의 내용을 업데이트하고, quad를 렌더링한다.
코드의 다음 라인은 몇 가지 추가 주의를 요구한다.
어떤 문자들 ('p' 또는 'g')는 다소 baseline아래로 렌더링된다. 그래서 그 quad는 또한 RenderText의 y 값 아래에 배치되어야 한다. 그 baseline 밑으로 ypos를 움직이게 하기 위해 필요한 정확한 양은 glyph metrics를 통해서 알아질수 있다.
이 거리를 계산하기 위해, 우리는 한 glyph가 그 baseline 아래로 확장되는 거리를 알아내야할 필요가 있다; 이 거리는 빨간색 화살표로 표시된다. 너가 glyph metrics로부터 볼 수 있듯이, 우리는 glyphy의 height로부터 bearingY를 빼서 이 벡터의 길이를 계산한다. 이 값은 baseline에 있는 ('X') 처럼 문자들에 대해서는 0.0이고, baseline아래에 있는 ('g' or 'j') 문자들에 대해서는 양수이다.
만약 너가 모든 것을 옳게 했다면, 너는 이제 다음의 문장으로 텍스트의 스트링들을 성공적으로 렌더링할 수 있을 것이다.
너는 여기에서 이 예제의 코드를 볼 수 있다.
너에게 우리가 그 quad의 vertices를 어떻게 계산했는지에 대한 느낌을 주기위해, 우리는 실제 렌더링이 어떻게 보이는지 보기위해 blending을 disable할 수 있다.
여기에서 너는 명백히 상상의 baseline에 있는 대부분의 quads를 볼 수 있다. 반면에, 'p', '('같은 glyphs에 대응되는 quads는 아래쪽으로 내려간다.
Going further
이 튜토리얼은 FreeType library를 사용하여 TrueType fonts로 text rendering technique을 보여주었다. 그 접근법은 유연하고, scalable하고, 많은 문자 인코딩들과 작동한다. 그러나, 이 접근법은 너의 어플리케이션에 대해 overkill할 가능성이 높다. 왜냐하면 우리가 각 glyph에 대해 텍스쳐들을 생성하고 렌더링하기 때문이다. Performance-wise bitmap fonts가 선호되는데, 우리가 오직 모든 우리의 glyphs에 대해 한 텍스쳐만을 필요하기 때문이다. 가장 좋은 접근법은 FreeType으로 로드된 모든 문자 glyphs를 특징으로 가지고 있는 bitmap font texture를 생성하여 두 개의 접근법을 합치는 것이다. 이것은 렌더러에게 중요한 텍스쳐 스위치를 덜어주고, 각 glyph가 얼마나 packed 되었는냐를 기반으로, 꽤 성능을 아낄 수 있다.
FreeType fonts가 가진 또 다른 문제는, glyph textures가 fixed font size로 저장된다는 것이다. 그래서, jagged edges를 도입할 scaling의 중요한 양이 요구될지도 모른다. 게다가, glyphs에 대한 rotations는 그것들이 blurry하게 보이게 할지도 모른다. 이것은 실제 rasterized pixel color를 저장하는 대신에, pixel당 가장 가까운 glyph outline에 대한 거리를 저장하여 완화될 수 있따. 이 기법은 singed distance fields라고 불려지고, Valve가 3D 렌더링 어플리케이션에 놀랍게도 잘 작동하는 이 기법의 그들의 구현에 대해 몇년 전에 한 논문을 출판했다.
=================================================
어쨋든 나중에 Signed Distance Field Text Rendering을 공부해야할 것이다.
https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf
https://wdobbie.com/post/gpu-text-rendering-with-vector-textures/
https://lambdacube3d.wordpress.com/2014/11/12/playing-around-with-font-rendering/
https://cs.uwaterloo.ca/~dberry/ATEP/StudentLectures/ThierryDelisle.pdf
https://forum.libcinder.org/#Topic/23286000001127007
이렇게 그래픽스랑 opengl 정리가 잘 된 블로그가 있었네요! 구글검색에선 안나와서 댓글이 별로 없는걸까요? 저도 다른 opengl 번역된 블로그에서 보고 찾아왔습니다. 여튼 감동이네요. 처음부터 잘 보겠습니다!
답글삭제안녕하세요! 도움이 되었다면 다행입니다. 저의 개인 공부 목적으로 번역하는거라 오타나 내용 상관없이 그냥 써서 올리고 있습니다. 감사합니다~
삭제16~17년도쯤에 원래사이트에서 제공하던 cookbook을 가지고 인쇄소에서 책으로 만든다음 군대에서 눈빠지도록 본 일들이 생각나네요. 내용들이 알차서 재미있게봤는데, 한국어로 번역된게 어뵤아서 살짝 아쉬웠긴했었어요. 오랜만에 다시 찾다가 번역된 글이 보여서 정말 반갑네요. 번역해주셔서 감사하고, 잘 읽겠습니다!
답글삭제도움이 되면 좋겠습니다~
삭제