【OSG学习笔记】Day 10: 字体与文字渲染(osgText)
osgText库简介
osgText
是OpenSceneGraph(OSG)中用于文本渲染的重要模块,支持在3D场景中添加静态/动态文字、自定义字体、文字样式(颜色、大小、对齐方式等)以及动态更新文本内容。通过结合OSG的场景图机制,可实现文字与3D模型的精准对齐、视角跟随等效果。
动态文字标签实现
1. 创建字体对象
#include <osgText/Text>
#include <osgDB/ReadFile>// 加载字体文件(需指定绝对路径或通过OSG_DATA_PATH环境变量查找)
osgText::Font* font = osgText::readFontFile("simhei.ttf"); // 示例:黑体
if (!font) {osg::notify(osg::FATAL) << "Failed to load font!" << std::endl;return nullptr;
}
2. 创建动态文本节点
osg::ref_ptr<osgText::Text> dynamicText = new osgText::Text;
dynamicText->setFont(font); // 设置字体
dynamicText->setCharacterSize(24); // 文字大小(像素)
dynamicText->setColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // 文字颜色(RGBA)
dynamicText->setPosition(osg::Vec3(0.0f, 0.0f, 1.0f)); // 文字在3D场景中的位置
dynamicText->setAlignment(osgText::Text::CENTER_CENTER); // 居中对齐
dynamicText->setDrawMode(osgText::Text::TEXT); // 绘制模式(仅文字)
3. 动态更新文本内容
// 在场景更新回调中修改文本(例如每帧更新时间)
class TextUpdater : public osg::NodeCallback {
public:virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) {osgText::Text* text = static_cast<osgText::Text*>(node);if (text) {// 动态生成文本(示例:显示当前时间)std::time_t now = std::time(nullptr);text->setText("当前时间:" + std::string(std::ctime(&now)));}traverse(node, nv); // 继续遍历场景}
};// 为文本节点添加更新回调
dynamicText->setUpdateCallback(new TextUpdater);
4. 将文本节点添加到场景图
osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(dynamicText); // 将文本节点加入根节点// 创建场景视图(示例:标准OSG窗口渲染)
osgViewer::Viewer viewer;
viewer.setSceneData(root);
viewer.run();
进阶功能:文字与3D对象绑定
1. 跟随模型位置
// 假设存在一个模型节点modelNode
osg::ref_ptr<osg::PositionAttitudeTransform> transform = new osg::PositionAttitudeTransform;
transform->addChild(dynamicText); // 文本作为子节点
transform->setPosition(modelNode->getWorldMatrices()[0].getTrans()); // 跟随模型世界坐标root->addChild(transform); // 将变换节点加入场景
2. 视角对齐(文字始终朝向摄像机)
dynamicText->setDataVariance(osg::Object::DYNAMIC); // 标记为动态更新
dynamicText->setInitialBound(osg::BoundingBox(-10, -10, -10, 10, 10, 10)); // 设置包围盒// 添加视角对齐回调(通过矩阵变换实现)
class ViewAlignedCallback : public osg::NodeCallback {
public:osg::Matrixd viewMatrix; // 摄像机视图矩阵virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) {if (osg::CullVisitor* cv = dynamic_cast<osg::CullVisitor*>(nv)) {viewMatrix = cv->getViewMatrix();osg::Matrixd invView = viewMatrix.inverse();node->setWorldMatrices(osg::Matrixd::translate(node->getPosition()) * invView.getRotate());}traverse(node, nv);}
};dynamicText->setUpdateCallback(new ViewAlignedCallback);
五、常见问题与解决方案
问题描述 | 解决方案 |
---|---|
文字显示为方块/乱码 | 检查字体文件路径是否正确,确保字体支持目标字符(如中文需使用中文字体) |
文字不随模型移动 | 将文本节点放入PositionAttitudeTransform 节点中,并绑定模型变换矩阵 |
文字闪烁/裁剪 | 调整文本节点的setInitialBound() 范围,或启用深度测试:dynamicText->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON); |
动态更新不生效 | 确保更新回调(UpdateCallback )正确绑定,且文本节点的DataVariance 设置为DYNAMIC |
注意:
当你去掉
text->setText("hello");
这一行代码后,动态显示失效,可能是因为以下原因:
初始文本未设置:osgText::Text
对象在创建时,若没有初始化文本内容,那么后续仅通过回调更新文本,可能无法正常显示,因为渲染系统可能不知道要显示什么内容。
回调未正确触发:回调类 TextUpdater 或许依赖于初始文本内容来进行后续更新操作,若初始文本为空,回调函数可能不会按预期工作。
实战
text.cpp
#include <osgViewer/Viewer>
#include <osgText/Text>
#include <osgDB/ReadFile>
#include <ctime>
#include <iostream>
#include <osg/PositionAttitudeTransform>
#include <osgUtil/CullVisitor>class TextUpdater : public osg::NodeCallback {osg::Matrixd viewMatrix; // 摄像机视图矩阵
public:virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) {osgText::Text* text = static_cast<osgText::Text*>(node);if (text) {std::time_t now = std::time(nullptr);text->setText("now time: " + std::string(std::ctime(&now)));}traverse(node, nv);}
};// 批量文字渲染函数
void batchTextRendering(osg::Group* root) {// 加载字体osgText::Font* font = osgText::readFontFile("../fonts/arial.ttf");if (!font) {std::cout << "read ttf error." << std::endl;return;}// 创建一个 Geode 节点用于批量管理文字标签osg::ref_ptr<osg::Geode> geode = new osg::Geode;// 定义要渲染的文字数量const int numTexts = 5;for (int i = 0; i < numTexts; ++i) {// 创建动态文本节点osg::ref_ptr<osgText::Text> text = new osgText::Text;text->setFont(font);text->setCharacterSize(20);text->setColor(osg::Vec4(0, 0, 1, 1)); // 设置文字颜色为蓝色text->setPosition(osg::Vec3(20.0f, i * 20, -(i + 1) * 20));text->setAlignment(osgText::Text::CENTER_CENTER);text->setText("Text ");text->setText("Text " + std::to_string(i));// 将文本节点添加到 Geode 节点geode->addDrawable(text);}// 将 Geode 节点添加到根节点root->addChild(geode);
}int main() {// 加载字体osgText::Font* font = osgText::readFontFile("../fonts/arial.ttf");if (!font) {std::cout <<"read ttf error." <<std::endl;return -1;}// 创建动态文本osg::ref_ptr<osgText::Text> text = new osgText::Text;text->setFont(font);text->setCharacterSize(24);text->setColor(osg::Vec4(1, 0, 0, 1)); // 红色text->setPosition(osg::Vec3(0, 10, 0));text->setAlignment(osgText::Text::CENTER_CENTER);text->setDataVariance(osg::Object::DYNAMIC);text->setText("hello");// text->setDrawMode(osgText::Text::TEXT); // 绘制模式(仅文字)text->setUpdateCallback(new TextUpdater);std::cout << "Text position: x=" << text->getPosition().x() << std::endl;std::cout << "Text position: y=" << text->getPosition().y() << std::endl;std::cout << "Text position: z=" << text->getPosition().z() << std::endl;// 创建一个 Geode 节点并将文本节点添加到其中osg::ref_ptr<osg::Geode> geode = new osg::Geode;geode->addDrawable(text);// // 创建一个 PositionAttitudeTransform 节点osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform;// // 设置旋转,使文本平面与 xz 平面平行osg::Quat rotation(osg::inDegrees(90.0), osg::Vec3(1.0, 0.0, 0.0));pat->setAttitude(rotation);pat->addChild(geode);// 创建场景根节点osg::ref_ptr<osg::Group> root = new osg::Group;root->addChild(pat);batchTextRendering(pat);// 渲染osgViewer::Viewer viewer;viewer.setSceneData(root);return viewer.run();
}
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.12)
project(OSG_Text)# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 查找OpenSceneGraph核心组件
find_package(OpenSceneGraph REQUIRED COMPONENTS osg # 核心库osgDB # 文件读写osgViewer # 查看器功能osgGA # 图形上下文osgUtil # 工具库osgText
)# 包含头文件路径
include_directories(${OPENSCENEGRAPH_INCLUDE_DIR}
)# 创建可执行文件
add_executable(${PROJECT_NAME} text.cpp)# 链接OpenSceneGraph库
target_link_libraries(${PROJECT_NAME}${OPENSCENEGRAPH_LIBRARIES}# Windows需要额外链接$<$<PLATFORM_ID:Windows>:OpenThreads>
)# 配置调试模式
if(CMAKE_BUILD_TYPE STREQUAL "Debug")target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG)message(STATUS "Building in DEBUG mode")
endif()
运行效果
几个有层次感的文字生成了。_