//---------------------------------------------------------------------------- // This is free and unencumbered software released into the public domain. // // Anyone is free to copy, modify, publish, use, compile, sell, or // distribute this software, either in source code form or as a compiled // binary, for any purpose, commercial or non-commercial, and by any // means. // // In jurisdictions that recognize copyright laws, the author or authors // of this software dedicate any and all copyright interest in the // software to the public domain. We make this dedication for the benefit // of the public at large and to the detriment of our heirs and // successors. We intend this dedication to be an overt act of // relinquishment in perpetuity of all present and future rights to this // software under copyright law. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to //---------------------------------------------------------------------------- // // Just a heads up: don't use this code as a guide for good style/practice! // #include #include #include #include FT_FREETYPE_H #define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_GLCOREARB #include const char *VERTEX_SHADER = "" "#version 410 core\n" "in vec4 in_Position;\n" "out vec2 texCoords;\n" "void main(void) {\n" " gl_Position = vec4(in_Position.xy, 0, 1);\n" " texCoords = in_Position.zw;\n" "}\n"; const char *FRAGMENT_SHADER = "" "#version 410 core\n" "precision highp float;\n" "uniform sampler2D tex;\n" "uniform vec4 color;\n" "in vec2 texCoords;\n" "out vec4 fragColor;\n" "void main(void) {\n" " fragColor = vec4(1, 1, 1, texture(tex, texCoords).r) * color;\n" "}\n"; void render_text(const std::string &str, FT_Face face, float x, float y, float sx, float sy) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); const FT_GlyphSlot glyph = face->glyph; for(auto c : str) { if(FT_Load_Char(face, c, FT_LOAD_RENDER) != 0) continue; glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, glyph->bitmap.width, glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, glyph->bitmap.buffer); const float vx = x + glyph->bitmap_left * sx; const float vy = y + glyph->bitmap_top * sy; const float w = glyph->bitmap.width * sx; const float h = glyph->bitmap.rows * sy; struct { float x, y, s, t; } data[6] = { {vx , vy , 0, 0}, {vx , vy - h, 0, 1}, {vx + w, vy , 1, 0}, {vx + w, vy , 1, 0}, {vx , vy - h, 0, 1}, {vx + w, vy - h, 1, 1} }; glBufferData(GL_ARRAY_BUFFER, 24*sizeof(float), data, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); glDrawArrays(GL_TRIANGLES, 0, 6); x += (glyph->advance.x >> 6) * sx; y += (glyph->advance.y >> 6) * sy; } glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } int main() { GLuint texture{0}, sampler{0}; GLuint vbo{0}, vao{0}; GLuint vs{0}, fs{0}, program{0}; FT_Library ft_lib{nullptr}; FT_Face face{nullptr}; auto cleanup = [&]() { FT_Done_Face(face); FT_Done_FreeType(ft_lib); glDeleteTextures(1, &texture); glDeleteSamplers(1, &sampler); glDeleteBuffers(1, &vbo); glDeleteVertexArrays(1, &vao); glDeleteShader(vs); glDeleteShader(fs); glDeleteProgram(program); }; // Initialize and load our freetype face if(FT_Init_FreeType(&ft_lib) != 0) { std::cerr << "Couldn't initialize FreeType library\n"; cleanup(); return 1; } if(FT_New_Face(ft_lib, "myfont.ttf", 0, &face) != 0) { std::cerr << "Unable to load myfont.ttf\n"; cleanup(); return 1; } // Create a GLFW window if(glfwInit() != GL_TRUE) { std::cerr << "Couldn't load GLFW library\n"; cleanup(); return 1; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); const int WIDTH = 640; const int HEIGHT = 480; const double SCALEX = 2.0 / WIDTH; const double SCALEY = 2.0 / HEIGHT; auto window = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL Text Rendering", nullptr, nullptr); glfwMakeContextCurrent(window); glViewport(0, 0, WIDTH, HEIGHT); // Initialize our texture and VBOs glGenBuffers(1, &vbo); glGenVertexArrays(1, &vao); glGenTextures(1, &texture); glGenSamplers(1, &sampler); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Initialize shader vs = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vs, 1, &VERTEX_SHADER, 0); glCompileShader(vs); fs = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fs, 1, &FRAGMENT_SHADER, 0); glCompileShader(fs); program = glCreateProgram(); glAttachShader(program, vs); glAttachShader(program, fs); glLinkProgram(program); // Set some initialize GL state glEnable(GL_BLEND); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0.1, 0.2, 0.4, 0); // Get shader uniforms glUseProgram(program); glBindAttribLocation(program, 0, "in_Position"); GLuint texUniform = glGetUniformLocation(program, "tex"); GLuint colorUniform = glGetUniformLocation(program, "color"); while(glfwWindowShouldClose(window) != GL_TRUE) { glfwMakeContextCurrent(window); glClear(GL_COLOR_BUFFER_BIT); // Bind stuff glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindSampler(0, sampler); glBindVertexArray(vao); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vbo); glUseProgram(program); glUniform4f(colorUniform, 1, 1, 1, 1); glUniform1i(texUniform, 0); FT_Set_Pixel_Sizes(face, 0, 50); render_text("Hello World!", face, -0.5, 0, SCALEX, SCALEY); glfwSwapBuffers(window); glfwPollEvents(); } cleanup(); return 0; }