OpenGL绘制文本
一:QPainter绘制
在 OpenGL 渲染的窗口中(如 QOpenGLWidget
),通过 QPainter
直接绘制文本。Qt 会自动将 2D 内容(文本、图形)与 OpenGL 内容合成。在paintGL()里面绘制,如果有其他纹理,在绘制纹理后解绑资源,再绘制文本。
m_program.bind();
// 绑定纹理
m_texture->bind(0);
m_program.setUniformValue("texture1", 0);
// 绘制矩形
glBindVertexArray(VAO[0]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// 解绑VAO
glBindVertexArray(0);
m_program.release();
// ----------------- 绘制文字 -----------------
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::yellow);
painter.setFont(QFont("Arial", 16, QFont::Bold));
// 带背景的文字
QString text = QString("%1:%2x%3").arg("宽高").arg(width()).arg(height());
QRect textRect = painter.fontMetrics().boundingRect(text);
textRect.moveTo(5, 5);
painter.fillRect(textRect.adjusted(-1, -1, 1, 1), QColor(0, 0, 0, 128));
painter.drawText(textRect, Qt::AlignLeft, text);
painter.end();
二:生成文本纹理并渲染四边形(高性能,适合动态文本)
将文本预渲染为纹理,通过 OpenGL 四边形显示,适合高频更新或大量文本。
步骤 1:创建文本纹理
QImage MyGLWidget::createTextTexture(const QString& text, int width, int height) {
QImage image(width, height, QImage::Format_ARGB32);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 24));
painter.drawText(image.rect(), Qt::AlignCenter, text);
painter.end();
// OpenGL 纹理坐标系原点在左下角,需垂直翻转图像
return image.mirrored(false, true);
}
步骤 2:绑定纹理并渲染四边形
QImage MyGLWidget::createTextTexture(const QString& text, int width, int height) {
QImage image(width, height, QImage::Format_ARGB32);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 24));
painter.drawText(image.rect(), Qt::AlignCenter, text);
painter.end();
// OpenGL 纹理坐标系原点在左下角,需垂直翻转图像
return image.mirrored(false, true);
}
优化技巧:
- 使用 纹理缓存 存储常用文本,避免重复生成。
- 动态更新纹理时,使用
glTexSubImage2D
局部更新数据。
三、使用 FreeType 库 + OpenGL(灵活但复杂)
通过 FreeType 加载字体文件生成字形纹理图集,实现高度定制的文本渲染(如游戏引擎风格)。
步骤 1:集成 FreeType 库
在 .pro
文件中添加依赖:
LIBS += -lfreetype
步骤 2:加载字体并生成字形
#include <ft2build.h>
#include FT_FREETYPE_H
struct Character {
GLuint textureID;
glm::ivec2 size;
glm::ivec2 bearing;
GLuint advance;
};
std::map<GLchar, Character> characters;
void loadFont(const char* fontPath) {
FT_Library ft;
FT_Init_FreeType(&ft);
FT_Face face;
FT_New_Face(ft, fontPath, 0, &face);
FT_Set_Pixel_Sizes(face, 0, 48);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // 禁用字节对齐限制
for (GLubyte c = 0; c < 128; c++) {
FT_Load_Char(face, c, FT_LOAD_RENDER);
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0, GL_RED, GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer);
// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
static_cast<GLuint>(face->glyph->advance.x)
};
characters.insert(std::make_pair(c, character));
}
FT_Done_Face(face);
FT_Done_FreeType(ft);
}
步骤 3:渲染文本
void renderText(QOpenGLShaderProgram& program, const std::string& text, GLfloat x, GLfloat y, GLfloat scale) {
program.bind();
glActiveTexture(GL_TEXTURE0);
for (auto c = text.begin(); c != text.end(); c++) {
Character ch = characters[*c];
GLfloat xpos = x + ch.bearing.x * scale;
GLfloat ypos = y - (ch.size.y - ch.bearing.y) * scale;
GLfloat w = ch.size.x * scale;
GLfloat h = ch.size.y * scale;
// 更新 VBO 数据(需预先创建)
GLfloat vertices = {
{xpos, ypos + h, 0.0, 0.0},
{xpos, ypos, 0.0, 1.0},
{xpos + w, ypos, 1.0, 1.0},
{xpos, ypos + h, 0.0, 0.0},
{xpos + w, ypos, 1.0, 1.0},
{xpos + w, ypos + h, 1.0, 0.0}
};
glBindTexture(GL_TEXTURE_2D, ch.textureID);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glDrawArrays(GL_TRIANGLES, 0, 6);
x += (ch.advance >> 6) * scale; // 单位转换为像素
}
program.release();
}
关键问题解决
-
文本模糊:
- 确保纹理过滤设置为
GL_LINEAR
。 - 使用高分辨率字体或 MSDF(多通道有符号距离场)技术。
- 确保纹理过滤设置为
-
中文支持:
- FreeType 方法需加载中文字体(如
.ttf
),并遍历 Unicode 字符集。
- FreeType 方法需加载中文字体(如
-
性能优化:
- 批处理文本绘制调用,减少状态切换。
- 使用实例化渲染(Instancing)处理大量相同字体的文本。