Commit 369d4951 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Finished work on fontrendering module for now:

* Cleaned up FontAtlas and completed documentation
* Cleaned up TextRenderer, added customizable font file, added exception handling
* Added two OpenSource default fonts (FreeSans and Bitstream Vera Mono)
parent b55963b2
......@@ -30,6 +30,4 @@ uniform vec4 _color = vec4(1.0, 1.0, 1.0, 1.0);
void main() {
out_Color = vec4(texture2D(_fontTexture, ex_TexCoord.xy).r) * _color;
//out_Color = vec4(ex_TexCoord, 1.f);
}
......@@ -23,6 +23,7 @@
// ================================================================================================
#include "fontrenderingdemo.h"
#include "cgt/shadermanager.h"
namespace campvis {
namespace fontrendering {
......@@ -38,6 +39,9 @@ namespace campvis {
}
void FontRenderingDemo::init() {
// set font before initing the pipeline to avoid error due to missing font.
_tr.p_fontFileName.setValue(ShdrMgr.completePath("/modules/fontrendering/fonts/FreeSans.ttf"));
AutoEvaluationPipeline::init();
_tr.p_outputImage.setValue("text.rendered");
......
......@@ -35,14 +35,16 @@ namespace fontrendering {
TextRenderer::TextRenderer(IVec2Property* viewportSizeProp)
: VisualizationProcessor(viewportSizeProp)
, p_text("Text", "Text", "The Quick Brown Fox Jumps Over The Lazy Dog")
, p_position("Position", "Position (in Viewport Coordinates)", cgt::ivec2(50), cgt::ivec2(-1000), cgt::ivec2(1000))
, p_position("Position", "Position (in Viewport Coordinates)", cgt::ivec2(32), cgt::ivec2(0), cgt::ivec2(1000))
, p_fontFileName("FontFileName", "Path to the Font File to Use", "", StringProperty::OPEN_FILENAME)
, p_fontSize("FontSize", "Font Size", 20, 4, 100)
, p_color("Color", "Font Color", cgt::vec4(1.f), cgt::vec4(0.f), cgt::vec4(1.f))
, p_outputImage("LightId", "Light Name/ID", "lightsource", DataNameProperty::WRITE)
, p_outputImage("OutputImage", "Output Image Name/ID", "TextRenderer.output", DataNameProperty::WRITE)
, _atlas(nullptr)
{
addProperty(p_text);
addProperty(p_position);
addProperty(p_fontFileName, INVALID_RESULT | INVALID_PROPERTIES);
addProperty(p_fontSize, INVALID_RESULT | INVALID_PROPERTIES);
addProperty(p_color);
addProperty(p_outputImage);
......@@ -54,27 +56,48 @@ namespace fontrendering {
void TextRenderer::init() {
VisualizationProcessor::init();
_atlas = new FontAtlas("C:\\temp\\FreeSans.ttf", 20);
initializeFontAtlas();
_viewportSizeProperty->s_changed.connect(this, &TextRenderer::onViewportSizeChanged);
}
void TextRenderer::denit() {
void TextRenderer::deinit() {
_viewportSizeProperty->s_changed.disconnect(this);
delete _atlas;
VisualizationProcessor::deinit();
}
void TextRenderer::updateResult(DataContainer& data) {
if (_atlas == nullptr)
return;
FramebufferActivationGuard fag(this);
createAndAttachColorTexture();
createAndAttachDepthTexture();
_atlas->renderText(p_text.getValue(), p_position.getValue(), p_color.getValue(), cgt::vec2(1.f) / cgt::vec2(_viewportSizeProperty->getValue()));
const cgt::mat4 trafoMatrix = cgt::mat4::createTranslation(cgt::vec3(-1.f, -1.f, 0.f)) * cgt::mat4::createScale(cgt::vec3(2.f / _viewportSizeProperty->getValue().x, 2.f / _viewportSizeProperty->getValue().y, 1.f));
cgt::vec2 pos(static_cast<float>(p_position.getValue().x), static_cast<float>(_viewportSizeProperty->getValue().y - p_position.getValue().y));
_atlas->renderText(p_text.getValue(), pos, p_color.getValue(), trafoMatrix);
data.addData(p_outputImage.getValue(), new RenderData(_fbo));
}
void TextRenderer::updateProperties(DataContainer& dataContainer) {
initializeFontAtlas();
}
void TextRenderer::initializeFontAtlas() {
delete _atlas;
_atlas = nullptr;
_atlas = new FontAtlas("C:\\temp\\FreeSans.ttf", p_fontSize.getValue());
try {
_atlas = new FontAtlas(p_fontFileName.getValue(), p_fontSize.getValue());
}
catch (...) {
LERROR("Could not create FontAtlas, TextRenderer will not do anything.");
}
}
void TextRenderer::onViewportSizeChanged(const AbstractProperty* prop) {
p_position.setMaxValue(_viewportSizeProperty->getValue());
}
......
......@@ -61,11 +61,12 @@ namespace fontrendering {
virtual void init();
virtual void denit();
virtual void deinit();
StringProperty p_text; ///< Text to render
IVec2Property p_position; ///< Position of text in viewport coordinates
IntProperty p_fontSize; ///< Font size to use
StringProperty p_fontFileName; ///< Path to the font file to use
IntProperty p_fontSize; ///< Font size to use
Vec4Property p_color; ///< Color to use
DataNameProperty p_outputImage; ///< Name/ID for the output image with the rendered image
......@@ -75,6 +76,11 @@ namespace fontrendering {
/// \see AbstractProcessor::updateProperties
virtual void updateProperties(DataContainer& dataContainer);
/// Initializes the FontAtlas used.
void initializeFontAtlas();
/// Callback when _viewportSizeProperty has changed
void onViewportSizeChanged(const AbstractProperty* prop);
FontAtlas* _atlas;
static const std::string loggerCat_;
......
......@@ -62,9 +62,10 @@ namespace campvis {
int rowh = 0;
int w = 0;
int h = 0;
memset(_glyphParameters, 0, sizeof(_glyphParameters));
/* Find minimum size for a texture holding all visible ASCII characters */
for (int i = 32; i < 128; i++) {
memset(_glyphs, 0, sizeof(_glyphs));
// Find minimum size for a texture holding all visible ASCII characters
for (int i = 32; i < 256; i++) {
if (FT_Load_Char(_ftFace, i, FT_LOAD_RENDER)) {
LERROR("Loading character " << i << " failed!");
continue;
......@@ -91,7 +92,7 @@ namespace campvis {
int ox = 0;
int oy = 0;
rowh = 0;
for (int i = 32; i < 128; i++) {
for (int i = 32; i < 256; i++) {
if (FT_Load_Char(_ftFace, i, FT_LOAD_RENDER)) {
LERROR("Loading character " << i << " failed!");
continue;
......@@ -101,11 +102,12 @@ namespace campvis {
rowh = 0;
ox = 0;
}
glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, g->bitmap.width, g->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, g->bitmap.buffer);
_glyphParameters[i].advance = cgt::vec2(static_cast<float>(g->advance.x >> 6), static_cast<float>(g->advance.y >> 6));
_glyphParameters[i].bitmapSize = cgt::vec2(static_cast<float>(g->bitmap.width), static_cast<float>(g->bitmap.rows));
_glyphParameters[i].bitmapOffset = cgt::vec2(static_cast<float>(g->bitmap_left), static_cast<float>(g->bitmap_top));
_glyphParameters[i].offset = cgt::vec2(ox / static_cast<float>(w), oy / static_cast<float>(h));
_glyphs[i].advance = cgt::vec2(static_cast<float>(g->advance.x >> 6), static_cast<float>(g->advance.y >> 6));
_glyphs[i].bitmapSize = cgt::vec2(static_cast<float>(g->bitmap.width), static_cast<float>(g->bitmap.rows));
_glyphs[i].bitmapOffset = cgt::vec2(static_cast<float>(g->bitmap_left), static_cast<float>(g->bitmap_top));
_glyphs[i].offset = cgt::vec2(ox / static_cast<float>(w), oy / static_cast<float>(h));
rowh = std::max(rowh, static_cast<int>(g->bitmap.rows));
ox += g->bitmap.width + 1;
}
......@@ -125,7 +127,12 @@ namespace campvis {
ShdrMgr.dispose(_shader);
}
void FontAtlas::renderText(const std::string& text, const cgt::vec2& position, const cgt::vec4& color, const cgt::vec2& scale /*= cgt::vec2(1.f)*/) {
void FontAtlas::renderText(const std::string& text, const cgt::vec2& position, const cgt::vec4& color, const cgt::mat4& transformationMatrix /*= cgt::mat4::identity*/) {
if (_shader == nullptr || _texture == nullptr) {
LERROR("Cannot render text, FontAtlas not correctly initialized.");
return;
}
cgt::TextureUnit fontUnit;
fontUnit.activate();
_texture->bind();
......@@ -133,7 +140,7 @@ namespace campvis {
_shader->activate();
_shader->setUniform("_fontTexture", fontUnit.getUnitNumber());
_shader->setUniform("_color", color);
_shader->setUniform("_viewMatrix", cgt::mat4::createTranslation(cgt::vec3(-1.f, -1.f, 0.f)) * cgt::mat4::createScale(cgt::vec3(2.f, 2.f, 1.f)));
_shader->setUniform("_viewMatrix", transformationMatrix);
// create billboard vertices
std::vector<cgt::vec3> vertices;
......@@ -141,19 +148,18 @@ namespace campvis {
vertices.reserve(6 * text.length());
texCoords.reserve(6 * text.length());
/* Set up the VBO for our vertex data */
int c = 0;
cgt::vec2 pos = position * scale;
cgt::vec2 pos = position;
/* Loop through all characters */
// Loop through all characters
for (size_t i = 0; i < text.length(); ++i) {
char p = text[i];
if (p >= 32 && p < 128) {
cgt::vec2 pos2(pos.x + (_glyphParameters[p].bitmapOffset.x * scale.x), -pos.y - (_glyphParameters[p].bitmapOffset.y * scale.y));
cgt::vec2 size = _glyphParameters[p].bitmapSize * scale;
unsigned char p = text[i];
if (p >= 32 && p < 256) {
cgt::vec2 pos2(pos.x + _glyphs[p].bitmapOffset.x, -pos.y - _glyphs[p].bitmapOffset.y);
cgt::vec2 size = _glyphs[p].bitmapSize;
// Advance the cursor to the start of the next character
pos += _glyphParameters[p].advance * scale;
pos += _glyphs[p].advance;
// Skip glyphs that have no pixels
if (size.x == 0.f || size.y == 0.f)
......@@ -164,25 +170,24 @@ namespace campvis {
// Calculate the vertex and texture coordinates
vertices.push_back(cgt::vec3(pos2.x, -pos2.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphParameters[p].offset, 0.f));
texCoords.push_back(cgt::vec3(_glyphs[p].offset, 0.f));
vertices.push_back(cgt::vec3(pos2.x + size.x, -pos2.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphParameters[p].offset.x + _glyphParameters[p].bitmapSize.x / tw, _glyphParameters[p].offset.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphs[p].offset.x + _glyphs[p].bitmapSize.x / tw, _glyphs[p].offset.y, 0.f));
vertices.push_back(cgt::vec3(pos2.x, -pos2.y - size.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphParameters[p].offset.x, _glyphParameters[p].offset.y + _glyphParameters[p].bitmapSize.y / th, 0.f));
texCoords.push_back(cgt::vec3(_glyphs[p].offset.x, _glyphs[p].offset.y + _glyphs[p].bitmapSize.y / th, 0.f));
vertices.push_back(cgt::vec3(pos2.x + size.x, -pos2.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphParameters[p].offset.x + _glyphParameters[p].bitmapSize.x / tw, _glyphParameters[p].offset.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphs[p].offset.x + _glyphs[p].bitmapSize.x / tw, _glyphs[p].offset.y, 0.f));
vertices.push_back(cgt::vec3(pos2.x, -pos2.y - size.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphParameters[p].offset.x, _glyphParameters[p].offset.y + _glyphParameters[p].bitmapSize.y / th, 0.f));
texCoords.push_back(cgt::vec3(_glyphs[p].offset.x, _glyphs[p].offset.y + _glyphs[p].bitmapSize.y / th, 0.f));
vertices.push_back(cgt::vec3(pos2.x + size.x, -pos2.y - size.y, 0.f));
texCoords.push_back(cgt::vec3(_glyphParameters[p].offset.x + _glyphParameters[p].bitmapSize.x / tw, _glyphParameters[p].offset.y + _glyphParameters[p].bitmapSize.y / th, 0.f));
}
texCoords.push_back(cgt::vec3(_glyphs[p].offset.x + _glyphs[p].bitmapSize.x / tw, _glyphs[p].offset.y + _glyphs[p].bitmapSize.y / th, 0.f));
}
}
FaceGeometry face(vertices, texCoords);
......
......@@ -25,12 +25,14 @@
#ifndef FONTATLAS_H__
#define FONTATLAS_H__
#include "cgt/matrix.h"
#include "cgt/shadermanager.h"
#include "cgt/texture.h"
#include "cgt/vector.h"
#include <string>
// forward declarations
struct FT_LibraryRec_;
typedef struct FT_LibraryRec_ *FT_Library;
struct FT_FaceRec_;
......@@ -40,16 +42,43 @@ namespace campvis {
namespace fontrendering {
/**
* A font atlas, inspired by http://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02
* A font atlas providing to render text with OpenGL.
* The FontAtlas manages a set of glyphs for each printable character of a font with given size
* at construction. You can then use renderText() to render any text with that font into the
* current OpenGL framebuffer.
*
* ATTENTION: Throws an exception when it cannot initialiize the font atlas (e.g. due to
* missing font file).
*
* Code inspired by http://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02
*
* \note Current implementation only supports the ASCII-256 character set.
* \throws std::exception on initialization error
*/
class FontAtlas {
public:
/**
* Creates a new FontAtlas of the given font and size.
* \note Needs a valid OpenGL context.
* \param fontFileName Filename of the font to use for rendering.
* \param height Font height in pixels. This is in viewport coordinates, i.e. \b after applying the optional transformation matrix in renderText()!
*/
FontAtlas(const std::string& fontFileName, int height);
/**
* Destructor of the FontAtlas, needs a valid OpenGL context.
*/
~FontAtlas();
void renderText(const std::string& text, const cgt::vec2& position, const cgt::vec4& color, const cgt::vec2& scale = cgt::vec2(1.f));
/**
* Renders the given text with the given parameters into the current framebuffer.
* \param text The text to render (currently only ASCII-256 support).
* \param position Position where to start rendering the text (in normalized [-1, 1] coordinates).
* \param color Color of the text to render.
* \param scale Additional transformation matrix that is applied to each rendered vertex.
*/
void renderText(const std::string& text, const cgt::vec2& position, const cgt::vec4& color, const cgt::mat4& transformationMatrix = cgt::mat4::identity);
private:
/// Struct storing glyph parameters of each printable ASCII character
......@@ -58,12 +87,12 @@ namespace fontrendering {
cgt::vec2 bitmapSize; ///< Bitmap size of this glyph
cgt::vec2 bitmapOffset; ///< Bitmap offset of this glyph
cgt::vec2 offset; ///< Offset of this glyph in texture coordinates
} _glyphParameters[128];
} _glyphs[256];
FT_Library _ftLibrary;
FT_Face _ftFace;
cgt::Texture* _texture;
cgt::Shader* _shader;
FT_Library _ftLibrary; ///< Pointer to the freetype2 library handle
FT_Face _ftFace; ///< Pointer to the font face
cgt::Texture* _texture; ///< OpenGL texture storing the texture atlas
cgt::Shader* _shader; ///< Shader used for font rendering
static const int MAX_TEXTURE_WIDTH;
static const std::string loggerCat_;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment