mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 12:27:08 +02:00
Introducing TextRenderer3D
This commit is contained in:
parent
c4ab18736d
commit
12d75481e5
6 changed files with 697 additions and 4 deletions
|
@ -32,7 +32,8 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
|||
glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f);
|
||||
glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f);
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
glm::vec2 bounds = glm::vec2(dimensions.x, dimensions.y);
|
||||
float leftMargin = 0.1f, topMargin = 0.1f;
|
||||
glm::vec2 bounds = glm::vec2(dimensions.x - 2 * leftMargin, dimensions.y - 2 * topMargin);
|
||||
|
||||
Transform transformToTopLeft = getTransformToCenter();
|
||||
transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left
|
||||
|
@ -51,7 +52,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
|||
float scale = _lineHeight / _textRenderer->getRowHeight();
|
||||
transformToTopLeft.setScale(scale);
|
||||
batch.setModelTransform(transformToTopLeft);
|
||||
_textRenderer->draw3D(batch, 0.0f, 0.0f, _text, textColor, bounds / scale);
|
||||
_textRenderer->draw(batch, leftMargin, topMargin, _text, textColor, bounds / scale);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#define hifi_RenderableTextEntityItem_h
|
||||
|
||||
#include <TextEntityItem.h>
|
||||
#include <TextRenderer.h>
|
||||
#include <TextRenderer3D.h>
|
||||
|
||||
const int FIXED_FONT_POINT_SIZE = 40;
|
||||
|
||||
|
@ -29,7 +29,7 @@ public:
|
|||
virtual void render(RenderArgs* args);
|
||||
|
||||
private:
|
||||
TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
|
||||
TextRenderer3D* _textRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
|
||||
};
|
||||
|
||||
|
||||
|
|
545
libraries/render-utils/src/TextRenderer3D.cpp
Normal file
545
libraries/render-utils/src/TextRenderer3D.cpp
Normal file
|
@ -0,0 +1,545 @@
|
|||
//
|
||||
// TextRenderer3D.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 4/24/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "TextRenderer3D.h"
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <gpu/Shader.h>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QStringList>
|
||||
#include <QFile>
|
||||
|
||||
#include "GLMHelpers.h"
|
||||
#include "MatrixStack.h"
|
||||
#include "RenderUtilsLogging.h"
|
||||
|
||||
#include "sdf_text3D_vert.h"
|
||||
#include "sdf_text3D_frag.h"
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "DeferredLightingEffect.h"
|
||||
|
||||
// FIXME support the shadow effect, or remove it from the API
|
||||
// FIXME figure out how to improve the anti-aliasing on the
|
||||
// interior of the outline fonts
|
||||
const float DEFAULT_POINT_SIZE = 12;
|
||||
|
||||
// Helper functions for reading binary data from an IO device
|
||||
template<class T>
|
||||
void readStream(QIODevice& in, T& t) {
|
||||
in.read((char*) &t, sizeof(t));
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
void readStream(QIODevice& in, T (&t)[N]) {
|
||||
in.read((char*) t, N);
|
||||
}
|
||||
|
||||
template<class T, size_t N>
|
||||
void fillBuffer(QBuffer& buffer, T (&t)[N]) {
|
||||
buffer.setData((const char*) t, N);
|
||||
}
|
||||
|
||||
// stores the font metrics for a single character
|
||||
struct Glyph3D {
|
||||
QChar c;
|
||||
glm::vec2 texOffset;
|
||||
glm::vec2 texSize;
|
||||
glm::vec2 size;
|
||||
glm::vec2 offset;
|
||||
float d; // xadvance - adjusts character positioning
|
||||
size_t indexOffset;
|
||||
|
||||
// We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect
|
||||
QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); }
|
||||
QRectF textureBounds() const { return glmToRect(texOffset, texSize); }
|
||||
|
||||
void read(QIODevice& in);
|
||||
};
|
||||
|
||||
void Glyph3D::read(QIODevice& in) {
|
||||
uint16_t charcode;
|
||||
readStream(in, charcode);
|
||||
c = charcode;
|
||||
readStream(in, texOffset);
|
||||
readStream(in, size);
|
||||
readStream(in, offset);
|
||||
readStream(in, d);
|
||||
texSize = size;
|
||||
}
|
||||
|
||||
struct TextureVertex {
|
||||
glm::vec2 pos;
|
||||
glm::vec2 tex;
|
||||
TextureVertex() {}
|
||||
TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {}
|
||||
};
|
||||
|
||||
struct QuadBuilder {
|
||||
TextureVertex vertices[4];
|
||||
QuadBuilder(const glm::vec2& min, const glm::vec2& size,
|
||||
const glm::vec2& texMin, const glm::vec2& texSize) {
|
||||
// min = bottomLeft
|
||||
vertices[0] = TextureVertex(min,
|
||||
texMin + glm::vec2(0.0f, texSize.y));
|
||||
vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f),
|
||||
texMin + texSize);
|
||||
vertices[2] = TextureVertex(min + size,
|
||||
texMin + glm::vec2(texSize.x, 0.0f));
|
||||
vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y),
|
||||
texMin);
|
||||
}
|
||||
QuadBuilder(const Glyph3D& glyph, const glm::vec2& offset) :
|
||||
QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size,
|
||||
glyph.texOffset, glyph.texSize) {}
|
||||
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, glm::vec2& value) {
|
||||
debug << value.x << value.y;
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, glm::vec3& value) {
|
||||
debug << value.x << value.y << value.z;
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, TextureVertex& value) {
|
||||
debug << "Pos:" << value.pos << ", Tex:" << value.tex;
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, QuadBuilder& value) {
|
||||
debug << '\n' << value.vertices[0]
|
||||
<< '\n' << value.vertices[1]
|
||||
<< '\n' << value.vertices[2]
|
||||
<< '\n' << value.vertices[3];
|
||||
return debug;
|
||||
}
|
||||
|
||||
class Font3D {
|
||||
public:
|
||||
Font3D();
|
||||
|
||||
void read(QIODevice& path);
|
||||
|
||||
glm::vec2 computeExtent(const QString& str) const;
|
||||
float getRowHeight() const { return _rowHeight; }
|
||||
|
||||
// Render string to batch
|
||||
void drawString(gpu::Batch& batch, float x, float y, const QString& str,
|
||||
const glm::vec4& color, TextRenderer3D::EffectType effectType,
|
||||
const glm::vec2& bound);
|
||||
|
||||
private:
|
||||
QStringList tokenizeForWrapping(const QString& str) const;
|
||||
QStringList splitLines(const QString& str) const;
|
||||
glm::vec2 computeTokenExtent(const QString& str) const;
|
||||
|
||||
const Glyph3D& getGlyph(const QChar& c) const;
|
||||
|
||||
void setupGPU();
|
||||
|
||||
// maps characters to cached glyph info
|
||||
// HACK... the operator[] const for QHash returns a
|
||||
// copy of the value, not a const value reference, so
|
||||
// we declare the hash as mutable in order to avoid such
|
||||
// copies
|
||||
mutable QHash<QChar, Glyph3D> _glyphs;
|
||||
|
||||
// Font characteristics
|
||||
QString _family;
|
||||
float _fontSize = 0.0f;
|
||||
float _rowHeight = 0.0f;
|
||||
float _leading = 0.0f;
|
||||
float _ascent = 0.0f;
|
||||
float _descent = 0.0f;
|
||||
float _spaceWidth = 0.0f;
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
// gpu structures
|
||||
gpu::PipelinePointer _pipeline;
|
||||
gpu::TexturePointer _texture;
|
||||
gpu::Stream::FormatPointer _format;
|
||||
gpu::BufferPointer _verticesBuffer;
|
||||
gpu::BufferStreamPointer _stream;
|
||||
unsigned int _numVertices = 0;
|
||||
|
||||
int _fontLoc = -1;
|
||||
int _outlineLoc = -1;
|
||||
int _colorLoc = -1;
|
||||
|
||||
// last string render characteristics
|
||||
QString _lastStringRendered;
|
||||
glm::vec2 _lastBounds;
|
||||
};
|
||||
|
||||
static QHash<QString, Font3D*> LOADED_FONTS;
|
||||
|
||||
Font3D* loadFont3D(QIODevice& fontFile) {
|
||||
Font3D* result = new Font3D();
|
||||
result->read(fontFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
Font3D* loadFont3D(const QString& family) {
|
||||
if (!LOADED_FONTS.contains(family)) {
|
||||
|
||||
const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff";
|
||||
const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff";
|
||||
const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff";
|
||||
const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff";
|
||||
|
||||
QString loadFilename;
|
||||
|
||||
if (family == MONO_FONT_FAMILY) {
|
||||
loadFilename = SDFF_COURIER_PRIME_FILENAME;
|
||||
} else if (family == INCONSOLATA_FONT_FAMILY) {
|
||||
loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME;
|
||||
} else if (family == SANS_FONT_FAMILY) {
|
||||
loadFilename = SDFF_ROBOTO_FILENAME;
|
||||
} else {
|
||||
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
|
||||
loadFilename = SDFF_TIMELESS_FILENAME;
|
||||
} else {
|
||||
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadFilename.isEmpty()) {
|
||||
QFile fontFile(loadFilename);
|
||||
fontFile.open(QIODevice::ReadOnly);
|
||||
|
||||
qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System.";
|
||||
|
||||
LOADED_FONTS[family] = loadFont3D(fontFile);
|
||||
}
|
||||
}
|
||||
return LOADED_FONTS[family];
|
||||
}
|
||||
|
||||
Font3D::Font3D() {
|
||||
static bool fontResourceInitComplete = false;
|
||||
if (!fontResourceInitComplete) {
|
||||
Q_INIT_RESOURCE(fonts);
|
||||
fontResourceInitComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
|
||||
const Glyph3D& Font3D::getGlyph(const QChar& c) const {
|
||||
if (!_glyphs.contains(c)) {
|
||||
return _glyphs[QChar('?')];
|
||||
}
|
||||
return _glyphs[c];
|
||||
}
|
||||
|
||||
QStringList Font3D::splitLines(const QString& str) const {
|
||||
return str.split('\n');
|
||||
}
|
||||
|
||||
QStringList Font3D::tokenizeForWrapping(const QString& str) const {
|
||||
QStringList tokens;
|
||||
for(auto line : splitLines(str)) {
|
||||
if (!tokens.empty()) {
|
||||
tokens << QString('\n');
|
||||
}
|
||||
tokens << line.split(' ');
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
glm::vec2 Font3D::computeTokenExtent(const QString& token) const {
|
||||
glm::vec2 advance(0, _fontSize);
|
||||
foreach(QChar c, token) {
|
||||
Q_ASSERT(c != '\n');
|
||||
advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d;
|
||||
}
|
||||
return advance;
|
||||
}
|
||||
|
||||
glm::vec2 Font3D::computeExtent(const QString& str) const {
|
||||
glm::vec2 extent = glm::vec2(0.0f, 0.0f);
|
||||
|
||||
QStringList tokens = splitLines(str);
|
||||
foreach(const QString& token, tokens) {
|
||||
glm::vec2 tokenExtent = computeTokenExtent(token);
|
||||
extent.x = std::max(tokenExtent.x, extent.x);
|
||||
}
|
||||
extent.y = tokens.count() * _rowHeight;
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
void Font3D::read(QIODevice& in) {
|
||||
uint8_t header[4];
|
||||
readStream(in, header);
|
||||
if (memcmp(header, "SDFF", 4)) {
|
||||
qFatal("Bad SDFF file");
|
||||
}
|
||||
|
||||
uint16_t version;
|
||||
readStream(in, version);
|
||||
|
||||
// read font name
|
||||
_family = "";
|
||||
if (version > 0x0001) {
|
||||
char c;
|
||||
readStream(in, c);
|
||||
while (c) {
|
||||
_family += c;
|
||||
readStream(in, c);
|
||||
}
|
||||
}
|
||||
|
||||
// read font data
|
||||
readStream(in, _leading);
|
||||
readStream(in, _ascent);
|
||||
readStream(in, _descent);
|
||||
readStream(in, _spaceWidth);
|
||||
_fontSize = _ascent + _descent;
|
||||
_rowHeight = _fontSize + _leading;
|
||||
|
||||
// Read character count
|
||||
uint16_t count;
|
||||
readStream(in, count);
|
||||
// read metrics data for each character
|
||||
QVector<Glyph3D> glyphs(count);
|
||||
// std::for_each instead of Qt foreach because we need non-const references
|
||||
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph3D& g) {
|
||||
g.read(in);
|
||||
});
|
||||
|
||||
// read image data
|
||||
QImage image;
|
||||
if (!image.loadFromData(in.readAll(), "PNG")) {
|
||||
qFatal("Failed to read SDFF image");
|
||||
}
|
||||
|
||||
_glyphs.clear();
|
||||
glm::vec2 imageSize = toGlm(image.size());
|
||||
foreach(Glyph3D g, glyphs) {
|
||||
// Adjust the pixel texture coordinates into UV coordinates,
|
||||
g.texSize /= imageSize;
|
||||
g.texOffset /= imageSize;
|
||||
// store in the character to glyph hash
|
||||
_glyphs[g.c] = g;
|
||||
};
|
||||
|
||||
qDebug() << _family << "size" << image.size();
|
||||
qDebug() << _family << "format" << image.format();
|
||||
image = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
qDebug() << _family << "size" << image.size();
|
||||
qDebug() << _family << "format" << image.format();
|
||||
|
||||
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB);
|
||||
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB);
|
||||
if (image.hasAlphaChannel()) {
|
||||
formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA);
|
||||
formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA);
|
||||
}
|
||||
_texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(),
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
_texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
_texture->autoGenerateMips(-1);
|
||||
}
|
||||
|
||||
void Font3D::setupGPU() {
|
||||
if (!_initialized) {
|
||||
_initialized = true;
|
||||
|
||||
// Setup render pipeline
|
||||
auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert)));
|
||||
auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_frag)));
|
||||
gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader));
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
_fontLoc = program->getTextures().findLocation("Font");
|
||||
_outlineLoc = program->getUniforms().findLocation("Outline");
|
||||
_colorLoc = program->getUniforms().findLocation("Color");
|
||||
|
||||
|
||||
auto f = [&] (QString str, const gpu::Shader::SlotSet& set) {
|
||||
if (set.size() == 0) {
|
||||
return;
|
||||
}
|
||||
qDebug() << str << set.size();
|
||||
for (auto slot : set) {
|
||||
qDebug() << " " << QString::fromStdString(slot._name) << slot._location;
|
||||
}
|
||||
};
|
||||
f("getUniforms:", program->getUniforms());
|
||||
f("getBuffers:", program->getBuffers());
|
||||
f("getTextures:", program->getTextures());
|
||||
f("getSamplers:", program->getSamplers());
|
||||
f("getInputs:", program->getInputs());
|
||||
f("getOutputs:", program->getOutputs());
|
||||
|
||||
qDebug() << "Texture:" << _texture.get();
|
||||
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setCullMode(gpu::State::CULL_BACK);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
_pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
|
||||
|
||||
// Sanity checks
|
||||
static const int OFFSET = offsetof(TextureVertex, tex);
|
||||
assert(OFFSET == sizeof(glm::vec2));
|
||||
assert(sizeof(glm::vec2) == 2 * sizeof(float));
|
||||
assert(sizeof(glm::vec3) == 3 * sizeof(float));
|
||||
assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2));
|
||||
assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex));
|
||||
|
||||
// Setup rendering structures
|
||||
_format.reset(new gpu::Stream::Format());
|
||||
_format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0);
|
||||
_format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
|
||||
TextRenderer3D::EffectType effectType, const glm::vec2& bounds) {
|
||||
if (str == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (str != _lastStringRendered || bounds != _lastBounds) {
|
||||
_verticesBuffer.reset(new gpu::Buffer());
|
||||
_numVertices = 0;
|
||||
_lastStringRendered = str;
|
||||
_lastBounds = bounds;
|
||||
|
||||
// Top left of text
|
||||
glm::vec2 advance = glm::vec2(x, y);
|
||||
foreach(const QString& token, tokenizeForWrapping(str)) {
|
||||
bool isNewLine = (token == QString('\n'));
|
||||
bool forceNewLine = false;
|
||||
|
||||
// Handle wrapping
|
||||
if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) {
|
||||
// We are out of the x bound, force new line
|
||||
forceNewLine = true;
|
||||
}
|
||||
if (isNewLine || forceNewLine) {
|
||||
// Character return, move the advance to a new line
|
||||
advance = glm::vec2(0.0f, advance.y - _rowHeight);
|
||||
|
||||
if (isNewLine) {
|
||||
// No need to draw anything, go directly to next token
|
||||
continue;
|
||||
} else if (computeExtent(token).x > bounds.x) {
|
||||
// token will never fit, stop drawing
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) {
|
||||
// We are out of the y bound, stop drawing
|
||||
break;
|
||||
}
|
||||
|
||||
// Draw the token
|
||||
if (!isNewLine) {
|
||||
for (auto c : token) {
|
||||
auto glyph = _glyphs[c];
|
||||
|
||||
QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize));
|
||||
_verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd);
|
||||
_numVertices += 4;
|
||||
|
||||
// Advance by glyph size
|
||||
advance.x += glyph.d;
|
||||
}
|
||||
|
||||
// Add space after all non return tokens
|
||||
advance.x += _spaceWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupGPU();
|
||||
batch.setPipeline(_pipeline);
|
||||
batch.setUniformTexture(_fontLoc, _texture);
|
||||
batch._glUniform1f(_outlineLoc, (effectType == TextRenderer3D::OUTLINE_EFFECT) ? 1.0f : 0.0f);
|
||||
batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color);
|
||||
|
||||
batch.setInputFormat(_format);
|
||||
batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride);
|
||||
batch.draw(gpu::QUADS, _numVertices, 0);
|
||||
}
|
||||
|
||||
TextRenderer3D* TextRenderer3D::getInstance(const char* family, float pointSize,
|
||||
int weight, bool italic, EffectType effect, int effectThickness,
|
||||
const QColor& color) {
|
||||
if (pointSize < 0) {
|
||||
pointSize = DEFAULT_POINT_SIZE;
|
||||
}
|
||||
return new TextRenderer3D(family, pointSize, weight, italic, effect,
|
||||
effectThickness, color);
|
||||
}
|
||||
|
||||
TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic,
|
||||
EffectType effect, int effectThickness, const QColor& color) :
|
||||
_effectType(effect),
|
||||
_effectThickness(effectThickness),
|
||||
_pointSize(pointSize),
|
||||
_color(toGlm(color)),
|
||||
_font(loadFont3D(family)) {
|
||||
if (!_font) {
|
||||
qWarning() << "Unable to load font with family " << family;
|
||||
_font = loadFont3D("Courier");
|
||||
}
|
||||
if (1 != _effectThickness) {
|
||||
qWarning() << "Effect thickness not current supported";
|
||||
}
|
||||
if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) {
|
||||
qWarning() << "Effect thickness not current supported";
|
||||
}
|
||||
}
|
||||
|
||||
TextRenderer3D::~TextRenderer3D() {
|
||||
}
|
||||
|
||||
glm::vec2 TextRenderer3D::computeExtent(const QString& str) const {
|
||||
if (_font) {
|
||||
return _font->computeExtent(str);
|
||||
}
|
||||
return glm::vec2(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
float TextRenderer3D::getRowHeight() const {
|
||||
if (_font) {
|
||||
return _font->getRowHeight();
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
|
||||
const glm::vec2& bounds) {
|
||||
// The font does all the OpenGL work
|
||||
if (_font) {
|
||||
glm::vec4 actualColor(color);
|
||||
if (actualColor.r < 0) {
|
||||
actualColor = _color;
|
||||
}
|
||||
_font->drawString(batch, x, y, str, actualColor, _effectType, bounds);
|
||||
}
|
||||
}
|
||||
|
77
libraries/render-utils/src/TextRenderer3D.h
Normal file
77
libraries/render-utils/src/TextRenderer3D.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// TextRenderer3D.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 4/26/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_TextRenderer3D_h
|
||||
#define hifi_TextRenderer3D_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <QColor>
|
||||
|
||||
// the standard sans serif font family
|
||||
#define SANS_FONT_FAMILY "Helvetica"
|
||||
|
||||
// the standard sans serif font family
|
||||
#define SERIF_FONT_FAMILY "Timeless"
|
||||
|
||||
// the standard mono font family
|
||||
#define MONO_FONT_FAMILY "Courier"
|
||||
|
||||
// the Inconsolata font family
|
||||
#ifdef Q_OS_WIN
|
||||
#define INCONSOLATA_FONT_FAMILY "Fixedsys"
|
||||
#define INCONSOLATA_FONT_WEIGHT QFont::Normal
|
||||
#else
|
||||
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
|
||||
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
||||
#endif
|
||||
|
||||
namespace gpu {
|
||||
class Batch;
|
||||
}
|
||||
class Font3D;
|
||||
|
||||
// TextRenderer3D is actually a fairly thin wrapper around a Font class
|
||||
// defined in the cpp file.
|
||||
class TextRenderer3D {
|
||||
public:
|
||||
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
||||
|
||||
static TextRenderer3D* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
|
||||
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
||||
|
||||
~TextRenderer3D();
|
||||
|
||||
glm::vec2 computeExtent(const QString& str) const;
|
||||
float getRowHeight() const;
|
||||
|
||||
void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f),
|
||||
const glm::vec2& bounds = glm::vec2(-1.0f));
|
||||
|
||||
private:
|
||||
TextRenderer3D(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
|
||||
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
||||
|
||||
// the type of effect to apply
|
||||
const EffectType _effectType;
|
||||
|
||||
// the thickness of the effect
|
||||
const int _effectThickness;
|
||||
|
||||
const float _pointSize;
|
||||
|
||||
// text color
|
||||
const glm::vec4 _color;
|
||||
|
||||
Font3D* _font;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_TextRenderer3D_h
|
47
libraries/render-utils/src/sdf_text3D.slf
Normal file
47
libraries/render-utils/src/sdf_text3D.slf
Normal file
|
@ -0,0 +1,47 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2015-02-04
|
||||
// Based on fragment shader code from
|
||||
// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
uniform sampler2D Font;
|
||||
uniform float Outline;
|
||||
uniform vec4 Color;
|
||||
|
||||
const float gamma = 2.6;
|
||||
const float smoothing = 100.0;
|
||||
const float interiorCutoff = 0.8;
|
||||
const float outlineExpansion = 0.2;
|
||||
|
||||
void main() {
|
||||
// retrieve signed distance
|
||||
float sdf = texture2D(Font, gl_TexCoord[0].xy).g;
|
||||
if (Outline == 1.0f) {
|
||||
if (sdf > interiorCutoff) {
|
||||
sdf = 1.0 - sdf;
|
||||
} else {
|
||||
sdf += outlineExpansion;
|
||||
}
|
||||
}
|
||||
// perform adaptive anti-aliasing of the edges
|
||||
// The larger we're rendering, the less anti-aliasing we need
|
||||
float s = smoothing * length(fwidth(gl_TexCoord[0]));
|
||||
float w = clamp( s, 0.0, 0.5);
|
||||
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
|
||||
|
||||
// gamma correction for linear attenuation
|
||||
a = pow(a, 1.0 / gamma);
|
||||
|
||||
if (a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// final color
|
||||
gl_FragColor = vec4(Color.rgb, a);
|
||||
}
|
23
libraries/render-utils/src/sdf_text3D.slv
Normal file
23
libraries/render-utils/src/sdf_text3D.slv
Normal file
|
@ -0,0 +1,23 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Brad Davis on 10/14/13.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
<@include gpu/Transform.slh@>
|
||||
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
void main() {
|
||||
gl_TexCoord[0] = gl_MultiTexCoord0;
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$>
|
||||
}
|
Loading…
Reference in a new issue